From 8da8c30fe0dbf6f20503f52687cddc9cd7e4e05b Mon Sep 17 00:00:00 2001 From: pgherveou Date: Tue, 30 Jun 2026 09:25:44 +0200 Subject: [PATCH 1/2] feat(host-wasm): add @parity/truapi-host-wasm runtime New WASM-backed host runtime package embedding the Rust core, with web iframe and Web Worker entry points. Updates the @parity/truapi client (SCALE, sandbox, transport) and drops the obsolete explorer 0.3.2 codegen snapshot. --- .gitignore | 19 + .prettierrc | 5 +- js/packages/truapi-host-wasm/.gitignore | 11 + js/packages/truapi-host-wasm/README.md | 64 + js/packages/truapi-host-wasm/package.json | 49 + .../truapi-host-wasm/scripts/build-wasm.mjs | 63 + .../truapi-host-wasm/src/adapter-support.ts | 125 + js/packages/truapi-host-wasm/src/error.ts | 6 + .../src/host-callbacks-adapter.test.ts | 355 ++ js/packages/truapi-host-wasm/src/index.ts | 29 + js/packages/truapi-host-wasm/src/runtime.ts | 95 + .../truapi-host-wasm/src/test-support.ts | 40 + .../src/web/create-iframe-host.test.ts | 215 + .../src/web/create-iframe-host.ts | 147 + .../src/web/create-worker-host-runtime.ts | 780 ++++ js/packages/truapi-host-wasm/src/web/index.ts | 7 + .../src/web/worker-provider.test.ts | 658 +++ .../truapi-host-wasm/src/worker-protocol.ts | 160 + .../truapi-host-wasm/src/worker-runtime.ts | 433 ++ js/packages/truapi-host-wasm/tsconfig.json | 20 + js/packages/truapi/package.json | 2 +- js/packages/truapi/src/client.ts | 32 +- .../codegen/versions/0.3.2/services.ts | 724 ---- .../explorer/codegen/versions/0.3.2/types.ts | 3836 ----------------- js/packages/truapi/src/sandbox.ts | 118 +- js/packages/truapi/src/transport.ts | 11 +- js/packages/truapi/tsconfig.json | 1 + package-lock.json | 31 + playground/tests/e2e/testing.spec.ts | 40 + 29 files changed, 3470 insertions(+), 4606 deletions(-) create mode 100644 js/packages/truapi-host-wasm/.gitignore create mode 100644 js/packages/truapi-host-wasm/README.md create mode 100644 js/packages/truapi-host-wasm/package.json create mode 100644 js/packages/truapi-host-wasm/scripts/build-wasm.mjs create mode 100644 js/packages/truapi-host-wasm/src/adapter-support.ts create mode 100644 js/packages/truapi-host-wasm/src/error.ts create mode 100644 js/packages/truapi-host-wasm/src/host-callbacks-adapter.test.ts create mode 100644 js/packages/truapi-host-wasm/src/index.ts create mode 100644 js/packages/truapi-host-wasm/src/runtime.ts create mode 100644 js/packages/truapi-host-wasm/src/test-support.ts create mode 100644 js/packages/truapi-host-wasm/src/web/create-iframe-host.test.ts create mode 100644 js/packages/truapi-host-wasm/src/web/create-iframe-host.ts create mode 100644 js/packages/truapi-host-wasm/src/web/create-worker-host-runtime.ts create mode 100644 js/packages/truapi-host-wasm/src/web/index.ts create mode 100644 js/packages/truapi-host-wasm/src/web/worker-provider.test.ts create mode 100644 js/packages/truapi-host-wasm/src/worker-protocol.ts create mode 100644 js/packages/truapi-host-wasm/src/worker-runtime.ts create mode 100644 js/packages/truapi-host-wasm/tsconfig.json delete mode 100644 js/packages/truapi/src/explorer/codegen/versions/0.3.2/services.ts delete mode 100644 js/packages/truapi/src/explorer/codegen/versions/0.3.2/types.ts create mode 100644 playground/tests/e2e/testing.spec.ts diff --git a/.gitignore b/.gitignore index 9138e1a8..620568d2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,12 @@ lerna-debug.log* node_modules target +# Gradle (Android workspace at repo root) +/.gradle/ +/build/ +/android/*/build/ +local.properties + # Environment / secrets (never commit real env files; keep example templates) .env .env.* @@ -39,3 +45,16 @@ playground/public/static.files # Auto-generated by truapi-codegen (typecheck fixtures for rustdoc ts blocks) playground/test/generated/ + +# Auto-generated FFI / WASM binding outputs +android/truapi-host/src/main/kotlin/generated/ +ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift +ios/truapi-host/Sources/truapi_serverFFI/ +rust/crates/truapi-server/pkg/ +js/packages/truapi/src/generated/ +js/packages/truapi/dist/generated/ +js/packages/truapi-host/src/generated/ +js/packages/truapi-host/dist/generated/ +js/packages/truapi-host-wasm/src/generated/ +js/packages/truapi-host-wasm/dist/generated/ +js/packages/truapi-host-wasm/dist/wasm/ diff --git a/.prettierrc b/.prettierrc index 8224a16a..7d4a0046 100644 --- a/.prettierrc +++ b/.prettierrc @@ -10,7 +10,10 @@ "endOfLine": "lf", "overrides": [ { - "files": "js/packages/truapi/src/**/*.test.ts", + "files": [ + "js/packages/truapi/src/**/*.test.ts", + "js/packages/truapi-host-wasm/src/**/*.test.ts" + ], "options": { "tabWidth": 4, "printWidth": 100 diff --git a/js/packages/truapi-host-wasm/.gitignore b/js/packages/truapi-host-wasm/.gitignore new file mode 100644 index 00000000..288deac9 --- /dev/null +++ b/js/packages/truapi-host-wasm/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +*.tsbuildinfo +# Ignore compiled TS output (top-level + the web/ and electron/ entry subdirs) +# Generated WASM artifacts under dist/wasm/ are ignored by the repo root. +dist/**/*.js +dist/**/*.d.ts +dist/**/*.js.map +dist/**/*.d.ts.map +dist/generated/ +# Codegen output from truapi-codegen --platform-ts-output. +src/generated/ diff --git a/js/packages/truapi-host-wasm/README.md b/js/packages/truapi-host-wasm/README.md new file mode 100644 index 00000000..c80822c1 --- /dev/null +++ b/js/packages/truapi-host-wasm/README.md @@ -0,0 +1,64 @@ +# @parity/truapi-host-wasm + +WASM-backed TrUAPI host runtime. It embeds the `truapi-server` Rust core (compiled to WASM) +behind a Web Worker provider, plus per-environment integration entry points. It is the +counterpart to the native Android/iOS host shells. + +## Entry points + +The package exposes tree-shakeable subpath exports — import only what your environment needs: + +| Import | Provides | +| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| `@parity/truapi-host-wasm` | Shared runtime types plus generated typed host callback contracts. | +| `@parity/truapi-host-wasm/web` | Browser host: `createIframeHost` (iframe MessageChannel handshake) and `createWebWorkerProvider`. | +| `@parity/truapi-host-wasm/worker-runtime` | Web Worker entrypoint (import with your bundler's `?worker` suffix) so the WASM core runs off the page main thread. | +| `@parity/truapi-host-wasm/wasm/web` | The raw browser `wasm-bindgen` glue, if you need to instantiate the core yourself. | + +## Generated WASM artefacts + +The ignored bundle under `dist/wasm/web/` is built with host-owned chain access. +Hosts wire their JSON-RPC provider through `chainConnect`; if they omit it, +chain calls fail with the core's standard unavailable error. The bundled WASM is +about 1 MB (release build with `wasm-opt`). + +Build them after editing `rust/crates/truapi-server` and before packaging, publishing, or running +tests that load the raw WASM bundle (requires `wasm-pack` on PATH): + +```bash +npm run build:wasm # or `make wasm` from the repo root +``` + +## Example — browser (Web Worker) + +```ts +import HostWorker from "@parity/truapi-host-wasm/worker-runtime?worker"; +import { createWebWorkerProvider } from "@parity/truapi-host-wasm/web"; + +const provider = await createWebWorkerProvider(new HostWorker(), callbacks, { + runtimeConfig, +}); +``` + +`@parity/truapi-host-wasm/web` also exports `createIframeHost` for the protocol-iframe +MessageChannel handshake. + +## Publishing + +The npm publish workflow is not wired yet. A release-process discussion is needed before adding a +publish job to `.github/workflows/`. Until then, consumers depend on the package via the workspace +`file:` link or by publishing locally with `npm pack`. + +## Architecture + +```text +JS host code + protocol handlers / typed callbacks + (types from @parity/truapi-host-wasm) + | + v +createWebWorkerProvider + | + v + truapi-server WASM core +``` diff --git a/js/packages/truapi-host-wasm/package.json b/js/packages/truapi-host-wasm/package.json new file mode 100644 index 00000000..c9125f87 --- /dev/null +++ b/js/packages/truapi-host-wasm/package.json @@ -0,0 +1,49 @@ +{ + "name": "@parity/truapi-host-wasm", + "version": "0.1.0", + "description": "WASM-backed TrUAPI host runtime: embeds the Rust core, with web iframe and Web Worker entry points", + "license": "MIT", + "author": "Parity Technologies ", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "sideEffects": [ + "./dist/worker-runtime.js", + "./dist/wasm/**" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./web": { + "types": "./dist/web/index.d.ts", + "import": "./dist/web/index.js" + }, + "./worker-runtime": { + "types": "./dist/worker-runtime.d.ts", + "import": "./dist/worker-runtime.js" + }, + "./wasm/web": { + "types": "./dist/wasm/web/truapi_server.d.ts", + "import": "./dist/wasm/web/truapi_server.js" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsc -b", + "build:wasm": "node scripts/build-wasm.mjs", + "test": "bun test" + }, + "dependencies": { + "@parity/truapi": "file:../truapi" + }, + "devDependencies": { + "@types/bun": "^1.3.0", + "neverthrow": "^8.2.0", + "typescript": "^5.7" + } +} diff --git a/js/packages/truapi-host-wasm/scripts/build-wasm.mjs b/js/packages/truapi-host-wasm/scripts/build-wasm.mjs new file mode 100644 index 00000000..97583e4a --- /dev/null +++ b/js/packages/truapi-host-wasm/scripts/build-wasm.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node +// Rebuild the browser truapi-server WASM artefacts generated under +// `dist/wasm/web/`. wasm-pack is required. + +import { execFile } from "node:child_process"; +import { rm } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; + +const execFileAsync = promisify(execFile); +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pkgRoot = resolve(__dirname, ".."); +const repoRoot = resolve(pkgRoot, "../../.."); +const rustCrate = resolve(repoRoot, "rust/crates/truapi-server"); +const wasmProfile = process.env.TRUAPI_WASM_PROFILE ?? "release"; + +function args(target, outDir) { + const command = [ + "build", + "--target", + target, + "--out-dir", + outDir, + "--out-name", + "truapi_server", + ]; + if (wasmProfile === "dev") { + command.push("--dev"); + } else if (wasmProfile === "profiling") { + command.push("--profiling"); + } else if (wasmProfile !== "release") { + throw new Error( + `Unsupported TRUAPI_WASM_PROFILE=${wasmProfile}; expected release, dev, or profiling`, + ); + } + command.push(rustCrate, "--no-default-features"); + return command; +} + +async function build(target, subdir) { + const outDir = resolve(pkgRoot, "dist/wasm", subdir); + process.stdout.write( + `wasm-pack build --target ${target} --${wasmProfile} → ${outDir}\n`, + ); + try { + await execFileAsync("wasm-pack", args(target, outDir), { cwd: repoRoot }); + } catch (err) { + if (err?.code === "ENOENT") { + console.error( + "wasm-pack is required. Install it with `cargo install wasm-pack` " + + "or see https://rustwasm.github.io/wasm-pack/installer/", + ); + process.exit(1); + } + throw err; + } + // wasm-pack writes a nested `.gitignore: *`; the repo-level ignore already + // owns generated WASM outputs. + await rm(resolve(outDir, ".gitignore"), { force: true }); +} + +await build("web", "web"); diff --git a/js/packages/truapi-host-wasm/src/adapter-support.ts b/js/packages/truapi-host-wasm/src/adapter-support.ts new file mode 100644 index 00000000..ec4a2052 --- /dev/null +++ b/js/packages/truapi-host-wasm/src/adapter-support.ts @@ -0,0 +1,125 @@ +// Hand-written runtime support for the generated `createWasmRawCallbacks` +// adapter (`./generated/host-callbacks-adapter.ts`). The adapter is mechanical +// (decode params, call the typed host callback, read the result); the pieces +// here are the genuinely bespoke runtime plumbing it leans on: stream driving +// and the chain-connection handle. + +import { type GenericError, type Result } from "@parity/truapi"; +import { hexToBytes } from "@parity/truapi/scale"; + +import type { ChainConnect, ChainConnection } from "./runtime.js"; +import type { HostCallbacks } from "./generated/host-callbacks.js"; + +type WireResult = + | { success: true; value: T } + | { success: false; value: E }; + +type StreamResult = Result | WireResult; + +type MaybeAsyncIterable = AsyncIterable | Iterable; + +/** + * Normalize both generated `Result` values and the plain + * `{ success, value }` envelope used by some JS fixtures into a raw item. + */ +function unwrapStreamResult(item: StreamResult): T { + if ("success" in item) { + if (item.success === false) { + throw new Error(item.value.reason); + } + return item.value; + } + if (item.isErr()) { + throw new Error(item.error.reason); + } + return item.value; +} + +/** + * Accept sync and async host streams behind one async-iterator interface. + * Host callbacks often use async iterables in production, while tests can use + * small synchronous fixtures without a custom wrapper. + */ +function toAsyncIterator(stream: MaybeAsyncIterable): AsyncIterator { + const asyncIterable = stream as AsyncIterable; + if (typeof asyncIterable[Symbol.asyncIterator] === "function") { + return asyncIterable[Symbol.asyncIterator](); + } + const iterator = (stream as Iterable)[Symbol.iterator](); + const asyncIterator: AsyncIterator = { + next: async () => iterator.next(), + }; + if (iterator.return) { + asyncIterator.return = async () => iterator.return!(); + } + return asyncIterator; +} + +/** + * Drain an async iterator into a sink until disposed. This is used for + * callback streams where the Rust core owns cancellation but JS owns the + * iterator and any transport cleanup behind `return()`. + */ +function pumpIterator( + iterator: AsyncIterator, + onItem: (value: T) => void, + label: string, +): () => void { + let stopped = false; + void (async () => { + try { + while (!stopped) { + const next = await iterator.next(); + if (next.done) return; + onItem(next.value); + } + } catch (err) { + console.error(`[truapi host callbacks] ${label} failed:`, err); + } + })(); + return () => { + stopped = true; + void iterator.return?.(); + }; +} + +/** + * Drive a typed host stream of `Result` items into the core's `sendItem` + * sink, unwrapping each `Result` (or throwing on its error). Returns a + * disposer that stops iteration. + */ +export function driveResultStream( + stream: MaybeAsyncIterable>, + sendItem: (value: T) => void, +): () => void { + return pumpIterator( + toAsyncIterator(stream), + (value) => sendItem(unwrapStreamResult(value)), + "subscription", + ); +} + +/** + * Bridge the typed `ChainProvider.connect` callback onto the raw + * `chainConnect` the WASM core invokes: decode the genesis hash, pump the + * connection's `responses()` stream into `onResponse`, and expose + * `send`/`close`. + */ +export function chainConnectAdapter( + host: Pick, +): ChainConnect { + return async (genesisHash, onResponse): Promise => { + const connection = await host.connect(hexToBytes(genesisHash)); + const iterator = connection.responses()[Symbol.asyncIterator](); + const stopResponses = pumpIterator(iterator, onResponse, "chain responses"); + return { + send(request: string): void { + connection.send(request); + }, + close(): void { + stopResponses(); + connection.close(); + }, + }; + }; +} diff --git a/js/packages/truapi-host-wasm/src/error.ts b/js/packages/truapi-host-wasm/src/error.ts new file mode 100644 index 00000000..5dc5674e --- /dev/null +++ b/js/packages/truapi-host-wasm/src/error.ts @@ -0,0 +1,6 @@ +/** Coerce an unknown thrown value into a human-readable message string. */ +export function errorMessage(err: unknown): string { + if (err instanceof Error) return err.message; + if (typeof err === "string") return err; + return JSON.stringify(err) ?? String(err); +} diff --git a/js/packages/truapi-host-wasm/src/host-callbacks-adapter.test.ts b/js/packages/truapi-host-wasm/src/host-callbacks-adapter.test.ts new file mode 100644 index 00000000..2a2c2740 --- /dev/null +++ b/js/packages/truapi-host-wasm/src/host-callbacks-adapter.test.ts @@ -0,0 +1,355 @@ +import { describe, expect, it } from "bun:test"; +import { ok } from "neverthrow"; + +import { + HostDevicePermissionRequest, + HostDevicePermissionResponse, + HostFeatureSupportedRequest, + HostFeatureSupportedResponse, + HostPushNotificationRequest, + HostPushNotificationResponse, + RemotePermissionRequest, + RemotePermissionResponse, + ThemeVariant, +} from "@parity/truapi"; +import type { HostSignPayloadData } from "@parity/truapi"; + +import { createWasmRawCallbacks } from "./generated/host-callbacks-adapter.js"; +import { CoreStorageKey, UserConfirmationReview } from "./generated/host-callbacks.js"; +import { makeHostCallbacks, settle } from "./test-support.js"; + +// The generated `createWasmRawCallbacks` adapter speaks the symmetric SCALE +// byte boundary: codec-typed requests arrive as `Uint8Array` and are decoded +// for the typed host callback; codec-typed responses are SCALE-encoded back to +// `Uint8Array`. Primitives, strings, byte blobs and the local `AuthState` pass +// through unchanged. + +const GENESIS = `0x${"11".repeat(32)}` as `0x${string}`; +const PRODUCT_ACCOUNT = { + dotNsIdentifier: "playground.dot", + derivationIndex: 0, +}; +const SIGN_PAYLOAD: HostSignPayloadData = { + blockHash: GENESIS, + blockNumber: "0x01", + era: "0x00", + genesisHash: GENESIS, + method: "0x0102", + nonce: "0x00", + specVersion: "0x01", + tip: "0x00", + transactionVersion: "0x01", + signedExtensions: [], + version: 4, + assetId: undefined, + metadataHash: undefined, + mode: undefined, +}; + +describe("createWasmRawCallbacks", () => { + it("decodes requests and encodes typed responses", async () => { + const writes: [string, number[]][] = []; + const clears: string[] = []; + const cancelled: number[] = []; + const raw = createWasmRawCallbacks( + makeHostCallbacks({ + pushNotification: async (notification) => ({ + id: notification.text.length, + }), + cancelNotification: async (id) => { + cancelled.push(id); + }, + devicePermission: async (request) => ({ + granted: request === "Camera", + }), + remotePermission: async (request) => ({ + granted: request.permission.tag === "ChainSubmit", + }), + featureSupported: async (request) => ({ + supported: request.tag === "Chain" && request.value.genesisHash === GENESIS, + }), + read: async (key) => new TextEncoder().encode(`read:${key}`), + write: async (key, value) => { + writes.push([key, [...value]]); + }, + clear: async (key) => { + clears.push(key); + }, + }), + ); + + expect( + HostPushNotificationResponse.dec( + await raw.pushNotification!( + HostPushNotificationRequest.enc({ + text: "hello", + deeplink: undefined, + scheduledAt: undefined, + }), + ), + ).id, + ).toBe(5); + expect( + HostDevicePermissionResponse.dec( + await raw.devicePermission!(HostDevicePermissionRequest.enc("Camera")), + ).granted, + ).toBe(true); + expect( + RemotePermissionResponse.dec( + await raw.remotePermission!( + RemotePermissionRequest.enc({ + permission: { tag: "ChainSubmit" }, + }), + ), + ).granted, + ).toBe(true); + expect( + HostFeatureSupportedResponse.dec( + await raw.featureSupported!( + HostFeatureSupportedRequest.enc({ + tag: "Chain", + value: { genesisHash: GENESIS }, + }), + ), + ).supported, + ).toBe(true); + expect(await raw.read!("session")).toEqual(new TextEncoder().encode("read:session")); + + await raw.write!("session", new Uint8Array([1, 2, 3])); + await raw.clear!("session"); + await raw.cancelNotification?.(9); + + expect(writes).toEqual([["session", [1, 2, 3]]]); + expect(clears).toEqual(["session"]); + expect(cancelled).toEqual([9]); + }); + + it("bridges lifecycle, confirmations, and preimage callbacks", async () => { + const calls: unknown[][] = []; + async function* preimages() { + yield ok(undefined); + yield ok(new Uint8Array([4, 5, 6])); + } + + const raw = createWasmRawCallbacks( + makeHostCallbacks({ + authStateChanged: (state) => { + calls.push(["authStateChanged", state]); + }, + readCoreStorage: async (key) => + key.tag === "AuthSession" ? new Uint8Array([1, 2, 3]) : undefined, + writeCoreStorage: async (key, value) => { + calls.push(["writeCoreStorage", key, [...value]]); + }, + clearCoreStorage: async (key) => { + calls.push(["clearCoreStorage", key]); + }, + confirmUserAction: async (review) => { + switch (review.tag) { + case "SignPayload": + return ( + review.value.tag === "Product" && + review.value.value.account.dotNsIdentifier === "playground.dot" && + review.value.value.payload.method === "0x0102" + ); + case "SignRaw": + return ( + review.value.tag === "Product" && + review.value.value.payload.tag === "Bytes" && + review.value.value.payload.value.bytes === "0x0304" + ); + case "CreateTransaction": + return ( + review.value.tag === "Product" && + review.value.value.signer.derivationIndex === 0 && + review.value.value.callData === "0x0506" + ); + case "AccountAlias": + return ( + review.value.requestingProductId === "playground.dot" && + review.value.targetProductId === "wallet.dot" + ); + case "ResourceAllocation": + return review.value.resources[0]?.tag === "StatementStoreAllowance"; + case "PreimageSubmit": + calls.push(["confirmUserAction:PreimageSubmit", review.value.size]); + return review.value.size === 42n; + } + }, + submitPreimage: async (value) => { + calls.push(["submitPreimage", [...value]]); + return new Uint8Array([7, 8, 9]); + }, + lookupPreimage: (key) => { + calls.push(["lookupPreimage", [...key]]); + return preimages(); + }, + }), + ); + + const preimageEvents: (number[] | null)[] = []; + const disposePreimages = raw.lookupPreimage!(new Uint8Array([9]), (value) => + preimageEvents.push(value ? [...value] : null), + ); + + raw.authStateChanged?.({ + tag: "Pairing", + value: { deeplink: "polkadotapp://example" }, + }); + const authSessionKey = CoreStorageKey.enc({ tag: "AuthSession" }); + expect(await raw.readCoreStorage!(authSessionKey)).toEqual(new Uint8Array([1, 2, 3])); + await raw.writeCoreStorage!(authSessionKey, new Uint8Array([3, 2, 1])); + await raw.clearCoreStorage!(authSessionKey); + expect( + await raw.confirmUserAction?.( + UserConfirmationReview.enc({ + tag: "SignPayload", + value: { + tag: "Product", + value: { + account: PRODUCT_ACCOUNT, + payload: SIGN_PAYLOAD, + }, + }, + }), + ), + ).toBe(true); + expect( + await raw.confirmUserAction?.( + UserConfirmationReview.enc({ + tag: "SignRaw", + value: { + tag: "Product", + value: { + account: PRODUCT_ACCOUNT, + payload: { + tag: "Bytes", + value: { bytes: "0x0304" }, + }, + }, + }, + }), + ), + ).toBe(true); + expect( + await raw.confirmUserAction?.( + UserConfirmationReview.enc({ + tag: "CreateTransaction", + value: { + tag: "Product", + value: { + signer: PRODUCT_ACCOUNT, + genesisHash: GENESIS, + callData: "0x0506", + extensions: [], + txExtVersion: 0, + }, + }, + }), + ), + ).toBe(true); + expect( + await raw.confirmUserAction?.( + UserConfirmationReview.enc({ + tag: "AccountAlias", + value: { + requestingProductId: "playground.dot", + targetProductId: "wallet.dot", + }, + }), + ), + ).toBe(true); + expect( + await raw.confirmUserAction?.( + UserConfirmationReview.enc({ + tag: "ResourceAllocation", + value: { + resources: [{ tag: "StatementStoreAllowance" }], + }, + }), + ), + ).toBe(true); + expect( + await raw.confirmUserAction?.( + UserConfirmationReview.enc({ + tag: "PreimageSubmit", + value: { size: 42n }, + }), + ), + ).toBe(true); + expect(await raw.submitPreimage!(new Uint8Array([6]))).toEqual(new Uint8Array([7, 8, 9])); + + await settle(); + await settle(); + + expect(preimageEvents).toEqual([null, [4, 5, 6]]); + expect(calls).toEqual([ + ["lookupPreimage", [9]], + ["authStateChanged", { tag: "Pairing", value: { deeplink: "polkadotapp://example" } }], + ["writeCoreStorage", { tag: "AuthSession", value: undefined }, [3, 2, 1]], + ["clearCoreStorage", { tag: "AuthSession", value: undefined }], + ["confirmUserAction:PreimageSubmit", 42n], + ["submitPreimage", [6]], + ]); + + disposePreimages?.(); + }); + + it("adapts typed result subscriptions", async () => { + async function* themes() { + yield ok("Dark"); + yield ok("Light"); + } + + const raw = createWasmRawCallbacks( + makeHostCallbacks({ + subscribeTheme: () => themes(), + }), + ); + const seen: ThemeVariant[] = []; + const dispose = raw.subscribeTheme?.((theme) => seen.push(ThemeVariant.dec(theme!))); + + await settle(); + await settle(); + + expect(seen).toEqual(["Dark", "Light"]); + dispose?.(); + }); + + it("bridges typed chain connections", async () => { + const sent: string[] = []; + const responses = ['{"jsonrpc":"2.0","id":1,"result":"ok"}']; + let closes = 0; + const raw = createWasmRawCallbacks( + makeHostCallbacks({ + connect: async (genesisHash) => { + expect([...genesisHash]).toEqual(Array(32).fill(0x11)); + return { + send(request) { + sent.push(request); + }, + async *responses() { + yield* responses; + }, + close() { + closes += 1; + }, + }; + }, + }), + ); + + expect(typeof raw.chainConnect).toBe("function"); + const received: string[] = []; + const connection = await raw.chainConnect!(GENESIS, (json) => received.push(json)); + expect(connection).toBeTruthy(); + + connection!.send('{"jsonrpc":"2.0","id":1,"method":"system_health"}'); + await settle(); + + expect(sent).toEqual(['{"jsonrpc":"2.0","id":1,"method":"system_health"}']); + expect(received).toEqual(responses); + connection!.close(); + expect(closes).toBe(1); + }); +}); diff --git a/js/packages/truapi-host-wasm/src/index.ts b/js/packages/truapi-host-wasm/src/index.ts new file mode 100644 index 00000000..6236052a --- /dev/null +++ b/js/packages/truapi-host-wasm/src/index.ts @@ -0,0 +1,29 @@ +export type { Payload, ProtocolMessage, WireProvider } from "@parity/truapi"; + +export { encodeCoreStorageKey } from "./runtime.js"; + +export type { + AuthState, + Awaitable, + ChainConnect, + ChainConnection, + ChainProvider, + CoreAdmin, + CoreStorage, + CoreStorageKey, + Features, + HostCallbacks, + LogLevel, + Navigation, + Notifications, + PermissionAuthorizationRequest, + PermissionAuthorizationStatus, + Permissions, + PreimageHost, + ProductStorage, + PlatformJsonRpcConnection, + SessionUiInfo, + ThemeHost, + TrUApiHostCoreProvider, + HostCoreRuntimeConfig, +} from "./runtime.js"; diff --git a/js/packages/truapi-host-wasm/src/runtime.ts b/js/packages/truapi-host-wasm/src/runtime.ts new file mode 100644 index 00000000..89f93cf1 --- /dev/null +++ b/js/packages/truapi-host-wasm/src/runtime.ts @@ -0,0 +1,95 @@ +import type { WireProvider } from "@parity/truapi"; +import { CoreStorageKey as GeneratedCoreStorageKey } from "./generated/host-callbacks.js"; +import type { CoreAdmin, CoreStorageKey } from "./generated/host-callbacks.js"; + +// The typed capability interfaces below come straight from the +// `truapi-platform` Rust crate via `truapi-codegen --platform-ts-output`. +// They are the host-author-facing surface: each method takes/returns +// typed wrappers (`HostDevicePermissionRequest`, etc.) rather than raw +// SCALE bytes. `createWebWorkerProvider` adapts this typed surface into +// the byte-oriented callback bridge consumed by the WASM core. +export type { + AuthState, + ChainProvider, + CoreAdmin, + CoreStorage, + CoreStorageKey, + Features, + HostCallbacks, + JsonRpcConnection as PlatformJsonRpcConnection, + Navigation, + Notifications, + PermissionAuthorizationRequest, + PermissionAuthorizationStatus, + Permissions, + PreimageHost, + ProductStorage, + SessionUiInfo, + ThemeHost, +} from "./generated/host-callbacks.js"; + +/** Encode a typed core-storage slot for hosts that need an opaque backing key. */ +export function encodeCoreStorageKey(key: CoreStorageKey): Uint8Array { + return GeneratedCoreStorageKey.enc(key); +} + +/** + * Async-or-sync return. Synchronous hosts (e.g. the dotli main-thread + * shell hitting localStorage) can return a plain value; the WASM bridge + * awaits every return so an `async` impl also works. + */ +export type Awaitable = T | Promise; + +/** + * Open a JSON-RPC connection for `genesisHash`. The wasm bridge passes + * `onResponse` so the host can push JSON-RPC replies back asynchronously. + * Returning `null` (or throwing) tells the core no provider is available. + */ +export type ChainConnect = ( + genesisHash: string, + onResponse: (json: string) => void, +) => Awaitable; + +/** + * Per-connection handle returned by `chainConnect`. `send` forwards a + * SCALE-encoded JSON-RPC request; `close` tears the connection down. + */ +export interface ChainConnection { + send(request: string): void; + close(): void; +} + +/** + * Verbosity threshold for the wasm core's `tracing` output. The Rust core + * parses the string; known values are `off`, `error`, `warn`, `info`, `debug`, + * and `trace`. + */ +export type LogLevel = string; + +export interface HostCoreRuntimeConfig { + productId: string; + host: { + name: string; + icon?: string; + version?: string; + }; + platform?: { + type?: string; + version?: string; + }; + people: { + genesisHash: string | Uint8Array; + }; + pairing: { + deeplinkScheme: string; + }; +} + +export interface TrUApiHostCoreProvider extends WireProvider, CoreAdmin { + /** + * Re-tune the wasm core's log level at runtime. Present on runtimes that + * keep a live channel to the core (e.g. the Web Worker provider); absent on + * one-shot constructions that only accept `logLevel` up front. + */ + setLogLevel?(level: LogLevel): void; +} diff --git a/js/packages/truapi-host-wasm/src/test-support.ts b/js/packages/truapi-host-wasm/src/test-support.ts new file mode 100644 index 00000000..7f5c5cde --- /dev/null +++ b/js/packages/truapi-host-wasm/src/test-support.ts @@ -0,0 +1,40 @@ +import type { HostCallbacks } from "./generated/host-callbacks.js"; + +/** `HostCallbacks` with every optional member required, for exhaustive test fixtures. */ +export type CompleteHostCallbacks = Required; + +/** Default no-op host callbacks with optional per-test overrides. */ +export function makeHostCallbacks( + overrides: Partial = {}, +): CompleteHostCallbacks { + return { + navigateTo: async () => {}, + pushNotification: async () => ({ id: 0 }), + cancelNotification: async () => {}, + devicePermission: async () => ({ granted: false }), + remotePermission: async () => ({ granted: false }), + featureSupported: async () => ({ supported: false }), + readCoreStorage: async () => undefined, + writeCoreStorage: async () => {}, + clearCoreStorage: async () => {}, + read: async () => undefined, + write: async () => {}, + clear: async () => {}, + authStateChanged: () => {}, + confirmUserAction: async () => false, + submitPreimage: async () => new Uint8Array(), + async *lookupPreimage() {}, + async *subscribeTheme() {}, + connect: async () => ({ + send() {}, + async *responses() {}, + close() {}, + }), + ...overrides, + }; +} + +/** Resolve after the current microtask/immediate queue, letting pending async work run. */ +export function settle(): Promise { + return new Promise((resolve) => setImmediate(resolve)); +} diff --git a/js/packages/truapi-host-wasm/src/web/create-iframe-host.test.ts b/js/packages/truapi-host-wasm/src/web/create-iframe-host.test.ts new file mode 100644 index 00000000..9a9108bc --- /dev/null +++ b/js/packages/truapi-host-wasm/src/web/create-iframe-host.test.ts @@ -0,0 +1,215 @@ +import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"; + +import { createIframeHost } from "./index.js"; + +// Verify that `createIframeHost` hands a MessagePort back through `onPort`, +// constructs an iframe with the expected attributes, and posts the +// `truapi-init` handshake after the iframe reports readiness. + +function setupFakeDom() { + // Track listeners on the synthetic `window` and the iframe so the + // test can simulate the iframe `load` event after construction. + const iframeListeners = new Map void>(); + const windowListeners = new Map void>(); + const windowRemove = mock((_name: string, _fn: unknown) => {}); + const contentPostMessage = mock((_body: unknown, _origin: string) => {}); + + const contentWindow = { + postMessage: contentPostMessage, + }; + + const iframe = { + style: {} as Record, + setAttribute: mock((_name: string, _value: string) => {}), + addEventListener: (name: string, fn: (event: unknown) => void) => { + iframeListeners.set(name, fn); + }, + removeEventListener: () => {}, + remove: mock(() => {}), + referrerPolicy: "", + credentialless: false, + allow: "", + src: "", + contentWindow, + }; + + const container = { + appendChild: mock((_child: unknown) => {}), + }; + + // Spy on both MessageChannel ports so dispose() teardown is observable. + const port1 = { postMessage: mock(() => {}), close: mock(() => {}) }; + const port2 = { postMessage: mock(() => {}), close: mock(() => {}) }; + globalThis.MessageChannel = class { + port1 = port1; + port2 = port2; + } as unknown as typeof MessageChannel; + + globalThis.document = { + createElement: (tag: string) => { + expect(tag).toBe("iframe"); + return iframe as unknown as HTMLIFrameElement; + }, + } as unknown as Document; + globalThis.window = { + location: { href: "http://localhost:5174/" }, + addEventListener: (name: string, fn: (event: unknown) => void) => { + windowListeners.set(name, fn); + }, + removeEventListener: windowRemove, + } as unknown as Window & typeof globalThis; + + return { + iframe, + container, + contentPostMessage, + contentWindow, + iframeListeners, + windowListeners, + windowRemove, + port1, + port2, + }; +} + +function teardownFakeDom() { + delete (globalThis as { document?: unknown }).document; + delete (globalThis as { window?: unknown }).window; + delete (globalThis as { MessageChannel?: unknown }).MessageChannel; +} + +describe("createIframeHost", () => { + let dom: ReturnType; + + beforeEach(() => { + dom = setupFakeDom(); + }); + + afterEach(() => { + teardownFakeDom(); + }); + + it("hands back a MessagePort and configures the iframe", () => { + const { iframe, container, iframeListeners, windowRemove, port1, port2 } = dom; + + let receivedPort: MessagePort | null = null; + const host = createIframeHost({ + iframeUrl: "http://localhost:5174/", + container: container as unknown as HTMLElement, + allow: "camera; cross-origin-isolated", + onPort: (port) => { + receivedPort = port; + }, + }); + + expect(receivedPort).toBeTruthy(); + expect(typeof receivedPort!.postMessage).toBe("function"); + expect(container.appendChild.mock.calls.length).toBe(1); + expect(host.iframe).toBe(iframe as unknown as HTMLIFrameElement); + expect(iframe.credentialless).toBe(true); + expect(iframe.allow).toBe("camera; cross-origin-isolated"); + expect(iframe.src).toBe("http://localhost:5174/"); + // port transfer waits for explicit iframe readiness + expect(iframeListeners.has("load")).toBe(false); + + host.dispose(); + expect(iframe.remove.mock.calls.length).toBe(1); + // dispose removes the window message listener + expect(windowRemove.mock.calls.length).toBe(1); + expect(windowRemove.mock.calls[0][0]).toBe("message"); + // host + product ports closed on dispose + expect(port1.close.mock.calls.length).toBe(1); + expect(port2.close.mock.calls.length).toBe(1); + }); + + it("sends truapi-init on a same-origin product-ready message", () => { + const { contentPostMessage, windowListeners, contentWindow } = dom; + + createIframeHost({ + iframeUrl: "http://localhost:5174/", + container: { appendChild: () => {} } as unknown as HTMLElement, + onPort: () => {}, + }); + + const onMessage = windowListeners.get("message"); + expect(onMessage).toBeTruthy(); + + // Wrong source is dropped. + onMessage!({ + source: { other: true }, + origin: "http://localhost:5174", + data: { type: "truapi-ready" }, + }); + expect(contentPostMessage.mock.calls.length).toBe(0); + + // Wrong origin is dropped. + onMessage!({ + source: contentWindow, + origin: "http://evil.example", + data: { type: "truapi-ready" }, + }); + expect(contentPostMessage.mock.calls.length).toBe(0); + + // Correct source + origin triggers the init handshake. + onMessage!({ + source: contentWindow, + origin: "http://localhost:5174", + data: { type: "truapi-ready" }, + }); + expect(contentPostMessage.mock.calls.length).toBe(1); + const [body, origin] = contentPostMessage.mock.calls[0]; + expect(body).toEqual({ type: "truapi-init" }); + expect(origin).toBe("*"); + + // The handshake is idempotent across repeated ready events too. + onMessage!({ + source: contentWindow, + origin: "http://localhost:5174", + data: { type: "truapi-ready" }, + }); + expect(contentPostMessage.mock.calls.length).toBe(1); + }); + + it("accepts product-ready from a credentialless opaque origin", () => { + const { contentPostMessage, windowListeners, contentWindow } = dom; + + createIframeHost({ + iframeUrl: "http://localhost:5174/", + container: { appendChild: () => {} } as unknown as HTMLElement, + onPort: () => {}, + }); + + const onMessage = windowListeners.get("message"); + expect(onMessage).toBeTruthy(); + + onMessage!({ + source: contentWindow, + origin: "null", + data: { type: "truapi-ready" }, + }); + expect(contentPostMessage.mock.calls.length).toBe(1); + const [, origin] = contentPostMessage.mock.calls[0]; + expect(origin).toBe("*"); + }); + + it("rejects a mismatched allowedOrigin", () => { + expect(() => + createIframeHost({ + iframeUrl: "http://localhost:5174/", + container: { appendChild: () => {} } as unknown as HTMLElement, + onPort: () => {}, + allowedOrigin: "http://localhost:9999", + }), + ).toThrow(/origin policy mismatch/); + }); + + it("rejects non-http(s) iframe URLs", () => { + expect(() => + createIframeHost({ + iframeUrl: "file:///etc/passwd", + container: { appendChild: () => {} } as unknown as HTMLElement, + onPort: () => {}, + }), + ).toThrow(/only allows http\(s\)/); + }); +}); diff --git a/js/packages/truapi-host-wasm/src/web/create-iframe-host.ts b/js/packages/truapi-host-wasm/src/web/create-iframe-host.ts new file mode 100644 index 00000000..b5e3ce36 --- /dev/null +++ b/js/packages/truapi-host-wasm/src/web/create-iframe-host.ts @@ -0,0 +1,147 @@ +/** + * Options for `createIframeHost`. + */ +export interface IframeHostOptions { + /** URL of the product iframe. */ + iframeUrl: string; + /** Container element the iframe is appended to. */ + container: HTMLElement; + /** + * Called with one end of the MessageChannel once the iframe has loaded. + * Hosts typically pipe this into a `WireProvider` (e.g. via + * `createMessagePortProvider` from `@parity/truapi`). + */ + onPort: (port: MessagePort) => void; + /** + * Optional explicit allow-list origin. Defaults to the origin of + * `iframeUrl`. Throws if it disagrees with the iframe URL's origin. + */ + allowedOrigin?: string; + /** Optional iframe Permissions Policy allow attribute. */ + allow?: string; + /** Override the default iframe sandbox attribute. */ + sandbox?: string; +} + +/** + * Handle returned by `createIframeHost`. + */ +export interface IframeHost { + iframe: HTMLIFrameElement; + dispose: () => void; +} + +const DEFAULT_IFRAME_SANDBOX = "allow-forms allow-same-origin allow-scripts"; +type CredentiallessIframe = HTMLIFrameElement & { credentialless?: boolean }; + +function resolveAllowedOrigin( + iframeUrl: string, + allowedOrigin?: string, +): string { + const targetUrl = new URL(iframeUrl, window.location.href); + if (targetUrl.protocol !== "http:" && targetUrl.protocol !== "https:") { + throw new Error( + `Iframe host only allows http(s) product URLs, received ${targetUrl.protocol}`, + ); + } + + if (!allowedOrigin) { + return targetUrl.origin; + } + + const normalizedOrigin = new URL(allowedOrigin).origin; + if (normalizedOrigin !== targetUrl.origin) { + throw new Error( + `Iframe host origin policy mismatch, expected ${normalizedOrigin}, got ${targetUrl.origin}`, + ); + } + + return normalizedOrigin; +} + +/** + * Embed a product iframe and transfer a `MessagePort` into it. The host + * keeps the other end and passes it to a `WireProvider` (typically via + * `createMessagePortProvider`). All product traffic flows over the + * MessageChannel. + */ +export function createIframeHost(options: IframeHostOptions): IframeHost { + const { + iframeUrl, + container, + onPort, + allowedOrigin, + allow, + sandbox = DEFAULT_IFRAME_SANDBOX, + } = options; + + const channel = new MessageChannel(); + const hostPort = channel.port1; + const productPort = channel.port2; + const targetOrigin = resolveAllowedOrigin(iframeUrl, allowedOrigin); + + // Hand the host-side port to the caller immediately so it can wire up + // a provider before the iframe finishes loading. Queued postMessage + // calls are delivered once the channel is started by the provider. + onPort(hostPort); + + const iframe = document.createElement("iframe"); + iframe.style.width = "100%"; + iframe.style.height = "100%"; + iframe.style.border = "none"; + // COEP hosts need credentialless product iframes when the product origin + // does not serve matching embedder headers. + const credentiallessIframe = iframe as CredentiallessIframe; + credentiallessIframe.credentialless = true; + if (allow !== undefined) { + iframe.allow = allow; + } + iframe.setAttribute("sandbox", sandbox); + iframe.referrerPolicy = "no-referrer"; + iframe.src = iframeUrl; + const initTargetOrigin = credentiallessIframe.credentialless + ? "*" + : targetOrigin; + + let initSent = false; + const sendInit = (): void => { + if (initSent) return; + const contentWindow = iframe.contentWindow; + if (!contentWindow) return; + initSent = true; + contentWindow.postMessage({ type: "truapi-init" }, initTargetOrigin, [ + productPort, + ]); + }; + + const onWindowMessage = (event: MessageEvent): void => { + if (event.source !== iframe.contentWindow) return; + if (event.origin !== targetOrigin && event.origin !== "null") return; + if (event.data?.type === "truapi-ready") { + sendInit(); + } + }; + window.addEventListener("message", onWindowMessage); + + container.appendChild(iframe); + + return { + iframe, + dispose() { + window.removeEventListener("message", onWindowMessage); + try { + hostPort.close(); + } catch { + // already closed + } + try { + productPort.close(); + } catch { + // already closed + } + iframe.remove(); + }, + }; +} + +export type { WireProvider } from "@parity/truapi"; diff --git a/js/packages/truapi-host-wasm/src/web/create-worker-host-runtime.ts b/js/packages/truapi-host-wasm/src/web/create-worker-host-runtime.ts new file mode 100644 index 00000000..f662ec8e --- /dev/null +++ b/js/packages/truapi-host-wasm/src/web/create-worker-host-runtime.ts @@ -0,0 +1,780 @@ +import type { + ChainConnection, + HostCallbacks, + LogLevel, + PermissionAuthorizationRequest, + PermissionAuthorizationStatus, + TrUApiHostCoreProvider, + HostCoreRuntimeConfig, +} from "../index.js"; +import { PermissionAuthorizationRequest as PermissionAuthorizationRequestCodec } from "../generated/host-callbacks.js"; +import { createWasmRawCallbacks } from "../generated/host-callbacks-adapter.js"; +import type { RawCallbacks } from "../generated/host-callbacks-adapter.js"; +import type { + CallbackName, + MainToWorker, + SubscriptionName, + WorkerToMain, +} from "../worker-protocol.js"; +import { bytesToHex } from "@parity/truapi/scale"; +import { startRawSubscription } from "../generated/worker-callbacks.js"; +import { errorMessage } from "../error.js"; + +interface WorkerProviderState { + worker: Worker; + rawCallbacks: RawCallbacks; + listeners: Set<(message: Uint8Array) => void>; + closeListeners: Set<(error: Error) => void>; + subscriptionDisposers: Map void>; + chainConnections: Map; + pendingDisconnects: Map< + number, + { resolve: () => void; reject: (error: Error) => void } + >; + pendingPermissionAuthorizationStatuses: Map< + number, + { + resolve: (status: PermissionAuthorizationStatus) => void; + reject: (error: Error) => void; + } + >; + pendingPermissionAuthorizationStatusBatches: Map< + number, + { + resolve: (statuses: PermissionAuthorizationStatus[]) => void; + reject: (error: Error) => void; + } + >; + pendingSetPermissionAuthorizationStatuses: Map< + number, + { resolve: () => void; reject: (error: Error) => void } + >; + closedError: Error | null; + logLevel: LogLevel; + disposed: boolean; +} + +function debugLoggingEnabled(state: WorkerProviderState): boolean { + return state.logLevel === "debug" || state.logLevel === "trace"; +} + +let nextDisconnectRequestId = 0; +let nextPermissionAuthorizationRequestId = 0; + +function encodePermissionAuthorizationRequest( + request: PermissionAuthorizationRequest, +): Uint8Array { + return PermissionAuthorizationRequestCodec.enc(request); +} + +/** localStorage key the dev log level is persisted under, so it survives reloads. */ +const DEV_LOG_LEVEL_KEY = "truapi:logLevel"; + +/** Read the persisted dev log level. Returns null when unset. */ +function readPersistedLogLevel(): LogLevel | null { + return globalThis.localStorage?.getItem(DEV_LOG_LEVEL_KEY) ?? null; +} + +/** Persist the dev log level so it re-applies on the next reload. */ +function persistLogLevel(level: LogLevel): void { + globalThis.localStorage?.setItem(DEV_LOG_LEVEL_KEY, level); +} + +let devLogLevelOverride: LogLevel | null = readPersistedLogLevel(); +const devGlobalProviders = new Set(); +interface TrUApiDevConsole { + setLogLevel(level: LogLevel): void; + getLogLevel(): LogLevel | null; +} + +function handleCallbackRequest( + state: WorkerProviderState, + msg: { + requestId: number; + name: CallbackName; + args: readonly unknown[]; + }, +): void { + // Own-property guard: `msg.name` is worker-supplied, never walk the + // prototype chain with it. + const fn = Object.hasOwn(state.rawCallbacks, msg.name) + ? ( + state.rawCallbacks as unknown as Record< + string, + (...args: readonly unknown[]) => unknown + > + )[msg.name] + : undefined; + if (!fn) { + const reply: MainToWorker = { + kind: "callbackResponse", + requestId: msg.requestId, + ok: false, + error: `unknown callback: ${msg.name}`, + }; + state.worker.postMessage(reply); + return; + } + Promise.resolve() + .then(() => fn(...msg.args)) + .then( + (value) => { + const reply: MainToWorker = { + kind: "callbackResponse", + requestId: msg.requestId, + ok: true, + value, + }; + state.worker.postMessage(reply); + }, + (err) => { + const reply: MainToWorker = { + kind: "callbackResponse", + requestId: msg.requestId, + ok: false, + error: errorMessage(err), + }; + state.worker.postMessage(reply); + }, + ); +} + +function handleSubscriptionStart( + state: WorkerProviderState, + msg: { + subId: number; + name: SubscriptionName; + payload: Uint8Array | null; + }, +): void { + const sendItem = (value?: unknown): void => { + if (state.disposed) return; + const post: MainToWorker = { + kind: "subscriptionItem", + subId: msg.subId, + value, + }; + state.worker.postMessage(post); + }; + let dispose: (() => void) | void = undefined; + try { + dispose = startRawSubscription( + state.rawCallbacks, + msg.name, + msg.payload, + sendItem, + ); + } catch (err) { + console.error(`[truapi worker] ${msg.name} threw on start:`, err); + return; + } + if (typeof dispose === "function") { + state.subscriptionDisposers.set(msg.subId, dispose); + } +} + +function handleSubscriptionStop( + state: WorkerProviderState, + msg: { subId: number }, +): void { + const dispose = state.subscriptionDisposers.get(msg.subId); + if (!dispose) return; + state.subscriptionDisposers.delete(msg.subId); + try { + dispose(); + } catch (err) { + console.warn("[truapi worker] subscription dispose threw:", err); + } +} + +async function handleChainConnectStart( + state: WorkerProviderState, + msg: { connId: number; genesisHash: string }, +): Promise { + const chainConnect = state.rawCallbacks.chainConnect; + const onResponse = (json: string): void => { + if (state.disposed) return; + const post: MainToWorker = { + kind: "chainResponse", + connId: msg.connId, + json, + }; + state.worker.postMessage(post); + }; + try { + const conn = await chainConnect(msg.genesisHash, onResponse); + if (!conn) { + const reply: MainToWorker = { + kind: "chainConnectAck", + connId: msg.connId, + ok: false, + error: `chainConnect returned null for genesisHash ${msg.genesisHash}`, + }; + state.worker.postMessage(reply); + return; + } + state.chainConnections.set(msg.connId, conn); + const reply: MainToWorker = { + kind: "chainConnectAck", + connId: msg.connId, + ok: true, + }; + state.worker.postMessage(reply); + } catch (err) { + const reply: MainToWorker = { + kind: "chainConnectAck", + connId: msg.connId, + ok: false, + error: errorMessage(err), + }; + state.worker.postMessage(reply); + } +} + +function handleChainSend( + state: WorkerProviderState, + msg: { connId: number; request: string }, +): void { + const conn = state.chainConnections.get(msg.connId); + if (!conn) return; + try { + if (debugLoggingEnabled(state)) { + console.debug("[truapi worker] chainSend", msg.connId, msg.request); + } + conn.send(msg.request); + } catch (err) { + console.warn("[truapi worker] chain send threw:", err); + } +} + +function handleChainClose( + state: WorkerProviderState, + msg: { connId: number }, +): void { + const conn = state.chainConnections.get(msg.connId); + if (!conn) return; + state.chainConnections.delete(msg.connId); + try { + conn.close(); + } catch (err) { + console.warn("[truapi worker] chain close threw:", err); + } +} + +interface PendingEntry { + resolve: (value: T) => void; + reject: (error: Error) => void; +} + +/** Settle and remove the pending entry for `requestId`, no-op if already gone. */ +function settlePending( + map: Map>, + requestId: number, + result: { ok: true; value: T } | { ok: false; error: string }, +): void { + const pending = map.get(requestId); + if (!pending) return; + map.delete(requestId); + if (result.ok) { + pending.resolve(result.value); + } else { + pending.reject(new Error(result.error)); + } +} + +/** Reject every pending entry in `map` with `error`, then drain the map. */ +function rejectAll(map: Map>, error: Error): void { + for (const pending of map.values()) { + pending.reject(error); + } + map.clear(); +} + +function handleDisconnectResponse( + state: WorkerProviderState, + msg: + | { requestId: number; ok: true } + | { requestId: number; ok: false; error: string }, +): void { + settlePending( + state.pendingDisconnects, + msg.requestId, + msg.ok ? { ok: true, value: undefined } : { ok: false, error: msg.error }, + ); +} + +function handlePermissionAuthorizationStatusResponse( + state: WorkerProviderState, + msg: + | { + requestId: number; + ok: true; + status: PermissionAuthorizationStatus; + } + | { requestId: number; ok: false; error: string }, +): void { + settlePending( + state.pendingPermissionAuthorizationStatuses, + msg.requestId, + msg.ok ? { ok: true, value: msg.status } : { ok: false, error: msg.error }, + ); +} + +function handlePermissionAuthorizationStatusesResponse( + state: WorkerProviderState, + msg: + | { + requestId: number; + ok: true; + statuses: PermissionAuthorizationStatus[]; + } + | { requestId: number; ok: false; error: string }, +): void { + settlePending( + state.pendingPermissionAuthorizationStatusBatches, + msg.requestId, + msg.ok + ? { ok: true, value: msg.statuses } + : { ok: false, error: msg.error }, + ); +} + +function handleSetPermissionAuthorizationStatusResponse( + state: WorkerProviderState, + msg: + | { requestId: number; ok: true } + | { requestId: number; ok: false; error: string }, +): void { + settlePending( + state.pendingSetPermissionAuthorizationStatuses, + msg.requestId, + msg.ok ? { ok: true, value: undefined } : { ok: false, error: msg.error }, + ); +} + +function rejectPendingDisconnects( + state: WorkerProviderState, + error: Error, +): void { + rejectAll(state.pendingDisconnects, error); +} + +function rejectPendingPermissionAuthorizationRequests( + state: WorkerProviderState, + error: Error, +): void { + rejectAll(state.pendingPermissionAuthorizationStatuses, error); + rejectAll(state.pendingPermissionAuthorizationStatusBatches, error); + rejectAll(state.pendingSetPermissionAuthorizationStatuses, error); +} + +/** + * Register a pending request, post its message to the worker, and resolve when + * the matching response arrives. Short-circuits to `disposedFallback` once the + * provider is disposed; rolls back the pending entry if `postMessage` throws. + */ +function sendWorkerRequest( + state: WorkerProviderState, + pending: Map>, + nextId: () => number, + disposedFallback: T, + buildMessage: (requestId: number) => MainToWorker, +): Promise { + if (state.disposed) return Promise.resolve(disposedFallback); + return new Promise((resolve, reject) => { + const requestId = nextId(); + pending.set(requestId, { resolve, reject }); + try { + state.worker.postMessage(buildMessage(requestId)); + } catch (err) { + pending.delete(requestId); + reject(err instanceof Error ? err : new Error(String(err))); + } + }); +} + +/** + * Shared terminal teardown for both `dispose()` and worker faults: rejects + * pending disconnects, runs subscription disposers, closes chain connections, + * and terminates the worker. A fault additionally notifies close listeners. + */ +function teardown( + state: WorkerProviderState, + error: Error, + fault: boolean, +): void { + if (state.disposed) return; + state.disposed = true; + state.closedError = error; + rejectPendingDisconnects(state, error); + rejectPendingPermissionAuthorizationRequests(state, error); + for (const fn of state.subscriptionDisposers.values()) { + try { + fn(); + } catch { + // ignore during teardown + } + } + state.subscriptionDisposers.clear(); + for (const conn of state.chainConnections.values()) { + try { + conn.close(); + } catch { + // ignore during teardown + } + } + state.chainConnections.clear(); + if (fault) { + state.worker.terminate(); + } else { + try { + const post: MainToWorker = { kind: "dispose" }; + state.worker.postMessage(post); + } catch { + // ignore if worker already gone + } + // Give the worker a tick to free the core before terminating. + setTimeout(() => state.worker.terminate(), 0); + } + for (const listener of [...state.closeListeners]) listener(error); + state.listeners.clear(); + state.closeListeners.clear(); +} + +export interface CreateWebWorkerProviderOptions { + /** Wasm core log level. Default: `"off"`. */ + logLevel?: LogLevel; + /** Static product/pairing config passed to the Rust core. */ + runtimeConfig: HostCoreRuntimeConfig; + /** + * Milliseconds to wait for the worker to report `ready` before rejecting + * and terminating it. Default: 30000. + */ + initTimeoutMs?: number; +} + +export type WebWorkerHostCallbacks = Required; + +/** + * Spawn the truapi-server WASM in `worker` and bridge it into a + * `WireProvider`. + * + * The caller is responsible for instantiating the Worker, Vite users + * typically import the worker entry-point with `?worker`: + * + * ```ts + * import HostWorker from "@parity/truapi-host-wasm/worker-runtime?worker"; + * const worker = new HostWorker(); + * const provider = await createWebWorkerProvider(worker, callbacks, { + * runtimeConfig, + * }); + * ``` + * + * Resolves once the worker reports `ready` and rejects if the WASM + * fails to load. + */ +export function createWebWorkerProvider( + worker: Worker, + host: WebWorkerHostCallbacks, + options: CreateWebWorkerProviderOptions, +): Promise { + const callbacks = createWasmRawCallbacks(host); + + return new Promise((resolve, reject) => { + const state: WorkerProviderState = { + worker, + rawCallbacks: callbacks, + listeners: new Set(), + closeListeners: new Set(), + subscriptionDisposers: new Map(), + chainConnections: new Map(), + pendingDisconnects: new Map(), + pendingPermissionAuthorizationStatuses: new Map(), + pendingPermissionAuthorizationStatusBatches: new Map(), + pendingSetPermissionAuthorizationStatuses: new Map(), + closedError: null, + logLevel: devLogLevelOverride ?? options.logLevel ?? "off", + disposed: false, + }; + + const onMessage = (ev: MessageEvent): void => { + const msg = ev.data; + switch (msg.kind) { + case "loaded": + break; + case "ready": + break; + case "fatalError": + console.error("[truapi worker]", msg.error); + notifyFault(new Error(`worker fatal error: ${msg.error}`)); + break; + case "frameError": + console.error("[truapi worker]", msg.error); + notifyFault(new Error(`worker frame error: ${msg.error}`)); + break; + case "disposeError": + console.warn("[truapi worker] dispose:", msg.error); + break; + case "frame": + if (debugLoggingEnabled(state)) { + console.debug("[truapi worker] frame <-", bytesToHex(msg.bytes)); + } + for (const listener of [...state.listeners]) listener(msg.bytes); + break; + case "disconnectSessionResponse": + handleDisconnectResponse(state, msg); + break; + case "permissionAuthorizationStatusResponse": + handlePermissionAuthorizationStatusResponse(state, msg); + break; + case "permissionAuthorizationStatusesResponse": + handlePermissionAuthorizationStatusesResponse(state, msg); + break; + case "setPermissionAuthorizationStatusResponse": + handleSetPermissionAuthorizationStatusResponse(state, msg); + break; + case "callbackRequest": + if (debugLoggingEnabled(state)) { + console.debug("[truapi worker] callbackRequest", msg.name); + } + handleCallbackRequest(state, msg); + break; + case "subscriptionStart": + handleSubscriptionStart(state, msg); + break; + case "subscriptionStop": + handleSubscriptionStop(state, msg); + break; + case "chainConnectStart": + if (debugLoggingEnabled(state)) { + console.debug("[truapi worker] chainConnectStart", msg.connId); + } + void handleChainConnectStart(state, msg); + break; + case "chainSend": + handleChainSend(state, msg); + break; + case "chainClose": + handleChainClose(state, msg); + break; + default: { + const { kind } = msg as { kind?: unknown }; + console.warn( + `[truapi worker] unknown worker message kind: ${String(kind)}`, + ); + } + } + }; + + const notifyFault = (error: Error): void => { + teardown(state, error, true); + }; + + const onError = (e: ErrorEvent): void => { + cleanupInit(); + worker.terminate(); + reject(new Error(`worker init failed: ${e.message}`)); + }; + + const onInitMessageError = (): void => { + cleanupInit(); + worker.terminate(); + reject(new Error("worker message could not be deserialized during init")); + }; + + const onRuntimeError = (e: ErrorEvent): void => { + console.error("[truapi worker]", e.message); + notifyFault(new Error(`worker error: ${e.message}`)); + }; + + const onMessageError = (): void => { + notifyFault(new Error("worker message could not be deserialized")); + }; + + const onInitMessage = (ev: MessageEvent): void => { + const msg = ev.data; + if (msg.kind === "loaded") { + const init: MainToWorker = { + kind: "init", + logLevel: devLogLevelOverride ?? options.logLevel ?? "off", + runtimeConfig: options.runtimeConfig, + }; + worker.postMessage(init); + } else if (msg.kind === "ready") { + cleanupInit(); + worker.addEventListener("message", onMessage); + // Surface a post-init worker fault (uncaught throw, OOM, killed + // worker) to close listeners for the provider's lifetime. + worker.addEventListener("error", onRuntimeError); + worker.addEventListener("messageerror", onMessageError); + const provider = buildProvider(state); + exposeDevGlobal(provider); + resolve(provider); + } else if (msg.kind === "fatalError") { + cleanupInit(); + worker.terminate(); + reject(new Error(`worker init reported error: ${msg.error}`)); + } + }; + + const cleanupInit = (): void => { + clearTimeout(initTimeout); + worker.removeEventListener("error", onError); + worker.removeEventListener("messageerror", onInitMessageError); + worker.removeEventListener("message", onInitMessage); + }; + + const timeoutMs = options.initTimeoutMs ?? 30_000; + const initTimeout = setTimeout(() => { + cleanupInit(); + worker.terminate(); + reject(new Error(`worker init timed out after ${timeoutMs}ms`)); + }, timeoutMs); + + worker.addEventListener("error", onError); + worker.addEventListener("messageerror", onInitMessageError); + worker.addEventListener("message", onInitMessage); + }); +} + +function buildProvider(state: WorkerProviderState): TrUApiHostCoreProvider { + const provider: TrUApiHostCoreProvider = { + postMessage(bytes: Uint8Array): void { + if (state.disposed) return; + const post: MainToWorker = { kind: "frame", bytes }; + if (debugLoggingEnabled(state)) { + console.debug("[truapi worker] frame ->", bytesToHex(bytes)); + } + state.worker.postMessage(post); + }, + subscribe(callback) { + state.listeners.add(callback); + return () => { + state.listeners.delete(callback); + }; + }, + subscribeClose(callback) { + if (state.closedError) { + callback(state.closedError); + return () => {}; + } + state.closeListeners.add(callback); + return () => { + state.closeListeners.delete(callback); + }; + }, + disconnectSession(): Promise { + return sendWorkerRequest( + state, + state.pendingDisconnects, + () => ++nextDisconnectRequestId, + undefined, + (requestId) => ({ kind: "disconnectSession", requestId }), + ); + }, + cancelPairing(): void { + if (state.disposed) return; + const post: MainToWorker = { kind: "cancelPairing" }; + state.worker.postMessage(post); + }, + notifySessionStoreChanged(): void { + if (state.disposed) return; + const post: MainToWorker = { kind: "notifySessionStoreChanged" }; + state.worker.postMessage(post); + }, + getPermissionAuthorizationStatus( + request: PermissionAuthorizationRequest, + ): Promise { + return sendWorkerRequest( + state, + state.pendingPermissionAuthorizationStatuses, + () => ++nextPermissionAuthorizationRequestId, + "NotDetermined", + (requestId) => ({ + kind: "getPermissionAuthorizationStatus", + requestId, + request: encodePermissionAuthorizationRequest(request), + }), + ); + }, + getPermissionAuthorizationStatuses( + requests: PermissionAuthorizationRequest[], + ): Promise { + return sendWorkerRequest( + state, + state.pendingPermissionAuthorizationStatusBatches, + () => ++nextPermissionAuthorizationRequestId, + requests.map(() => "NotDetermined"), + (requestId) => ({ + kind: "getPermissionAuthorizationStatuses", + requestId, + requests: requests.map(encodePermissionAuthorizationRequest), + }), + ); + }, + setPermissionAuthorizationStatus( + request: PermissionAuthorizationRequest, + status: PermissionAuthorizationStatus, + ): Promise { + return sendWorkerRequest( + state, + state.pendingSetPermissionAuthorizationStatuses, + () => ++nextPermissionAuthorizationRequestId, + undefined, + (requestId) => ({ + kind: "setPermissionAuthorizationStatus", + requestId, + request: encodePermissionAuthorizationRequest(request), + status, + }), + ); + }, + setLogLevel(level: LogLevel): void { + if (state.disposed) return; + state.logLevel = level; + const post: MainToWorker = { kind: "setLogLevel", level }; + state.worker.postMessage(post); + }, + dispose() { + devGlobalProviders.delete(provider); + teardown(state, new Error("provider disposed"), false); + }, + }; + return provider; +} + +/** + * Publish `globalThis.__truapi.setLogLevel(level)` so a developer can re-tune + * the wasm core's verbosity live from the browser console without a reload. The + * level is persisted to `localStorage["truapi:logLevel"]` and re-applied on the + * next load, so it survives refreshes. Pair with the DevTools console "Verbose" + * level to surface debug/trace. + */ +function exposeDevGlobal(provider: TrUApiHostCoreProvider): void { + devGlobalProviders.add(provider); + if (devLogLevelOverride !== null) { + provider.setLogLevel?.(devLogLevelOverride); + } + publishDevGlobal(); +} + +function publishDevGlobal(): void { + const target = globalThis as { + __truapi?: TrUApiDevConsole; + }; + target.__truapi = { + setLogLevel(level: LogLevel): void { + devLogLevelOverride = level; + persistLogLevel(level); + for (const provider of [...devGlobalProviders]) { + provider.setLogLevel?.(level); + } + console.info(`[truapi worker] logLevel=${level}`); + }, + getLogLevel(): LogLevel | null { + return devLogLevelOverride; + }, + }; +} + +publishDevGlobal(); diff --git a/js/packages/truapi-host-wasm/src/web/index.ts b/js/packages/truapi-host-wasm/src/web/index.ts new file mode 100644 index 00000000..430c7098 --- /dev/null +++ b/js/packages/truapi-host-wasm/src/web/index.ts @@ -0,0 +1,7 @@ +export type { IframeHost, IframeHostOptions } from "./create-iframe-host.js"; +export { createIframeHost } from "./create-iframe-host.js"; +export type { + CreateWebWorkerProviderOptions, + WebWorkerHostCallbacks, +} from "./create-worker-host-runtime.js"; +export { createWebWorkerProvider } from "./create-worker-host-runtime.js"; diff --git a/js/packages/truapi-host-wasm/src/web/worker-provider.test.ts b/js/packages/truapi-host-wasm/src/web/worker-provider.test.ts new file mode 100644 index 00000000..a7f98d9c --- /dev/null +++ b/js/packages/truapi-host-wasm/src/web/worker-provider.test.ts @@ -0,0 +1,658 @@ +import { describe, expect, it } from "bun:test"; +import { ok } from "neverthrow"; + +import { HostPushNotificationRequest, HostPushNotificationResponse } from "@parity/truapi"; +import type { GenericError, Result, ThemeVariant } from "@parity/truapi"; + +import { createWasmRawCallbacks } from "../generated/host-callbacks-adapter.js"; +import { CoreStorageKey } from "../generated/host-callbacks.js"; +import type { AuthState, HostCallbacks } from "../generated/host-callbacks.js"; +import type { HostCoreRuntimeConfig } from "../runtime.js"; +import { makeHostCallbacks, settle } from "../test-support.js"; +import { createWebWorkerProvider } from "./index.js"; +import type { CreateWebWorkerProviderOptions } from "./index.js"; + +type WorkerMessage = Record; + +/** Minimal `Worker` stand-in that records posted messages and lets a test + * drive the `message`/`error`/`messageerror` events by hand. */ +class FakeWorker { + listeners = new Map void>>(); + messages: WorkerMessage[] = []; + terminated = false; + + addEventListener(name: string, fn: (event: unknown) => void) { + const listeners = this.listeners.get(name) ?? new Set(); + listeners.add(fn); + this.listeners.set(name, listeners); + } + + removeEventListener(name: string, fn: (event: unknown) => void) { + this.listeners.get(name)?.delete(fn); + } + + postMessage(message: WorkerMessage) { + this.messages.push(message); + } + + terminate() { + this.terminated = true; + } + + emit(message: WorkerMessage) { + for (const listener of this.listeners.get("message") ?? []) { + listener({ data: message }); + } + } + + emitError(message: string) { + for (const listener of this.listeners.get("error") ?? []) { + listener({ message }); + } + } + + emitMessageError() { + for (const listener of this.listeners.get("messageerror") ?? []) { + listener({ data: null }); + } + } +} + +/** Coerce the `FakeWorker` to the `Worker` shape the provider expects. */ +function asWorker(worker: FakeWorker): Worker { + return worker as unknown as Worker; +} + +function runtimeConfig(overrides: Partial = {}): HostCoreRuntimeConfig { + return { + productId: "dotli.dot", + host: { + name: "Polkadot Web", + icon: "https://dot.li/dotli.png", + version: "0.5.0", + }, + platform: { + type: "node", + version: process.versions.node, + }, + people: { + genesisHash: "0xa22a2424d2cbf561eaecf7da8b1b548fa9d1939f60265e942b1049616a012f71", + }, + pairing: { + deeplinkScheme: "polkadotapp", + }, + ...overrides, + }; +} + +type ReadyOptions = Partial & { + createWebWorkerProvider?: typeof createWebWorkerProvider; +}; + +async function readyProvider(worker: FakeWorker, options: ReadyOptions = {}) { + const { + createWebWorkerProvider: createProvider = createWebWorkerProvider, + runtimeConfig: cfg = runtimeConfig(), + ...rest + } = options; + const providerPromise = createProvider(asWorker(worker), makeHostCallbacks(), { + runtimeConfig: cfg, + ...rest, + }); + worker.emit({ kind: "loaded" }); + worker.emit({ kind: "ready" }); + return providerPromise; +} + +/** Typed view of the dev console the worker runtime publishes on `globalThis`. */ +type TruapiDevConsole = { + setLogLevel(level: string): void; + getLogLevel(): string | null; +}; +const devGlobal = globalThis as typeof globalThis & { + __truapi?: TruapiDevConsole; +}; + +describe("createWebWorkerProvider", () => { + it("initializes the worker without a callback manifest", async () => { + const worker = new FakeWorker(); + const config = runtimeConfig(); + const providerPromise = createWebWorkerProvider(asWorker(worker), makeHostCallbacks(), { + logLevel: "debug", + runtimeConfig: config, + }); + + worker.emit({ kind: "loaded" }); + expect(worker.messages.length).toBe(1); + expect(worker.messages[0]).toEqual({ + kind: "init", + logLevel: "debug", + runtimeConfig: config, + }); + + worker.emit({ kind: "ready" }); + const provider = await providerPromise; + expect(typeof provider.disconnectSession).toBe("function"); + expect(typeof provider.cancelPairing).toBe("function"); + expect(typeof provider.notifySessionStoreChanged).toBe("function"); + + provider.dispose(); + }); + + it("dev global setLogLevel updates every live worker provider", async () => { + const previous = devGlobal.__truapi; + delete devGlobal.__truapi; + const firstWorker = new FakeWorker(); + const secondWorker = new FakeWorker(); + const first = await readyProvider(firstWorker); + const second = await readyProvider(secondWorker); + + devGlobal.__truapi!.setLogLevel("debug"); + + expect(firstWorker.messages.at(-1)).toEqual({ + kind: "setLogLevel", + level: "debug", + }); + expect(secondWorker.messages.at(-1)).toEqual({ + kind: "setLogLevel", + level: "debug", + }); + expect(devGlobal.__truapi!.getLogLevel()).toBe("debug"); + + devGlobal.__truapi!.setLogLevel("off"); + first.dispose(); + second.dispose(); + if (previous === undefined) { + delete devGlobal.__truapi; + } else { + devGlobal.__truapi = previous; + } + }); + + it("dev global setLogLevel applies to providers created later", async () => { + const previous = devGlobal.__truapi; + delete devGlobal.__truapi; + const moduleUrl = `./create-worker-host-runtime.js?dev-global-${Date.now()}`; + const { createWebWorkerProvider: freshCreateWebWorkerProvider } = (await import( + moduleUrl + )) as typeof import("./create-worker-host-runtime.js"); + + expect(typeof devGlobal.__truapi!.setLogLevel).toBe("function"); + devGlobal.__truapi!.setLogLevel("trace"); + + const firstWorker = new FakeWorker(); + const first = await readyProvider(firstWorker, { + createWebWorkerProvider: freshCreateWebWorkerProvider, + }); + first.dispose(); + + const secondWorker = new FakeWorker(); + const second = await readyProvider(secondWorker, { + createWebWorkerProvider: freshCreateWebWorkerProvider, + }); + + expect(secondWorker.messages[0].kind).toBe("init"); + expect(secondWorker.messages[0].logLevel).toBe("trace"); + expect(secondWorker.messages.at(-1)).toEqual({ + kind: "setLogLevel", + level: "trace", + }); + + second.dispose(); + devGlobal.__truapi!.setLogLevel("off"); + if (previous === undefined) { + delete devGlobal.__truapi; + } else { + devGlobal.__truapi = previous; + } + }); + + it("dev global setLogLevel persists the level to localStorage", async () => { + const previousGlobal = devGlobal.__truapi; + const previousStorage = globalThis.localStorage; + delete devGlobal.__truapi; + const store = new Map(); + globalThis.localStorage = { + getItem: (key: string) => (store.has(key) ? store.get(key)! : null), + setItem: (key: string, value: string) => store.set(key, String(value)), + } as unknown as Storage; + + const worker = new FakeWorker(); + const provider = await readyProvider(worker); + + devGlobal.__truapi!.setLogLevel("debug"); + expect(store.get("truapi:logLevel")).toBe("debug"); + + devGlobal.__truapi!.setLogLevel("off"); + expect(store.get("truapi:logLevel")).toBe("off"); + + provider.dispose(); + globalThis.localStorage = previousStorage; + if (previousGlobal === undefined) { + delete devGlobal.__truapi; + } else { + devGlobal.__truapi = previousGlobal; + } + }); + + it("resolves disconnect responses", async () => { + const worker = new FakeWorker(); + const providerPromise = createWebWorkerProvider(asWorker(worker), makeHostCallbacks(), { + runtimeConfig: runtimeConfig(), + }); + worker.emit({ kind: "loaded" }); + worker.emit({ kind: "ready" }); + const provider = await providerPromise; + + const disconnect = provider.disconnectSession(); + const msg = worker.messages.at(-1)!; + expect(msg.kind).toBe("disconnectSession"); + expect(typeof msg.requestId).toBe("number"); + + worker.emit({ + kind: "disconnectSessionResponse", + requestId: msg.requestId, + ok: true, + }); + await disconnect; + + provider.dispose(); + }); + + it("dispatches callback requests to host hooks", async () => { + const worker = new FakeWorker(); + let clears = 0; + const authSessionKey = CoreStorageKey.enc({ tag: "AuthSession" }); + const providerPromise = createWebWorkerProvider( + asWorker(worker), + makeHostCallbacks({ + clearCoreStorage: async (key) => { + expect(key).toEqual({ tag: "AuthSession", value: undefined }); + clears += 1; + }, + }), + { runtimeConfig: runtimeConfig() }, + ); + worker.emit({ kind: "loaded" }); + worker.emit({ kind: "ready" }); + const provider = await providerPromise; + + worker.emit({ + kind: "callbackRequest", + requestId: 7, + name: "clearCoreStorage", + args: [authSessionKey], + }); + await settle(); + + expect(clears).toBe(1); + expect(worker.messages.at(-1)).toEqual({ + kind: "callbackResponse", + requestId: 7, + ok: true, + value: undefined, + }); + + provider.dispose(); + }); + + it("reports unknown callback requests", async () => { + const worker = new FakeWorker(); + const providerPromise = createWebWorkerProvider(asWorker(worker), makeHostCallbacks(), { + runtimeConfig: runtimeConfig(), + }); + worker.emit({ kind: "loaded" }); + worker.emit({ kind: "ready" }); + const provider = await providerPromise; + + worker.emit({ + kind: "callbackRequest", + requestId: 11, + name: "someFutureCallback", + args: [new Uint8Array([1, 2, 3])], + }); + await settle(); + + expect(worker.messages.at(-1)).toEqual({ + kind: "callbackResponse", + requestId: 11, + ok: false, + error: "unknown callback: someFutureCallback", + }); + + provider.dispose(); + }); + + it("forwards authStateChanged callback requests", async () => { + const worker = new FakeWorker(); + const states: AuthState[] = []; + const providerPromise = createWebWorkerProvider( + asWorker(worker), + makeHostCallbacks({ + authStateChanged: (state) => { + states.push(state); + }, + }), + { runtimeConfig: runtimeConfig() }, + ); + worker.emit({ kind: "loaded" }); + worker.emit({ kind: "ready" }); + const provider = await providerPromise; + + worker.emit({ + kind: "callbackRequest", + requestId: 3, + name: "authStateChanged", + args: [ + { + tag: "Connected", + value: { + publicKey: new Uint8Array([1, 2]), + liteUsername: "alice", + }, + }, + ], + }); + await settle(); + + expect(states).toEqual([ + { + tag: "Connected", + value: { + publicKey: new Uint8Array([1, 2]), + liteUsername: "alice", + }, + }, + ]); + expect(worker.messages.at(-1)).toEqual({ + kind: "callbackResponse", + requestId: 3, + ok: true, + value: undefined, + }); + + provider.dispose(); + }); + + it("posts cancelPairing to the worker", async () => { + const worker = new FakeWorker(); + const provider = await readyProvider(worker); + + provider.cancelPairing(); + + expect(worker.messages.at(-1)).toEqual({ kind: "cancelPairing" }); + provider.dispose(); + }); + + it("posts notifySessionStoreChanged to the worker", async () => { + const worker = new FakeWorker(); + const provider = await readyProvider(worker); + + provider.notifySessionStoreChanged(); + + expect(worker.messages.at(-1)).toEqual({ + kind: "notifySessionStoreChanged", + }); + provider.dispose(); + }); + + it("worker fault terminates the worker and runs the full teardown", async () => { + const worker = new FakeWorker(); + let subscriptionDisposes = 0; + let chainResponseStops = 0; + let chainCloses = 0; + const providerPromise = createWebWorkerProvider( + asWorker(worker), + makeHostCallbacks({ + // Manual async iterables whose `return()` records disposal; the + // provider disposes subscriptions and closes chain connections + // on a worker fault. + subscribeTheme: () => + ({ + [Symbol.asyncIterator]() { + return this; + }, + next: () => new Promise(() => {}), + return: async () => { + subscriptionDisposes += 1; + return { done: true, value: undefined }; + }, + }) as unknown as AsyncIterable>, + connect: async () => ({ + send() {}, + responses: () => + ({ + [Symbol.asyncIterator]() { + return this; + }, + next: () => new Promise(() => {}), + return: async () => { + chainResponseStops += 1; + return { done: true, value: undefined }; + }, + }) as unknown as AsyncIterable, + close() { + chainCloses += 1; + }, + }), + }), + { runtimeConfig: runtimeConfig() }, + ); + worker.emit({ kind: "loaded" }); + worker.emit({ kind: "ready" }); + const provider = await providerPromise; + + worker.emit({ + kind: "subscriptionStart", + subId: 1, + name: "subscribeTheme", + payload: null, + }); + worker.emit({ + kind: "chainConnectStart", + connId: 1, + genesisHash: "0xab", + }); + await settle(); + await settle(); + + const closes: Error[] = []; + provider.subscribeClose!((error) => closes.push(error)); + + worker.emitError("boom"); + await settle(); + await settle(); + + expect(worker.terminated).toBe(true); + expect(subscriptionDisposes).toBe(1); + expect(chainResponseStops).toBe(1); + expect(chainCloses).toBe(1); + expect(closes.length).toBe(1); + expect(closes[0].message).toMatch(/boom/); + + // The fault teardown is terminal; a second fault is a no-op. + worker.emitError("again"); + expect(closes.length).toBe(1); + + let lateClose: Error | null = null; + provider.subscribeClose!((error) => { + lateClose = error; + }); + expect(lateClose).toBeInstanceOf(Error); + expect(lateClose!.message).toMatch(/boom/); + }); + + it("worker fatalError during init rejects provider creation", async () => { + const worker = new FakeWorker(); + const providerPromise = createWebWorkerProvider(asWorker(worker), makeHostCallbacks(), { + runtimeConfig: runtimeConfig(), + }); + + worker.emit({ kind: "fatalError", error: "bad wasm" }); + + await expect(providerPromise).rejects.toThrow(/worker init reported error: bad wasm/); + expect(worker.terminated).toBe(true); + }); + + it("worker frameError after init closes the provider", async () => { + const worker = new FakeWorker(); + const provider = await readyProvider(worker); + const closes: Error[] = []; + provider.subscribeClose!((error) => closes.push(error)); + + worker.emit({ kind: "frameError", error: "bad frame" }); + + expect(worker.terminated).toBe(true); + expect(closes.length).toBe(1); + expect(closes[0].message).toMatch(/worker frame error: bad frame/); + + let lateClose: Error | null = null; + provider.subscribeClose!((error) => { + lateClose = error; + }); + expect(lateClose).toBeInstanceOf(Error); + }); + + it("routes payload-carrying subscriptions by name", async () => { + const worker = new FakeWorker(); + const keys: Uint8Array[] = []; + const providerPromise = createWebWorkerProvider( + asWorker(worker), + makeHostCallbacks({ + lookupPreimage: async function* (key) { + keys.push(key); + yield ok(new Uint8Array([1])); + }, + }), + { runtimeConfig: runtimeConfig() }, + ); + worker.emit({ kind: "loaded" }); + worker.emit({ kind: "ready" }); + const provider = await providerPromise; + + worker.emit({ + kind: "subscriptionStart", + subId: 4, + name: "lookupPreimage", + payload: new Uint8Array([9, 9]), + }); + + await settle(); + await settle(); + expect(keys).toEqual([new Uint8Array([9, 9])]); + expect(worker.messages.at(-1)).toEqual({ + kind: "subscriptionItem", + subId: 4, + value: new Uint8Array([1]), + }); + + provider.dispose(); + }); + + it("never falls through unknown subscription names to another callback", async () => { + const worker = new FakeWorker(); + let preimageStarts = 0; + const providerPromise = createWebWorkerProvider( + asWorker(worker), + makeHostCallbacks({ + lookupPreimage: (() => { + preimageStarts += 1; + return () => {}; + }) as unknown as HostCallbacks["lookupPreimage"], + }), + { runtimeConfig: runtimeConfig() }, + ); + worker.emit({ kind: "loaded" }); + worker.emit({ kind: "ready" }); + const provider = await providerPromise; + + worker.emit({ + kind: "subscriptionStart", + subId: 5, + name: "someFutureSubscribe", + payload: new Uint8Array([1, 2, 3]), + }); + + expect(preimageStarts).toBe(0); + expect(worker.messages.some((m) => m.kind === "subscriptionItem")).toBe(false); + + provider.dispose(); + }); + + it("does not dispatch a payload-carrying subscription without payload", async () => { + const worker = new FakeWorker(); + let preimageStarts = 0; + const providerPromise = createWebWorkerProvider( + asWorker(worker), + makeHostCallbacks({ + lookupPreimage: (() => { + preimageStarts += 1; + return () => {}; + }) as unknown as HostCallbacks["lookupPreimage"], + }), + { runtimeConfig: runtimeConfig() }, + ); + worker.emit({ kind: "loaded" }); + worker.emit({ kind: "ready" }); + const provider = await providerPromise; + + worker.emit({ + kind: "subscriptionStart", + subId: 6, + name: "lookupPreimage", + payload: null, + }); + + expect(preimageStarts).toBe(0); + + provider.dispose(); + }); + + it("rejects when init times out", async () => { + const worker = new FakeWorker(); + const providerPromise = createWebWorkerProvider(asWorker(worker), makeHostCallbacks(), { + runtimeConfig: runtimeConfig(), + initTimeoutMs: 20, + }); + worker.emit({ kind: "loaded" }); + await expect(providerPromise).rejects.toThrow(/worker init timed out after 20ms/); + expect(worker.terminated).toBe(true); + }); + + it("rejects on messageerror during init", async () => { + const worker = new FakeWorker(); + const providerPromise = createWebWorkerProvider(asWorker(worker), makeHostCallbacks(), { + runtimeConfig: runtimeConfig(), + }); + worker.emitMessageError(); + await expect(providerPromise).rejects.toThrow(/could not be deserialized/); + expect(worker.terminated).toBe(true); + }); + + it("decodes raw v01 push notification payloads", async () => { + let notification: HostPushNotificationRequest | undefined; + const callbacks = createWasmRawCallbacks( + makeHostCallbacks({ + pushNotification: async (request) => { + notification = request; + return { id: 42 }; + }, + }), + ); + + const encoded = await callbacks.pushNotification!( + HostPushNotificationRequest.enc({ + text: "Hello!", + deeplink: undefined, + scheduledAt: undefined, + }), + ); + + expect(HostPushNotificationResponse.dec(encoded).id).toBe(42); + expect(notification).toEqual({ + text: "Hello!", + deeplink: undefined, + scheduledAt: undefined, + }); + }); +}); diff --git a/js/packages/truapi-host-wasm/src/worker-protocol.ts b/js/packages/truapi-host-wasm/src/worker-protocol.ts new file mode 100644 index 00000000..f3dbf9c5 --- /dev/null +++ b/js/packages/truapi-host-wasm/src/worker-protocol.ts @@ -0,0 +1,160 @@ +// Wire format between the main thread (`createWebWorkerProvider`) and the +// Web Worker that hosts the truapi-server WASM core. +// +// Main window / host JS +// ┌─────────────────────────────────────────────────────────────────┐ +// │ createWebWorkerProvider │ +// │ host callbacks: storage, DOM prompts, chain provider, logging │ +// └───────────────┬─────────────────────────────────────────────────┘ +// │ MainToWorker: init, frame, callbackResponse, +// │ subscriptionItem, chainResponse +// v +// Dedicated Worker +// ┌─────────────────────────────────────────────────────────────────┐ +// │ truapi-server WASM HostCore │ +// │ generated raw-callback proxy │ +// └───────────────┬─────────────────────────────────────────────────┘ +// │ WorkerToMain: frame, callbackRequest, +// │ subscriptionStart, chainConnect +// v +// Main window dispatches those requests to the actual host callbacks. +// +// Frames (`kind: 'frame'`) carry SCALE-encoded `ProtocolMessage` bytes +// untouched in either direction. Everything else is a control message +// for callback dispatch, subscription bookkeeping, or chain connections. +// +// Frame bytes cross the boundary by structured clone, deliberately not as +// transferables: the sender keeps using its buffer (the worker side posts +// views into WASM memory) and frames are small, so the copy is the simpler +// safe choice. + +import type { LogLevel, PermissionAuthorizationStatus } from "./runtime.js"; +import type { + CallbackName, + SubscriptionName, +} from "./generated/worker-callbacks.js"; +/** + * Generated callback-name unions used by the worker transport. They keep the + * hand-written protocol aligned with the Rust platform callback catalog. + */ +export type { + CallbackName, + SubscriptionName, +} from "./generated/worker-callbacks.js"; + +/** + * Positional arguments for a callback. The wasm core calls each callback + * at a fixed arity; a uniform `unknown[]` keeps the wire protocol simple. + */ +export type CallbackArgs = readonly unknown[]; + +/** + * Messages posted by the main window to the WASM worker. These either control + * worker/core lifecycle, forward encoded TrUAPI frames into the core, or return + * host callback/subscription/chain responses requested by the worker. + */ +export type MainToWorker = + | { + kind: "init"; + logLevel: LogLevel; + runtimeConfig: unknown; + } + | { kind: "setLogLevel"; level: LogLevel } + | { kind: "frame"; bytes: Uint8Array } + | { kind: "disconnectSession"; requestId: number } + | { kind: "cancelPairing" } + | { kind: "notifySessionStoreChanged" } + | { + kind: "getPermissionAuthorizationStatus"; + requestId: number; + request: Uint8Array; + } + | { + kind: "getPermissionAuthorizationStatuses"; + requestId: number; + requests: Uint8Array[]; + } + | { + kind: "setPermissionAuthorizationStatus"; + requestId: number; + request: Uint8Array; + status: PermissionAuthorizationStatus; + } + | { kind: "callbackResponse"; requestId: number; ok: true; value: unknown } + | { kind: "callbackResponse"; requestId: number; ok: false; error: string } + | { kind: "subscriptionItem"; subId: number; value: unknown } + | { kind: "chainConnectAck"; connId: number; ok: true } + | { kind: "chainConnectAck"; connId: number; ok: false; error: string } + | { kind: "chainResponse"; connId: number; json: string } + | { kind: "dispose" }; + +/** + * Messages posted by the WASM worker back to the main window. These either + * report worker lifecycle/errors, emit encoded TrUAPI frames from the core, or + * request host callbacks, subscriptions, and chain-provider operations. + */ +export type WorkerToMain = + | { kind: "loaded" } + | { kind: "ready" } + | { kind: "fatalError"; error: string } + | { kind: "frameError"; error: string } + | { kind: "disposeError"; error: string } + | { kind: "frame"; bytes: Uint8Array } + | { kind: "disconnectSessionResponse"; requestId: number; ok: true } + | { + kind: "disconnectSessionResponse"; + requestId: number; + ok: false; + error: string; + } + | { + kind: "permissionAuthorizationStatusResponse"; + requestId: number; + ok: true; + status: PermissionAuthorizationStatus; + } + | { + kind: "permissionAuthorizationStatusResponse"; + requestId: number; + ok: false; + error: string; + } + | { + kind: "permissionAuthorizationStatusesResponse"; + requestId: number; + ok: true; + statuses: PermissionAuthorizationStatus[]; + } + | { + kind: "permissionAuthorizationStatusesResponse"; + requestId: number; + ok: false; + error: string; + } + | { + kind: "setPermissionAuthorizationStatusResponse"; + requestId: number; + ok: true; + } + | { + kind: "setPermissionAuthorizationStatusResponse"; + requestId: number; + ok: false; + error: string; + } + | { + kind: "callbackRequest"; + requestId: number; + name: CallbackName; + args: CallbackArgs; + } + | { + kind: "subscriptionStart"; + subId: number; + name: SubscriptionName; + payload: Uint8Array | null; + } + | { kind: "subscriptionStop"; subId: number } + | { kind: "chainConnectStart"; connId: number; genesisHash: string } + | { kind: "chainSend"; connId: number; request: string } + | { kind: "chainClose"; connId: number }; diff --git a/js/packages/truapi-host-wasm/src/worker-runtime.ts b/js/packages/truapi-host-wasm/src/worker-runtime.ts new file mode 100644 index 00000000..77a1d4bc --- /dev/null +++ b/js/packages/truapi-host-wasm/src/worker-runtime.ts @@ -0,0 +1,433 @@ +/// +// Worker entrypoint. Loads the web-targeted truapi-server WASM bundle and +// bridges every host callback over postMessage. The main thread keeps the +// state that needs DOM access (localStorage, prompts) while the core dispatcher +// runs here off the page main thread. + +import type { + MainToWorker, + SubscriptionName, + WorkerToMain, +} from "./worker-protocol.js"; +import { + createWorkerRawCallbacks, + type CallbackName, +} from "./generated/worker-callbacks.js"; +import { errorMessage } from "./error.js"; + +type PermissionAuthorizationStatus = + | "NotDetermined" + | "Denied" + | "Authorized"; + +interface WorkerHostCore { + receiveFrame(frame: Uint8Array): Promise; + disconnectSession(): Promise; + cancelPairing(): void; + notifySessionStoreChanged(): void; + permissionAuthorizationStatus(request: Uint8Array): Promise; + permissionAuthorizationStatuses(requests: Uint8Array[]): Promise; + setPermissionAuthorizationStatus( + request: Uint8Array, + status: string, + ): Promise; + dispose(): void; + free(): void; +} + +interface WasmModuleShape { + default: (input?: unknown) => Promise; + WasmHostCore: new ( + callbacks: unknown, + runtimeConfig: unknown, + ) => WorkerHostCore; + setLogLevel?: (level: string) => void; +} + +// Resolved at runtime, the wasm-pack artifact lives outside `src/` so a +// static import would leak into the TS rootDir. The relative path is +// resolved against `dist/worker-runtime.js` once compiled. Indirected +// through a variable so TS skips the static module-existence check. +const WASM_WEB_PATH = "./wasm/web/truapi_server.js"; +const wasmModulePromise = import( + /* @vite-ignore */ WASM_WEB_PATH +) as Promise; + +const ctx = self as unknown as DedicatedWorkerGlobalScope; + +function postToMain(msg: WorkerToMain): void { + ctx.postMessage(msg); +} + +let nextRequestId = 0; +const pendingCallbacks = new Map< + number, + (result: { ok: true; value: unknown } | { ok: false; error: string }) => void +>(); + +let nextSubId = 0; +const subscriptionItemListeners = new Map void>(); + +let nextConnId = 0; +type ChainConnectAck = { ok: true } | { ok: false; error: string }; +const chainConnectAcks = new Map void>(); +const chainResponseListeners = new Map void>(); + +function callbackRequest( + name: CallbackName, + args: readonly unknown[], +): Promise { + return new Promise((resolve, reject) => { + const requestId = ++nextRequestId; + pendingCallbacks.set(requestId, (r) => { + if (r.ok) resolve(r.value); + else reject(new Error(r.error)); + }); + postToMain({ kind: "callbackRequest", requestId, name, args }); + }); +} + +function startSubscription( + name: SubscriptionName, + payload: Uint8Array | null, + sendItem: (value: T) => void, +): () => void { + const subId = ++nextSubId; + subscriptionItemListeners.set(subId, sendItem as (value: unknown) => void); + postToMain({ kind: "subscriptionStart", subId, name, payload }); + return () => { + subscriptionItemListeners.delete(subId); + postToMain({ kind: "subscriptionStop", subId }); + }; +} + +interface WorkerChainConnection { + send(request: string): void; + close(): void; +} + +/** + * Worker-side half of the host chain-connect bridge. + * + * The Rust core runs in this worker but owns no socket. When it needs chain + * access (chainHead v1 for People-chain identity / statement-store SSO) it + * calls this; the actual transport lives on the host main thread and is reached + * over postMessage. The data crossing here is JSON-RPC strings, not SCALE: only + * the product<->core wire is SCALE. + * + * per-tab / sandboxed core-owned (this Web Worker) host-owned (main thread) + * +-------------------+ SCALE +--------------------------+ +--------------------------------+ + * | Product (iframe) |<------->| truapi-server WASM core | | host.connect() (ChainProvider) | + * | speaks TrUAPI | frames | chainHead v1, SSO, | | host-owned JSON-RPC transport | + * | never sees chains | | People-chain identity | | remote RPC, native client, ... | + * +-------------------+ +--------------------------+ +--------------------------------+ + * | ^ JSON-RPC strings (not SCALE) ^ | + * chainConnect() | | onResponse(json) connect | | responses() + * (this fn) v | | v + * worker-runtime.ts <======== postMessage ========> create-worker-host-runtime.ts + * chainConnectStart / chainSend / chainClose --> handleChainConnect* -> host.connect() + * chainConnectAck / chainResponse <-- (pumped from connection.responses()) + * + * Allocates a `connId`, posts `chainConnectStart`, and resolves a + * `{ send, close }` handle once the main thread acks. `send` posts `chainSend`, + * `close` posts `chainClose`, and every `chainResponse` for this `connId` is + * delivered to `onResponse`. + */ +function chainConnect( + genesisHash: string, + onResponse: (json: string) => void, +): Promise { + const connId = ++nextConnId; + return new Promise((resolve, reject) => { + chainConnectAcks.set(connId, (ack) => { + if (!ack.ok) { + chainResponseListeners.delete(connId); + reject(new Error(ack.error)); + return; + } + resolve({ + send(request: string) { + postToMain({ kind: "chainSend", connId, request }); + }, + close() { + chainResponseListeners.delete(connId); + postToMain({ kind: "chainClose", connId }); + }, + }); + }); + chainResponseListeners.set(connId, onResponse); + postToMain({ kind: "chainConnectStart", connId, genesisHash }); + }); +} + +/** + * Build the callback object passed to the WASM core. Most entries are + * generated proxy functions that bounce from the worker to the main window; + * `emitFrame` is filled here because it is the core-to-provider data path. + */ +function buildRawCallbacks() { + const callbacks = createWorkerRawCallbacks({ + callbackRequest, + startSubscription, + chainConnect, + }); + callbacks.emitFrame = (frame: Uint8Array): void => { + postToMain({ kind: "frame", bytes: frame }); + }; + callbacks.dispose = (): void => { + // Main thread terminates the worker, no separate cleanup needed here. + }; + return callbacks; +} + +let core: WorkerHostCore | null = null; +let wasm: WasmModuleShape | null = null; + +(async () => { + try { + wasm = await wasmModulePromise; + await wasm.default(); + postToMain({ kind: "loaded" }); + } catch (err) { + postToMain({ kind: "fatalError", error: errorMessage(err) }); + } +})(); + +ctx.addEventListener("message", (ev: MessageEvent) => { + const msg = ev.data; + switch (msg.kind) { + case "init": + if (!wasm) { + postToMain({ + kind: "fatalError", + error: "init received before WASM loaded", + }); + break; + } + if (core) { + postToMain({ + kind: "fatalError", + error: "init: core already initialized", + }); + break; + } + wasm.setLogLevel?.(msg.logLevel); + try { + core = new wasm.WasmHostCore(buildRawCallbacks(), msg.runtimeConfig); + postToMain({ kind: "ready" }); + } catch (err) { + postToMain({ kind: "fatalError", error: `init: ${errorMessage(err)}` }); + } + break; + case "setLogLevel": + wasm?.setLogLevel?.(msg.level); + break; + case "frame": + void handleFrame(msg.bytes); + break; + case "disconnectSession": + void handleDisconnectSession(msg.requestId); + break; + case "cancelPairing": + core?.cancelPairing(); + break; + case "notifySessionStoreChanged": + core?.notifySessionStoreChanged(); + break; + case "getPermissionAuthorizationStatus": + void handleGetPermissionAuthorizationStatus(msg.requestId, msg.request); + break; + case "getPermissionAuthorizationStatuses": + void handleGetPermissionAuthorizationStatuses( + msg.requestId, + msg.requests, + ); + break; + case "setPermissionAuthorizationStatus": + void handleSetPermissionAuthorizationStatus( + msg.requestId, + msg.request, + msg.status, + ); + break; + case "callbackResponse": { + const cb = pendingCallbacks.get(msg.requestId); + if (cb) { + pendingCallbacks.delete(msg.requestId); + cb( + msg.ok + ? { ok: true, value: msg.value } + : { ok: false, error: msg.error }, + ); + } + break; + } + case "subscriptionItem": { + const listener = subscriptionItemListeners.get(msg.subId); + if (listener) listener(msg.value); + break; + } + case "chainConnectAck": { + const cb = chainConnectAcks.get(msg.connId); + if (cb) { + chainConnectAcks.delete(msg.connId); + cb(msg.ok ? { ok: true } : { ok: false, error: msg.error }); + } + break; + } + case "chainResponse": { + const listener = chainResponseListeners.get(msg.connId); + if (listener) listener(msg.json); + break; + } + case "dispose": + try { + core?.dispose(); + core?.free(); + } catch (err) { + postToMain({ kind: "disposeError", error: errorMessage(err) }); + } + core = null; + break; + default: { + const { kind } = msg as { kind?: unknown }; + console.warn( + `[truapi worker-runtime] unknown message kind: ${String(kind)}`, + ); + } + } +}); + +async function handleDisconnectSession(requestId: number): Promise { + if (!core) { + postToMain({ + kind: "disconnectSessionResponse", + requestId, + ok: false, + error: "disconnectSession received before core is ready", + }); + return; + } + try { + await core.disconnectSession(); + postToMain({ kind: "disconnectSessionResponse", requestId, ok: true }); + } catch (err) { + postToMain({ + kind: "disconnectSessionResponse", + requestId, + ok: false, + error: errorMessage(err), + }); + } +} + +async function handleGetPermissionAuthorizationStatus( + requestId: number, + request: Uint8Array, +): Promise { + if (!core) { + postToMain({ + kind: "permissionAuthorizationStatusResponse", + requestId, + ok: false, + error: "permissionAuthorizationStatus received before core is ready", + }); + return; + } + try { + const status = await core.permissionAuthorizationStatus(request); + postToMain({ + kind: "permissionAuthorizationStatusResponse", + requestId, + ok: true, + status: status as PermissionAuthorizationStatus, + }); + } catch (err) { + postToMain({ + kind: "permissionAuthorizationStatusResponse", + requestId, + ok: false, + error: errorMessage(err), + }); + } +} + +async function handleGetPermissionAuthorizationStatuses( + requestId: number, + requests: Uint8Array[], +): Promise { + if (!core) { + postToMain({ + kind: "permissionAuthorizationStatusesResponse", + requestId, + ok: false, + error: "permissionAuthorizationStatuses received before core is ready", + }); + return; + } + try { + const statuses = await core.permissionAuthorizationStatuses(requests); + postToMain({ + kind: "permissionAuthorizationStatusesResponse", + requestId, + ok: true, + statuses: statuses as PermissionAuthorizationStatus[], + }); + } catch (err) { + postToMain({ + kind: "permissionAuthorizationStatusesResponse", + requestId, + ok: false, + error: errorMessage(err), + }); + } +} + +async function handleSetPermissionAuthorizationStatus( + requestId: number, + request: Uint8Array, + status: PermissionAuthorizationStatus, +): Promise { + if (!core) { + postToMain({ + kind: "setPermissionAuthorizationStatusResponse", + requestId, + ok: false, + error: "setPermissionAuthorizationStatus received before core is ready", + }); + return; + } + try { + await core.setPermissionAuthorizationStatus(request, status); + postToMain({ + kind: "setPermissionAuthorizationStatusResponse", + requestId, + ok: true, + }); + } catch (err) { + postToMain({ + kind: "setPermissionAuthorizationStatusResponse", + requestId, + ok: false, + error: errorMessage(err), + }); + } +} + +async function handleFrame(bytes: Uint8Array): Promise { + if (!core) { + postToMain({ + kind: "frameError", + error: "frame received before core is ready", + }); + return; + } + try { + await core.receiveFrame(bytes); + } catch (err) { + postToMain({ + kind: "frameError", + error: errorMessage(err), + }); + } +} diff --git a/js/packages/truapi-host-wasm/tsconfig.json b/js/packages/truapi-host-wasm/tsconfig.json new file mode 100644 index 00000000..9d2dcad8 --- /dev/null +++ b/js/packages/truapi-host-wasm/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "composite": true, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "esModuleInterop": true, + "skipLibCheck": true, + "lib": ["ES2022", "DOM", "WebWorker"] + }, + "include": ["src"], + "exclude": ["src/**/*.test.ts", "src/test-support.ts"], + "references": [{ "path": "../truapi" }] +} diff --git a/js/packages/truapi/package.json b/js/packages/truapi/package.json index 6b8e2f61..db961565 100644 --- a/js/packages/truapi/package.json +++ b/js/packages/truapi/package.json @@ -66,7 +66,7 @@ }, "scripts": { "ensure-generated": "./scripts/ensure-generated.sh", - "build": "tsc", + "build": "tsc -b", "prebuild": "npm run ensure-generated", "codegen": "cargo run -p truapi-codegen -- --input ../../../target/doc/truapi.json --output src/generated --playground-output src/playground --explorer-output src/explorer", "typecheck": "npm run build", diff --git a/js/packages/truapi/src/client.ts b/js/packages/truapi/src/client.ts index fc13022d..f45481f4 100644 --- a/js/packages/truapi/src/client.ts +++ b/js/packages/truapi/src/client.ts @@ -13,13 +13,15 @@ import { type WireProvider, } from "./transport.js"; import { + CallError, indexedTaggedUnion, Result, _void, + type CallErrorValue, type Codec, type ResultPayload, } from "./scale.js"; -import { TRUAPI_CODEC_VERSION, TRUAPI_VERSION } from "./generated/client.js"; +import { TRUAPI_CODEC_VERSION } from "./generated/client.js"; import * as T from "./generated/types.js"; import * as W from "./generated/wire-table.js"; @@ -29,13 +31,12 @@ export type { Subscription, TrUApiTransport }; * Version overrides used when constructing a transport. */ export interface CreateTransportOptions { - /** - * Highest TrUAPI protocol version exposed by the transport. - */ - truapiVersion?: number; - /** * SCALE codec version advertised during host handshake negotiation. + * + * @deprecated TODO(shared-core-wire): remove this override with + * `TrUApiTransport.codecVersion` once generated handshake requests use + * `TRUAPI_CODEC_VERSION` directly. */ codecVersion?: number; } @@ -51,7 +52,10 @@ function protocolVersionTag(version: number): `V${number}` { return `V${version}` as `V${number}`; } -type HandshakeResponse = ResultPayload; +type HandshakeResponse = ResultPayload< + undefined, + CallErrorValue +>; const HANDSHAKE_WIRE_VERSION = 1; /** @@ -63,7 +67,7 @@ function handshakeResponseCodec( return indexedTaggedUnion({ [protocolVersionTag(version)]: [ version - 1, - Result(_void, T.HostHandshakeError), + Result(_void, CallError(T.VersionedHostHandshakeError)), ] as const, }) as Codec<{ tag: `V${number}`; value: HandshakeResponse }>; } @@ -90,8 +94,14 @@ function encodeUnsupportedHandshakeResponse(version: number): Uint8Array { value: { success: false, value: { - tag: "UnsupportedProtocolVersion", - value: undefined, + tag: "Domain", + value: { + tag: "V1", + value: { + tag: "UnsupportedProtocolVersion", + value: undefined, + }, + }, }, }, }); @@ -140,7 +150,6 @@ export function createTransport( provider: WireProvider, options: CreateTransportOptions = {}, ): TrUApiTransport { - const truapiVersion = options.truapiVersion ?? TRUAPI_VERSION; const codecVersion = options.codecVersion ?? TRUAPI_CODEC_VERSION; let idCounter = 0; let closedError: Error | null = null; @@ -305,7 +314,6 @@ export function createTransport( } return { - truapiVersion, codecVersion, /** * Send one request frame and resolve with the typed Ok/Err outcome diff --git a/js/packages/truapi/src/explorer/codegen/versions/0.3.2/services.ts b/js/packages/truapi/src/explorer/codegen/versions/0.3.2/services.ts deleted file mode 100644 index 8916c096..00000000 --- a/js/packages/truapi/src/explorer/codegen/versions/0.3.2/services.ts +++ /dev/null @@ -1,724 +0,0 @@ -// Auto-generated by truapi-codegen. Do not edit. -import type { ServiceInfo } from "../../../../playground/services-types.js"; - -export const services: ServiceInfo[] = [ - { - name: "Account", - methods: [ - { - name: "connection_status_subscribe", - type: "subscription", - signature: - "connectionStatusSubscribe(): ObservableLike", - docUrl: - "api/account/trait.Account.html#method.connection_status_subscribe", - description: "Subscribe to account connection status changes.", - responseType: "host-account-connection-status-subscribe-item", - }, - { - name: "get_account", - type: "unary", - signature: - "getAccount(request: HostAccountGetRequest): Promise>", - docUrl: "api/account/trait.Account.html#method.get_account", - description: "Retrieve a product-scoped account.", - requestDescription: "HostAccountGetRequest", - requestType: "host-account-get-request", - responseType: "host-account-get-response", - errorType: "host-account-get-error", - }, - { - name: "get_account_alias", - type: "unary", - signature: - "getAccountAlias(request: HostAccountGetAliasRequest): Promise>", - docUrl: "api/account/trait.Account.html#method.get_account_alias", - description: "Retrieve a contextual alias for a product account.", - requestDescription: "HostAccountGetAliasRequest", - requestType: "host-account-get-alias-request", - responseType: "host-account-get-alias-response", - errorType: "host-account-get-error", - }, - { - name: "create_account_proof", - type: "unary", - signature: - "createAccountProof(request: HostAccountCreateProofRequest): Promise>", - docUrl: "api/account/trait.Account.html#method.create_account_proof", - description: "Generate a ring VRF proof for a product account.", - requestDescription: "HostAccountCreateProofRequest", - requestType: "host-account-create-proof-request", - responseType: "host-account-create-proof-response", - errorType: "host-account-create-proof-error", - }, - { - name: "get_legacy_accounts", - type: "unary", - signature: - "getLegacyAccounts(): Promise>", - docUrl: "api/account/trait.Account.html#method.get_legacy_accounts", - description: "List non-product accounts the user owns.", - responseType: "host-get-legacy-accounts-response", - errorType: "host-account-get-error", - }, - { - name: "get_user_id", - type: "unary", - signature: - "getUserId(): Promise>", - docUrl: "api/account/trait.Account.html#method.get_user_id", - description: "Fetch the user's primary identity.", - responseType: "host-get-user-id-response", - errorType: "host-get-user-id-error", - }, - { - name: "request_login", - type: "unary", - signature: - "requestLogin(request: HostRequestLoginRequest): Promise>", - docUrl: "api/account/trait.Account.html#method.request_login", - description: - 'Request the host to present the login flow to the user.\n\nProducts should call this in response to a user action (e.g. tapping a\n"Sign in" button), not automatically on load.', - requestDescription: "HostRequestLoginRequest", - requestType: "host-request-login-request", - responseType: "host-request-login-response", - errorType: "host-request-login-error", - }, - ], - }, - { - name: "Chain", - methods: [ - { - name: "follow_head_subscribe", - type: "subscription", - signature: - "followHeadSubscribe(request: RemoteChainHeadFollowRequest): ObservableLike", - docUrl: "api/chain/trait.Chain.html#method.follow_head_subscribe", - description: "Follow the chain head and receive block events.", - requestDescription: "RemoteChainHeadFollowRequest", - requestType: "remote-chain-head-follow-request", - responseType: "remote-chain-head-follow-item", - }, - { - name: "get_head_header", - type: "unary", - signature: - "getHeadHeader(request: RemoteChainHeadHeaderRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.get_head_header", - description: "Fetch a block header.", - requestDescription: "RemoteChainHeadHeaderRequest", - requestType: "remote-chain-head-header-request", - responseType: "remote-chain-head-header-response", - errorType: "generic-error", - }, - { - name: "get_head_body", - type: "unary", - signature: - "getHeadBody(request: RemoteChainHeadBodyRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.get_head_body", - description: "Fetch a block body.", - requestDescription: "RemoteChainHeadBodyRequest", - requestType: "remote-chain-head-body-request", - responseType: "remote-chain-head-body-response", - errorType: "generic-error", - }, - { - name: "get_head_storage", - type: "unary", - signature: - "getHeadStorage(request: RemoteChainHeadStorageRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.get_head_storage", - description: "Query runtime storage at a specific block.", - requestDescription: "RemoteChainHeadStorageRequest", - requestType: "remote-chain-head-storage-request", - responseType: "remote-chain-head-storage-response", - errorType: "generic-error", - }, - { - name: "call_head", - type: "unary", - signature: - "callHead(request: RemoteChainHeadCallRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.call_head", - description: "Invoke a runtime call at a specific block.", - requestDescription: "RemoteChainHeadCallRequest", - requestType: "remote-chain-head-call-request", - responseType: "remote-chain-head-call-response", - errorType: "generic-error", - }, - { - name: "unpin_head", - type: "unary", - signature: - "unpinHead(request: RemoteChainHeadUnpinRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.unpin_head", - description: "Release pinned blocks.", - requestDescription: "RemoteChainHeadUnpinRequest", - requestType: "remote-chain-head-unpin-request", - errorType: "generic-error", - }, - { - name: "continue_head", - type: "unary", - signature: - "continueHead(request: RemoteChainHeadContinueRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.continue_head", - description: "Continue a paused chain-head operation.", - requestDescription: "RemoteChainHeadContinueRequest", - requestType: "remote-chain-head-continue-request", - errorType: "generic-error", - }, - { - name: "stop_head_operation", - type: "unary", - signature: - "stopHeadOperation(request: RemoteChainHeadStopOperationRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.stop_head_operation", - description: "Stop a chain-head operation.", - requestDescription: "RemoteChainHeadStopOperationRequest", - requestType: "remote-chain-head-stop-operation-request", - errorType: "generic-error", - }, - { - name: "get_spec_genesis_hash", - type: "unary", - signature: - "getSpecGenesisHash(request: RemoteChainSpecGenesisHashRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.get_spec_genesis_hash", - description: "Fetch the canonical genesis hash for a chain.", - requestDescription: "RemoteChainSpecGenesisHashRequest", - requestType: "remote-chain-spec-genesis-hash-request", - responseType: "remote-chain-spec-genesis-hash-response", - errorType: "generic-error", - }, - { - name: "get_spec_chain_name", - type: "unary", - signature: - "getSpecChainName(request: RemoteChainSpecChainNameRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.get_spec_chain_name", - description: "Fetch the display name of a chain.", - requestDescription: "RemoteChainSpecChainNameRequest", - requestType: "remote-chain-spec-chain-name-request", - responseType: "remote-chain-spec-chain-name-response", - errorType: "generic-error", - }, - { - name: "get_spec_properties", - type: "unary", - signature: - "getSpecProperties(request: RemoteChainSpecPropertiesRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.get_spec_properties", - description: "Fetch the JSON-encoded properties of a chain.", - requestDescription: "RemoteChainSpecPropertiesRequest", - requestType: "remote-chain-spec-properties-request", - responseType: "remote-chain-spec-properties-response", - errorType: "generic-error", - }, - { - name: "broadcast_transaction", - type: "unary", - signature: - "broadcastTransaction(request: RemoteChainTransactionBroadcastRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.broadcast_transaction", - description: "Broadcast a signed transaction.", - requestDescription: "RemoteChainTransactionBroadcastRequest", - requestType: "remote-chain-transaction-broadcast-request", - responseType: "remote-chain-transaction-broadcast-response", - errorType: "generic-error", - }, - { - name: "stop_transaction", - type: "unary", - signature: - "stopTransaction(request: RemoteChainTransactionStopRequest): Promise>", - docUrl: "api/chain/trait.Chain.html#method.stop_transaction", - description: "Stop a transaction broadcast.", - requestDescription: "RemoteChainTransactionStopRequest", - requestType: "remote-chain-transaction-stop-request", - errorType: "generic-error", - }, - ], - }, - { - name: "Chat", - methods: [ - { - name: "create_room", - type: "unary", - signature: - "createRoom(request: HostChatCreateRoomRequest): Promise>", - docUrl: "api/chat/trait.Chat.html#method.create_room", - description: "Create a chat room.", - requestDescription: "HostChatCreateRoomRequest", - requestType: "host-chat-create-room-request", - responseType: "host-chat-create-room-response", - errorType: "host-chat-create-room-error", - }, - { - name: "register_bot", - type: "unary", - signature: - "registerBot(request: HostChatRegisterBotRequest): Promise>", - docUrl: "api/chat/trait.Chat.html#method.register_bot", - description: "Register a chat bot.", - requestDescription: "HostChatRegisterBotRequest", - requestType: "host-chat-register-bot-request", - responseType: "host-chat-register-bot-response", - errorType: "host-chat-register-bot-error", - }, - { - name: "list_subscribe", - type: "subscription", - signature: "listSubscribe(): ObservableLike", - docUrl: "api/chat/trait.Chat.html#method.list_subscribe", - description: "Subscribe to the list of chat rooms.", - responseType: "host-chat-list-subscribe-item", - }, - { - name: "post_message", - type: "unary", - signature: - "postMessage(request: HostChatPostMessageRequest): Promise>", - docUrl: "api/chat/trait.Chat.html#method.post_message", - description: "Post a message to a chat room.", - requestDescription: "HostChatPostMessageRequest", - requestType: "host-chat-post-message-request", - responseType: "host-chat-post-message-response", - errorType: "host-chat-post-message-error", - }, - { - name: "action_subscribe", - type: "subscription", - signature: - "actionSubscribe(): ObservableLike", - docUrl: "api/chat/trait.Chat.html#method.action_subscribe", - description: "Subscribe to received chat actions.", - responseType: "host-chat-action-subscribe-item", - }, - { - name: "custom_message_render_subscribe", - type: "subscription", - signature: - "customMessageRenderSubscribe(request: ProductChatCustomMessageRenderSubscribeRequest): ObservableLike", - docUrl: - "api/chat/trait.Chat.html#method.custom_message_render_subscribe", - description: - "Subscribe to custom message render requests from the host. Each\nemitted item is a [`CustomRendererNode`](crate::v01::CustomRendererNode)\ntree describing the rendered UI.", - requestDescription: "ProductChatCustomMessageRenderSubscribeRequest", - requestType: "product-chat-custom-message-render-subscribe-request", - responseType: "custom-renderer-node", - }, - ], - }, - { - name: "Entropy", - methods: [ - { - name: "derive", - type: "unary", - signature: - "derive(request: HostDeriveEntropyRequest): Promise>", - docUrl: "api/entropy/trait.Entropy.html#method.derive", - description: "Derive deterministic entropy.", - requestDescription: "HostDeriveEntropyRequest", - requestType: "host-derive-entropy-request", - responseType: "host-derive-entropy-response", - errorType: "host-derive-entropy-error", - }, - ], - }, - { - name: "Local Storage", - methods: [ - { - name: "read", - type: "unary", - signature: - "read(request: HostLocalStorageReadRequest): Promise>", - docUrl: "api/local_storage/trait.LocalStorage.html#method.read", - description: "Read a value by key.", - requestDescription: "HostLocalStorageReadRequest", - requestType: "host-local-storage-read-request", - responseType: "host-local-storage-read-response", - errorType: "host-local-storage-read-error", - }, - { - name: "write", - type: "unary", - signature: - "write(request: HostLocalStorageWriteRequest): Promise>", - docUrl: "api/local_storage/trait.LocalStorage.html#method.write", - description: "Write a value to a key.", - requestDescription: "HostLocalStorageWriteRequest", - requestType: "host-local-storage-write-request", - errorType: "host-local-storage-read-error", - }, - { - name: "clear", - type: "unary", - signature: - "clear(request: HostLocalStorageClearRequest): Promise>", - docUrl: "api/local_storage/trait.LocalStorage.html#method.clear", - description: "Clear a value by key.", - requestDescription: "HostLocalStorageClearRequest", - requestType: "host-local-storage-clear-request", - errorType: "host-local-storage-read-error", - }, - ], - }, - { - name: "Notifications", - methods: [ - { - name: "send_push_notification", - type: "unary", - signature: - "sendPushNotification(request: HostPushNotificationRequest): Promise>", - docUrl: - "api/notifications/trait.Notifications.html#method.send_push_notification", - description: - "Send a push notification to the user.\n\nReturns a [`NotificationId`](crate::v01::NotificationId) that can be\npassed to [`cancel_push_notification`](Self::cancel_push_notification)\nto retract a scheduled notification. When `scheduled_at` is set the host\npersists the notification across restarts and fires it through the\nplatform-native scheduler. See [RFC 0019].\n\n[RFC 0019]: https://github.com/paritytech/truapi/blob/main/docs/rfcs/0019-scheduled-notifications.md", - requestDescription: "HostPushNotificationRequest", - requestType: "host-push-notification-request", - responseType: "host-push-notification-response", - errorType: "host-push-notification-error", - }, - { - name: "cancel_push_notification", - type: "unary", - signature: - "cancelPushNotification(request: HostPushNotificationCancelRequest): Promise>", - docUrl: - "api/notifications/trait.Notifications.html#method.cancel_push_notification", - description: - "Cancels a previously issued push notification.\n\nCancellation is idempotent: returns `Ok(())` whether the notification is\nstill pending, already fired, or was never issued. See [RFC 0019].\n\n[RFC 0019]: https://github.com/paritytech/truapi/blob/main/docs/rfcs/0019-scheduled-notifications.md", - requestDescription: "HostPushNotificationCancelRequest", - requestType: "host-push-notification-cancel-request", - errorType: "generic-error", - }, - ], - }, - { - name: "Payment", - methods: [ - { - name: "balance_subscribe", - type: "subscription", - signature: - "balanceSubscribe(request: HostPaymentBalanceSubscribeRequest): ObservableLike", - docUrl: "api/payment/trait.Payment.html#method.balance_subscribe", - description: "Subscribe to payment balance updates.", - requestDescription: "HostPaymentBalanceSubscribeRequest", - requestType: "host-payment-balance-subscribe-request", - responseType: "host-payment-balance-subscribe-item", - errorType: "host-payment-balance-subscribe-error", - }, - { - name: "top_up", - type: "unary", - signature: - "topUp(request: HostPaymentTopUpRequest): Promise>", - docUrl: "api/payment/trait.Payment.html#method.top_up", - description: "Top up the user's payment balance.", - requestDescription: "HostPaymentTopUpRequest", - requestType: "host-payment-top-up-request", - errorType: "host-payment-top-up-error", - }, - { - name: "request", - type: "unary", - signature: - "request(request: HostPaymentRequest): Promise>", - docUrl: "api/payment/trait.Payment.html#method.request", - description: "Request a payment from the user.", - requestDescription: "HostPaymentRequest", - requestType: "host-payment-request", - responseType: "host-payment-response", - errorType: "host-payment-error", - }, - { - name: "status_subscribe", - type: "subscription", - signature: - "statusSubscribe(request: HostPaymentStatusSubscribeRequest): ObservableLike", - docUrl: "api/payment/trait.Payment.html#method.status_subscribe", - description: - "Subscribe to payment lifecycle updates for a specific payment.", - requestDescription: "HostPaymentStatusSubscribeRequest", - requestType: "host-payment-status-subscribe-request", - responseType: "host-payment-status-subscribe-item", - errorType: "host-payment-status-subscribe-error", - }, - ], - }, - { - name: "Permissions", - methods: [ - { - name: "request_device_permission", - type: "unary", - signature: - "requestDevicePermission(request: HostDevicePermissionRequest): Promise>", - docUrl: - "api/permissions/trait.Permissions.html#method.request_device_permission", - description: "Request a device-capability permission from the user.", - requestDescription: - "Enum values: Notifications / Camera / Microphone / Bluetooth / NFC / Location / Clipboard / OpenUrl / Biometrics", - requestType: "host-device-permission-request", - responseType: "host-device-permission-response", - errorType: "generic-error", - }, - { - name: "request_remote_permission", - type: "unary", - signature: - "requestRemotePermission(request: RemotePermissionRequest): Promise>", - docUrl: - "api/permissions/trait.Permissions.html#method.request_remote_permission", - description: "Request a remote-operation permission.", - requestDescription: "RemotePermissionRequest", - requestType: "remote-permission-request", - responseType: "remote-permission-response", - errorType: "generic-error", - }, - ], - }, - { - name: "Preimage", - methods: [ - { - name: "lookup_subscribe", - type: "subscription", - signature: - "lookupSubscribe(request: RemotePreimageLookupSubscribeRequest): ObservableLike", - docUrl: "api/preimage/trait.Preimage.html#method.lookup_subscribe", - description: "Subscribe to preimage lookups for a given key.", - requestDescription: "RemotePreimageLookupSubscribeRequest", - requestType: "remote-preimage-lookup-subscribe-request", - responseType: "remote-preimage-lookup-subscribe-item", - }, - { - name: "submit", - type: "unary", - signature: - "submit(request: HexString): Promise>", - docUrl: "api/preimage/trait.Preimage.html#method.submit", - description: - "Submit a preimage. Returns the preimage key (hash) on success.", - requestDescription: "HexString", - errorType: "preimage-submit-error", - }, - ], - }, - { - name: "Resource Allocation", - methods: [ - { - name: "request", - type: "unary", - signature: - "request(request: HostRequestResourceAllocationRequest): Promise>", - docUrl: - "api/resource_allocation/trait.ResourceAllocation.html#method.request", - description: "Request the host to pre-allocate one or more resources.", - requestDescription: "HostRequestResourceAllocationRequest", - requestType: "host-request-resource-allocation-request", - responseType: "host-request-resource-allocation-response", - errorType: "resource-allocation-error", - }, - ], - }, - { - name: "Signing", - methods: [ - { - name: "create_transaction", - type: "unary", - signature: - "createTransaction(request: ProductAccountTxPayload): Promise>", - docUrl: "api/signing/trait.Signing.html#method.create_transaction", - description: "Construct a signed transaction for a product account.", - requestDescription: "ProductAccountTxPayload", - requestType: "product-account-tx-payload", - responseType: "host-create-transaction-response", - errorType: "host-create-transaction-error", - }, - { - name: "create_transaction_with_legacy_account", - type: "unary", - signature: - "createTransactionWithLegacyAccount(request: LegacyAccountTxPayload): Promise>", - docUrl: - "api/signing/trait.Signing.html#method.create_transaction_with_legacy_account", - description: - "Construct a signed transaction for a non-product (legacy) account.", - requestDescription: "LegacyAccountTxPayload", - requestType: "legacy-account-tx-payload", - responseType: "host-create-transaction-with-legacy-account-response", - errorType: "host-create-transaction-error", - }, - { - name: "sign_raw_with_legacy_account", - type: "unary", - signature: - "signRawWithLegacyAccount(request: HostSignRawWithLegacyAccountRequest): Promise>", - docUrl: - "api/signing/trait.Signing.html#method.sign_raw_with_legacy_account", - description: "Sign raw bytes with a non-product account.", - requestDescription: "HostSignRawWithLegacyAccountRequest", - requestType: "host-sign-raw-with-legacy-account-request", - responseType: "host-sign-payload-response", - errorType: "host-sign-payload-error", - }, - { - name: "sign_payload_with_legacy_account", - type: "unary", - signature: - "signPayloadWithLegacyAccount(request: HostSignPayloadWithLegacyAccountRequest): Promise>", - docUrl: - "api/signing/trait.Signing.html#method.sign_payload_with_legacy_account", - description: "Sign an extrinsic payload with a non-product account.", - requestDescription: "HostSignPayloadWithLegacyAccountRequest", - requestType: "host-sign-payload-with-legacy-account-request", - responseType: "host-sign-payload-response", - errorType: "host-sign-payload-error", - }, - { - name: "sign_raw", - type: "unary", - signature: - "signRaw(request: HostSignRawRequest): Promise>", - docUrl: "api/signing/trait.Signing.html#method.sign_raw", - description: "Sign raw bytes or a message.", - requestDescription: "HostSignRawRequest", - requestType: "host-sign-raw-request", - responseType: "host-sign-payload-response", - errorType: "host-sign-payload-error", - }, - { - name: "sign_payload", - type: "unary", - signature: - "signPayload(request: HostSignPayloadRequest): Promise>", - docUrl: "api/signing/trait.Signing.html#method.sign_payload", - description: "Sign an extrinsic payload.", - requestDescription: "HostSignPayloadRequest", - requestType: "host-sign-payload-request", - responseType: "host-sign-payload-response", - errorType: "host-sign-payload-error", - }, - ], - }, - { - name: "Statement Store", - methods: [ - { - name: "subscribe", - type: "subscription", - signature: - "subscribe(request: RemoteStatementStoreSubscribeRequest): ObservableLike", - docUrl: - "api/statement_store/trait.StatementStore.html#method.subscribe", - description: "Subscribe to statements matching a topic filter.", - requestDescription: "RemoteStatementStoreSubscribeRequest", - requestType: "remote-statement-store-subscribe-request", - responseType: "remote-statement-store-subscribe-item", - }, - { - name: "create_proof", - type: "unary", - signature: - "createProof(request: RemoteStatementStoreCreateProofRequest): Promise>", - docUrl: - "api/statement_store/trait.StatementStore.html#method.create_proof", - description: - "Create a proof for a statement.\n\n**Deprecated:** use [`create_proof_authorized`](Self::create_proof_authorized)\ninstead, which uses a pre-allocated allowance account and does not\nrequire a per-call signing prompt.", - requestDescription: "RemoteStatementStoreCreateProofRequest", - requestType: "remote-statement-store-create-proof-request", - responseType: "remote-statement-store-create-proof-response", - errorType: "remote-statement-store-create-proof-error", - }, - { - name: "submit", - type: "unary", - signature: - "submit(request: SignedStatement): Promise>", - docUrl: "api/statement_store/trait.StatementStore.html#method.submit", - description: - "Submit a signed statement to the network. The request body is the\n[`SignedStatement`](crate::v01::SignedStatement) directly (no wrapping\nstruct), matching upstream `triangle-js-sdks`.", - requestDescription: "SignedStatement", - requestType: "signed-statement", - errorType: "generic-error", - }, - { - name: "create_proof_authorized", - type: "unary", - signature: - "createProofAuthorized(request: Statement): Promise>", - docUrl: - "api/statement_store/trait.StatementStore.html#method.create_proof_authorized", - description: - "Create a proof for a statement using a pre-allocated allowance account,\nbypassing the per-call signing prompt.", - requestDescription: "Statement", - requestType: "statement", - responseType: "remote-statement-store-create-proof-response", - errorType: "remote-statement-store-create-proof-error", - }, - ], - }, - { - name: "System", - methods: [ - { - name: "handshake", - type: "unary", - signature: - "handshake(request: HostHandshakeRequest): Promise>", - docUrl: "api/system/trait.System.html#method.handshake", - description: "Negotiate the wire codec version with the product.", - requestDescription: "HostHandshakeRequest", - requestType: "host-handshake-request", - errorType: "host-handshake-error", - }, - { - name: "feature_supported", - type: "unary", - signature: - "featureSupported(request: HostFeatureSupportedRequest): Promise>", - docUrl: "api/system/trait.System.html#method.feature_supported", - description: "Query whether the host supports a specific feature.", - requestDescription: "HostFeatureSupportedRequest", - requestType: "host-feature-supported-request", - responseType: "host-feature-supported-response", - errorType: "generic-error", - }, - { - name: "navigate_to", - type: "unary", - signature: - "navigateTo(request: HostNavigateToRequest): Promise>", - docUrl: "api/system/trait.System.html#method.navigate_to", - description: "Request the host to open a URL.", - requestDescription: "HostNavigateToRequest", - requestType: "host-navigate-to-request", - errorType: "host-navigate-to-error", - }, - ], - }, - { - name: "Theme", - methods: [ - { - name: "subscribe", - type: "subscription", - signature: "subscribe(): ObservableLike", - docUrl: "api/theme/trait.Theme.html#method.subscribe", - description: "Subscribe to host theme changes.", - responseType: "host-theme-subscribe-item", - }, - ], - }, -]; diff --git a/js/packages/truapi/src/explorer/codegen/versions/0.3.2/types.ts b/js/packages/truapi/src/explorer/codegen/versions/0.3.2/types.ts deleted file mode 100644 index 37f778be..00000000 --- a/js/packages/truapi/src/explorer/codegen/versions/0.3.2/types.ts +++ /dev/null @@ -1,3836 +0,0 @@ -// Auto-generated by truapi-codegen. Do not edit. -import type { DataType } from "../../../data-types.js"; - -export const types: DataType[] = [ - { - id: "account-id", - name: "AccountId", - category: "transaction", - definition: "export type AccountId = HexString;", - description: - "A 32-byte raw account identifier used for legacy (non-product) accounts.", - }, - { - id: "action-trigger", - name: "ActionTrigger", - category: "chat", - definition: - "export interface ActionTrigger {\n messageId: string;\n actionId: string;\n payload?: HexString;\n}", - description: "Payload when a user clicks an action button.", - fields: [ - { - name: "message_id", - type: "string", - description: "Message containing the action.", - }, - { - name: "action_id", - type: "string", - description: "Which action was triggered.", - }, - { - name: "payload", - type: "HexString | undefined", - description: "Optional additional data.", - }, - ], - }, - { - id: "allocatable-resource", - name: "AllocatableResource", - category: "resource_allocation", - definition: - 'export type AllocatableResource =\n | { tag: "StatementStoreAllowance"; value?: undefined }\n | { tag: "BulletinAllowance"; value?: undefined }\n | { tag: "SmartContractAllowance"; value: number }\n | { tag: "AutoSigning"; value?: undefined }\n;', - description: - "A resource the host can pre-allocate on behalf of the product (RFC 0010).\n\nFor the slot-table allowances (`StatementStoreAllowance`,\n`BulletinAllowance`, `SmartContractAllowance`), pre-allocation is\nopportunistic and the host may also fulfil the allowance implicitly on the\nfirst submission. `AutoSigning` must be requested explicitly through this\ncall.", - variants: [ - { - name: "StatementStoreAllowance", - type: '{ tag: "StatementStoreAllowance"; value?: undefined }', - description: - "Statement Store slot allowance for the product's own allowance account.", - }, - { - name: "BulletinAllowance", - type: '{ tag: "BulletinAllowance"; value?: undefined }', - description: - "Bulletin chain slot allowance for the product's own allowance account.", - }, - { - name: "SmartContractAllowance", - type: '{ tag: "SmartContractAllowance"; value: number }', - description: - "Pre-warmed PGAS balance for the smart-contract account at the given\nderivation index.", - }, - { - name: "AutoSigning", - type: '{ tag: "AutoSigning"; value?: undefined }', - description: - "Permission to sign on the product's behalf without per-call user prompts.", - }, - ], - }, - { - id: "allocation-outcome", - name: "AllocationOutcome", - category: "resource_allocation", - definition: - 'export type AllocationOutcome = "Allocated" | "Rejected" | "NotAvailable";', - description: "Outcome of allocating a single resource (RFC 0010).", - variants: [ - { - name: "Allocated", - type: '{ tag: "Allocated"; value?: undefined }', - description: "Resource is now available for use.", - }, - { - name: "Rejected", - type: '{ tag: "Rejected"; value?: undefined }', - description: "User or host refused the allocation.", - }, - { - name: "NotAvailable", - type: '{ tag: "NotAvailable"; value?: undefined }', - description: - "Host cannot provide this resource on the current chain or environment.", - }, - ], - }, - { - id: "arrangement", - name: "Arrangement", - category: "chat", - definition: - 'export type Arrangement = "Start" | "End" | "Center" | "SpaceBetween" | "SpaceAround" | "SpaceEvenly";', - description: "Layout arrangement (like CSS flexbox `justify-content`).", - variants: [ - { - name: "Start", - type: '{ tag: "Start"; value?: undefined }', - }, - { - name: "End", - type: '{ tag: "End"; value?: undefined }', - }, - { - name: "Center", - type: '{ tag: "Center"; value?: undefined }', - }, - { - name: "SpaceBetween", - type: '{ tag: "SpaceBetween"; value?: undefined }', - }, - { - name: "SpaceAround", - type: '{ tag: "SpaceAround"; value?: undefined }', - }, - { - name: "SpaceEvenly", - type: '{ tag: "SpaceEvenly"; value?: undefined }', - }, - ], - }, - { - id: "background", - name: "Background", - category: "chat", - definition: - "export interface Background {\n color: ColorToken;\n shape?: Shape;\n}", - description: "Background styling.", - fields: [ - { - name: "color", - type: "ColorToken", - description: "Background color.", - }, - { - name: "shape", - type: "Shape | undefined", - description: "Background shape.", - }, - ], - }, - { - id: "balance", - name: "Balance", - category: "payment", - definition: "export type Balance = bigint;", - description: - "Balance amount for payment operations. Interpreted according to the host's\nsingle fixed payment asset (e.g. pUSD).", - }, - { - id: "border-style", - name: "BorderStyle", - category: "chat", - definition: - "export interface BorderStyle {\n width: Size;\n color: ColorToken;\n shape?: Shape;\n}", - description: "Border styling.", - fields: [ - { - name: "width", - type: "Size", - description: "Border width.", - }, - { - name: "color", - type: "ColorToken", - description: "Border color.", - }, - { - name: "shape", - type: "Shape | undefined", - description: "Border shape.", - }, - ], - }, - { - id: "box-props", - name: "BoxProps", - category: "chat", - definition: - "export interface BoxProps {\n contentAlignment?: ContentAlignment;\n}", - description: "Properties for a [`CustomRendererNode::Box`] container.", - fields: [ - { - name: "content_alignment", - type: "ContentAlignment | undefined", - description: "Content alignment within the box.", - }, - ], - }, - { - id: "button-props", - name: "ButtonProps", - category: "chat", - definition: - "export interface ButtonProps {\n text: string;\n variant?: ButtonVariant;\n enabled: boolean | undefined;\n loading: boolean | undefined;\n clickAction?: string;\n}", - description: "Properties for a [`CustomRendererNode::Button`].", - fields: [ - { - name: "text", - type: "string", - description: "Button label text.", - }, - { - name: "variant", - type: "ButtonVariant | undefined", - description: "Button style variant.", - }, - { - name: "enabled", - type: "boolean | undefined", - description: - "Whether the button is enabled. Absent leaves the default to the host.", - }, - { - name: "loading", - type: "boolean | undefined", - description: - "Whether the button shows a loading state. Absent leaves the default to the host.", - }, - { - name: "click_action", - type: "string | undefined", - description: "Action identifier triggered on click.", - }, - ], - }, - { - id: "button-variant", - name: "ButtonVariant", - category: "chat", - definition: 'export type ButtonVariant = "Primary" | "Secondary" | "Text";', - description: "Button style variants.", - variants: [ - { - name: "Primary", - type: '{ tag: "Primary"; value?: undefined }', - }, - { - name: "Secondary", - type: '{ tag: "Secondary"; value?: undefined }', - }, - { - name: "Text", - type: '{ tag: "Text"; value?: undefined }', - }, - ], - }, - { - id: "chat-action", - name: "ChatAction", - category: "chat", - definition: - "export interface ChatAction {\n actionId: string;\n title: string;\n}", - description: "A clickable action button in a chat message.", - fields: [ - { - name: "action_id", - type: "string", - description: "Action identifier.", - }, - { - name: "title", - type: "string", - description: "Button label.", - }, - ], - }, - { - id: "chat-action-layout", - name: "ChatActionLayout", - category: "chat", - definition: 'export type ChatActionLayout = "Column" | "Grid";', - description: "Layout for action buttons.", - variants: [ - { - name: "Column", - type: '{ tag: "Column"; value?: undefined }', - }, - { - name: "Grid", - type: '{ tag: "Grid"; value?: undefined }', - }, - ], - }, - { - id: "chat-action-payload", - name: "ChatActionPayload", - category: "chat", - definition: - 'export type ChatActionPayload =\n | { tag: "MessagePosted"; value: ChatMessageContent }\n | { tag: "ActionTriggered"; value: ActionTrigger }\n | { tag: "Command"; value: ChatCommand }\n;', - description: "Payload of a received chat action.", - variants: [ - { - name: "MessagePosted", - type: '{ tag: "MessagePosted"; value: ChatMessageContent }', - description: "A peer posted a message.", - }, - { - name: "ActionTriggered", - type: '{ tag: "ActionTriggered"; value: ActionTrigger }', - description: "A user triggered an action button.", - }, - { - name: "Command", - type: '{ tag: "Command"; value: ChatCommand }', - description: "A user issued a command.", - }, - ], - }, - { - id: "chat-actions", - name: "ChatActions", - category: "chat", - definition: - "export interface ChatActions {\n text?: string;\n actions: Array;\n layout: ChatActionLayout;\n}", - description: "A set of action buttons with optional text.", - fields: [ - { - name: "text", - type: "string | undefined", - description: "Optional message text.", - }, - { - name: "actions", - type: "Array", - description: "List of action buttons.", - }, - { - name: "layout", - type: "ChatActionLayout", - description: "`Column` or `Grid` layout.", - }, - ], - }, - { - id: "chat-bot-registration-status", - name: "ChatBotRegistrationStatus", - category: "chat", - definition: 'export type ChatBotRegistrationStatus = "New" | "Exists";', - description: "Whether the bot was newly registered or already existed.", - variants: [ - { - name: "New", - type: '{ tag: "New"; value?: undefined }', - }, - { - name: "Exists", - type: '{ tag: "Exists"; value?: undefined }', - }, - ], - }, - { - id: "chat-command", - name: "ChatCommand", - category: "chat", - definition: - "export interface ChatCommand {\n command: string;\n payload: string;\n}", - description: "A slash command from a chat user.", - fields: [ - { - name: "command", - type: "string", - description: "Command name.", - }, - { - name: "payload", - type: "string", - description: "Command arguments.", - }, - ], - }, - { - id: "chat-custom-message", - name: "ChatCustomMessage", - category: "chat", - definition: - "export interface ChatCustomMessage {\n messageType: string;\n payload: HexString;\n}", - description: - "A custom message with application-defined type and binary payload.", - fields: [ - { - name: "message_type", - type: "string", - description: "Application-defined type key.", - }, - { - name: "payload", - type: "HexString", - description: "Binary payload.", - }, - ], - }, - { - id: "chat-file", - name: "ChatFile", - category: "chat", - definition: - "export interface ChatFile {\n url: string;\n fileName: string;\n mimeType: string;\n sizeBytes: bigint;\n text?: string;\n}", - description: "A file attachment in a chat message.", - fields: [ - { - name: "url", - type: "string", - description: "File download URL.", - }, - { - name: "file_name", - type: "string", - description: "File name.", - }, - { - name: "mime_type", - type: "string", - description: "MIME type.", - }, - { - name: "size_bytes", - type: "bigint", - description: "File size in bytes.", - }, - { - name: "text", - type: "string | undefined", - description: "Optional caption text.", - }, - ], - }, - { - id: "chat-media", - name: "ChatMedia", - category: "chat", - definition: "export interface ChatMedia {\n url: string;\n}", - description: "A media attachment.", - fields: [ - { - name: "url", - type: "string", - description: "Media URL.", - }, - ], - }, - { - id: "chat-message-content", - name: "ChatMessageContent", - category: "chat", - definition: - 'export type ChatMessageContent =\n | { tag: "Text"; value: { text: string } }\n | { tag: "RichText"; value: ChatRichText }\n | { tag: "Actions"; value: ChatActions }\n | { tag: "File"; value: ChatFile }\n | { tag: "Reaction"; value: ChatReaction }\n | { tag: "ReactionRemoved"; value: ChatReaction }\n | { tag: "Custom"; value: ChatCustomMessage }\n;', - description: "Content of a chat message -- one of several types.", - variants: [ - { - name: "Text", - type: '{ tag: "Text"; value: { text: string } }', - description: "Plain text message.", - }, - { - name: "RichText", - type: '{ tag: "RichText"; value: ChatRichText }', - description: "Rich text with media.", - }, - { - name: "Actions", - type: '{ tag: "Actions"; value: ChatActions }', - description: "Action button set.", - }, - { - name: "File", - type: '{ tag: "File"; value: ChatFile }', - description: "File attachment.", - }, - { - name: "Reaction", - type: '{ tag: "Reaction"; value: ChatReaction }', - description: "Emoji reaction.", - }, - { - name: "ReactionRemoved", - type: '{ tag: "ReactionRemoved"; value: ChatReaction }', - description: "Reaction removal.", - }, - { - name: "Custom", - type: '{ tag: "Custom"; value: ChatCustomMessage }', - description: "Custom message.", - }, - ], - }, - { - id: "chat-reaction", - name: "ChatReaction", - category: "chat", - definition: - "export interface ChatReaction {\n messageId: string;\n emoji: string;\n}", - description: "A reaction to a chat message.", - fields: [ - { - name: "message_id", - type: "string", - description: "Message being reacted to.", - }, - { - name: "emoji", - type: "string", - description: "Emoji reaction.", - }, - ], - }, - { - id: "chat-rich-text", - name: "ChatRichText", - category: "chat", - definition: - "export interface ChatRichText {\n text?: string;\n media: Array;\n}", - description: "Rich text message with optional media.", - fields: [ - { - name: "text", - type: "string | undefined", - description: "Optional text content.", - }, - { - name: "media", - type: "Array", - description: "Attached media items.", - }, - ], - }, - { - id: "chat-room", - name: "ChatRoom", - category: "chat", - definition: - "export interface ChatRoom {\n roomId: string;\n participatingAs: ChatRoomParticipation;\n}", - description: "A chat room the product participates in.", - fields: [ - { - name: "room_id", - type: "string", - description: "Room identifier.", - }, - { - name: "participating_as", - type: "ChatRoomParticipation", - description: "`RoomHost` or `Bot`.", - }, - ], - }, - { - id: "chat-room-participation", - name: "ChatRoomParticipation", - category: "chat", - definition: 'export type ChatRoomParticipation = "RoomHost" | "Bot";', - description: "How the product participates in a chat room.", - variants: [ - { - name: "RoomHost", - type: '{ tag: "RoomHost"; value?: undefined }', - }, - { - name: "Bot", - type: '{ tag: "Bot"; value?: undefined }', - }, - ], - }, - { - id: "chat-room-registration-status", - name: "ChatRoomRegistrationStatus", - category: "chat", - definition: 'export type ChatRoomRegistrationStatus = "New" | "Exists";', - description: "Whether the room was newly created or already existed.", - variants: [ - { - name: "New", - type: '{ tag: "New"; value?: undefined }', - }, - { - name: "Exists", - type: '{ tag: "Exists"; value?: undefined }', - }, - ], - }, - { - id: "color-token", - name: "ColorToken", - category: "chat", - definition: - 'export type ColorToken = "FgPrimary" | "FgSecondary" | "FgTertiary" | "BgSurfaceMain" | "BgSurfaceContainer" | "BgSurfaceNested" | "FgSuccess" | "FgError" | "FgWarning";', - description: "Semantic color tokens for theming.", - variants: [ - { - name: "FgPrimary", - type: '{ tag: "FgPrimary"; value?: undefined }', - }, - { - name: "FgSecondary", - type: '{ tag: "FgSecondary"; value?: undefined }', - }, - { - name: "FgTertiary", - type: '{ tag: "FgTertiary"; value?: undefined }', - }, - { - name: "BgSurfaceMain", - type: '{ tag: "BgSurfaceMain"; value?: undefined }', - }, - { - name: "BgSurfaceContainer", - type: '{ tag: "BgSurfaceContainer"; value?: undefined }', - }, - { - name: "BgSurfaceNested", - type: '{ tag: "BgSurfaceNested"; value?: undefined }', - }, - { - name: "FgSuccess", - type: '{ tag: "FgSuccess"; value?: undefined }', - }, - { - name: "FgError", - type: '{ tag: "FgError"; value?: undefined }', - }, - { - name: "FgWarning", - type: '{ tag: "FgWarning"; value?: undefined }', - }, - ], - }, - { - id: "column-props", - name: "ColumnProps", - category: "chat", - definition: - "export interface ColumnProps {\n horizontalAlignment?: HorizontalAlignment;\n verticalArrangement?: Arrangement;\n}", - description: "Properties for a [`CustomRendererNode::Column`] layout.", - fields: [ - { - name: "horizontal_alignment", - type: "HorizontalAlignment | undefined", - description: "Horizontal alignment of children.", - }, - { - name: "vertical_arrangement", - type: "Arrangement | undefined", - description: "Vertical arrangement of children.", - }, - ], - }, - { - id: "component", - name: "Component", - category: "chat", - definition: - "export interface Component

{\n modifiers: Array;\n props: P;\n children: Array;\n}", - description: - "A component in the custom renderer UI tree, combining modifiers, typed props,\nand recursive children.", - fields: [ - { - name: "modifiers", - type: "Array", - description: "Layout and styling modifiers.", - }, - { - name: "props", - type: "P", - description: "Component-specific properties.", - }, - { - name: "children", - type: "Array", - description: "Child nodes.", - }, - ], - }, - { - id: "content-alignment", - name: "ContentAlignment", - category: "chat", - definition: - 'export type ContentAlignment = "TopStart" | "TopCenter" | "TopEnd" | "CenterStart" | "Center" | "CenterEnd" | "BottomStart" | "BottomCenter" | "BottomEnd";', - description: "2D content alignment.", - variants: [ - { - name: "TopStart", - type: '{ tag: "TopStart"; value?: undefined }', - }, - { - name: "TopCenter", - type: '{ tag: "TopCenter"; value?: undefined }', - }, - { - name: "TopEnd", - type: '{ tag: "TopEnd"; value?: undefined }', - }, - { - name: "CenterStart", - type: '{ tag: "CenterStart"; value?: undefined }', - }, - { - name: "Center", - type: '{ tag: "Center"; value?: undefined }', - }, - { - name: "CenterEnd", - type: '{ tag: "CenterEnd"; value?: undefined }', - }, - { - name: "BottomStart", - type: '{ tag: "BottomStart"; value?: undefined }', - }, - { - name: "BottomCenter", - type: '{ tag: "BottomCenter"; value?: undefined }', - }, - { - name: "BottomEnd", - type: '{ tag: "BottomEnd"; value?: undefined }', - }, - ], - }, - { - id: "custom-renderer-node", - name: "CustomRendererNode", - category: "chat", - definition: - 'export type CustomRendererNode =\n | { tag: "Nil"; value?: undefined }\n | { tag: "String"; value: { text: string } }\n | { tag: "Box"; value: Component }\n | { tag: "Column"; value: Component }\n | { tag: "Row"; value: Component }\n | { tag: "Spacer"; value: Component }\n | { tag: "Text"; value: Component }\n | { tag: "Button"; value: Component }\n | { tag: "TextField"; value: Component }\n;', - description: - "A node in the custom renderer UI tree. Can be nested recursively via the\n`children` field of each [`Component`].", - variants: [ - { - name: "Nil", - type: '{ tag: "Nil"; value?: undefined }', - description: "Empty node.", - }, - { - name: "String", - type: '{ tag: "String"; value: { text: string } }', - description: "Raw text string.", - }, - { - name: "Box", - type: '{ tag: "Box"; value: Component }', - description: "Generic container.", - }, - { - name: "Column", - type: '{ tag: "Column"; value: Component }', - description: "Vertical layout.", - }, - { - name: "Row", - type: '{ tag: "Row"; value: Component }', - description: "Horizontal layout.", - }, - { - name: "Spacer", - type: '{ tag: "Spacer"; value: Component }', - description: "Flexible space.", - }, - { - name: "Text", - type: '{ tag: "Text"; value: Component }', - description: "Text display.", - }, - { - name: "Button", - type: '{ tag: "Button"; value: Component }', - description: "Interactive button.", - }, - { - name: "TextField", - type: '{ tag: "TextField"; value: Component }', - description: "Text input.", - }, - ], - }, - { - id: "dimensions", - name: "Dimensions", - category: "chat", - definition: - "export interface Dimensions {\n top: Size;\n end: Size;\n bottom?: Size;\n start?: Size;\n}", - description: - "CSS-like dimensions: (top, end, bottom, start).\nBottom defaults to top, start defaults to end when `None`.", - fields: [ - { - name: "top", - type: "Size", - description: "Top dimension.", - }, - { - name: "end", - type: "Size", - description: "End dimension.", - }, - { - name: "bottom", - type: "Size | undefined", - description: "Bottom dimension. Defaults to top when absent.", - }, - { - name: "start", - type: "Size | undefined", - description: "Start dimension. Defaults to end when absent.", - }, - ], - }, - { - id: "generic-error", - name: "GenericError", - category: "common", - definition: "export interface GenericError {\n reason: string;\n}", - description: - "Generic error payload carrying a human-readable reason string. Used by many\nmethods as a catch-all error type.", - fields: [ - { - name: "reason", - type: "string", - }, - ], - }, - { - id: "genesis-hash", - name: "GenesisHash", - category: "transaction", - definition: "export type GenesisHash = HexString;", - description: - "A 32-byte chain genesis hash used to identify the target chain.", - }, - { - id: "horizontal-alignment", - name: "HorizontalAlignment", - category: "chat", - definition: 'export type HorizontalAlignment = "Start" | "Center" | "End";', - description: "Horizontal alignment options.", - variants: [ - { - name: "Start", - type: '{ tag: "Start"; value?: undefined }', - }, - { - name: "Center", - type: '{ tag: "Center"; value?: undefined }', - }, - { - name: "End", - type: '{ tag: "End"; value?: undefined }', - }, - ], - }, - { - id: "host-account-connection-status-subscribe-item", - name: "HostAccountConnectionStatusSubscribeItem", - category: "account", - definition: - 'export type HostAccountConnectionStatusSubscribeItem = "Disconnected" | "Connected";', - description: "User's authentication state.", - variants: [ - { - name: "Disconnected", - type: '{ tag: "Disconnected"; value?: undefined }', - }, - { - name: "Connected", - type: '{ tag: "Connected"; value?: undefined }', - }, - ], - }, - { - id: "host-account-create-proof-error", - name: "HostAccountCreateProofError", - category: "account", - definition: - 'export type HostAccountCreateProofError =\n | { tag: "RingNotFound"; value?: undefined }\n | { tag: "Rejected"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Error returned when ring VRF proof creation fails.", - variants: [ - { - name: "RingNotFound", - type: '{ tag: "RingNotFound"; value?: undefined }', - description: "Ring not available at the specified location.", - }, - { - name: "Rejected", - type: '{ tag: "Rejected"; value?: undefined }', - description: "User or host rejected.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-account-create-proof-request", - name: "HostAccountCreateProofRequest", - category: "account", - definition: - "export interface HostAccountCreateProofRequest {\n productAccountId: ProductAccountId;\n ringLocation: RingLocation;\n context: HexString;\n}", - description: "Request to create a ring VRF proof for a product account.", - fields: [ - { - name: "product_account_id", - type: "ProductAccountId", - description: "Product account that should create the proof.", - }, - { - name: "ring_location", - type: "RingLocation", - description: "Ring location to use for proof generation.", - }, - { - name: "context", - type: "HexString", - description: "Context bytes bound to the proof.", - }, - ], - }, - { - id: "host-account-create-proof-response", - name: "HostAccountCreateProofResponse", - category: "account", - definition: - "export interface HostAccountCreateProofResponse {\n proof: HexString;\n}", - description: "Response containing a ring VRF proof.", - fields: [ - { - name: "proof", - type: "HexString", - description: "Variable-length ring VRF proof bytes.", - }, - ], - }, - { - id: "host-account-get-alias-request", - name: "HostAccountGetAliasRequest", - category: "account", - definition: - "export interface HostAccountGetAliasRequest {\n productAccountId: ProductAccountId;\n}", - description: - "Request to retrieve a contextual alias for a product account.", - fields: [ - { - name: "product_account_id", - type: "ProductAccountId", - description: "Product account to derive the alias for.", - }, - ], - }, - { - id: "host-account-get-alias-response", - name: "HostAccountGetAliasResponse", - category: "account", - definition: - "export interface HostAccountGetAliasResponse {\n context: HexString;\n alias: HexString;\n}", - description: - "A privacy-preserving alias derived via ring VRF, bound to a specific context.", - fields: [ - { - name: "context", - type: "HexString", - description: "32-byte context identifier.", - }, - { - name: "alias", - type: "HexString", - description: "Ring VRF alias (variable length).", - }, - ], - }, - { - id: "host-account-get-error", - name: "HostAccountGetError", - category: "account", - definition: - 'export type HostAccountGetError =\n | { tag: "NotConnected"; value?: undefined }\n | { tag: "Rejected"; value?: undefined }\n | { tag: "DomainNotValid"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Error returned when credential/account requests fail.", - variants: [ - { - name: "NotConnected", - type: '{ tag: "NotConnected"; value?: undefined }', - description: "User is not logged in.", - }, - { - name: "Rejected", - type: '{ tag: "Rejected"; value?: undefined }', - description: "User or host rejected the request.", - }, - { - name: "DomainNotValid", - type: '{ tag: "DomainNotValid"; value?: undefined }', - description: "Domain identifier is invalid.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all error with reason.", - }, - ], - }, - { - id: "host-account-get-request", - name: "HostAccountGetRequest", - category: "account", - definition: - "export interface HostAccountGetRequest {\n productAccountId: ProductAccountId;\n}", - description: "Request to retrieve a product-scoped account.", - fields: [ - { - name: "product_account_id", - type: "ProductAccountId", - description: "Product account to retrieve.", - }, - ], - }, - { - id: "host-account-get-response", - name: "HostAccountGetResponse", - category: "account", - definition: - "export interface HostAccountGetResponse {\n account: ProductAccount;\n}", - description: "Response containing a product-scoped account.", - fields: [ - { - name: "account", - type: "ProductAccount", - description: "Retrieved product account.", - }, - ], - }, - { - id: "host-chat-action-subscribe-item", - name: "HostChatActionSubscribeItem", - category: "chat", - definition: - "export interface HostChatActionSubscribeItem {\n roomId: string;\n peer: string;\n payload: ChatActionPayload;\n}", - description: "A chat action received from the host.", - fields: [ - { - name: "room_id", - type: "string", - description: "Room where the action occurred.", - }, - { - name: "peer", - type: "string", - description: "Peer who initiated the action.", - }, - { - name: "payload", - type: "ChatActionPayload", - description: "The action payload.", - }, - ], - }, - { - id: "host-chat-create-room-error", - name: "HostChatCreateRoomError", - category: "chat", - definition: - 'export type HostChatCreateRoomError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Chat room registration error.", - variants: [ - { - name: "PermissionDenied", - type: '{ tag: "PermissionDenied"; value?: undefined }', - description: "Not allowed.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-chat-create-room-request", - name: "HostChatCreateRoomRequest", - category: "chat", - definition: - "export interface HostChatCreateRoomRequest {\n roomId: string;\n name: string;\n icon: string;\n}", - description: "Request to create a chat room.", - fields: [ - { - name: "room_id", - type: "string", - description: "Unique room identifier.", - }, - { - name: "name", - type: "string", - description: "Room display name.", - }, - { - name: "icon", - type: "string", - description: "URL or base64 image.", - }, - ], - }, - { - id: "host-chat-create-room-response", - name: "HostChatCreateRoomResponse", - category: "chat", - definition: - "export interface HostChatCreateRoomResponse {\n status: ChatRoomRegistrationStatus;\n}", - description: "Result of a room registration.", - fields: [ - { - name: "status", - type: "ChatRoomRegistrationStatus", - description: "`New` or `Exists`.", - }, - ], - }, - { - id: "host-chat-list-subscribe-item", - name: "HostChatListSubscribeItem", - category: "chat", - definition: - "export interface HostChatListSubscribeItem {\n rooms: Array;\n}", - description: "Item containing the current chat rooms.", - fields: [ - { - name: "rooms", - type: "Array", - description: "Chat rooms the product participates in.", - }, - ], - }, - { - id: "host-chat-post-message-error", - name: "HostChatPostMessageError", - category: "chat", - definition: - 'export type HostChatPostMessageError =\n | { tag: "MessageTooLarge"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Chat message posting error.", - variants: [ - { - name: "MessageTooLarge", - type: '{ tag: "MessageTooLarge"; value?: undefined }', - description: "Message exceeded size limit.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-chat-post-message-request", - name: "HostChatPostMessageRequest", - category: "chat", - definition: - "export interface HostChatPostMessageRequest {\n roomId: string;\n payload: ChatMessageContent;\n}", - description: "Request to post a message to a chat room.", - fields: [ - { - name: "room_id", - type: "string", - description: "Room to post to.", - }, - { - name: "payload", - type: "ChatMessageContent", - description: "Message content.", - }, - ], - }, - { - id: "host-chat-post-message-response", - name: "HostChatPostMessageResponse", - category: "chat", - definition: - "export interface HostChatPostMessageResponse {\n messageId: string;\n}", - description: "Result of posting a message.", - fields: [ - { - name: "message_id", - type: "string", - description: "Assigned message ID.", - }, - ], - }, - { - id: "host-chat-register-bot-error", - name: "HostChatRegisterBotError", - category: "chat", - definition: - 'export type HostChatRegisterBotError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Chat bot registration error.", - variants: [ - { - name: "PermissionDenied", - type: '{ tag: "PermissionDenied"; value?: undefined }', - description: "Not allowed.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-chat-register-bot-request", - name: "HostChatRegisterBotRequest", - category: "chat", - definition: - "export interface HostChatRegisterBotRequest {\n botId: string;\n name: string;\n icon: string;\n}", - description: "Request to register a chat bot.", - fields: [ - { - name: "bot_id", - type: "string", - description: "Unique bot identifier.", - }, - { - name: "name", - type: "string", - description: "Bot display name.", - }, - { - name: "icon", - type: "string", - description: "URL or base64 image.", - }, - ], - }, - { - id: "host-chat-register-bot-response", - name: "HostChatRegisterBotResponse", - category: "chat", - definition: - "export interface HostChatRegisterBotResponse {\n status: ChatBotRegistrationStatus;\n}", - description: "Result of a bot registration.", - fields: [ - { - name: "status", - type: "ChatBotRegistrationStatus", - description: "`New` or `Exists`.", - }, - ], - }, - { - id: "host-create-transaction-error", - name: "HostCreateTransactionError", - category: "transaction", - definition: - 'export type HostCreateTransactionError =\n | { tag: "FailedToDecode"; value?: undefined }\n | { tag: "Rejected"; value?: undefined }\n | { tag: "NotSupported"; value: { reason: string } }\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Transaction creation error.", - variants: [ - { - name: "FailedToDecode", - type: '{ tag: "FailedToDecode"; value?: undefined }', - description: "Payload could not be deserialized.", - }, - { - name: "Rejected", - type: '{ tag: "Rejected"; value?: undefined }', - description: "User rejected.", - }, - { - name: "NotSupported", - type: '{ tag: "NotSupported"; value: { reason: string } }', - description: "Unsupported payload version or extension.", - }, - { - name: "PermissionDenied", - type: '{ tag: "PermissionDenied"; value?: undefined }', - description: "Not authenticated.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-create-transaction-response", - name: "HostCreateTransactionResponse", - category: "signing", - definition: - "export interface HostCreateTransactionResponse {\n transaction: HexString;\n}", - description: "Response containing a created transaction.", - fields: [ - { - name: "transaction", - type: "HexString", - description: "SCALE-encoded signed transaction.", - }, - ], - }, - { - id: "host-create-transaction-with-legacy-account-response", - name: "HostCreateTransactionWithLegacyAccountResponse", - category: "signing", - definition: - "export interface HostCreateTransactionWithLegacyAccountResponse {\n transaction: HexString;\n}", - description: - "Response containing a transaction created with a non-product account.", - fields: [ - { - name: "transaction", - type: "HexString", - description: "SCALE-encoded signed transaction.", - }, - ], - }, - { - id: "host-derive-entropy-error", - name: "HostDeriveEntropyError", - category: "entropy", - definition: - 'export type HostDeriveEntropyError =\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Error from [`crate::api::Entropy::derive`] (RFC 0007).", - variants: [ - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-derive-entropy-request", - name: "HostDeriveEntropyRequest", - category: "entropy", - definition: - "export interface HostDeriveEntropyRequest {\n context: HexString;\n}", - description: - "Request to derive deterministic per-product entropy (RFC 0007).\n\nThe host derives 32 bytes from product-scoped seed material and `context`.\nRepeated calls with the same `context` for the same product yield the same\nentropy.", - fields: [ - { - name: "context", - type: "HexString", - description: "Domain-separated derivation context.", - }, - ], - }, - { - id: "host-derive-entropy-response", - name: "HostDeriveEntropyResponse", - category: "entropy", - definition: - "export interface HostDeriveEntropyResponse {\n entropy: HexString;\n}", - description: - "Response carrying 32 bytes of deterministically derived entropy.", - fields: [ - { - name: "entropy", - type: "HexString", - description: "32 bytes of derived entropy.", - }, - ], - }, - { - id: "host-device-permission-request", - name: "HostDevicePermissionRequest", - category: "permissions", - definition: - 'export type HostDevicePermissionRequest = "Notifications" | "Camera" | "Microphone" | "Bluetooth" | "NFC" | "Location" | "Clipboard" | "OpenUrl" | "Biometrics";', - description: - "Device-capability permission requested from the host (RFC 0002).\n\nThe user's decision is persisted indefinitely after the first prompt and\nsurvives app restarts, whether the decision was grant or deny; the host\ndoes not re-prompt on subsequent requests for the same capability.", - variants: [ - { - name: "Notifications", - type: '{ tag: "Notifications"; value?: undefined }', - }, - { - name: "Camera", - type: '{ tag: "Camera"; value?: undefined }', - }, - { - name: "Microphone", - type: '{ tag: "Microphone"; value?: undefined }', - }, - { - name: "Bluetooth", - type: '{ tag: "Bluetooth"; value?: undefined }', - }, - { - name: "NFC", - type: '{ tag: "NFC"; value?: undefined }', - }, - { - name: "Location", - type: '{ tag: "Location"; value?: undefined }', - }, - { - name: "Clipboard", - type: '{ tag: "Clipboard"; value?: undefined }', - }, - { - name: "OpenUrl", - type: '{ tag: "OpenUrl"; value?: undefined }', - }, - { - name: "Biometrics", - type: '{ tag: "Biometrics"; value?: undefined }', - }, - ], - }, - { - id: "host-device-permission-response", - name: "HostDevicePermissionResponse", - category: "permissions", - definition: - "export interface HostDevicePermissionResponse {\n granted: boolean;\n}", - description: "Outcome of a device-permission request.", - fields: [ - { - name: "granted", - type: "boolean", - description: "Whether the permission was granted.", - }, - ], - }, - { - id: "host-feature-supported-request", - name: "HostFeatureSupportedRequest", - category: "system", - definition: - 'export type HostFeatureSupportedRequest =\n | { tag: "Chain"; value: { genesisHash: HexString } }\n;', - description: "Request to query whether a feature is supported by the host.", - variants: [ - { - name: "Chain", - type: '{ tag: "Chain"; value: { genesisHash: HexString } }', - description: - "Ask whether the host can interact with the chain identified by genesis hash.", - }, - ], - }, - { - id: "host-feature-supported-response", - name: "HostFeatureSupportedResponse", - category: "system", - definition: - "export interface HostFeatureSupportedResponse {\n supported: boolean;\n}", - description: "Response to a feature-support query.", - fields: [ - { - name: "supported", - type: "boolean", - description: "Whether the feature is supported.", - }, - ], - }, - { - id: "host-get-legacy-accounts-response", - name: "HostGetLegacyAccountsResponse", - category: "account", - definition: - "export interface HostGetLegacyAccountsResponse {\n accounts: Array;\n}", - description: - "Response containing all legacy (user-imported) accounts owned by the user.", - fields: [ - { - name: "accounts", - type: "Array", - description: "Legacy accounts.", - }, - ], - }, - { - id: "host-get-user-id-error", - name: "HostGetUserIdError", - category: "account", - definition: - 'export type HostGetUserIdError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "NotConnected"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Error from [`crate::api::Account::get_user_id`].", - variants: [ - { - name: "PermissionDenied", - type: '{ tag: "PermissionDenied"; value?: undefined }', - description: "User denied the identity disclosure request.", - }, - { - name: "NotConnected", - type: '{ tag: "NotConnected"; value?: undefined }', - description: "User is not logged in.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-get-user-id-response", - name: "HostGetUserIdResponse", - category: "account", - definition: - "export interface HostGetUserIdResponse {\n primaryUsername: string;\n}", - description: "The user's primary DotNS account identity.", - fields: [ - { - name: "primary_username", - type: "string", - description: "The user's primary DotNS username.", - }, - ], - }, - { - id: "host-handshake-error", - name: "HostHandshakeError", - category: "system", - definition: - 'export type HostHandshakeError =\n | { tag: "Timeout"; value?: undefined }\n | { tag: "UnsupportedProtocolVersion"; value?: undefined }\n | { tag: "Unknown"; value: GenericError }\n;', - description: - "Error from [`crate::api::System::handshake`] (RFC 0009).\n\nThe handshake is the first call on a fresh connection; it does not require\nuser authentication and is used to negotiate the wire codec version.", - variants: [ - { - name: "Timeout", - type: '{ tag: "Timeout"; value?: undefined }', - description: "Host did not complete the handshake in time.", - }, - { - name: "UnsupportedProtocolVersion", - type: '{ tag: "UnsupportedProtocolVersion"; value?: undefined }', - description: - "Host does not speak the codec version requested by the product.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: GenericError }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-handshake-request", - name: "HostHandshakeRequest", - category: "system", - definition: - "export interface HostHandshakeRequest {\n codecVersion: number;\n}", - description: - "Wire-codec negotiation payload sent by the product (RFC 0009).", - fields: [ - { - name: "codec_version", - type: "number", - description: "Wire codec version requested by the product.", - }, - ], - }, - { - id: "host-local-storage-clear-request", - name: "HostLocalStorageClearRequest", - category: "local_storage", - definition: - "export interface HostLocalStorageClearRequest {\n key: string;\n}", - description: "Request to clear a local storage key.", - fields: [ - { - name: "key", - type: "string", - description: "Storage key to clear.", - }, - ], - }, - { - id: "host-local-storage-read-error", - name: "HostLocalStorageReadError", - category: "local_storage", - definition: - 'export type HostLocalStorageReadError =\n | { tag: "Full"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Local storage operation error.", - variants: [ - { - name: "Full", - type: '{ tag: "Full"; value?: undefined }', - description: "Storage quota exceeded.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-local-storage-read-request", - name: "HostLocalStorageReadRequest", - category: "local_storage", - definition: - "export interface HostLocalStorageReadRequest {\n key: string;\n}", - description: "Request to read a local storage value.", - fields: [ - { - name: "key", - type: "string", - description: "Storage key to read.", - }, - ], - }, - { - id: "host-local-storage-read-response", - name: "HostLocalStorageReadResponse", - category: "local_storage", - definition: - "export interface HostLocalStorageReadResponse {\n value?: HexString;\n}", - description: "Response containing an optional local storage value.", - fields: [ - { - name: "value", - type: "HexString | undefined", - description: "Stored value, if present.", - }, - ], - }, - { - id: "host-local-storage-write-request", - name: "HostLocalStorageWriteRequest", - category: "local_storage", - definition: - "export interface HostLocalStorageWriteRequest {\n key: string;\n value: HexString;\n}", - description: "Request to write a value into local storage.", - fields: [ - { - name: "key", - type: "string", - description: "Storage key to write.", - }, - { - name: "value", - type: "HexString", - description: "Value to store at the key.", - }, - ], - }, - { - id: "host-navigate-to-error", - name: "HostNavigateToError", - category: "system", - definition: - 'export type HostNavigateToError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Error from [`crate::api::System::navigate_to`].", - variants: [ - { - name: "PermissionDenied", - type: '{ tag: "PermissionDenied"; value?: undefined }', - description: "User denied the navigation prompt.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-navigate-to-request", - name: "HostNavigateToRequest", - category: "system", - definition: "export interface HostNavigateToRequest {\n url: string;\n}", - description: "Request to navigate the host to an external URL.", - fields: [ - { - name: "url", - type: "string", - description: "URL to open.", - }, - ], - }, - { - id: "host-payment-balance-subscribe-error", - name: "HostPaymentBalanceSubscribeError", - category: "payment", - definition: - 'export type HostPaymentBalanceSubscribeError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: - "Error from [`crate::api::Payment::balance_subscribe`].\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", - variants: [ - { - name: "PermissionDenied", - type: '{ tag: "PermissionDenied"; value?: undefined }', - description: "User denied the balance disclosure request.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-payment-balance-subscribe-item", - name: "HostPaymentBalanceSubscribeItem", - category: "payment", - definition: - "export interface HostPaymentBalanceSubscribeItem {\n available: Balance;\n}", - description: - "Current payment balance state pushed to subscribers.\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", - fields: [ - { - name: "available", - type: "Balance", - description: "Balance that can be spent right now.", - }, - ], - }, - { - id: "host-payment-balance-subscribe-request", - name: "HostPaymentBalanceSubscribeRequest", - category: "payment", - definition: - "export interface HostPaymentBalanceSubscribeRequest {\n purse?: PaymentPurseId;\n}", - description: "Request to subscribe to payment balance updates.", - fields: [ - { - name: "purse", - type: "PaymentPurseId | undefined", - description: "Optional purse selector. `None` means MAIN_PURSE.", - }, - ], - }, - { - id: "host-payment-error", - name: "HostPaymentError", - category: "payment", - definition: - 'export type HostPaymentError =\n | { tag: "Rejected"; value?: undefined }\n | { tag: "InsufficientBalance"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: - "Error from [`crate::api::Payment::request`].\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", - variants: [ - { - name: "Rejected", - type: '{ tag: "Rejected"; value?: undefined }', - description: "User rejected the payment request.", - }, - { - name: "InsufficientBalance", - type: '{ tag: "InsufficientBalance"; value?: undefined }', - description: - "User's available balance is not sufficient for the requested amount.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-payment-request", - name: "HostPaymentRequest", - category: "payment", - definition: - "export interface HostPaymentRequest {\n from?: PaymentPurseId;\n amount: Balance;\n destination: HexString;\n}", - description: "Request to initiate a payment to another account.", - fields: [ - { - name: "from", - type: "PaymentPurseId | undefined", - description: "Optional purse selector. `None` means MAIN_PURSE.", - }, - { - name: "amount", - type: "Balance", - description: "Amount to pay.", - }, - { - name: "destination", - type: "HexString", - description: "Destination account.", - }, - ], - }, - { - id: "host-payment-response", - name: "HostPaymentResponse", - category: "payment", - definition: "export interface HostPaymentResponse {\n id: string;\n}", - description: - "Receipt returned after a successful payment request.\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", - fields: [ - { - name: "id", - type: "string", - description: "The assigned payment identifier.", - }, - ], - }, - { - id: "host-payment-status-subscribe-error", - name: "HostPaymentStatusSubscribeError", - category: "payment", - definition: - 'export type HostPaymentStatusSubscribeError =\n | { tag: "PaymentNotFound"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: - "Error from [`crate::api::Payment::status_subscribe`].\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", - variants: [ - { - name: "PaymentNotFound", - type: '{ tag: "PaymentNotFound"; value?: undefined }', - description: - "Payment ID was not found or does not belong to the current product.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-payment-status-subscribe-item", - name: "HostPaymentStatusSubscribeItem", - category: "payment", - definition: - 'export type HostPaymentStatusSubscribeItem =\n | { tag: "Processing"; value?: undefined }\n | { tag: "Completed"; value?: undefined }\n | { tag: "Failed"; value: { reason: string } }\n;', - description: - "Payment lifecycle status pushed to subscribers.\n\nOnce a terminal state (`Completed` or `Failed`) is reached, the host\ndelivers it and may close the subscription.\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", - variants: [ - { - name: "Processing", - type: '{ tag: "Processing"; value?: undefined }', - description: "Payment is being processed.", - }, - { - name: "Completed", - type: '{ tag: "Completed"; value?: undefined }', - description: "Payment has been settled successfully.", - }, - { - name: "Failed", - type: '{ tag: "Failed"; value: { reason: string } }', - description: "Payment has failed.", - }, - ], - }, - { - id: "host-payment-status-subscribe-request", - name: "HostPaymentStatusSubscribeRequest", - category: "payment", - definition: - "export interface HostPaymentStatusSubscribeRequest {\n paymentId: string;\n}", - description: "Request to subscribe to a payment status.", - fields: [ - { - name: "payment_id", - type: "string", - description: "Payment identifier to watch.", - }, - ], - }, - { - id: "host-payment-top-up-error", - name: "HostPaymentTopUpError", - category: "payment", - definition: - 'export type HostPaymentTopUpError =\n | { tag: "InsufficientFunds"; value?: undefined }\n | { tag: "InvalidSource"; value?: undefined }\n | { tag: "PartialPayment"; value: { credited: Balance } }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: - "Error from [`crate::api::Payment::top_up`].\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", - variants: [ - { - name: "InsufficientFunds", - type: '{ tag: "InsufficientFunds"; value?: undefined }', - description: "The source account does not hold sufficient funds.", - }, - { - name: "InvalidSource", - type: '{ tag: "InvalidSource"; value?: undefined }', - description: "The source account was not found or is invalid.", - }, - { - name: "PartialPayment", - type: '{ tag: "PartialPayment"; value: { credited: Balance } }', - description: - "Some coins were claimed but the total fell short of the requested amount.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-payment-top-up-request", - name: "HostPaymentTopUpRequest", - category: "payment", - definition: - "export interface HostPaymentTopUpRequest {\n into?: PaymentPurseId;\n amount: Balance;\n source: PaymentTopUpSource;\n}", - description: "Request to top up the product payment balance.", - fields: [ - { - name: "into", - type: "PaymentPurseId | undefined", - description: "Optional purse selector. `None` means MAIN_PURSE.", - }, - { - name: "amount", - type: "Balance", - description: "Amount to top up.", - }, - { - name: "source", - type: "PaymentTopUpSource", - description: "Funding source for the top-up.", - }, - ], - }, - { - id: "host-push-notification-cancel-request", - name: "HostPushNotificationCancelRequest", - category: "notifications", - definition: - "export interface HostPushNotificationCancelRequest {\n id: NotificationId;\n}", - description: "Request to cancel a previously scheduled notification.", - fields: [ - { - name: "id", - type: "NotificationId", - description: - "The notification identifier returned by [`HostPushNotificationResponse`].", - }, - ], - }, - { - id: "host-push-notification-error", - name: "HostPushNotificationError", - category: "notifications", - definition: - 'export type HostPushNotificationError =\n | { tag: "ScheduleLimitReached"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Push notification error.", - variants: [ - { - name: "ScheduleLimitReached", - type: '{ tag: "ScheduleLimitReached"; value?: undefined }', - description: - "The host-wide queue of pending scheduled notifications is full.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-push-notification-request", - name: "HostPushNotificationRequest", - category: "notifications", - definition: - "export interface HostPushNotificationRequest {\n text: string;\n deeplink?: string;\n scheduledAt?: bigint;\n}", - description: - "Push notification payload.\n\nWhen `scheduled_at` is `Some`, the notification is deferred to the given\nwall-clock instant (Unix milliseconds UTC). `None` fires immediately,\npreserving prior behaviour. See [RFC 0019].\n\n[RFC 0019]: https://github.com/paritytech/truapi/blob/main/docs/rfcs/0019-scheduled-notifications.md", - fields: [ - { - name: "text", - type: "string", - description: "Notification text.", - }, - { - name: "deeplink", - type: "string | undefined", - description: "Optional URL to open on tap.", - }, - { - name: "scheduled_at", - type: "bigint | undefined", - description: - "Optional Unix timestamp in milliseconds (UTC) at which the notification\nshould fire. `None` fires immediately.", - }, - ], - }, - { - id: "host-push-notification-response", - name: "HostPushNotificationResponse", - category: "notifications", - definition: - "export interface HostPushNotificationResponse {\n id: NotificationId;\n}", - description: - "Successful push notification response carrying the assigned id.", - fields: [ - { - name: "id", - type: "NotificationId", - description: "Host-assigned notification identifier.", - }, - ], - }, - { - id: "host-request-login-error", - name: "HostRequestLoginError", - category: "account", - definition: - 'export type HostRequestLoginError =\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Login request error.", - variants: [ - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-request-login-request", - name: "HostRequestLoginRequest", - category: "account", - definition: - "export interface HostRequestLoginRequest {\n reason?: string;\n}", - description: "Request to present the host login flow.", - fields: [ - { - name: "reason", - type: "string | undefined", - description: "Optional human-readable reason shown in the login UI.", - }, - ], - }, - { - id: "host-request-login-response", - name: "HostRequestLoginResponse", - category: "account", - definition: - 'export type HostRequestLoginResponse = "Success" | "AlreadyConnected" | "Rejected";', - description: "Result of a login request.", - variants: [ - { - name: "Success", - type: '{ tag: "Success"; value?: undefined }', - description: "User successfully authenticated.", - }, - { - name: "AlreadyConnected", - type: '{ tag: "AlreadyConnected"; value?: undefined }', - description: "User is already authenticated — no action was taken.", - }, - { - name: "Rejected", - type: '{ tag: "Rejected"; value?: undefined }', - description: "User dismissed/rejected the login UI.", - }, - ], - }, - { - id: "host-request-resource-allocation-request", - name: "HostRequestResourceAllocationRequest", - category: "resource_allocation", - definition: - "export interface HostRequestResourceAllocationRequest {\n resources: Array;\n}", - description: "Batched resource pre-allocation request (RFC 0010).", - fields: [ - { - name: "resources", - type: "Array", - description: "Resources to allocate.", - }, - ], - }, - { - id: "host-request-resource-allocation-response", - name: "HostRequestResourceAllocationResponse", - category: "resource_allocation", - definition: - "export interface HostRequestResourceAllocationResponse {\n outcomes: Array;\n}", - description: - "Per-resource outcomes for a batched allocation request (RFC 0010).", - fields: [ - { - name: "outcomes", - type: "Array", - description: - "Per-resource allocation outcomes, in the same order as the request.", - }, - ], - }, - { - id: "host-sign-payload-data", - name: "HostSignPayloadData", - category: "signing", - definition: - "export interface HostSignPayloadData {\n blockHash: HexString;\n blockNumber: HexString;\n era: HexString;\n genesisHash: HexString;\n method: HexString;\n nonce: HexString;\n specVersion: HexString;\n tip: HexString;\n transactionVersion: HexString;\n signedExtensions: Array;\n version: number;\n assetId?: HexString;\n metadataHash?: HexString;\n mode?: number;\n withSignedTransaction?: boolean;\n}", - description: - "Full Substrate extrinsic signing payload with all fields needed for signature\ngeneration.", - fields: [ - { - name: "block_hash", - type: "HexString", - description: "Reference block hash.", - }, - { - name: "block_number", - type: "HexString", - description: "Reference block number.", - }, - { - name: "era", - type: "HexString", - description: "Mortality era encoding.", - }, - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "method", - type: "HexString", - description: "SCALE-encoded call data.", - }, - { - name: "nonce", - type: "HexString", - description: "Account nonce.", - }, - { - name: "spec_version", - type: "HexString", - description: "Runtime spec version.", - }, - { - name: "tip", - type: "HexString", - description: "Transaction tip.", - }, - { - name: "transaction_version", - type: "HexString", - description: "Transaction format version.", - }, - { - name: "signed_extensions", - type: "Array", - description: "Extension identifiers.", - }, - { - name: "version", - type: "number", - description: "Extrinsic version.", - }, - { - name: "asset_id", - type: "HexString | undefined", - description: "For multi-asset tips.", - }, - { - name: "metadata_hash", - type: "HexString | undefined", - description: "CheckMetadataHash extension.", - }, - { - name: "mode", - type: "number | undefined", - description: "Metadata mode.", - }, - { - name: "with_signed_transaction", - type: "boolean | undefined", - description: "Request signed transaction back.", - }, - ], - }, - { - id: "host-sign-payload-error", - name: "HostSignPayloadError", - category: "signing", - definition: - 'export type HostSignPayloadError =\n | { tag: "FailedToDecode"; value?: undefined }\n | { tag: "Rejected"; value?: undefined }\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Signing operation error.", - variants: [ - { - name: "FailedToDecode", - type: '{ tag: "FailedToDecode"; value?: undefined }', - description: "Payload could not be deserialized.", - }, - { - name: "Rejected", - type: '{ tag: "Rejected"; value?: undefined }', - description: "User rejected signing.", - }, - { - name: "PermissionDenied", - type: '{ tag: "PermissionDenied"; value?: undefined }', - description: "Not authenticated.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "host-sign-payload-request", - name: "HostSignPayloadRequest", - category: "signing", - definition: - "export interface HostSignPayloadRequest {\n account: ProductAccountId;\n payload: HostSignPayloadData;\n}", - description: "Request to sign an extrinsic payload with a product account.", - fields: [ - { - name: "account", - type: "ProductAccountId", - description: "Product account that will sign this payload.", - }, - { - name: "payload", - type: "HostSignPayloadData", - description: "The extrinsic payload to sign.", - }, - ], - }, - { - id: "host-sign-payload-response", - name: "HostSignPayloadResponse", - category: "signing", - definition: - "export interface HostSignPayloadResponse {\n signature: HexString;\n signedTransaction?: HexString;\n}", - description: "Result of a signing operation.", - fields: [ - { - name: "signature", - type: "HexString", - description: "The cryptographic signature.", - }, - { - name: "signed_transaction", - type: "HexString | undefined", - description: "Full signed transaction, if requested.", - }, - ], - }, - { - id: "host-sign-payload-with-legacy-account-request", - name: "HostSignPayloadWithLegacyAccountRequest", - category: "signing", - definition: - "export interface HostSignPayloadWithLegacyAccountRequest {\n signer: string;\n payload: HostSignPayloadData;\n}", - description: - "Sign a Substrate extrinsic payload with a non-product (legacy) account.\nContains the same fields as [`HostSignPayloadRequest`] minus `address`\n(replaced by `signer`).", - fields: [ - { - name: "signer", - type: "string", - description: "Signer address (SS58 or hex) of the legacy account.", - }, - { - name: "payload", - type: "HostSignPayloadData", - description: "The extrinsic payload to sign.", - }, - ], - }, - { - id: "host-sign-raw-request", - name: "HostSignRawRequest", - category: "signing", - definition: - "export interface HostSignRawRequest {\n account: ProductAccountId;\n payload: RawPayload;\n}", - description: - "A raw signing request pairing an account with the payload to sign.", - fields: [ - { - name: "account", - type: "ProductAccountId", - description: "Product account that will sign this payload.", - }, - { - name: "payload", - type: "RawPayload", - description: "The payload to sign.", - }, - ], - }, - { - id: "host-sign-raw-with-legacy-account-request", - name: "HostSignRawWithLegacyAccountRequest", - category: "signing", - definition: - "export interface HostSignRawWithLegacyAccountRequest {\n signer: string;\n payload: RawPayload;\n}", - description: - "Sign raw bytes with a non-product (legacy) account. The signer field\nidentifies which legacy account to use.", - fields: [ - { - name: "signer", - type: "string", - description: "Signer address (SS58 or hex) of the legacy account.", - }, - { - name: "payload", - type: "RawPayload", - description: "The data to sign.", - }, - ], - }, - { - id: "host-theme-subscribe-item", - name: "HostThemeSubscribeItem", - category: "theme", - definition: - "export interface HostThemeSubscribeItem {\n name: ThemeName;\n variant: ThemeVariant;\n}", - description: "Current theme state pushed to subscribers.", - fields: [ - { - name: "name", - type: "ThemeName", - description: "Theme name.", - }, - { - name: "variant", - type: "ThemeVariant", - description: "Light or dark variant.", - }, - ], - }, - { - id: "legacy-account", - name: "LegacyAccount", - category: "account", - definition: - "export interface LegacyAccount {\n publicKey: HexString;\n name?: string;\n}", - description: - "A user-imported (legacy) account: public key plus an optional user-chosen\ndisplay name.\n\nReturned by [`HostGetLegacyAccountsResponse`]. Distinct from\n[`ProductAccount`], which is protocol-derived and never carries a label.", - fields: [ - { - name: "public_key", - type: "HexString", - description: "The account public key (variable-length bytes).", - }, - { - name: "name", - type: "string | undefined", - description: "Optional user-chosen display name.", - }, - ], - }, - { - id: "legacy-account-tx-payload", - name: "LegacyAccountTxPayload", - category: "transaction", - definition: - "export interface LegacyAccountTxPayload {\n signer: AccountId;\n genesisHash: GenesisHash;\n callData: HexString;\n extensions: Array;\n txExtVersion: number;\n}", - description: - "Transaction payload for a legacy (non-product) account.\n\nIdentical to [`ProductAccountTxPayload`] except the signer is a raw\n32-byte [`AccountId`].", - fields: [ - { - name: "signer", - type: "AccountId", - description: "Raw 32-byte public key of the legacy account.", - }, - { - name: "genesis_hash", - type: "GenesisHash", - description: "Chain where the transaction will execute.", - }, - { - name: "call_data", - type: "HexString", - description: "SCALE-encoded Call data.", - }, - { - name: "extensions", - type: "Array", - description: "Transaction extensions supplied by the caller.", - }, - { - name: "tx_ext_version", - type: "number", - description: "0 for Extrinsic V4, runtime-supported value for V5.", - }, - ], - }, - { - id: "modifier", - name: "Modifier", - category: "chat", - definition: - 'export type Modifier =\n | { tag: "Margin"; value: Dimensions }\n | { tag: "Padding"; value: Dimensions }\n | { tag: "Background"; value: Background }\n | { tag: "Border"; value: BorderStyle }\n | { tag: "Height"; value: { height: Size } }\n | { tag: "Width"; value: { width: Size } }\n | { tag: "MinWidth"; value: { width: Size } }\n | { tag: "MinHeight"; value: { height: Size } }\n | { tag: "FillWidth"; value: { enabled: boolean } }\n | { tag: "FillHeight"; value: { enabled: boolean } }\n;', - description: - "Layout and styling modifiers applied to custom renderer components.", - variants: [ - { - name: "Margin", - type: '{ tag: "Margin"; value: Dimensions }', - description: "Outer spacing.", - }, - { - name: "Padding", - type: '{ tag: "Padding"; value: Dimensions }', - description: "Inner spacing.", - }, - { - name: "Background", - type: '{ tag: "Background"; value: Background }', - description: "Background fill.", - }, - { - name: "Border", - type: '{ tag: "Border"; value: BorderStyle }', - description: "Border style.", - }, - { - name: "Height", - type: '{ tag: "Height"; value: { height: Size } }', - description: "Fixed height.", - }, - { - name: "Width", - type: '{ tag: "Width"; value: { width: Size } }', - description: "Fixed width.", - }, - { - name: "MinWidth", - type: '{ tag: "MinWidth"; value: { width: Size } }', - description: "Minimum width.", - }, - { - name: "MinHeight", - type: '{ tag: "MinHeight"; value: { height: Size } }', - description: "Minimum height.", - }, - { - name: "FillWidth", - type: '{ tag: "FillWidth"; value: { enabled: boolean } }', - description: "Fill available width.", - }, - { - name: "FillHeight", - type: '{ tag: "FillHeight"; value: { enabled: boolean } }', - description: "Fill available height.", - }, - ], - }, - { - id: "notification-id", - name: "NotificationId", - category: "notifications", - definition: "export type NotificationId = number;", - description: - "Opaque identifier for a push notification, unique per product.", - }, - { - id: "operation-started-result", - name: "OperationStartedResult", - category: "chain", - definition: - 'export type OperationStartedResult =\n | { tag: "Started"; value: { operationId: string } }\n | { tag: "LimitReached"; value?: undefined }\n;', - variants: [ - { - name: "Started", - type: '{ tag: "Started"; value: { operationId: string } }', - }, - { - name: "LimitReached", - type: '{ tag: "LimitReached"; value?: undefined }', - }, - ], - }, - { - id: "payment-purse-id", - name: "PaymentPurseId", - category: "payment", - definition: "export type PaymentPurseId = number;", - description: "Identifier selecting a product payment purse.", - }, - { - id: "payment-top-up-source", - name: "PaymentTopUpSource", - category: "payment", - definition: - 'export type PaymentTopUpSource =\n | { tag: "ProductAccount"; value: { derivationIndex: number } }\n | { tag: "PrivateKey"; value: { sr25519SecretKey: HexString } }\n | { tag: "Coins"; value: { sr25519SecretKeys: Array } }\n;', - description: - "Source for a payment top-up operation.\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", - variants: [ - { - name: "ProductAccount", - type: '{ tag: "ProductAccount"; value: { derivationIndex: number } }', - description: "Fund from one of the calling product's scoped accounts.", - }, - { - name: "PrivateKey", - type: '{ tag: "PrivateKey"; value: { sr25519SecretKey: HexString } }', - description: - "Fund from a one-time account represented by its private key. This is a\nstandard account holding public funds, not a coin key.", - }, - { - name: "Coins", - type: '{ tag: "Coins"; value: { sr25519SecretKeys: Array } }', - description: - "Fund directly from coin secret keys. Each key is an sr25519 secret\ncontrolling a single coin.", - }, - ], - }, - { - id: "preimage-submit-error", - name: "PreimageSubmitError", - category: "preimage", - definition: - 'export type PreimageSubmitError =\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Preimage submission error.", - variants: [ - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "product-account", - name: "ProductAccount", - category: "account", - definition: "export interface ProductAccount {\n publicKey: HexString;\n}", - description: "A product account: public key only, no display name.", - fields: [ - { - name: "public_key", - type: "HexString", - description: "The account public key (variable-length bytes).", - }, - ], - }, - { - id: "product-account-id", - name: "ProductAccountId", - category: "account", - definition: - "export interface ProductAccountId {\n dotNsIdentifier: string;\n derivationIndex: number;\n}", - description: - "Identifies a product-specific account by combining a dotNS domain name with a\nderivation index.", - fields: [ - { - name: "dot_ns_identifier", - type: "string", - description: - 'A dotNS domain name identifier (e.g., `"my-product.dot"`).', - }, - { - name: "derivation_index", - type: "number", - description: - "Key derivation index for generating product-specific accounts.", - }, - ], - }, - { - id: "product-account-tx-payload", - name: "ProductAccountTxPayload", - category: "transaction", - definition: - "export interface ProductAccountTxPayload {\n signer: ProductAccountId;\n genesisHash: GenesisHash;\n callData: HexString;\n extensions: Array;\n txExtVersion: number;\n}", - description: - "Transaction payload for a product account.\n\nContains everything the host needs to construct a signed extrinsic.\nThe signer is a [`ProductAccountId`]; the host resolves the\ncorresponding key pair through its account management layer.", - fields: [ - { - name: "signer", - type: "ProductAccountId", - description: "Product account that will sign the transaction.", - }, - { - name: "genesis_hash", - type: "GenesisHash", - description: "Chain where the transaction will execute.", - }, - { - name: "call_data", - type: "HexString", - description: "SCALE-encoded Call data.", - }, - { - name: "extensions", - type: "Array", - description: "Transaction extensions supplied by the caller.", - }, - { - name: "tx_ext_version", - type: "number", - description: "0 for Extrinsic V4, runtime-supported value for V5.", - }, - ], - }, - { - id: "product-chat-custom-message-render-subscribe-request", - name: "ProductChatCustomMessageRenderSubscribeRequest", - category: "chat", - definition: - "export interface ProductChatCustomMessageRenderSubscribeRequest {\n messageId: string;\n messageType: string;\n payload: HexString;\n}", - description: - "Subscribe payload identifying the chat message to render. The host responds\nwith a stream of [`CustomRendererNode`] trees describing the rendered UI.", - fields: [ - { - name: "message_id", - type: "string", - description: "Message identifier.", - }, - { - name: "message_type", - type: "string", - description: "Application-defined message type.", - }, - { - name: "payload", - type: "HexString", - description: "Binary payload.", - }, - ], - }, - { - id: "raw-payload", - name: "RawPayload", - category: "signing", - definition: - 'export type RawPayload =\n | { tag: "Bytes"; value: { bytes: HexString } }\n | { tag: "Payload"; value: { payload: string } }\n;', - description: "Raw data to sign -- either binary bytes or a string message.", - variants: [ - { - name: "Bytes", - type: '{ tag: "Bytes"; value: { bytes: HexString } }', - description: "Raw binary data to sign.", - }, - { - name: "Payload", - type: '{ tag: "Payload"; value: { payload: string } }', - description: "String message to sign.", - }, - ], - }, - { - id: "remote-chain-head-body-request", - name: "RemoteChainHeadBodyRequest", - category: "chain", - definition: - "export interface RemoteChainHeadBodyRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hash: HexString;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "follow_subscription_id", - type: "string", - description: "Follow subscription identifier.", - }, - { - name: "hash", - type: "HexString", - description: "Block hash.", - }, - ], - }, - { - id: "remote-chain-head-body-response", - name: "RemoteChainHeadBodyResponse", - category: "chain", - definition: - "export interface RemoteChainHeadBodyResponse {\n operation: OperationStartedResult;\n}", - fields: [ - { - name: "operation", - type: "OperationStartedResult", - description: "Started operation result.", - }, - ], - }, - { - id: "remote-chain-head-call-request", - name: "RemoteChainHeadCallRequest", - category: "chain", - definition: - "export interface RemoteChainHeadCallRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hash: HexString;\n function: string;\n callParameters: HexString;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "follow_subscription_id", - type: "string", - description: "Follow subscription identifier.", - }, - { - name: "hash", - type: "HexString", - description: "Block hash.", - }, - { - name: "function", - type: "string", - description: "Runtime API function name.", - }, - { - name: "call_parameters", - type: "HexString", - description: "SCALE-encoded call parameters.", - }, - ], - }, - { - id: "remote-chain-head-call-response", - name: "RemoteChainHeadCallResponse", - category: "chain", - definition: - "export interface RemoteChainHeadCallResponse {\n operation: OperationStartedResult;\n}", - fields: [ - { - name: "operation", - type: "OperationStartedResult", - description: "Started operation result.", - }, - ], - }, - { - id: "remote-chain-head-continue-request", - name: "RemoteChainHeadContinueRequest", - category: "chain", - definition: - "export interface RemoteChainHeadContinueRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n operationId: string;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "follow_subscription_id", - type: "string", - description: "Follow subscription identifier.", - }, - { - name: "operation_id", - type: "string", - description: "Operation identifier.", - }, - ], - }, - { - id: "remote-chain-head-follow-item", - name: "RemoteChainHeadFollowItem", - category: "chain", - definition: - 'export type RemoteChainHeadFollowItem =\n | { tag: "Initialized"; value: { finalizedBlockHashes: Array; finalizedBlockRuntime?: RuntimeType } }\n | { tag: "NewBlock"; value: { blockHash: HexString; parentBlockHash: HexString; newRuntime?: RuntimeType } }\n | { tag: "BestBlockChanged"; value: { bestBlockHash: HexString } }\n | { tag: "Finalized"; value: { finalizedBlockHashes: Array; prunedBlockHashes: Array } }\n | { tag: "OperationBodyDone"; value: { operationId: string; value: Array } }\n | { tag: "OperationCallDone"; value: { operationId: string; output: HexString } }\n | { tag: "OperationStorageItems"; value: { operationId: string; items: Array } }\n | { tag: "OperationStorageDone"; value: { operationId: string } }\n | { tag: "OperationWaitingForContinue"; value: { operationId: string } }\n | { tag: "OperationInaccessible"; value: { operationId: string } }\n | { tag: "OperationError"; value: { operationId: string; error: string } }\n | { tag: "Stop"; value?: undefined }\n;', - variants: [ - { - name: "Initialized", - type: '{ tag: "Initialized"; value: { finalizedBlockHashes: Array; finalizedBlockRuntime?: RuntimeType } }', - }, - { - name: "NewBlock", - type: '{ tag: "NewBlock"; value: { blockHash: HexString; parentBlockHash: HexString; newRuntime?: RuntimeType } }', - }, - { - name: "BestBlockChanged", - type: '{ tag: "BestBlockChanged"; value: { bestBlockHash: HexString } }', - }, - { - name: "Finalized", - type: '{ tag: "Finalized"; value: { finalizedBlockHashes: Array; prunedBlockHashes: Array } }', - }, - { - name: "OperationBodyDone", - type: '{ tag: "OperationBodyDone"; value: { operationId: string; value: Array } }', - }, - { - name: "OperationCallDone", - type: '{ tag: "OperationCallDone"; value: { operationId: string; output: HexString } }', - }, - { - name: "OperationStorageItems", - type: '{ tag: "OperationStorageItems"; value: { operationId: string; items: Array } }', - }, - { - name: "OperationStorageDone", - type: '{ tag: "OperationStorageDone"; value: { operationId: string } }', - }, - { - name: "OperationWaitingForContinue", - type: '{ tag: "OperationWaitingForContinue"; value: { operationId: string } }', - }, - { - name: "OperationInaccessible", - type: '{ tag: "OperationInaccessible"; value: { operationId: string } }', - }, - { - name: "OperationError", - type: '{ tag: "OperationError"; value: { operationId: string; error: string } }', - }, - { - name: "Stop", - type: '{ tag: "Stop"; value?: undefined }', - }, - ], - }, - { - id: "remote-chain-head-follow-request", - name: "RemoteChainHeadFollowRequest", - category: "chain", - definition: - "export interface RemoteChainHeadFollowRequest {\n genesisHash: HexString;\n withRuntime: boolean;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "with_runtime", - type: "boolean", - description: "Whether to include runtime information in events.", - }, - ], - }, - { - id: "remote-chain-head-header-request", - name: "RemoteChainHeadHeaderRequest", - category: "chain", - definition: - "export interface RemoteChainHeadHeaderRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hash: HexString;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "follow_subscription_id", - type: "string", - description: "Follow subscription identifier.", - }, - { - name: "hash", - type: "HexString", - description: "Block hash.", - }, - ], - }, - { - id: "remote-chain-head-header-response", - name: "RemoteChainHeadHeaderResponse", - category: "chain", - definition: - "export interface RemoteChainHeadHeaderResponse {\n header?: HexString;\n}", - fields: [ - { - name: "header", - type: "HexString | undefined", - description: "SCALE-encoded block header.", - }, - ], - }, - { - id: "remote-chain-head-stop-operation-request", - name: "RemoteChainHeadStopOperationRequest", - category: "chain", - definition: - "export interface RemoteChainHeadStopOperationRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n operationId: string;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "follow_subscription_id", - type: "string", - description: "Follow subscription identifier.", - }, - { - name: "operation_id", - type: "string", - description: "Operation identifier.", - }, - ], - }, - { - id: "remote-chain-head-storage-request", - name: "RemoteChainHeadStorageRequest", - category: "chain", - definition: - "export interface RemoteChainHeadStorageRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hash: HexString;\n items: Array;\n childTrie?: HexString;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "follow_subscription_id", - type: "string", - description: "Follow subscription identifier.", - }, - { - name: "hash", - type: "HexString", - description: "Block hash.", - }, - { - name: "items", - type: "Array", - description: "Storage items to query.", - }, - { - name: "child_trie", - type: "HexString | undefined", - description: "Optional child trie.", - }, - ], - }, - { - id: "remote-chain-head-storage-response", - name: "RemoteChainHeadStorageResponse", - category: "chain", - definition: - "export interface RemoteChainHeadStorageResponse {\n operation: OperationStartedResult;\n}", - fields: [ - { - name: "operation", - type: "OperationStartedResult", - description: "Started operation result.", - }, - ], - }, - { - id: "remote-chain-head-unpin-request", - name: "RemoteChainHeadUnpinRequest", - category: "chain", - definition: - "export interface RemoteChainHeadUnpinRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hashes: Array;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "follow_subscription_id", - type: "string", - description: "Follow subscription identifier.", - }, - { - name: "hashes", - type: "Array", - description: "Block hashes to unpin.", - }, - ], - }, - { - id: "remote-chain-spec-chain-name-request", - name: "RemoteChainSpecChainNameRequest", - category: "chain", - definition: - "export interface RemoteChainSpecChainNameRequest {\n genesisHash: HexString;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - ], - }, - { - id: "remote-chain-spec-chain-name-response", - name: "RemoteChainSpecChainNameResponse", - category: "chain", - definition: - "export interface RemoteChainSpecChainNameResponse {\n chainName: string;\n}", - fields: [ - { - name: "chain_name", - type: "string", - description: "Chain display name.", - }, - ], - }, - { - id: "remote-chain-spec-genesis-hash-request", - name: "RemoteChainSpecGenesisHashRequest", - category: "chain", - definition: - "export interface RemoteChainSpecGenesisHashRequest {\n genesisHash: HexString;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash requested by the product.", - }, - ], - }, - { - id: "remote-chain-spec-genesis-hash-response", - name: "RemoteChainSpecGenesisHashResponse", - category: "chain", - definition: - "export interface RemoteChainSpecGenesisHashResponse {\n genesisHash: HexString;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - ], - }, - { - id: "remote-chain-spec-properties-request", - name: "RemoteChainSpecPropertiesRequest", - category: "chain", - definition: - "export interface RemoteChainSpecPropertiesRequest {\n genesisHash: HexString;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - ], - }, - { - id: "remote-chain-spec-properties-response", - name: "RemoteChainSpecPropertiesResponse", - category: "chain", - definition: - "export interface RemoteChainSpecPropertiesResponse {\n properties: string;\n}", - fields: [ - { - name: "properties", - type: "string", - description: "JSON-encoded properties.", - }, - ], - }, - { - id: "remote-chain-transaction-broadcast-request", - name: "RemoteChainTransactionBroadcastRequest", - category: "chain", - definition: - "export interface RemoteChainTransactionBroadcastRequest {\n genesisHash: HexString;\n transaction: HexString;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "transaction", - type: "HexString", - description: "Signed transaction bytes.", - }, - ], - }, - { - id: "remote-chain-transaction-broadcast-response", - name: "RemoteChainTransactionBroadcastResponse", - category: "chain", - definition: - "export interface RemoteChainTransactionBroadcastResponse {\n operationId?: string;\n}", - fields: [ - { - name: "operation_id", - type: "string | undefined", - description: "Broadcast operation identifier, if available.", - }, - ], - }, - { - id: "remote-chain-transaction-stop-request", - name: "RemoteChainTransactionStopRequest", - category: "chain", - definition: - "export interface RemoteChainTransactionStopRequest {\n genesisHash: HexString;\n operationId: string;\n}", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "operation_id", - type: "string", - description: "Operation identifier of the broadcast to stop.", - }, - ], - }, - { - id: "remote-permission", - name: "RemotePermission", - category: "permissions", - definition: - 'export type RemotePermission =\n | { tag: "Remote"; value: { domains: Array } }\n | { tag: "WebRtc"; value?: undefined }\n | { tag: "ChainSubmit"; value?: undefined }\n | { tag: "PreimageSubmit"; value?: undefined }\n | { tag: "StatementSubmit"; value?: undefined }\n;', - description: - "One remote-operation permission requested by the product (RFC 0002).\n\n`ChainSubmit`, `PreimageSubmit`, and `StatementSubmit` are also triggered\nimplicitly by the corresponding business calls when not yet granted.", - variants: [ - { - name: "Remote", - type: '{ tag: "Remote"; value: { domains: Array } }', - description: "Outbound HTTP/WebSocket access to a set of domains.", - }, - { - name: "WebRtc", - type: '{ tag: "WebRtc"; value?: undefined }', - description: "WebRTC media access.", - }, - { - name: "ChainSubmit", - type: '{ tag: "ChainSubmit"; value?: undefined }', - description: - "Submitting transactions on behalf of the user via `remote_chain_transaction_broadcast`.", - }, - { - name: "PreimageSubmit", - type: '{ tag: "PreimageSubmit"; value?: undefined }', - description: - "Submitting preimages on behalf of the user via `remote_preimage_submit`.", - }, - { - name: "StatementSubmit", - type: '{ tag: "StatementSubmit"; value?: undefined }', - description: - "Submitting statements on behalf of the user via `remote_statement_store_submit`.", - }, - ], - }, - { - id: "remote-permission-request", - name: "RemotePermissionRequest", - category: "permissions", - definition: - "export interface RemotePermissionRequest {\n permission: RemotePermission;\n}", - description: "remote-permission request (RFC 0002).", - fields: [ - { - name: "permission", - type: "RemotePermission", - description: "Permission requested by the product.", - }, - ], - }, - { - id: "remote-permission-response", - name: "RemotePermissionResponse", - category: "permissions", - definition: - "export interface RemotePermissionResponse {\n granted: boolean;\n}", - description: "Outcome of a remote-permission request.", - fields: [ - { - name: "granted", - type: "boolean", - description: "Whether the permission was granted.", - }, - ], - }, - { - id: "remote-preimage-lookup-subscribe-item", - name: "RemotePreimageLookupSubscribeItem", - category: "preimage", - definition: - "export interface RemotePreimageLookupSubscribeItem {\n value?: HexString;\n}", - description: "Item containing an optional preimage lookup result.", - fields: [ - { - name: "value", - type: "HexString | undefined", - description: "Preimage data, if found.", - }, - ], - }, - { - id: "remote-preimage-lookup-subscribe-request", - name: "RemotePreimageLookupSubscribeRequest", - category: "preimage", - definition: - "export interface RemotePreimageLookupSubscribeRequest {\n key: HexString;\n}", - description: "Request to subscribe to preimage lookup results.", - fields: [ - { - name: "key", - type: "HexString", - description: "Hash of the preimage.", - }, - ], - }, - { - id: "remote-statement-store-create-proof-error", - name: "RemoteStatementStoreCreateProofError", - category: "statement_store", - definition: - 'export type RemoteStatementStoreCreateProofError =\n | { tag: "UnableToSign"; value?: undefined }\n | { tag: "UnknownAccount"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Statement proof creation error.", - variants: [ - { - name: "UnableToSign", - type: '{ tag: "UnableToSign"; value?: undefined }', - description: "Signing operation failed.", - }, - { - name: "UnknownAccount", - type: '{ tag: "UnknownAccount"; value?: undefined }', - description: "Account not recognized.", - }, - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "remote-statement-store-create-proof-request", - name: "RemoteStatementStoreCreateProofRequest", - category: "statement_store", - definition: - "export interface RemoteStatementStoreCreateProofRequest {\n productAccountId: ProductAccountId;\n statement: Statement;\n}", - description: "Request to create a cryptographic proof for a statement.", - fields: [ - { - name: "product_account_id", - type: "ProductAccountId", - description: "Product account that should create the proof.", - }, - { - name: "statement", - type: "Statement", - description: "Statement to prove.", - }, - ], - }, - { - id: "remote-statement-store-create-proof-response", - name: "RemoteStatementStoreCreateProofResponse", - category: "statement_store", - definition: - "export interface RemoteStatementStoreCreateProofResponse {\n proof: StatementProof;\n}", - description: "Response containing a statement proof.", - fields: [ - { - name: "proof", - type: "StatementProof", - description: "Created statement proof.", - }, - ], - }, - { - id: "remote-statement-store-subscribe-item", - name: "RemoteStatementStoreSubscribeItem", - category: "statement_store", - definition: - "export interface RemoteStatementStoreSubscribeItem {\n statements: Array;\n isComplete: boolean;\n}", - description: - "Page of signed statements delivered by the statement store subscription\n(RFC 0008). The `is_complete` flag distinguishes the historical-dump phase\n(`false`) from the live-update phase (`true`).", - fields: [ - { - name: "statements", - type: "Array", - description: "Signed statements matching the subscription.", - }, - { - name: "is_complete", - type: "boolean", - description: - "`false` while the host is still streaming the historical dump (more\npages to follow). `true` once the dump is complete; all subsequent\npages are also `true` and carry only newly-arrived statements.", - }, - ], - }, - { - id: "remote-statement-store-subscribe-request", - name: "RemoteStatementStoreSubscribeRequest", - category: "statement_store", - definition: - 'export type RemoteStatementStoreSubscribeRequest =\n | { tag: "MatchAll"; value: Array }\n | { tag: "MatchAny"; value: Array }\n;', - description: - "Request to subscribe to statements via a topic filter (RFC 0008).", - variants: [ - { - name: "MatchAll", - type: '{ tag: "MatchAll"; value: Array }', - description: "AND: statement must contain every listed topic.", - }, - { - name: "MatchAny", - type: '{ tag: "MatchAny"; value: Array }', - description: "OR: statement must contain at least one listed topic.", - }, - ], - }, - { - id: "resource-allocation-error", - name: "ResourceAllocationError", - category: "resource_allocation", - definition: - 'export type ResourceAllocationError =\n | { tag: "Unknown"; value: { reason: string } }\n;', - description: "Error from [`crate::api::ResourceAllocation::request`].", - variants: [ - { - name: "Unknown", - type: '{ tag: "Unknown"; value: { reason: string } }', - description: "Catch-all.", - }, - ], - }, - { - id: "ring-location", - name: "RingLocation", - category: "account", - definition: - "export interface RingLocation {\n genesisHash: HexString;\n ringRootHash: HexString;\n hints?: RingLocationHint;\n}", - description: - "Locates a specific ring on a specific chain for ring VRF operations.", - fields: [ - { - name: "genesis_hash", - type: "HexString", - description: "Chain genesis hash.", - }, - { - name: "ring_root_hash", - type: "HexString", - description: "Root hash of the ring.", - }, - { - name: "hints", - type: "RingLocationHint | undefined", - description: "Optional location hints.", - }, - ], - }, - { - id: "ring-location-hint", - name: "RingLocationHint", - category: "account", - definition: - "export interface RingLocationHint {\n palletInstance?: number;\n}", - description: "Hints for locating a ring on-chain.", - fields: [ - { - name: "pallet_instance", - type: "number | undefined", - description: "Optional pallet instance index.", - }, - ], - }, - { - id: "row-props", - name: "RowProps", - category: "chat", - definition: - "export interface RowProps {\n verticalAlignment?: VerticalAlignment;\n horizontalArrangement?: Arrangement;\n}", - description: "Properties for a [`CustomRendererNode::Row`] layout.", - fields: [ - { - name: "vertical_alignment", - type: "VerticalAlignment | undefined", - description: "Vertical alignment of children.", - }, - { - name: "horizontal_arrangement", - type: "Arrangement | undefined", - description: "Horizontal arrangement of children.", - }, - ], - }, - { - id: "runtime-api", - name: "RuntimeApi", - category: "chain", - definition: - "export interface RuntimeApi {\n name: string;\n version: number;\n}", - fields: [ - { - name: "name", - type: "string", - description: "Runtime API name.", - }, - { - name: "version", - type: "number", - description: "Runtime API version.", - }, - ], - }, - { - id: "runtime-spec", - name: "RuntimeSpec", - category: "chain", - definition: - "export interface RuntimeSpec {\n specName: string;\n implName: string;\n specVersion: number;\n implVersion: number;\n transactionVersion?: number;\n apis: Array;\n}", - fields: [ - { - name: "spec_name", - type: "string", - description: "Specification name.", - }, - { - name: "impl_name", - type: "string", - description: "Implementation name.", - }, - { - name: "spec_version", - type: "number", - description: "Spec version number.", - }, - { - name: "impl_version", - type: "number", - description: "Implementation version.", - }, - { - name: "transaction_version", - type: "number | undefined", - description: "Transaction format version.", - }, - { - name: "apis", - type: "Array", - description: "Supported runtime APIs.", - }, - ], - }, - { - id: "runtime-type", - name: "RuntimeType", - category: "chain", - definition: - 'export type RuntimeType =\n | { tag: "Valid"; value: RuntimeSpec }\n | { tag: "Invalid"; value: { error: string } }\n;', - variants: [ - { - name: "Valid", - type: '{ tag: "Valid"; value: RuntimeSpec }', - }, - { - name: "Invalid", - type: '{ tag: "Invalid"; value: { error: string } }', - }, - ], - }, - { - id: "shape", - name: "Shape", - category: "chat", - definition: - 'export type Shape =\n | { tag: "Rounded"; value: { radius: Size } }\n | { tag: "Circle"; value?: undefined }\n;', - description: "Shape for borders and backgrounds.", - variants: [ - { - name: "Rounded", - type: '{ tag: "Rounded"; value: { radius: Size } }', - description: "Border radius value.", - }, - { - name: "Circle", - type: '{ tag: "Circle"; value?: undefined }', - description: "Circular shape.", - }, - ], - }, - { - id: "signed-statement", - name: "SignedStatement", - category: "statement_store", - definition: - "export interface SignedStatement {\n proof: StatementProof;\n decryptionKey?: HexString;\n expiry?: bigint;\n channel?: HexString;\n topics: Array;\n data?: HexString;\n}", - description: "A statement with a required (not optional) proof.", - fields: [ - { - name: "proof", - type: "StatementProof", - description: "Required cryptographic proof.", - }, - { - name: "decryption_key", - type: "HexString | undefined", - description: "Optional decryption key.", - }, - { - name: "expiry", - type: "bigint | undefined", - description: "Optional Unix timestamp expiry.", - }, - { - name: "channel", - type: "HexString | undefined", - description: "Optional channel.", - }, - { - name: "topics", - type: "Array", - description: "[u8; 32] tags.", - }, - { - name: "data", - type: "HexString | undefined", - description: "Optional data payload.", - }, - ], - }, - { - id: "size", - name: "Size", - category: "chat", - definition: "export type Size = number | bigint;", - description: - "A size/dimension value (logical pixels) used across the custom renderer.\n\nEncoded as a SCALE `Compact`: the common small values cost a single\nbyte on the wire instead of eight.", - }, - { - id: "statement", - name: "Statement", - category: "statement_store", - definition: - "export interface Statement {\n proof?: StatementProof;\n decryptionKey?: HexString;\n expiry?: bigint;\n channel?: HexString;\n topics: Array;\n data?: HexString;\n}", - description: "A statement with optional proof and metadata.", - fields: [ - { - name: "proof", - type: "StatementProof | undefined", - description: "Optional cryptographic proof.", - }, - { - name: "decryption_key", - type: "HexString | undefined", - description: "Optional decryption key.", - }, - { - name: "expiry", - type: "bigint | undefined", - description: "Optional Unix timestamp expiry.", - }, - { - name: "channel", - type: "HexString | undefined", - description: "Optional channel.", - }, - { - name: "topics", - type: "Array", - description: "[u8; 32] tags.", - }, - { - name: "data", - type: "HexString | undefined", - description: "Optional data payload.", - }, - ], - }, - { - id: "statement-proof", - name: "StatementProof", - category: "statement_store", - definition: - 'export type StatementProof =\n | { tag: "Sr25519"; value: { signature: HexString; signer: HexString } }\n | { tag: "Ed25519"; value: { signature: HexString; signer: HexString } }\n | { tag: "Ecdsa"; value: { signature: HexString; signer: HexString } }\n | { tag: "OnChain"; value: { who: HexString; blockHash: HexString; event: bigint } }\n;', - description: "Cryptographic proof for a statement.", - variants: [ - { - name: "Sr25519", - type: '{ tag: "Sr25519"; value: { signature: HexString; signer: HexString } }', - description: "Sr25519 signature proof.", - }, - { - name: "Ed25519", - type: '{ tag: "Ed25519"; value: { signature: HexString; signer: HexString } }', - description: "Ed25519 signature proof.", - }, - { - name: "Ecdsa", - type: '{ tag: "Ecdsa"; value: { signature: HexString; signer: HexString } }', - description: "ECDSA signature proof.", - }, - { - name: "OnChain", - type: '{ tag: "OnChain"; value: { who: HexString; blockHash: HexString; event: bigint } }', - description: "On-chain event proof.", - }, - ], - }, - { - id: "storage-query-item", - name: "StorageQueryItem", - category: "chain", - definition: - "export interface StorageQueryItem {\n key: HexString;\n queryType: StorageQueryType;\n}", - fields: [ - { - name: "key", - type: "HexString", - description: "Storage key to query.", - }, - { - name: "query_type", - type: "StorageQueryType", - description: "What to return.", - }, - ], - }, - { - id: "storage-query-type", - name: "StorageQueryType", - category: "chain", - definition: - 'export type StorageQueryType = "Value" | "Hash" | "ClosestDescendantMerkleValue" | "DescendantsValues" | "DescendantsHashes";', - variants: [ - { - name: "Value", - type: '{ tag: "Value"; value?: undefined }', - }, - { - name: "Hash", - type: '{ tag: "Hash"; value?: undefined }', - }, - { - name: "ClosestDescendantMerkleValue", - type: '{ tag: "ClosestDescendantMerkleValue"; value?: undefined }', - }, - { - name: "DescendantsValues", - type: '{ tag: "DescendantsValues"; value?: undefined }', - }, - { - name: "DescendantsHashes", - type: '{ tag: "DescendantsHashes"; value?: undefined }', - }, - ], - }, - { - id: "storage-result-item", - name: "StorageResultItem", - category: "chain", - definition: - "export interface StorageResultItem {\n key: HexString;\n value?: HexString;\n hash?: HexString;\n closestDescendantMerkleValue?: HexString;\n}", - fields: [ - { - name: "key", - type: "HexString", - description: "The queried key.", - }, - { - name: "value", - type: "HexString | undefined", - description: "Value, if requested.", - }, - { - name: "hash", - type: "HexString | undefined", - description: "Hash, if requested.", - }, - { - name: "closest_descendant_merkle_value", - type: "HexString | undefined", - description: "Merkle value, if requested.", - }, - ], - }, - { - id: "text-field-props", - name: "TextFieldProps", - category: "chat", - definition: - "export interface TextFieldProps {\n text: string;\n placeholder?: string;\n label?: string;\n enabled: boolean | undefined;\n valueChangeAction?: string;\n}", - description: "Properties for a [`CustomRendererNode::TextField`].", - fields: [ - { - name: "text", - type: "string", - description: "Current text value.", - }, - { - name: "placeholder", - type: "string | undefined", - description: "Placeholder text.", - }, - { - name: "label", - type: "string | undefined", - description: "Field label.", - }, - { - name: "enabled", - type: "boolean | undefined", - description: - "Whether the field is enabled. Absent leaves the default to the host.", - }, - { - name: "value_change_action", - type: "string | undefined", - description: "Action identifier triggered when the value changes.", - }, - ], - }, - { - id: "text-props", - name: "TextProps", - category: "chat", - definition: - "export interface TextProps {\n style?: TypographyStyle;\n color?: ColorToken;\n}", - description: "Properties for a [`CustomRendererNode::Text`] display.", - fields: [ - { - name: "style", - type: "TypographyStyle | undefined", - description: "Typography preset.", - }, - { - name: "color", - type: "ColorToken | undefined", - description: "Text color.", - }, - ], - }, - { - id: "theme-name", - name: "ThemeName", - category: "theme", - definition: - 'export type ThemeName =\n | { tag: "Custom"; value: string }\n | { tag: "Default"; value?: undefined }\n;', - description: "Identifies a named theme.", - variants: [ - { - name: "Custom", - type: '{ tag: "Custom"; value: string }', - description: "A custom named theme.", - }, - { - name: "Default", - type: '{ tag: "Default"; value?: undefined }', - description: "The host's default theme.", - }, - ], - }, - { - id: "theme-variant", - name: "ThemeVariant", - category: "theme", - definition: 'export type ThemeVariant = "Light" | "Dark";', - description: "Light or dark variant.", - variants: [ - { - name: "Light", - type: '{ tag: "Light"; value?: undefined }', - }, - { - name: "Dark", - type: '{ tag: "Dark"; value?: undefined }', - }, - ], - }, - { - id: "topic", - name: "Topic", - category: "statement_store", - definition: "export type Topic = HexString;", - description: "32-byte statement topic.", - }, - { - id: "tx-payload-extension", - name: "TxPayloadExtension", - category: "transaction", - definition: - "export interface TxPayloadExtension {\n id: string;\n extra: HexString;\n additionalSigned: HexString;\n}", - description: "A signed extension for a transaction payload.", - fields: [ - { - name: "id", - type: "string", - description: 'Extension name (e.g., `"CheckSpecVersion"`).', - }, - { - name: "extra", - type: "HexString", - description: "SCALE-encoded extra data (in extrinsic body).", - }, - { - name: "additional_signed", - type: "HexString", - description: "SCALE-encoded implicit data (signed, not in body).", - }, - ], - }, - { - id: "typography-style", - name: "TypographyStyle", - category: "chat", - definition: - 'export type TypographyStyle = "HeadlineLarge" | "TitleMediumRegular" | "BodyLargeRegular" | "BodyMediumRegular" | "BodySmallRegular";', - description: "Text typography presets.", - variants: [ - { - name: "HeadlineLarge", - type: '{ tag: "HeadlineLarge"; value?: undefined }', - }, - { - name: "TitleMediumRegular", - type: '{ tag: "TitleMediumRegular"; value?: undefined }', - }, - { - name: "BodyLargeRegular", - type: '{ tag: "BodyLargeRegular"; value?: undefined }', - }, - { - name: "BodyMediumRegular", - type: '{ tag: "BodyMediumRegular"; value?: undefined }', - }, - { - name: "BodySmallRegular", - type: '{ tag: "BodySmallRegular"; value?: undefined }', - }, - ], - }, - { - id: "vertical-alignment", - name: "VerticalAlignment", - category: "chat", - definition: 'export type VerticalAlignment = "Top" | "Center" | "Bottom";', - description: "Vertical alignment options.", - variants: [ - { - name: "Top", - type: '{ tag: "Top"; value?: undefined }', - }, - { - name: "Center", - type: '{ tag: "Center"; value?: undefined }', - }, - { - name: "Bottom", - type: '{ tag: "Bottom"; value?: undefined }', - }, - ], - }, -]; diff --git a/js/packages/truapi/src/sandbox.ts b/js/packages/truapi/src/sandbox.ts index 51861e4e..702cda26 100644 --- a/js/packages/truapi/src/sandbox.ts +++ b/js/packages/truapi/src/sandbox.ts @@ -10,11 +10,7 @@ * @module */ -import { - createIframeProvider, - createMessagePortProvider, - type WireProvider, -} from "./transport.js"; +import { createMessagePortProvider, type WireProvider } from "./transport.js"; import { createTransport } from "./client.js"; import { createClient, type TrUApiClient } from "./generated/index.js"; @@ -62,10 +58,7 @@ export function isCorrectEnvironment(): boolean { } /** - * Origin used as the `targetOrigin` for outbound `postMessage` frames. Frames - * carry signed payloads and account ids, so this fails closed: when no concrete - * origin can be pinned it returns `null` (rather than falling back to `"*"`) and - * provider construction throws. + * Origin used as the `targetOrigin` for iframe bootstrap messages. */ function resolveHostOrigin(): string | null { if (typeof document !== "undefined" && document.referrer) { @@ -80,7 +73,8 @@ function resolveHostOrigin(): string | null { return null; } -const WEBVIEW_PORT_TIMEOUT_MS = 20_000; +const HOST_PORT_TIMEOUT_MS = 20_000; +let iframePortPromise: Promise | null = null; /** * Resolve the host-injected `MessagePort`, polling `window.__HOST_API_PORT__` @@ -93,7 +87,7 @@ const WEBVIEW_PORT_TIMEOUT_MS = 20_000; */ async function waitForWebviewPort( signal?: AbortSignal, - timeoutMs = WEBVIEW_PORT_TIMEOUT_MS, + timeoutMs = HOST_PORT_TIMEOUT_MS, ): Promise { const start = Date.now(); while (Date.now() - start < timeoutMs) { @@ -107,21 +101,86 @@ async function waitForWebviewPort( ); } -/** Build the {@link WireProvider} matching the detected environment (iframe or webview). */ -function createSandboxProvider(): WireProvider { - if (isIframe()) { +/** + * Resolve the iframe `MessagePort` transferred by `createIframeHost`. + */ +function waitForIframePort( + signal?: AbortSignal, + timeoutMs = HOST_PORT_TIMEOUT_MS, +): Promise { + const existing = hostWindow()?.__HOST_API_PORT__; + if (existing) return Promise.resolve(existing); + if (iframePortPromise) return iframePortPromise; + + iframePortPromise = new Promise((resolve, reject) => { + const win = hostWindow(); + if (!win) { + reject(new Error("window is unavailable")); + return; + } + const hostOrigin = resolveHostOrigin(); - if (!hostOrigin) { - throw new Error( - "TrUAPI iframe provider could not resolve the host origin from document.referrer / ancestorOrigins.", + let done = false; + const cleanup = (): void => { + win.removeEventListener("message", onMessage); + signal?.removeEventListener("abort", onAbort); + clearTimeout(timer); + }; + const finish = (result: MessagePort | Error): void => { + if (done) return; + done = true; + cleanup(); + if (result instanceof Error) { + reject(result); + } else { + win.__HOST_API_PORT__ = result; + resolve(result); + } + }; + const onAbort = (): void => { + finish(new Error("waitForIframePort aborted")); + }; + const onMessage = (event: MessageEvent): void => { + if (event.source !== win.parent) return; + if ( + hostOrigin !== null && + event.origin !== hostOrigin && + event.origin !== "null" + ) { + return; + } + if (event.data?.type !== "truapi-init") return; + const [port] = event.ports; + if (!port) { + finish(new Error("truapi-init did not include a MessagePort")); + return; + } + finish(port); + }; + const timer = setTimeout(() => { + finish( + new Error(`Timed out waiting for iframe MessagePort (${timeoutMs}ms)`), ); - } - return createIframeProvider({ target: window.parent, hostOrigin }); - } + }, timeoutMs); + + win.addEventListener("message", onMessage); + signal?.addEventListener("abort", onAbort, { once: true }); + win.parent.postMessage({ type: "truapi-ready" }, hostOrigin ?? "*"); + }).catch((error: unknown) => { + iframePortPromise = null; + throw error; + }); + + return iframePortPromise; +} + +/** Build the {@link WireProvider} matching the detected environment (iframe or webview). */ +function createSandboxProvider(): WireProvider { const portController = new AbortController(); - const provider = createMessagePortProvider( - waitForWebviewPort(portController.signal), - ); + const portPromise = isIframe() + ? waitForIframePort(portController.signal) + : waitForWebviewPort(portController.signal); + const provider = createMessagePortProvider(portPromise); const baseDispose = provider.dispose; provider.dispose = () => { portController.abort(); @@ -166,14 +225,21 @@ export function getClientSync(): TrUApiClient | null { export function subscribeConnectionStatus( callback: (status: ConnectionStatus) => void, ): () => void { - statusListeners.add(callback); - callback(status); + let emitted = false; + const listener = (next: ConnectionStatus) => { + emitted = true; + callback(next); + }; + statusListeners.add(listener); if (status === "disconnected") { setStatus(getClientSync() ? "connected" : "disconnected"); } + if (!emitted) { + callback(status); + } return () => { - statusListeners.delete(callback); + statusListeners.delete(listener); }; } diff --git a/js/packages/truapi/src/transport.ts b/js/packages/truapi/src/transport.ts index 0f9ad4de..926ec77b 100644 --- a/js/packages/truapi/src/transport.ts +++ b/js/packages/truapi/src/transport.ts @@ -201,12 +201,11 @@ export interface SubscribeRawParams { **/ export interface TrUApiTransport { /** - * Highest TrUAPI protocol version supported by this generated client. - **/ - readonly truapiVersion: number; - - /** - * SCALE codec version negotiated through the handshake. + * SCALE codec version used by generated handshake calls. + * + * @deprecated TODO(shared-core-wire): remove this public transport field once + * generated handshake requests read `TRUAPI_CODEC_VERSION` directly instead + * of going through transport state. **/ readonly codecVersion: number; diff --git a/js/packages/truapi/tsconfig.json b/js/packages/truapi/tsconfig.json index 79d35ea5..56a11d6f 100644 --- a/js/packages/truapi/tsconfig.json +++ b/js/packages/truapi/tsconfig.json @@ -3,6 +3,7 @@ "target": "ES2022", "module": "ES2022", "moduleResolution": "bundler", + "composite": true, "declaration": true, "outDir": "dist", "rootDir": "src", diff --git a/package-lock.json b/package-lock.json index 51905f9a..36199f2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,19 @@ "typescript": "^6.0" } }, + "js/packages/truapi-host-wasm": { + "name": "@parity/truapi-host-wasm", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@parity/truapi": "file:../truapi" + }, + "devDependencies": { + "@types/bun": "^1.3.0", + "neverthrow": "^8.2.0", + "typescript": "^5.7" + } + }, "js/packages/truapi/node_modules/typescript": { "version": "6.0.3", "dev": true, @@ -414,6 +427,10 @@ "resolved": "js/packages/truapi", "link": true }, + "node_modules/@parity/truapi-host-wasm": { + "resolved": "js/packages/truapi-host-wasm", + "link": true + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", @@ -1173,6 +1190,20 @@ "node": ">=8.0" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", diff --git a/playground/tests/e2e/testing.spec.ts b/playground/tests/e2e/testing.spec.ts new file mode 100644 index 00000000..74b2ba81 --- /dev/null +++ b/playground/tests/e2e/testing.spec.ts @@ -0,0 +1,40 @@ +import { expect, test } from "@playwright/test"; +import { openPlaygroundInDotli, selectMethod, waitForOnline } from "./helpers"; + +test.describe("testing service", () => { + test("version_probe runs through the latest generated request version", async ({ + page, + }) => { + const frame = await openPlaygroundInDotli(page); + await waitForOnline(frame); + + await selectMethod(frame, "Testing", "version_probe"); + await frame.locator('[data-testid="call-button"]').click(); + + const entries = frame.locator('[data-testid="stream-entry"]'); + await expect(entries.first()).toBeVisible({ timeout: 5_000 }); + await expect(frame.locator('[data-testid="error-display"]')).toHaveCount(0); + + const text = await entries.first().innerText(); + expect(text).toContain("testing version probe:"); + expect(text).toContain("receivedVersion"); + expect(text).toContain("2"); + }); + + test("echo_error surfaces a framework call error", async ({ page }) => { + const frame = await openPlaygroundInDotli(page); + await waitForOnline(frame); + + await selectMethod(frame, "Testing", "echo_error"); + await frame.locator('[data-testid="call-button"]').click(); + + const entries = frame.locator('[data-testid="stream-entry"]'); + await expect(entries.first()).toBeVisible({ timeout: 5_000 }); + await expect(frame.locator('[data-testid="error-display"]')).toHaveCount(0); + + const text = await entries.first().innerText(); + expect(text).toContain("echo error:"); + expect(text).toContain("HostFailure"); + expect(text).toContain("forced by test"); + }); +}); From 3502aca8fdf3411da3b0fb1f183cde9e7135a2a8 Mon Sep 17 00:00:00 2001 From: pgherveou Date: Wed, 1 Jul 2026 15:20:44 +0200 Subject: [PATCH 2/2] fixup! feat(host-wasm): add @parity/truapi-host-wasm runtime --- .../codegen/versions/0.3.2/services.ts | 724 ++++ .../explorer/codegen/versions/0.3.2/types.ts | 3836 +++++++++++++++++ 2 files changed, 4560 insertions(+) create mode 100644 js/packages/truapi/src/explorer/codegen/versions/0.3.2/services.ts create mode 100644 js/packages/truapi/src/explorer/codegen/versions/0.3.2/types.ts diff --git a/js/packages/truapi/src/explorer/codegen/versions/0.3.2/services.ts b/js/packages/truapi/src/explorer/codegen/versions/0.3.2/services.ts new file mode 100644 index 00000000..8916c096 --- /dev/null +++ b/js/packages/truapi/src/explorer/codegen/versions/0.3.2/services.ts @@ -0,0 +1,724 @@ +// Auto-generated by truapi-codegen. Do not edit. +import type { ServiceInfo } from "../../../../playground/services-types.js"; + +export const services: ServiceInfo[] = [ + { + name: "Account", + methods: [ + { + name: "connection_status_subscribe", + type: "subscription", + signature: + "connectionStatusSubscribe(): ObservableLike", + docUrl: + "api/account/trait.Account.html#method.connection_status_subscribe", + description: "Subscribe to account connection status changes.", + responseType: "host-account-connection-status-subscribe-item", + }, + { + name: "get_account", + type: "unary", + signature: + "getAccount(request: HostAccountGetRequest): Promise>", + docUrl: "api/account/trait.Account.html#method.get_account", + description: "Retrieve a product-scoped account.", + requestDescription: "HostAccountGetRequest", + requestType: "host-account-get-request", + responseType: "host-account-get-response", + errorType: "host-account-get-error", + }, + { + name: "get_account_alias", + type: "unary", + signature: + "getAccountAlias(request: HostAccountGetAliasRequest): Promise>", + docUrl: "api/account/trait.Account.html#method.get_account_alias", + description: "Retrieve a contextual alias for a product account.", + requestDescription: "HostAccountGetAliasRequest", + requestType: "host-account-get-alias-request", + responseType: "host-account-get-alias-response", + errorType: "host-account-get-error", + }, + { + name: "create_account_proof", + type: "unary", + signature: + "createAccountProof(request: HostAccountCreateProofRequest): Promise>", + docUrl: "api/account/trait.Account.html#method.create_account_proof", + description: "Generate a ring VRF proof for a product account.", + requestDescription: "HostAccountCreateProofRequest", + requestType: "host-account-create-proof-request", + responseType: "host-account-create-proof-response", + errorType: "host-account-create-proof-error", + }, + { + name: "get_legacy_accounts", + type: "unary", + signature: + "getLegacyAccounts(): Promise>", + docUrl: "api/account/trait.Account.html#method.get_legacy_accounts", + description: "List non-product accounts the user owns.", + responseType: "host-get-legacy-accounts-response", + errorType: "host-account-get-error", + }, + { + name: "get_user_id", + type: "unary", + signature: + "getUserId(): Promise>", + docUrl: "api/account/trait.Account.html#method.get_user_id", + description: "Fetch the user's primary identity.", + responseType: "host-get-user-id-response", + errorType: "host-get-user-id-error", + }, + { + name: "request_login", + type: "unary", + signature: + "requestLogin(request: HostRequestLoginRequest): Promise>", + docUrl: "api/account/trait.Account.html#method.request_login", + description: + 'Request the host to present the login flow to the user.\n\nProducts should call this in response to a user action (e.g. tapping a\n"Sign in" button), not automatically on load.', + requestDescription: "HostRequestLoginRequest", + requestType: "host-request-login-request", + responseType: "host-request-login-response", + errorType: "host-request-login-error", + }, + ], + }, + { + name: "Chain", + methods: [ + { + name: "follow_head_subscribe", + type: "subscription", + signature: + "followHeadSubscribe(request: RemoteChainHeadFollowRequest): ObservableLike", + docUrl: "api/chain/trait.Chain.html#method.follow_head_subscribe", + description: "Follow the chain head and receive block events.", + requestDescription: "RemoteChainHeadFollowRequest", + requestType: "remote-chain-head-follow-request", + responseType: "remote-chain-head-follow-item", + }, + { + name: "get_head_header", + type: "unary", + signature: + "getHeadHeader(request: RemoteChainHeadHeaderRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.get_head_header", + description: "Fetch a block header.", + requestDescription: "RemoteChainHeadHeaderRequest", + requestType: "remote-chain-head-header-request", + responseType: "remote-chain-head-header-response", + errorType: "generic-error", + }, + { + name: "get_head_body", + type: "unary", + signature: + "getHeadBody(request: RemoteChainHeadBodyRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.get_head_body", + description: "Fetch a block body.", + requestDescription: "RemoteChainHeadBodyRequest", + requestType: "remote-chain-head-body-request", + responseType: "remote-chain-head-body-response", + errorType: "generic-error", + }, + { + name: "get_head_storage", + type: "unary", + signature: + "getHeadStorage(request: RemoteChainHeadStorageRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.get_head_storage", + description: "Query runtime storage at a specific block.", + requestDescription: "RemoteChainHeadStorageRequest", + requestType: "remote-chain-head-storage-request", + responseType: "remote-chain-head-storage-response", + errorType: "generic-error", + }, + { + name: "call_head", + type: "unary", + signature: + "callHead(request: RemoteChainHeadCallRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.call_head", + description: "Invoke a runtime call at a specific block.", + requestDescription: "RemoteChainHeadCallRequest", + requestType: "remote-chain-head-call-request", + responseType: "remote-chain-head-call-response", + errorType: "generic-error", + }, + { + name: "unpin_head", + type: "unary", + signature: + "unpinHead(request: RemoteChainHeadUnpinRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.unpin_head", + description: "Release pinned blocks.", + requestDescription: "RemoteChainHeadUnpinRequest", + requestType: "remote-chain-head-unpin-request", + errorType: "generic-error", + }, + { + name: "continue_head", + type: "unary", + signature: + "continueHead(request: RemoteChainHeadContinueRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.continue_head", + description: "Continue a paused chain-head operation.", + requestDescription: "RemoteChainHeadContinueRequest", + requestType: "remote-chain-head-continue-request", + errorType: "generic-error", + }, + { + name: "stop_head_operation", + type: "unary", + signature: + "stopHeadOperation(request: RemoteChainHeadStopOperationRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.stop_head_operation", + description: "Stop a chain-head operation.", + requestDescription: "RemoteChainHeadStopOperationRequest", + requestType: "remote-chain-head-stop-operation-request", + errorType: "generic-error", + }, + { + name: "get_spec_genesis_hash", + type: "unary", + signature: + "getSpecGenesisHash(request: RemoteChainSpecGenesisHashRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.get_spec_genesis_hash", + description: "Fetch the canonical genesis hash for a chain.", + requestDescription: "RemoteChainSpecGenesisHashRequest", + requestType: "remote-chain-spec-genesis-hash-request", + responseType: "remote-chain-spec-genesis-hash-response", + errorType: "generic-error", + }, + { + name: "get_spec_chain_name", + type: "unary", + signature: + "getSpecChainName(request: RemoteChainSpecChainNameRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.get_spec_chain_name", + description: "Fetch the display name of a chain.", + requestDescription: "RemoteChainSpecChainNameRequest", + requestType: "remote-chain-spec-chain-name-request", + responseType: "remote-chain-spec-chain-name-response", + errorType: "generic-error", + }, + { + name: "get_spec_properties", + type: "unary", + signature: + "getSpecProperties(request: RemoteChainSpecPropertiesRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.get_spec_properties", + description: "Fetch the JSON-encoded properties of a chain.", + requestDescription: "RemoteChainSpecPropertiesRequest", + requestType: "remote-chain-spec-properties-request", + responseType: "remote-chain-spec-properties-response", + errorType: "generic-error", + }, + { + name: "broadcast_transaction", + type: "unary", + signature: + "broadcastTransaction(request: RemoteChainTransactionBroadcastRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.broadcast_transaction", + description: "Broadcast a signed transaction.", + requestDescription: "RemoteChainTransactionBroadcastRequest", + requestType: "remote-chain-transaction-broadcast-request", + responseType: "remote-chain-transaction-broadcast-response", + errorType: "generic-error", + }, + { + name: "stop_transaction", + type: "unary", + signature: + "stopTransaction(request: RemoteChainTransactionStopRequest): Promise>", + docUrl: "api/chain/trait.Chain.html#method.stop_transaction", + description: "Stop a transaction broadcast.", + requestDescription: "RemoteChainTransactionStopRequest", + requestType: "remote-chain-transaction-stop-request", + errorType: "generic-error", + }, + ], + }, + { + name: "Chat", + methods: [ + { + name: "create_room", + type: "unary", + signature: + "createRoom(request: HostChatCreateRoomRequest): Promise>", + docUrl: "api/chat/trait.Chat.html#method.create_room", + description: "Create a chat room.", + requestDescription: "HostChatCreateRoomRequest", + requestType: "host-chat-create-room-request", + responseType: "host-chat-create-room-response", + errorType: "host-chat-create-room-error", + }, + { + name: "register_bot", + type: "unary", + signature: + "registerBot(request: HostChatRegisterBotRequest): Promise>", + docUrl: "api/chat/trait.Chat.html#method.register_bot", + description: "Register a chat bot.", + requestDescription: "HostChatRegisterBotRequest", + requestType: "host-chat-register-bot-request", + responseType: "host-chat-register-bot-response", + errorType: "host-chat-register-bot-error", + }, + { + name: "list_subscribe", + type: "subscription", + signature: "listSubscribe(): ObservableLike", + docUrl: "api/chat/trait.Chat.html#method.list_subscribe", + description: "Subscribe to the list of chat rooms.", + responseType: "host-chat-list-subscribe-item", + }, + { + name: "post_message", + type: "unary", + signature: + "postMessage(request: HostChatPostMessageRequest): Promise>", + docUrl: "api/chat/trait.Chat.html#method.post_message", + description: "Post a message to a chat room.", + requestDescription: "HostChatPostMessageRequest", + requestType: "host-chat-post-message-request", + responseType: "host-chat-post-message-response", + errorType: "host-chat-post-message-error", + }, + { + name: "action_subscribe", + type: "subscription", + signature: + "actionSubscribe(): ObservableLike", + docUrl: "api/chat/trait.Chat.html#method.action_subscribe", + description: "Subscribe to received chat actions.", + responseType: "host-chat-action-subscribe-item", + }, + { + name: "custom_message_render_subscribe", + type: "subscription", + signature: + "customMessageRenderSubscribe(request: ProductChatCustomMessageRenderSubscribeRequest): ObservableLike", + docUrl: + "api/chat/trait.Chat.html#method.custom_message_render_subscribe", + description: + "Subscribe to custom message render requests from the host. Each\nemitted item is a [`CustomRendererNode`](crate::v01::CustomRendererNode)\ntree describing the rendered UI.", + requestDescription: "ProductChatCustomMessageRenderSubscribeRequest", + requestType: "product-chat-custom-message-render-subscribe-request", + responseType: "custom-renderer-node", + }, + ], + }, + { + name: "Entropy", + methods: [ + { + name: "derive", + type: "unary", + signature: + "derive(request: HostDeriveEntropyRequest): Promise>", + docUrl: "api/entropy/trait.Entropy.html#method.derive", + description: "Derive deterministic entropy.", + requestDescription: "HostDeriveEntropyRequest", + requestType: "host-derive-entropy-request", + responseType: "host-derive-entropy-response", + errorType: "host-derive-entropy-error", + }, + ], + }, + { + name: "Local Storage", + methods: [ + { + name: "read", + type: "unary", + signature: + "read(request: HostLocalStorageReadRequest): Promise>", + docUrl: "api/local_storage/trait.LocalStorage.html#method.read", + description: "Read a value by key.", + requestDescription: "HostLocalStorageReadRequest", + requestType: "host-local-storage-read-request", + responseType: "host-local-storage-read-response", + errorType: "host-local-storage-read-error", + }, + { + name: "write", + type: "unary", + signature: + "write(request: HostLocalStorageWriteRequest): Promise>", + docUrl: "api/local_storage/trait.LocalStorage.html#method.write", + description: "Write a value to a key.", + requestDescription: "HostLocalStorageWriteRequest", + requestType: "host-local-storage-write-request", + errorType: "host-local-storage-read-error", + }, + { + name: "clear", + type: "unary", + signature: + "clear(request: HostLocalStorageClearRequest): Promise>", + docUrl: "api/local_storage/trait.LocalStorage.html#method.clear", + description: "Clear a value by key.", + requestDescription: "HostLocalStorageClearRequest", + requestType: "host-local-storage-clear-request", + errorType: "host-local-storage-read-error", + }, + ], + }, + { + name: "Notifications", + methods: [ + { + name: "send_push_notification", + type: "unary", + signature: + "sendPushNotification(request: HostPushNotificationRequest): Promise>", + docUrl: + "api/notifications/trait.Notifications.html#method.send_push_notification", + description: + "Send a push notification to the user.\n\nReturns a [`NotificationId`](crate::v01::NotificationId) that can be\npassed to [`cancel_push_notification`](Self::cancel_push_notification)\nto retract a scheduled notification. When `scheduled_at` is set the host\npersists the notification across restarts and fires it through the\nplatform-native scheduler. See [RFC 0019].\n\n[RFC 0019]: https://github.com/paritytech/truapi/blob/main/docs/rfcs/0019-scheduled-notifications.md", + requestDescription: "HostPushNotificationRequest", + requestType: "host-push-notification-request", + responseType: "host-push-notification-response", + errorType: "host-push-notification-error", + }, + { + name: "cancel_push_notification", + type: "unary", + signature: + "cancelPushNotification(request: HostPushNotificationCancelRequest): Promise>", + docUrl: + "api/notifications/trait.Notifications.html#method.cancel_push_notification", + description: + "Cancels a previously issued push notification.\n\nCancellation is idempotent: returns `Ok(())` whether the notification is\nstill pending, already fired, or was never issued. See [RFC 0019].\n\n[RFC 0019]: https://github.com/paritytech/truapi/blob/main/docs/rfcs/0019-scheduled-notifications.md", + requestDescription: "HostPushNotificationCancelRequest", + requestType: "host-push-notification-cancel-request", + errorType: "generic-error", + }, + ], + }, + { + name: "Payment", + methods: [ + { + name: "balance_subscribe", + type: "subscription", + signature: + "balanceSubscribe(request: HostPaymentBalanceSubscribeRequest): ObservableLike", + docUrl: "api/payment/trait.Payment.html#method.balance_subscribe", + description: "Subscribe to payment balance updates.", + requestDescription: "HostPaymentBalanceSubscribeRequest", + requestType: "host-payment-balance-subscribe-request", + responseType: "host-payment-balance-subscribe-item", + errorType: "host-payment-balance-subscribe-error", + }, + { + name: "top_up", + type: "unary", + signature: + "topUp(request: HostPaymentTopUpRequest): Promise>", + docUrl: "api/payment/trait.Payment.html#method.top_up", + description: "Top up the user's payment balance.", + requestDescription: "HostPaymentTopUpRequest", + requestType: "host-payment-top-up-request", + errorType: "host-payment-top-up-error", + }, + { + name: "request", + type: "unary", + signature: + "request(request: HostPaymentRequest): Promise>", + docUrl: "api/payment/trait.Payment.html#method.request", + description: "Request a payment from the user.", + requestDescription: "HostPaymentRequest", + requestType: "host-payment-request", + responseType: "host-payment-response", + errorType: "host-payment-error", + }, + { + name: "status_subscribe", + type: "subscription", + signature: + "statusSubscribe(request: HostPaymentStatusSubscribeRequest): ObservableLike", + docUrl: "api/payment/trait.Payment.html#method.status_subscribe", + description: + "Subscribe to payment lifecycle updates for a specific payment.", + requestDescription: "HostPaymentStatusSubscribeRequest", + requestType: "host-payment-status-subscribe-request", + responseType: "host-payment-status-subscribe-item", + errorType: "host-payment-status-subscribe-error", + }, + ], + }, + { + name: "Permissions", + methods: [ + { + name: "request_device_permission", + type: "unary", + signature: + "requestDevicePermission(request: HostDevicePermissionRequest): Promise>", + docUrl: + "api/permissions/trait.Permissions.html#method.request_device_permission", + description: "Request a device-capability permission from the user.", + requestDescription: + "Enum values: Notifications / Camera / Microphone / Bluetooth / NFC / Location / Clipboard / OpenUrl / Biometrics", + requestType: "host-device-permission-request", + responseType: "host-device-permission-response", + errorType: "generic-error", + }, + { + name: "request_remote_permission", + type: "unary", + signature: + "requestRemotePermission(request: RemotePermissionRequest): Promise>", + docUrl: + "api/permissions/trait.Permissions.html#method.request_remote_permission", + description: "Request a remote-operation permission.", + requestDescription: "RemotePermissionRequest", + requestType: "remote-permission-request", + responseType: "remote-permission-response", + errorType: "generic-error", + }, + ], + }, + { + name: "Preimage", + methods: [ + { + name: "lookup_subscribe", + type: "subscription", + signature: + "lookupSubscribe(request: RemotePreimageLookupSubscribeRequest): ObservableLike", + docUrl: "api/preimage/trait.Preimage.html#method.lookup_subscribe", + description: "Subscribe to preimage lookups for a given key.", + requestDescription: "RemotePreimageLookupSubscribeRequest", + requestType: "remote-preimage-lookup-subscribe-request", + responseType: "remote-preimage-lookup-subscribe-item", + }, + { + name: "submit", + type: "unary", + signature: + "submit(request: HexString): Promise>", + docUrl: "api/preimage/trait.Preimage.html#method.submit", + description: + "Submit a preimage. Returns the preimage key (hash) on success.", + requestDescription: "HexString", + errorType: "preimage-submit-error", + }, + ], + }, + { + name: "Resource Allocation", + methods: [ + { + name: "request", + type: "unary", + signature: + "request(request: HostRequestResourceAllocationRequest): Promise>", + docUrl: + "api/resource_allocation/trait.ResourceAllocation.html#method.request", + description: "Request the host to pre-allocate one or more resources.", + requestDescription: "HostRequestResourceAllocationRequest", + requestType: "host-request-resource-allocation-request", + responseType: "host-request-resource-allocation-response", + errorType: "resource-allocation-error", + }, + ], + }, + { + name: "Signing", + methods: [ + { + name: "create_transaction", + type: "unary", + signature: + "createTransaction(request: ProductAccountTxPayload): Promise>", + docUrl: "api/signing/trait.Signing.html#method.create_transaction", + description: "Construct a signed transaction for a product account.", + requestDescription: "ProductAccountTxPayload", + requestType: "product-account-tx-payload", + responseType: "host-create-transaction-response", + errorType: "host-create-transaction-error", + }, + { + name: "create_transaction_with_legacy_account", + type: "unary", + signature: + "createTransactionWithLegacyAccount(request: LegacyAccountTxPayload): Promise>", + docUrl: + "api/signing/trait.Signing.html#method.create_transaction_with_legacy_account", + description: + "Construct a signed transaction for a non-product (legacy) account.", + requestDescription: "LegacyAccountTxPayload", + requestType: "legacy-account-tx-payload", + responseType: "host-create-transaction-with-legacy-account-response", + errorType: "host-create-transaction-error", + }, + { + name: "sign_raw_with_legacy_account", + type: "unary", + signature: + "signRawWithLegacyAccount(request: HostSignRawWithLegacyAccountRequest): Promise>", + docUrl: + "api/signing/trait.Signing.html#method.sign_raw_with_legacy_account", + description: "Sign raw bytes with a non-product account.", + requestDescription: "HostSignRawWithLegacyAccountRequest", + requestType: "host-sign-raw-with-legacy-account-request", + responseType: "host-sign-payload-response", + errorType: "host-sign-payload-error", + }, + { + name: "sign_payload_with_legacy_account", + type: "unary", + signature: + "signPayloadWithLegacyAccount(request: HostSignPayloadWithLegacyAccountRequest): Promise>", + docUrl: + "api/signing/trait.Signing.html#method.sign_payload_with_legacy_account", + description: "Sign an extrinsic payload with a non-product account.", + requestDescription: "HostSignPayloadWithLegacyAccountRequest", + requestType: "host-sign-payload-with-legacy-account-request", + responseType: "host-sign-payload-response", + errorType: "host-sign-payload-error", + }, + { + name: "sign_raw", + type: "unary", + signature: + "signRaw(request: HostSignRawRequest): Promise>", + docUrl: "api/signing/trait.Signing.html#method.sign_raw", + description: "Sign raw bytes or a message.", + requestDescription: "HostSignRawRequest", + requestType: "host-sign-raw-request", + responseType: "host-sign-payload-response", + errorType: "host-sign-payload-error", + }, + { + name: "sign_payload", + type: "unary", + signature: + "signPayload(request: HostSignPayloadRequest): Promise>", + docUrl: "api/signing/trait.Signing.html#method.sign_payload", + description: "Sign an extrinsic payload.", + requestDescription: "HostSignPayloadRequest", + requestType: "host-sign-payload-request", + responseType: "host-sign-payload-response", + errorType: "host-sign-payload-error", + }, + ], + }, + { + name: "Statement Store", + methods: [ + { + name: "subscribe", + type: "subscription", + signature: + "subscribe(request: RemoteStatementStoreSubscribeRequest): ObservableLike", + docUrl: + "api/statement_store/trait.StatementStore.html#method.subscribe", + description: "Subscribe to statements matching a topic filter.", + requestDescription: "RemoteStatementStoreSubscribeRequest", + requestType: "remote-statement-store-subscribe-request", + responseType: "remote-statement-store-subscribe-item", + }, + { + name: "create_proof", + type: "unary", + signature: + "createProof(request: RemoteStatementStoreCreateProofRequest): Promise>", + docUrl: + "api/statement_store/trait.StatementStore.html#method.create_proof", + description: + "Create a proof for a statement.\n\n**Deprecated:** use [`create_proof_authorized`](Self::create_proof_authorized)\ninstead, which uses a pre-allocated allowance account and does not\nrequire a per-call signing prompt.", + requestDescription: "RemoteStatementStoreCreateProofRequest", + requestType: "remote-statement-store-create-proof-request", + responseType: "remote-statement-store-create-proof-response", + errorType: "remote-statement-store-create-proof-error", + }, + { + name: "submit", + type: "unary", + signature: + "submit(request: SignedStatement): Promise>", + docUrl: "api/statement_store/trait.StatementStore.html#method.submit", + description: + "Submit a signed statement to the network. The request body is the\n[`SignedStatement`](crate::v01::SignedStatement) directly (no wrapping\nstruct), matching upstream `triangle-js-sdks`.", + requestDescription: "SignedStatement", + requestType: "signed-statement", + errorType: "generic-error", + }, + { + name: "create_proof_authorized", + type: "unary", + signature: + "createProofAuthorized(request: Statement): Promise>", + docUrl: + "api/statement_store/trait.StatementStore.html#method.create_proof_authorized", + description: + "Create a proof for a statement using a pre-allocated allowance account,\nbypassing the per-call signing prompt.", + requestDescription: "Statement", + requestType: "statement", + responseType: "remote-statement-store-create-proof-response", + errorType: "remote-statement-store-create-proof-error", + }, + ], + }, + { + name: "System", + methods: [ + { + name: "handshake", + type: "unary", + signature: + "handshake(request: HostHandshakeRequest): Promise>", + docUrl: "api/system/trait.System.html#method.handshake", + description: "Negotiate the wire codec version with the product.", + requestDescription: "HostHandshakeRequest", + requestType: "host-handshake-request", + errorType: "host-handshake-error", + }, + { + name: "feature_supported", + type: "unary", + signature: + "featureSupported(request: HostFeatureSupportedRequest): Promise>", + docUrl: "api/system/trait.System.html#method.feature_supported", + description: "Query whether the host supports a specific feature.", + requestDescription: "HostFeatureSupportedRequest", + requestType: "host-feature-supported-request", + responseType: "host-feature-supported-response", + errorType: "generic-error", + }, + { + name: "navigate_to", + type: "unary", + signature: + "navigateTo(request: HostNavigateToRequest): Promise>", + docUrl: "api/system/trait.System.html#method.navigate_to", + description: "Request the host to open a URL.", + requestDescription: "HostNavigateToRequest", + requestType: "host-navigate-to-request", + errorType: "host-navigate-to-error", + }, + ], + }, + { + name: "Theme", + methods: [ + { + name: "subscribe", + type: "subscription", + signature: "subscribe(): ObservableLike", + docUrl: "api/theme/trait.Theme.html#method.subscribe", + description: "Subscribe to host theme changes.", + responseType: "host-theme-subscribe-item", + }, + ], + }, +]; diff --git a/js/packages/truapi/src/explorer/codegen/versions/0.3.2/types.ts b/js/packages/truapi/src/explorer/codegen/versions/0.3.2/types.ts new file mode 100644 index 00000000..37f778be --- /dev/null +++ b/js/packages/truapi/src/explorer/codegen/versions/0.3.2/types.ts @@ -0,0 +1,3836 @@ +// Auto-generated by truapi-codegen. Do not edit. +import type { DataType } from "../../../data-types.js"; + +export const types: DataType[] = [ + { + id: "account-id", + name: "AccountId", + category: "transaction", + definition: "export type AccountId = HexString;", + description: + "A 32-byte raw account identifier used for legacy (non-product) accounts.", + }, + { + id: "action-trigger", + name: "ActionTrigger", + category: "chat", + definition: + "export interface ActionTrigger {\n messageId: string;\n actionId: string;\n payload?: HexString;\n}", + description: "Payload when a user clicks an action button.", + fields: [ + { + name: "message_id", + type: "string", + description: "Message containing the action.", + }, + { + name: "action_id", + type: "string", + description: "Which action was triggered.", + }, + { + name: "payload", + type: "HexString | undefined", + description: "Optional additional data.", + }, + ], + }, + { + id: "allocatable-resource", + name: "AllocatableResource", + category: "resource_allocation", + definition: + 'export type AllocatableResource =\n | { tag: "StatementStoreAllowance"; value?: undefined }\n | { tag: "BulletinAllowance"; value?: undefined }\n | { tag: "SmartContractAllowance"; value: number }\n | { tag: "AutoSigning"; value?: undefined }\n;', + description: + "A resource the host can pre-allocate on behalf of the product (RFC 0010).\n\nFor the slot-table allowances (`StatementStoreAllowance`,\n`BulletinAllowance`, `SmartContractAllowance`), pre-allocation is\nopportunistic and the host may also fulfil the allowance implicitly on the\nfirst submission. `AutoSigning` must be requested explicitly through this\ncall.", + variants: [ + { + name: "StatementStoreAllowance", + type: '{ tag: "StatementStoreAllowance"; value?: undefined }', + description: + "Statement Store slot allowance for the product's own allowance account.", + }, + { + name: "BulletinAllowance", + type: '{ tag: "BulletinAllowance"; value?: undefined }', + description: + "Bulletin chain slot allowance for the product's own allowance account.", + }, + { + name: "SmartContractAllowance", + type: '{ tag: "SmartContractAllowance"; value: number }', + description: + "Pre-warmed PGAS balance for the smart-contract account at the given\nderivation index.", + }, + { + name: "AutoSigning", + type: '{ tag: "AutoSigning"; value?: undefined }', + description: + "Permission to sign on the product's behalf without per-call user prompts.", + }, + ], + }, + { + id: "allocation-outcome", + name: "AllocationOutcome", + category: "resource_allocation", + definition: + 'export type AllocationOutcome = "Allocated" | "Rejected" | "NotAvailable";', + description: "Outcome of allocating a single resource (RFC 0010).", + variants: [ + { + name: "Allocated", + type: '{ tag: "Allocated"; value?: undefined }', + description: "Resource is now available for use.", + }, + { + name: "Rejected", + type: '{ tag: "Rejected"; value?: undefined }', + description: "User or host refused the allocation.", + }, + { + name: "NotAvailable", + type: '{ tag: "NotAvailable"; value?: undefined }', + description: + "Host cannot provide this resource on the current chain or environment.", + }, + ], + }, + { + id: "arrangement", + name: "Arrangement", + category: "chat", + definition: + 'export type Arrangement = "Start" | "End" | "Center" | "SpaceBetween" | "SpaceAround" | "SpaceEvenly";', + description: "Layout arrangement (like CSS flexbox `justify-content`).", + variants: [ + { + name: "Start", + type: '{ tag: "Start"; value?: undefined }', + }, + { + name: "End", + type: '{ tag: "End"; value?: undefined }', + }, + { + name: "Center", + type: '{ tag: "Center"; value?: undefined }', + }, + { + name: "SpaceBetween", + type: '{ tag: "SpaceBetween"; value?: undefined }', + }, + { + name: "SpaceAround", + type: '{ tag: "SpaceAround"; value?: undefined }', + }, + { + name: "SpaceEvenly", + type: '{ tag: "SpaceEvenly"; value?: undefined }', + }, + ], + }, + { + id: "background", + name: "Background", + category: "chat", + definition: + "export interface Background {\n color: ColorToken;\n shape?: Shape;\n}", + description: "Background styling.", + fields: [ + { + name: "color", + type: "ColorToken", + description: "Background color.", + }, + { + name: "shape", + type: "Shape | undefined", + description: "Background shape.", + }, + ], + }, + { + id: "balance", + name: "Balance", + category: "payment", + definition: "export type Balance = bigint;", + description: + "Balance amount for payment operations. Interpreted according to the host's\nsingle fixed payment asset (e.g. pUSD).", + }, + { + id: "border-style", + name: "BorderStyle", + category: "chat", + definition: + "export interface BorderStyle {\n width: Size;\n color: ColorToken;\n shape?: Shape;\n}", + description: "Border styling.", + fields: [ + { + name: "width", + type: "Size", + description: "Border width.", + }, + { + name: "color", + type: "ColorToken", + description: "Border color.", + }, + { + name: "shape", + type: "Shape | undefined", + description: "Border shape.", + }, + ], + }, + { + id: "box-props", + name: "BoxProps", + category: "chat", + definition: + "export interface BoxProps {\n contentAlignment?: ContentAlignment;\n}", + description: "Properties for a [`CustomRendererNode::Box`] container.", + fields: [ + { + name: "content_alignment", + type: "ContentAlignment | undefined", + description: "Content alignment within the box.", + }, + ], + }, + { + id: "button-props", + name: "ButtonProps", + category: "chat", + definition: + "export interface ButtonProps {\n text: string;\n variant?: ButtonVariant;\n enabled: boolean | undefined;\n loading: boolean | undefined;\n clickAction?: string;\n}", + description: "Properties for a [`CustomRendererNode::Button`].", + fields: [ + { + name: "text", + type: "string", + description: "Button label text.", + }, + { + name: "variant", + type: "ButtonVariant | undefined", + description: "Button style variant.", + }, + { + name: "enabled", + type: "boolean | undefined", + description: + "Whether the button is enabled. Absent leaves the default to the host.", + }, + { + name: "loading", + type: "boolean | undefined", + description: + "Whether the button shows a loading state. Absent leaves the default to the host.", + }, + { + name: "click_action", + type: "string | undefined", + description: "Action identifier triggered on click.", + }, + ], + }, + { + id: "button-variant", + name: "ButtonVariant", + category: "chat", + definition: 'export type ButtonVariant = "Primary" | "Secondary" | "Text";', + description: "Button style variants.", + variants: [ + { + name: "Primary", + type: '{ tag: "Primary"; value?: undefined }', + }, + { + name: "Secondary", + type: '{ tag: "Secondary"; value?: undefined }', + }, + { + name: "Text", + type: '{ tag: "Text"; value?: undefined }', + }, + ], + }, + { + id: "chat-action", + name: "ChatAction", + category: "chat", + definition: + "export interface ChatAction {\n actionId: string;\n title: string;\n}", + description: "A clickable action button in a chat message.", + fields: [ + { + name: "action_id", + type: "string", + description: "Action identifier.", + }, + { + name: "title", + type: "string", + description: "Button label.", + }, + ], + }, + { + id: "chat-action-layout", + name: "ChatActionLayout", + category: "chat", + definition: 'export type ChatActionLayout = "Column" | "Grid";', + description: "Layout for action buttons.", + variants: [ + { + name: "Column", + type: '{ tag: "Column"; value?: undefined }', + }, + { + name: "Grid", + type: '{ tag: "Grid"; value?: undefined }', + }, + ], + }, + { + id: "chat-action-payload", + name: "ChatActionPayload", + category: "chat", + definition: + 'export type ChatActionPayload =\n | { tag: "MessagePosted"; value: ChatMessageContent }\n | { tag: "ActionTriggered"; value: ActionTrigger }\n | { tag: "Command"; value: ChatCommand }\n;', + description: "Payload of a received chat action.", + variants: [ + { + name: "MessagePosted", + type: '{ tag: "MessagePosted"; value: ChatMessageContent }', + description: "A peer posted a message.", + }, + { + name: "ActionTriggered", + type: '{ tag: "ActionTriggered"; value: ActionTrigger }', + description: "A user triggered an action button.", + }, + { + name: "Command", + type: '{ tag: "Command"; value: ChatCommand }', + description: "A user issued a command.", + }, + ], + }, + { + id: "chat-actions", + name: "ChatActions", + category: "chat", + definition: + "export interface ChatActions {\n text?: string;\n actions: Array;\n layout: ChatActionLayout;\n}", + description: "A set of action buttons with optional text.", + fields: [ + { + name: "text", + type: "string | undefined", + description: "Optional message text.", + }, + { + name: "actions", + type: "Array", + description: "List of action buttons.", + }, + { + name: "layout", + type: "ChatActionLayout", + description: "`Column` or `Grid` layout.", + }, + ], + }, + { + id: "chat-bot-registration-status", + name: "ChatBotRegistrationStatus", + category: "chat", + definition: 'export type ChatBotRegistrationStatus = "New" | "Exists";', + description: "Whether the bot was newly registered or already existed.", + variants: [ + { + name: "New", + type: '{ tag: "New"; value?: undefined }', + }, + { + name: "Exists", + type: '{ tag: "Exists"; value?: undefined }', + }, + ], + }, + { + id: "chat-command", + name: "ChatCommand", + category: "chat", + definition: + "export interface ChatCommand {\n command: string;\n payload: string;\n}", + description: "A slash command from a chat user.", + fields: [ + { + name: "command", + type: "string", + description: "Command name.", + }, + { + name: "payload", + type: "string", + description: "Command arguments.", + }, + ], + }, + { + id: "chat-custom-message", + name: "ChatCustomMessage", + category: "chat", + definition: + "export interface ChatCustomMessage {\n messageType: string;\n payload: HexString;\n}", + description: + "A custom message with application-defined type and binary payload.", + fields: [ + { + name: "message_type", + type: "string", + description: "Application-defined type key.", + }, + { + name: "payload", + type: "HexString", + description: "Binary payload.", + }, + ], + }, + { + id: "chat-file", + name: "ChatFile", + category: "chat", + definition: + "export interface ChatFile {\n url: string;\n fileName: string;\n mimeType: string;\n sizeBytes: bigint;\n text?: string;\n}", + description: "A file attachment in a chat message.", + fields: [ + { + name: "url", + type: "string", + description: "File download URL.", + }, + { + name: "file_name", + type: "string", + description: "File name.", + }, + { + name: "mime_type", + type: "string", + description: "MIME type.", + }, + { + name: "size_bytes", + type: "bigint", + description: "File size in bytes.", + }, + { + name: "text", + type: "string | undefined", + description: "Optional caption text.", + }, + ], + }, + { + id: "chat-media", + name: "ChatMedia", + category: "chat", + definition: "export interface ChatMedia {\n url: string;\n}", + description: "A media attachment.", + fields: [ + { + name: "url", + type: "string", + description: "Media URL.", + }, + ], + }, + { + id: "chat-message-content", + name: "ChatMessageContent", + category: "chat", + definition: + 'export type ChatMessageContent =\n | { tag: "Text"; value: { text: string } }\n | { tag: "RichText"; value: ChatRichText }\n | { tag: "Actions"; value: ChatActions }\n | { tag: "File"; value: ChatFile }\n | { tag: "Reaction"; value: ChatReaction }\n | { tag: "ReactionRemoved"; value: ChatReaction }\n | { tag: "Custom"; value: ChatCustomMessage }\n;', + description: "Content of a chat message -- one of several types.", + variants: [ + { + name: "Text", + type: '{ tag: "Text"; value: { text: string } }', + description: "Plain text message.", + }, + { + name: "RichText", + type: '{ tag: "RichText"; value: ChatRichText }', + description: "Rich text with media.", + }, + { + name: "Actions", + type: '{ tag: "Actions"; value: ChatActions }', + description: "Action button set.", + }, + { + name: "File", + type: '{ tag: "File"; value: ChatFile }', + description: "File attachment.", + }, + { + name: "Reaction", + type: '{ tag: "Reaction"; value: ChatReaction }', + description: "Emoji reaction.", + }, + { + name: "ReactionRemoved", + type: '{ tag: "ReactionRemoved"; value: ChatReaction }', + description: "Reaction removal.", + }, + { + name: "Custom", + type: '{ tag: "Custom"; value: ChatCustomMessage }', + description: "Custom message.", + }, + ], + }, + { + id: "chat-reaction", + name: "ChatReaction", + category: "chat", + definition: + "export interface ChatReaction {\n messageId: string;\n emoji: string;\n}", + description: "A reaction to a chat message.", + fields: [ + { + name: "message_id", + type: "string", + description: "Message being reacted to.", + }, + { + name: "emoji", + type: "string", + description: "Emoji reaction.", + }, + ], + }, + { + id: "chat-rich-text", + name: "ChatRichText", + category: "chat", + definition: + "export interface ChatRichText {\n text?: string;\n media: Array;\n}", + description: "Rich text message with optional media.", + fields: [ + { + name: "text", + type: "string | undefined", + description: "Optional text content.", + }, + { + name: "media", + type: "Array", + description: "Attached media items.", + }, + ], + }, + { + id: "chat-room", + name: "ChatRoom", + category: "chat", + definition: + "export interface ChatRoom {\n roomId: string;\n participatingAs: ChatRoomParticipation;\n}", + description: "A chat room the product participates in.", + fields: [ + { + name: "room_id", + type: "string", + description: "Room identifier.", + }, + { + name: "participating_as", + type: "ChatRoomParticipation", + description: "`RoomHost` or `Bot`.", + }, + ], + }, + { + id: "chat-room-participation", + name: "ChatRoomParticipation", + category: "chat", + definition: 'export type ChatRoomParticipation = "RoomHost" | "Bot";', + description: "How the product participates in a chat room.", + variants: [ + { + name: "RoomHost", + type: '{ tag: "RoomHost"; value?: undefined }', + }, + { + name: "Bot", + type: '{ tag: "Bot"; value?: undefined }', + }, + ], + }, + { + id: "chat-room-registration-status", + name: "ChatRoomRegistrationStatus", + category: "chat", + definition: 'export type ChatRoomRegistrationStatus = "New" | "Exists";', + description: "Whether the room was newly created or already existed.", + variants: [ + { + name: "New", + type: '{ tag: "New"; value?: undefined }', + }, + { + name: "Exists", + type: '{ tag: "Exists"; value?: undefined }', + }, + ], + }, + { + id: "color-token", + name: "ColorToken", + category: "chat", + definition: + 'export type ColorToken = "FgPrimary" | "FgSecondary" | "FgTertiary" | "BgSurfaceMain" | "BgSurfaceContainer" | "BgSurfaceNested" | "FgSuccess" | "FgError" | "FgWarning";', + description: "Semantic color tokens for theming.", + variants: [ + { + name: "FgPrimary", + type: '{ tag: "FgPrimary"; value?: undefined }', + }, + { + name: "FgSecondary", + type: '{ tag: "FgSecondary"; value?: undefined }', + }, + { + name: "FgTertiary", + type: '{ tag: "FgTertiary"; value?: undefined }', + }, + { + name: "BgSurfaceMain", + type: '{ tag: "BgSurfaceMain"; value?: undefined }', + }, + { + name: "BgSurfaceContainer", + type: '{ tag: "BgSurfaceContainer"; value?: undefined }', + }, + { + name: "BgSurfaceNested", + type: '{ tag: "BgSurfaceNested"; value?: undefined }', + }, + { + name: "FgSuccess", + type: '{ tag: "FgSuccess"; value?: undefined }', + }, + { + name: "FgError", + type: '{ tag: "FgError"; value?: undefined }', + }, + { + name: "FgWarning", + type: '{ tag: "FgWarning"; value?: undefined }', + }, + ], + }, + { + id: "column-props", + name: "ColumnProps", + category: "chat", + definition: + "export interface ColumnProps {\n horizontalAlignment?: HorizontalAlignment;\n verticalArrangement?: Arrangement;\n}", + description: "Properties for a [`CustomRendererNode::Column`] layout.", + fields: [ + { + name: "horizontal_alignment", + type: "HorizontalAlignment | undefined", + description: "Horizontal alignment of children.", + }, + { + name: "vertical_arrangement", + type: "Arrangement | undefined", + description: "Vertical arrangement of children.", + }, + ], + }, + { + id: "component", + name: "Component", + category: "chat", + definition: + "export interface Component

{\n modifiers: Array;\n props: P;\n children: Array;\n}", + description: + "A component in the custom renderer UI tree, combining modifiers, typed props,\nand recursive children.", + fields: [ + { + name: "modifiers", + type: "Array", + description: "Layout and styling modifiers.", + }, + { + name: "props", + type: "P", + description: "Component-specific properties.", + }, + { + name: "children", + type: "Array", + description: "Child nodes.", + }, + ], + }, + { + id: "content-alignment", + name: "ContentAlignment", + category: "chat", + definition: + 'export type ContentAlignment = "TopStart" | "TopCenter" | "TopEnd" | "CenterStart" | "Center" | "CenterEnd" | "BottomStart" | "BottomCenter" | "BottomEnd";', + description: "2D content alignment.", + variants: [ + { + name: "TopStart", + type: '{ tag: "TopStart"; value?: undefined }', + }, + { + name: "TopCenter", + type: '{ tag: "TopCenter"; value?: undefined }', + }, + { + name: "TopEnd", + type: '{ tag: "TopEnd"; value?: undefined }', + }, + { + name: "CenterStart", + type: '{ tag: "CenterStart"; value?: undefined }', + }, + { + name: "Center", + type: '{ tag: "Center"; value?: undefined }', + }, + { + name: "CenterEnd", + type: '{ tag: "CenterEnd"; value?: undefined }', + }, + { + name: "BottomStart", + type: '{ tag: "BottomStart"; value?: undefined }', + }, + { + name: "BottomCenter", + type: '{ tag: "BottomCenter"; value?: undefined }', + }, + { + name: "BottomEnd", + type: '{ tag: "BottomEnd"; value?: undefined }', + }, + ], + }, + { + id: "custom-renderer-node", + name: "CustomRendererNode", + category: "chat", + definition: + 'export type CustomRendererNode =\n | { tag: "Nil"; value?: undefined }\n | { tag: "String"; value: { text: string } }\n | { tag: "Box"; value: Component }\n | { tag: "Column"; value: Component }\n | { tag: "Row"; value: Component }\n | { tag: "Spacer"; value: Component }\n | { tag: "Text"; value: Component }\n | { tag: "Button"; value: Component }\n | { tag: "TextField"; value: Component }\n;', + description: + "A node in the custom renderer UI tree. Can be nested recursively via the\n`children` field of each [`Component`].", + variants: [ + { + name: "Nil", + type: '{ tag: "Nil"; value?: undefined }', + description: "Empty node.", + }, + { + name: "String", + type: '{ tag: "String"; value: { text: string } }', + description: "Raw text string.", + }, + { + name: "Box", + type: '{ tag: "Box"; value: Component }', + description: "Generic container.", + }, + { + name: "Column", + type: '{ tag: "Column"; value: Component }', + description: "Vertical layout.", + }, + { + name: "Row", + type: '{ tag: "Row"; value: Component }', + description: "Horizontal layout.", + }, + { + name: "Spacer", + type: '{ tag: "Spacer"; value: Component }', + description: "Flexible space.", + }, + { + name: "Text", + type: '{ tag: "Text"; value: Component }', + description: "Text display.", + }, + { + name: "Button", + type: '{ tag: "Button"; value: Component }', + description: "Interactive button.", + }, + { + name: "TextField", + type: '{ tag: "TextField"; value: Component }', + description: "Text input.", + }, + ], + }, + { + id: "dimensions", + name: "Dimensions", + category: "chat", + definition: + "export interface Dimensions {\n top: Size;\n end: Size;\n bottom?: Size;\n start?: Size;\n}", + description: + "CSS-like dimensions: (top, end, bottom, start).\nBottom defaults to top, start defaults to end when `None`.", + fields: [ + { + name: "top", + type: "Size", + description: "Top dimension.", + }, + { + name: "end", + type: "Size", + description: "End dimension.", + }, + { + name: "bottom", + type: "Size | undefined", + description: "Bottom dimension. Defaults to top when absent.", + }, + { + name: "start", + type: "Size | undefined", + description: "Start dimension. Defaults to end when absent.", + }, + ], + }, + { + id: "generic-error", + name: "GenericError", + category: "common", + definition: "export interface GenericError {\n reason: string;\n}", + description: + "Generic error payload carrying a human-readable reason string. Used by many\nmethods as a catch-all error type.", + fields: [ + { + name: "reason", + type: "string", + }, + ], + }, + { + id: "genesis-hash", + name: "GenesisHash", + category: "transaction", + definition: "export type GenesisHash = HexString;", + description: + "A 32-byte chain genesis hash used to identify the target chain.", + }, + { + id: "horizontal-alignment", + name: "HorizontalAlignment", + category: "chat", + definition: 'export type HorizontalAlignment = "Start" | "Center" | "End";', + description: "Horizontal alignment options.", + variants: [ + { + name: "Start", + type: '{ tag: "Start"; value?: undefined }', + }, + { + name: "Center", + type: '{ tag: "Center"; value?: undefined }', + }, + { + name: "End", + type: '{ tag: "End"; value?: undefined }', + }, + ], + }, + { + id: "host-account-connection-status-subscribe-item", + name: "HostAccountConnectionStatusSubscribeItem", + category: "account", + definition: + 'export type HostAccountConnectionStatusSubscribeItem = "Disconnected" | "Connected";', + description: "User's authentication state.", + variants: [ + { + name: "Disconnected", + type: '{ tag: "Disconnected"; value?: undefined }', + }, + { + name: "Connected", + type: '{ tag: "Connected"; value?: undefined }', + }, + ], + }, + { + id: "host-account-create-proof-error", + name: "HostAccountCreateProofError", + category: "account", + definition: + 'export type HostAccountCreateProofError =\n | { tag: "RingNotFound"; value?: undefined }\n | { tag: "Rejected"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Error returned when ring VRF proof creation fails.", + variants: [ + { + name: "RingNotFound", + type: '{ tag: "RingNotFound"; value?: undefined }', + description: "Ring not available at the specified location.", + }, + { + name: "Rejected", + type: '{ tag: "Rejected"; value?: undefined }', + description: "User or host rejected.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-account-create-proof-request", + name: "HostAccountCreateProofRequest", + category: "account", + definition: + "export interface HostAccountCreateProofRequest {\n productAccountId: ProductAccountId;\n ringLocation: RingLocation;\n context: HexString;\n}", + description: "Request to create a ring VRF proof for a product account.", + fields: [ + { + name: "product_account_id", + type: "ProductAccountId", + description: "Product account that should create the proof.", + }, + { + name: "ring_location", + type: "RingLocation", + description: "Ring location to use for proof generation.", + }, + { + name: "context", + type: "HexString", + description: "Context bytes bound to the proof.", + }, + ], + }, + { + id: "host-account-create-proof-response", + name: "HostAccountCreateProofResponse", + category: "account", + definition: + "export interface HostAccountCreateProofResponse {\n proof: HexString;\n}", + description: "Response containing a ring VRF proof.", + fields: [ + { + name: "proof", + type: "HexString", + description: "Variable-length ring VRF proof bytes.", + }, + ], + }, + { + id: "host-account-get-alias-request", + name: "HostAccountGetAliasRequest", + category: "account", + definition: + "export interface HostAccountGetAliasRequest {\n productAccountId: ProductAccountId;\n}", + description: + "Request to retrieve a contextual alias for a product account.", + fields: [ + { + name: "product_account_id", + type: "ProductAccountId", + description: "Product account to derive the alias for.", + }, + ], + }, + { + id: "host-account-get-alias-response", + name: "HostAccountGetAliasResponse", + category: "account", + definition: + "export interface HostAccountGetAliasResponse {\n context: HexString;\n alias: HexString;\n}", + description: + "A privacy-preserving alias derived via ring VRF, bound to a specific context.", + fields: [ + { + name: "context", + type: "HexString", + description: "32-byte context identifier.", + }, + { + name: "alias", + type: "HexString", + description: "Ring VRF alias (variable length).", + }, + ], + }, + { + id: "host-account-get-error", + name: "HostAccountGetError", + category: "account", + definition: + 'export type HostAccountGetError =\n | { tag: "NotConnected"; value?: undefined }\n | { tag: "Rejected"; value?: undefined }\n | { tag: "DomainNotValid"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Error returned when credential/account requests fail.", + variants: [ + { + name: "NotConnected", + type: '{ tag: "NotConnected"; value?: undefined }', + description: "User is not logged in.", + }, + { + name: "Rejected", + type: '{ tag: "Rejected"; value?: undefined }', + description: "User or host rejected the request.", + }, + { + name: "DomainNotValid", + type: '{ tag: "DomainNotValid"; value?: undefined }', + description: "Domain identifier is invalid.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all error with reason.", + }, + ], + }, + { + id: "host-account-get-request", + name: "HostAccountGetRequest", + category: "account", + definition: + "export interface HostAccountGetRequest {\n productAccountId: ProductAccountId;\n}", + description: "Request to retrieve a product-scoped account.", + fields: [ + { + name: "product_account_id", + type: "ProductAccountId", + description: "Product account to retrieve.", + }, + ], + }, + { + id: "host-account-get-response", + name: "HostAccountGetResponse", + category: "account", + definition: + "export interface HostAccountGetResponse {\n account: ProductAccount;\n}", + description: "Response containing a product-scoped account.", + fields: [ + { + name: "account", + type: "ProductAccount", + description: "Retrieved product account.", + }, + ], + }, + { + id: "host-chat-action-subscribe-item", + name: "HostChatActionSubscribeItem", + category: "chat", + definition: + "export interface HostChatActionSubscribeItem {\n roomId: string;\n peer: string;\n payload: ChatActionPayload;\n}", + description: "A chat action received from the host.", + fields: [ + { + name: "room_id", + type: "string", + description: "Room where the action occurred.", + }, + { + name: "peer", + type: "string", + description: "Peer who initiated the action.", + }, + { + name: "payload", + type: "ChatActionPayload", + description: "The action payload.", + }, + ], + }, + { + id: "host-chat-create-room-error", + name: "HostChatCreateRoomError", + category: "chat", + definition: + 'export type HostChatCreateRoomError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Chat room registration error.", + variants: [ + { + name: "PermissionDenied", + type: '{ tag: "PermissionDenied"; value?: undefined }', + description: "Not allowed.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-chat-create-room-request", + name: "HostChatCreateRoomRequest", + category: "chat", + definition: + "export interface HostChatCreateRoomRequest {\n roomId: string;\n name: string;\n icon: string;\n}", + description: "Request to create a chat room.", + fields: [ + { + name: "room_id", + type: "string", + description: "Unique room identifier.", + }, + { + name: "name", + type: "string", + description: "Room display name.", + }, + { + name: "icon", + type: "string", + description: "URL or base64 image.", + }, + ], + }, + { + id: "host-chat-create-room-response", + name: "HostChatCreateRoomResponse", + category: "chat", + definition: + "export interface HostChatCreateRoomResponse {\n status: ChatRoomRegistrationStatus;\n}", + description: "Result of a room registration.", + fields: [ + { + name: "status", + type: "ChatRoomRegistrationStatus", + description: "`New` or `Exists`.", + }, + ], + }, + { + id: "host-chat-list-subscribe-item", + name: "HostChatListSubscribeItem", + category: "chat", + definition: + "export interface HostChatListSubscribeItem {\n rooms: Array;\n}", + description: "Item containing the current chat rooms.", + fields: [ + { + name: "rooms", + type: "Array", + description: "Chat rooms the product participates in.", + }, + ], + }, + { + id: "host-chat-post-message-error", + name: "HostChatPostMessageError", + category: "chat", + definition: + 'export type HostChatPostMessageError =\n | { tag: "MessageTooLarge"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Chat message posting error.", + variants: [ + { + name: "MessageTooLarge", + type: '{ tag: "MessageTooLarge"; value?: undefined }', + description: "Message exceeded size limit.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-chat-post-message-request", + name: "HostChatPostMessageRequest", + category: "chat", + definition: + "export interface HostChatPostMessageRequest {\n roomId: string;\n payload: ChatMessageContent;\n}", + description: "Request to post a message to a chat room.", + fields: [ + { + name: "room_id", + type: "string", + description: "Room to post to.", + }, + { + name: "payload", + type: "ChatMessageContent", + description: "Message content.", + }, + ], + }, + { + id: "host-chat-post-message-response", + name: "HostChatPostMessageResponse", + category: "chat", + definition: + "export interface HostChatPostMessageResponse {\n messageId: string;\n}", + description: "Result of posting a message.", + fields: [ + { + name: "message_id", + type: "string", + description: "Assigned message ID.", + }, + ], + }, + { + id: "host-chat-register-bot-error", + name: "HostChatRegisterBotError", + category: "chat", + definition: + 'export type HostChatRegisterBotError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Chat bot registration error.", + variants: [ + { + name: "PermissionDenied", + type: '{ tag: "PermissionDenied"; value?: undefined }', + description: "Not allowed.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-chat-register-bot-request", + name: "HostChatRegisterBotRequest", + category: "chat", + definition: + "export interface HostChatRegisterBotRequest {\n botId: string;\n name: string;\n icon: string;\n}", + description: "Request to register a chat bot.", + fields: [ + { + name: "bot_id", + type: "string", + description: "Unique bot identifier.", + }, + { + name: "name", + type: "string", + description: "Bot display name.", + }, + { + name: "icon", + type: "string", + description: "URL or base64 image.", + }, + ], + }, + { + id: "host-chat-register-bot-response", + name: "HostChatRegisterBotResponse", + category: "chat", + definition: + "export interface HostChatRegisterBotResponse {\n status: ChatBotRegistrationStatus;\n}", + description: "Result of a bot registration.", + fields: [ + { + name: "status", + type: "ChatBotRegistrationStatus", + description: "`New` or `Exists`.", + }, + ], + }, + { + id: "host-create-transaction-error", + name: "HostCreateTransactionError", + category: "transaction", + definition: + 'export type HostCreateTransactionError =\n | { tag: "FailedToDecode"; value?: undefined }\n | { tag: "Rejected"; value?: undefined }\n | { tag: "NotSupported"; value: { reason: string } }\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Transaction creation error.", + variants: [ + { + name: "FailedToDecode", + type: '{ tag: "FailedToDecode"; value?: undefined }', + description: "Payload could not be deserialized.", + }, + { + name: "Rejected", + type: '{ tag: "Rejected"; value?: undefined }', + description: "User rejected.", + }, + { + name: "NotSupported", + type: '{ tag: "NotSupported"; value: { reason: string } }', + description: "Unsupported payload version or extension.", + }, + { + name: "PermissionDenied", + type: '{ tag: "PermissionDenied"; value?: undefined }', + description: "Not authenticated.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-create-transaction-response", + name: "HostCreateTransactionResponse", + category: "signing", + definition: + "export interface HostCreateTransactionResponse {\n transaction: HexString;\n}", + description: "Response containing a created transaction.", + fields: [ + { + name: "transaction", + type: "HexString", + description: "SCALE-encoded signed transaction.", + }, + ], + }, + { + id: "host-create-transaction-with-legacy-account-response", + name: "HostCreateTransactionWithLegacyAccountResponse", + category: "signing", + definition: + "export interface HostCreateTransactionWithLegacyAccountResponse {\n transaction: HexString;\n}", + description: + "Response containing a transaction created with a non-product account.", + fields: [ + { + name: "transaction", + type: "HexString", + description: "SCALE-encoded signed transaction.", + }, + ], + }, + { + id: "host-derive-entropy-error", + name: "HostDeriveEntropyError", + category: "entropy", + definition: + 'export type HostDeriveEntropyError =\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Error from [`crate::api::Entropy::derive`] (RFC 0007).", + variants: [ + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-derive-entropy-request", + name: "HostDeriveEntropyRequest", + category: "entropy", + definition: + "export interface HostDeriveEntropyRequest {\n context: HexString;\n}", + description: + "Request to derive deterministic per-product entropy (RFC 0007).\n\nThe host derives 32 bytes from product-scoped seed material and `context`.\nRepeated calls with the same `context` for the same product yield the same\nentropy.", + fields: [ + { + name: "context", + type: "HexString", + description: "Domain-separated derivation context.", + }, + ], + }, + { + id: "host-derive-entropy-response", + name: "HostDeriveEntropyResponse", + category: "entropy", + definition: + "export interface HostDeriveEntropyResponse {\n entropy: HexString;\n}", + description: + "Response carrying 32 bytes of deterministically derived entropy.", + fields: [ + { + name: "entropy", + type: "HexString", + description: "32 bytes of derived entropy.", + }, + ], + }, + { + id: "host-device-permission-request", + name: "HostDevicePermissionRequest", + category: "permissions", + definition: + 'export type HostDevicePermissionRequest = "Notifications" | "Camera" | "Microphone" | "Bluetooth" | "NFC" | "Location" | "Clipboard" | "OpenUrl" | "Biometrics";', + description: + "Device-capability permission requested from the host (RFC 0002).\n\nThe user's decision is persisted indefinitely after the first prompt and\nsurvives app restarts, whether the decision was grant or deny; the host\ndoes not re-prompt on subsequent requests for the same capability.", + variants: [ + { + name: "Notifications", + type: '{ tag: "Notifications"; value?: undefined }', + }, + { + name: "Camera", + type: '{ tag: "Camera"; value?: undefined }', + }, + { + name: "Microphone", + type: '{ tag: "Microphone"; value?: undefined }', + }, + { + name: "Bluetooth", + type: '{ tag: "Bluetooth"; value?: undefined }', + }, + { + name: "NFC", + type: '{ tag: "NFC"; value?: undefined }', + }, + { + name: "Location", + type: '{ tag: "Location"; value?: undefined }', + }, + { + name: "Clipboard", + type: '{ tag: "Clipboard"; value?: undefined }', + }, + { + name: "OpenUrl", + type: '{ tag: "OpenUrl"; value?: undefined }', + }, + { + name: "Biometrics", + type: '{ tag: "Biometrics"; value?: undefined }', + }, + ], + }, + { + id: "host-device-permission-response", + name: "HostDevicePermissionResponse", + category: "permissions", + definition: + "export interface HostDevicePermissionResponse {\n granted: boolean;\n}", + description: "Outcome of a device-permission request.", + fields: [ + { + name: "granted", + type: "boolean", + description: "Whether the permission was granted.", + }, + ], + }, + { + id: "host-feature-supported-request", + name: "HostFeatureSupportedRequest", + category: "system", + definition: + 'export type HostFeatureSupportedRequest =\n | { tag: "Chain"; value: { genesisHash: HexString } }\n;', + description: "Request to query whether a feature is supported by the host.", + variants: [ + { + name: "Chain", + type: '{ tag: "Chain"; value: { genesisHash: HexString } }', + description: + "Ask whether the host can interact with the chain identified by genesis hash.", + }, + ], + }, + { + id: "host-feature-supported-response", + name: "HostFeatureSupportedResponse", + category: "system", + definition: + "export interface HostFeatureSupportedResponse {\n supported: boolean;\n}", + description: "Response to a feature-support query.", + fields: [ + { + name: "supported", + type: "boolean", + description: "Whether the feature is supported.", + }, + ], + }, + { + id: "host-get-legacy-accounts-response", + name: "HostGetLegacyAccountsResponse", + category: "account", + definition: + "export interface HostGetLegacyAccountsResponse {\n accounts: Array;\n}", + description: + "Response containing all legacy (user-imported) accounts owned by the user.", + fields: [ + { + name: "accounts", + type: "Array", + description: "Legacy accounts.", + }, + ], + }, + { + id: "host-get-user-id-error", + name: "HostGetUserIdError", + category: "account", + definition: + 'export type HostGetUserIdError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "NotConnected"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Error from [`crate::api::Account::get_user_id`].", + variants: [ + { + name: "PermissionDenied", + type: '{ tag: "PermissionDenied"; value?: undefined }', + description: "User denied the identity disclosure request.", + }, + { + name: "NotConnected", + type: '{ tag: "NotConnected"; value?: undefined }', + description: "User is not logged in.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-get-user-id-response", + name: "HostGetUserIdResponse", + category: "account", + definition: + "export interface HostGetUserIdResponse {\n primaryUsername: string;\n}", + description: "The user's primary DotNS account identity.", + fields: [ + { + name: "primary_username", + type: "string", + description: "The user's primary DotNS username.", + }, + ], + }, + { + id: "host-handshake-error", + name: "HostHandshakeError", + category: "system", + definition: + 'export type HostHandshakeError =\n | { tag: "Timeout"; value?: undefined }\n | { tag: "UnsupportedProtocolVersion"; value?: undefined }\n | { tag: "Unknown"; value: GenericError }\n;', + description: + "Error from [`crate::api::System::handshake`] (RFC 0009).\n\nThe handshake is the first call on a fresh connection; it does not require\nuser authentication and is used to negotiate the wire codec version.", + variants: [ + { + name: "Timeout", + type: '{ tag: "Timeout"; value?: undefined }', + description: "Host did not complete the handshake in time.", + }, + { + name: "UnsupportedProtocolVersion", + type: '{ tag: "UnsupportedProtocolVersion"; value?: undefined }', + description: + "Host does not speak the codec version requested by the product.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: GenericError }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-handshake-request", + name: "HostHandshakeRequest", + category: "system", + definition: + "export interface HostHandshakeRequest {\n codecVersion: number;\n}", + description: + "Wire-codec negotiation payload sent by the product (RFC 0009).", + fields: [ + { + name: "codec_version", + type: "number", + description: "Wire codec version requested by the product.", + }, + ], + }, + { + id: "host-local-storage-clear-request", + name: "HostLocalStorageClearRequest", + category: "local_storage", + definition: + "export interface HostLocalStorageClearRequest {\n key: string;\n}", + description: "Request to clear a local storage key.", + fields: [ + { + name: "key", + type: "string", + description: "Storage key to clear.", + }, + ], + }, + { + id: "host-local-storage-read-error", + name: "HostLocalStorageReadError", + category: "local_storage", + definition: + 'export type HostLocalStorageReadError =\n | { tag: "Full"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Local storage operation error.", + variants: [ + { + name: "Full", + type: '{ tag: "Full"; value?: undefined }', + description: "Storage quota exceeded.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-local-storage-read-request", + name: "HostLocalStorageReadRequest", + category: "local_storage", + definition: + "export interface HostLocalStorageReadRequest {\n key: string;\n}", + description: "Request to read a local storage value.", + fields: [ + { + name: "key", + type: "string", + description: "Storage key to read.", + }, + ], + }, + { + id: "host-local-storage-read-response", + name: "HostLocalStorageReadResponse", + category: "local_storage", + definition: + "export interface HostLocalStorageReadResponse {\n value?: HexString;\n}", + description: "Response containing an optional local storage value.", + fields: [ + { + name: "value", + type: "HexString | undefined", + description: "Stored value, if present.", + }, + ], + }, + { + id: "host-local-storage-write-request", + name: "HostLocalStorageWriteRequest", + category: "local_storage", + definition: + "export interface HostLocalStorageWriteRequest {\n key: string;\n value: HexString;\n}", + description: "Request to write a value into local storage.", + fields: [ + { + name: "key", + type: "string", + description: "Storage key to write.", + }, + { + name: "value", + type: "HexString", + description: "Value to store at the key.", + }, + ], + }, + { + id: "host-navigate-to-error", + name: "HostNavigateToError", + category: "system", + definition: + 'export type HostNavigateToError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Error from [`crate::api::System::navigate_to`].", + variants: [ + { + name: "PermissionDenied", + type: '{ tag: "PermissionDenied"; value?: undefined }', + description: "User denied the navigation prompt.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-navigate-to-request", + name: "HostNavigateToRequest", + category: "system", + definition: "export interface HostNavigateToRequest {\n url: string;\n}", + description: "Request to navigate the host to an external URL.", + fields: [ + { + name: "url", + type: "string", + description: "URL to open.", + }, + ], + }, + { + id: "host-payment-balance-subscribe-error", + name: "HostPaymentBalanceSubscribeError", + category: "payment", + definition: + 'export type HostPaymentBalanceSubscribeError =\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: + "Error from [`crate::api::Payment::balance_subscribe`].\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", + variants: [ + { + name: "PermissionDenied", + type: '{ tag: "PermissionDenied"; value?: undefined }', + description: "User denied the balance disclosure request.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-payment-balance-subscribe-item", + name: "HostPaymentBalanceSubscribeItem", + category: "payment", + definition: + "export interface HostPaymentBalanceSubscribeItem {\n available: Balance;\n}", + description: + "Current payment balance state pushed to subscribers.\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", + fields: [ + { + name: "available", + type: "Balance", + description: "Balance that can be spent right now.", + }, + ], + }, + { + id: "host-payment-balance-subscribe-request", + name: "HostPaymentBalanceSubscribeRequest", + category: "payment", + definition: + "export interface HostPaymentBalanceSubscribeRequest {\n purse?: PaymentPurseId;\n}", + description: "Request to subscribe to payment balance updates.", + fields: [ + { + name: "purse", + type: "PaymentPurseId | undefined", + description: "Optional purse selector. `None` means MAIN_PURSE.", + }, + ], + }, + { + id: "host-payment-error", + name: "HostPaymentError", + category: "payment", + definition: + 'export type HostPaymentError =\n | { tag: "Rejected"; value?: undefined }\n | { tag: "InsufficientBalance"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: + "Error from [`crate::api::Payment::request`].\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", + variants: [ + { + name: "Rejected", + type: '{ tag: "Rejected"; value?: undefined }', + description: "User rejected the payment request.", + }, + { + name: "InsufficientBalance", + type: '{ tag: "InsufficientBalance"; value?: undefined }', + description: + "User's available balance is not sufficient for the requested amount.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-payment-request", + name: "HostPaymentRequest", + category: "payment", + definition: + "export interface HostPaymentRequest {\n from?: PaymentPurseId;\n amount: Balance;\n destination: HexString;\n}", + description: "Request to initiate a payment to another account.", + fields: [ + { + name: "from", + type: "PaymentPurseId | undefined", + description: "Optional purse selector. `None` means MAIN_PURSE.", + }, + { + name: "amount", + type: "Balance", + description: "Amount to pay.", + }, + { + name: "destination", + type: "HexString", + description: "Destination account.", + }, + ], + }, + { + id: "host-payment-response", + name: "HostPaymentResponse", + category: "payment", + definition: "export interface HostPaymentResponse {\n id: string;\n}", + description: + "Receipt returned after a successful payment request.\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", + fields: [ + { + name: "id", + type: "string", + description: "The assigned payment identifier.", + }, + ], + }, + { + id: "host-payment-status-subscribe-error", + name: "HostPaymentStatusSubscribeError", + category: "payment", + definition: + 'export type HostPaymentStatusSubscribeError =\n | { tag: "PaymentNotFound"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: + "Error from [`crate::api::Payment::status_subscribe`].\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", + variants: [ + { + name: "PaymentNotFound", + type: '{ tag: "PaymentNotFound"; value?: undefined }', + description: + "Payment ID was not found or does not belong to the current product.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-payment-status-subscribe-item", + name: "HostPaymentStatusSubscribeItem", + category: "payment", + definition: + 'export type HostPaymentStatusSubscribeItem =\n | { tag: "Processing"; value?: undefined }\n | { tag: "Completed"; value?: undefined }\n | { tag: "Failed"; value: { reason: string } }\n;', + description: + "Payment lifecycle status pushed to subscribers.\n\nOnce a terminal state (`Completed` or `Failed`) is reached, the host\ndelivers it and may close the subscription.\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", + variants: [ + { + name: "Processing", + type: '{ tag: "Processing"; value?: undefined }', + description: "Payment is being processed.", + }, + { + name: "Completed", + type: '{ tag: "Completed"; value?: undefined }', + description: "Payment has been settled successfully.", + }, + { + name: "Failed", + type: '{ tag: "Failed"; value: { reason: string } }', + description: "Payment has failed.", + }, + ], + }, + { + id: "host-payment-status-subscribe-request", + name: "HostPaymentStatusSubscribeRequest", + category: "payment", + definition: + "export interface HostPaymentStatusSubscribeRequest {\n paymentId: string;\n}", + description: "Request to subscribe to a payment status.", + fields: [ + { + name: "payment_id", + type: "string", + description: "Payment identifier to watch.", + }, + ], + }, + { + id: "host-payment-top-up-error", + name: "HostPaymentTopUpError", + category: "payment", + definition: + 'export type HostPaymentTopUpError =\n | { tag: "InsufficientFunds"; value?: undefined }\n | { tag: "InvalidSource"; value?: undefined }\n | { tag: "PartialPayment"; value: { credited: Balance } }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: + "Error from [`crate::api::Payment::top_up`].\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", + variants: [ + { + name: "InsufficientFunds", + type: '{ tag: "InsufficientFunds"; value?: undefined }', + description: "The source account does not hold sufficient funds.", + }, + { + name: "InvalidSource", + type: '{ tag: "InvalidSource"; value?: undefined }', + description: "The source account was not found or is invalid.", + }, + { + name: "PartialPayment", + type: '{ tag: "PartialPayment"; value: { credited: Balance } }', + description: + "Some coins were claimed but the total fell short of the requested amount.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-payment-top-up-request", + name: "HostPaymentTopUpRequest", + category: "payment", + definition: + "export interface HostPaymentTopUpRequest {\n into?: PaymentPurseId;\n amount: Balance;\n source: PaymentTopUpSource;\n}", + description: "Request to top up the product payment balance.", + fields: [ + { + name: "into", + type: "PaymentPurseId | undefined", + description: "Optional purse selector. `None` means MAIN_PURSE.", + }, + { + name: "amount", + type: "Balance", + description: "Amount to top up.", + }, + { + name: "source", + type: "PaymentTopUpSource", + description: "Funding source for the top-up.", + }, + ], + }, + { + id: "host-push-notification-cancel-request", + name: "HostPushNotificationCancelRequest", + category: "notifications", + definition: + "export interface HostPushNotificationCancelRequest {\n id: NotificationId;\n}", + description: "Request to cancel a previously scheduled notification.", + fields: [ + { + name: "id", + type: "NotificationId", + description: + "The notification identifier returned by [`HostPushNotificationResponse`].", + }, + ], + }, + { + id: "host-push-notification-error", + name: "HostPushNotificationError", + category: "notifications", + definition: + 'export type HostPushNotificationError =\n | { tag: "ScheduleLimitReached"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Push notification error.", + variants: [ + { + name: "ScheduleLimitReached", + type: '{ tag: "ScheduleLimitReached"; value?: undefined }', + description: + "The host-wide queue of pending scheduled notifications is full.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-push-notification-request", + name: "HostPushNotificationRequest", + category: "notifications", + definition: + "export interface HostPushNotificationRequest {\n text: string;\n deeplink?: string;\n scheduledAt?: bigint;\n}", + description: + "Push notification payload.\n\nWhen `scheduled_at` is `Some`, the notification is deferred to the given\nwall-clock instant (Unix milliseconds UTC). `None` fires immediately,\npreserving prior behaviour. See [RFC 0019].\n\n[RFC 0019]: https://github.com/paritytech/truapi/blob/main/docs/rfcs/0019-scheduled-notifications.md", + fields: [ + { + name: "text", + type: "string", + description: "Notification text.", + }, + { + name: "deeplink", + type: "string | undefined", + description: "Optional URL to open on tap.", + }, + { + name: "scheduled_at", + type: "bigint | undefined", + description: + "Optional Unix timestamp in milliseconds (UTC) at which the notification\nshould fire. `None` fires immediately.", + }, + ], + }, + { + id: "host-push-notification-response", + name: "HostPushNotificationResponse", + category: "notifications", + definition: + "export interface HostPushNotificationResponse {\n id: NotificationId;\n}", + description: + "Successful push notification response carrying the assigned id.", + fields: [ + { + name: "id", + type: "NotificationId", + description: "Host-assigned notification identifier.", + }, + ], + }, + { + id: "host-request-login-error", + name: "HostRequestLoginError", + category: "account", + definition: + 'export type HostRequestLoginError =\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Login request error.", + variants: [ + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-request-login-request", + name: "HostRequestLoginRequest", + category: "account", + definition: + "export interface HostRequestLoginRequest {\n reason?: string;\n}", + description: "Request to present the host login flow.", + fields: [ + { + name: "reason", + type: "string | undefined", + description: "Optional human-readable reason shown in the login UI.", + }, + ], + }, + { + id: "host-request-login-response", + name: "HostRequestLoginResponse", + category: "account", + definition: + 'export type HostRequestLoginResponse = "Success" | "AlreadyConnected" | "Rejected";', + description: "Result of a login request.", + variants: [ + { + name: "Success", + type: '{ tag: "Success"; value?: undefined }', + description: "User successfully authenticated.", + }, + { + name: "AlreadyConnected", + type: '{ tag: "AlreadyConnected"; value?: undefined }', + description: "User is already authenticated — no action was taken.", + }, + { + name: "Rejected", + type: '{ tag: "Rejected"; value?: undefined }', + description: "User dismissed/rejected the login UI.", + }, + ], + }, + { + id: "host-request-resource-allocation-request", + name: "HostRequestResourceAllocationRequest", + category: "resource_allocation", + definition: + "export interface HostRequestResourceAllocationRequest {\n resources: Array;\n}", + description: "Batched resource pre-allocation request (RFC 0010).", + fields: [ + { + name: "resources", + type: "Array", + description: "Resources to allocate.", + }, + ], + }, + { + id: "host-request-resource-allocation-response", + name: "HostRequestResourceAllocationResponse", + category: "resource_allocation", + definition: + "export interface HostRequestResourceAllocationResponse {\n outcomes: Array;\n}", + description: + "Per-resource outcomes for a batched allocation request (RFC 0010).", + fields: [ + { + name: "outcomes", + type: "Array", + description: + "Per-resource allocation outcomes, in the same order as the request.", + }, + ], + }, + { + id: "host-sign-payload-data", + name: "HostSignPayloadData", + category: "signing", + definition: + "export interface HostSignPayloadData {\n blockHash: HexString;\n blockNumber: HexString;\n era: HexString;\n genesisHash: HexString;\n method: HexString;\n nonce: HexString;\n specVersion: HexString;\n tip: HexString;\n transactionVersion: HexString;\n signedExtensions: Array;\n version: number;\n assetId?: HexString;\n metadataHash?: HexString;\n mode?: number;\n withSignedTransaction?: boolean;\n}", + description: + "Full Substrate extrinsic signing payload with all fields needed for signature\ngeneration.", + fields: [ + { + name: "block_hash", + type: "HexString", + description: "Reference block hash.", + }, + { + name: "block_number", + type: "HexString", + description: "Reference block number.", + }, + { + name: "era", + type: "HexString", + description: "Mortality era encoding.", + }, + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "method", + type: "HexString", + description: "SCALE-encoded call data.", + }, + { + name: "nonce", + type: "HexString", + description: "Account nonce.", + }, + { + name: "spec_version", + type: "HexString", + description: "Runtime spec version.", + }, + { + name: "tip", + type: "HexString", + description: "Transaction tip.", + }, + { + name: "transaction_version", + type: "HexString", + description: "Transaction format version.", + }, + { + name: "signed_extensions", + type: "Array", + description: "Extension identifiers.", + }, + { + name: "version", + type: "number", + description: "Extrinsic version.", + }, + { + name: "asset_id", + type: "HexString | undefined", + description: "For multi-asset tips.", + }, + { + name: "metadata_hash", + type: "HexString | undefined", + description: "CheckMetadataHash extension.", + }, + { + name: "mode", + type: "number | undefined", + description: "Metadata mode.", + }, + { + name: "with_signed_transaction", + type: "boolean | undefined", + description: "Request signed transaction back.", + }, + ], + }, + { + id: "host-sign-payload-error", + name: "HostSignPayloadError", + category: "signing", + definition: + 'export type HostSignPayloadError =\n | { tag: "FailedToDecode"; value?: undefined }\n | { tag: "Rejected"; value?: undefined }\n | { tag: "PermissionDenied"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Signing operation error.", + variants: [ + { + name: "FailedToDecode", + type: '{ tag: "FailedToDecode"; value?: undefined }', + description: "Payload could not be deserialized.", + }, + { + name: "Rejected", + type: '{ tag: "Rejected"; value?: undefined }', + description: "User rejected signing.", + }, + { + name: "PermissionDenied", + type: '{ tag: "PermissionDenied"; value?: undefined }', + description: "Not authenticated.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "host-sign-payload-request", + name: "HostSignPayloadRequest", + category: "signing", + definition: + "export interface HostSignPayloadRequest {\n account: ProductAccountId;\n payload: HostSignPayloadData;\n}", + description: "Request to sign an extrinsic payload with a product account.", + fields: [ + { + name: "account", + type: "ProductAccountId", + description: "Product account that will sign this payload.", + }, + { + name: "payload", + type: "HostSignPayloadData", + description: "The extrinsic payload to sign.", + }, + ], + }, + { + id: "host-sign-payload-response", + name: "HostSignPayloadResponse", + category: "signing", + definition: + "export interface HostSignPayloadResponse {\n signature: HexString;\n signedTransaction?: HexString;\n}", + description: "Result of a signing operation.", + fields: [ + { + name: "signature", + type: "HexString", + description: "The cryptographic signature.", + }, + { + name: "signed_transaction", + type: "HexString | undefined", + description: "Full signed transaction, if requested.", + }, + ], + }, + { + id: "host-sign-payload-with-legacy-account-request", + name: "HostSignPayloadWithLegacyAccountRequest", + category: "signing", + definition: + "export interface HostSignPayloadWithLegacyAccountRequest {\n signer: string;\n payload: HostSignPayloadData;\n}", + description: + "Sign a Substrate extrinsic payload with a non-product (legacy) account.\nContains the same fields as [`HostSignPayloadRequest`] minus `address`\n(replaced by `signer`).", + fields: [ + { + name: "signer", + type: "string", + description: "Signer address (SS58 or hex) of the legacy account.", + }, + { + name: "payload", + type: "HostSignPayloadData", + description: "The extrinsic payload to sign.", + }, + ], + }, + { + id: "host-sign-raw-request", + name: "HostSignRawRequest", + category: "signing", + definition: + "export interface HostSignRawRequest {\n account: ProductAccountId;\n payload: RawPayload;\n}", + description: + "A raw signing request pairing an account with the payload to sign.", + fields: [ + { + name: "account", + type: "ProductAccountId", + description: "Product account that will sign this payload.", + }, + { + name: "payload", + type: "RawPayload", + description: "The payload to sign.", + }, + ], + }, + { + id: "host-sign-raw-with-legacy-account-request", + name: "HostSignRawWithLegacyAccountRequest", + category: "signing", + definition: + "export interface HostSignRawWithLegacyAccountRequest {\n signer: string;\n payload: RawPayload;\n}", + description: + "Sign raw bytes with a non-product (legacy) account. The signer field\nidentifies which legacy account to use.", + fields: [ + { + name: "signer", + type: "string", + description: "Signer address (SS58 or hex) of the legacy account.", + }, + { + name: "payload", + type: "RawPayload", + description: "The data to sign.", + }, + ], + }, + { + id: "host-theme-subscribe-item", + name: "HostThemeSubscribeItem", + category: "theme", + definition: + "export interface HostThemeSubscribeItem {\n name: ThemeName;\n variant: ThemeVariant;\n}", + description: "Current theme state pushed to subscribers.", + fields: [ + { + name: "name", + type: "ThemeName", + description: "Theme name.", + }, + { + name: "variant", + type: "ThemeVariant", + description: "Light or dark variant.", + }, + ], + }, + { + id: "legacy-account", + name: "LegacyAccount", + category: "account", + definition: + "export interface LegacyAccount {\n publicKey: HexString;\n name?: string;\n}", + description: + "A user-imported (legacy) account: public key plus an optional user-chosen\ndisplay name.\n\nReturned by [`HostGetLegacyAccountsResponse`]. Distinct from\n[`ProductAccount`], which is protocol-derived and never carries a label.", + fields: [ + { + name: "public_key", + type: "HexString", + description: "The account public key (variable-length bytes).", + }, + { + name: "name", + type: "string | undefined", + description: "Optional user-chosen display name.", + }, + ], + }, + { + id: "legacy-account-tx-payload", + name: "LegacyAccountTxPayload", + category: "transaction", + definition: + "export interface LegacyAccountTxPayload {\n signer: AccountId;\n genesisHash: GenesisHash;\n callData: HexString;\n extensions: Array;\n txExtVersion: number;\n}", + description: + "Transaction payload for a legacy (non-product) account.\n\nIdentical to [`ProductAccountTxPayload`] except the signer is a raw\n32-byte [`AccountId`].", + fields: [ + { + name: "signer", + type: "AccountId", + description: "Raw 32-byte public key of the legacy account.", + }, + { + name: "genesis_hash", + type: "GenesisHash", + description: "Chain where the transaction will execute.", + }, + { + name: "call_data", + type: "HexString", + description: "SCALE-encoded Call data.", + }, + { + name: "extensions", + type: "Array", + description: "Transaction extensions supplied by the caller.", + }, + { + name: "tx_ext_version", + type: "number", + description: "0 for Extrinsic V4, runtime-supported value for V5.", + }, + ], + }, + { + id: "modifier", + name: "Modifier", + category: "chat", + definition: + 'export type Modifier =\n | { tag: "Margin"; value: Dimensions }\n | { tag: "Padding"; value: Dimensions }\n | { tag: "Background"; value: Background }\n | { tag: "Border"; value: BorderStyle }\n | { tag: "Height"; value: { height: Size } }\n | { tag: "Width"; value: { width: Size } }\n | { tag: "MinWidth"; value: { width: Size } }\n | { tag: "MinHeight"; value: { height: Size } }\n | { tag: "FillWidth"; value: { enabled: boolean } }\n | { tag: "FillHeight"; value: { enabled: boolean } }\n;', + description: + "Layout and styling modifiers applied to custom renderer components.", + variants: [ + { + name: "Margin", + type: '{ tag: "Margin"; value: Dimensions }', + description: "Outer spacing.", + }, + { + name: "Padding", + type: '{ tag: "Padding"; value: Dimensions }', + description: "Inner spacing.", + }, + { + name: "Background", + type: '{ tag: "Background"; value: Background }', + description: "Background fill.", + }, + { + name: "Border", + type: '{ tag: "Border"; value: BorderStyle }', + description: "Border style.", + }, + { + name: "Height", + type: '{ tag: "Height"; value: { height: Size } }', + description: "Fixed height.", + }, + { + name: "Width", + type: '{ tag: "Width"; value: { width: Size } }', + description: "Fixed width.", + }, + { + name: "MinWidth", + type: '{ tag: "MinWidth"; value: { width: Size } }', + description: "Minimum width.", + }, + { + name: "MinHeight", + type: '{ tag: "MinHeight"; value: { height: Size } }', + description: "Minimum height.", + }, + { + name: "FillWidth", + type: '{ tag: "FillWidth"; value: { enabled: boolean } }', + description: "Fill available width.", + }, + { + name: "FillHeight", + type: '{ tag: "FillHeight"; value: { enabled: boolean } }', + description: "Fill available height.", + }, + ], + }, + { + id: "notification-id", + name: "NotificationId", + category: "notifications", + definition: "export type NotificationId = number;", + description: + "Opaque identifier for a push notification, unique per product.", + }, + { + id: "operation-started-result", + name: "OperationStartedResult", + category: "chain", + definition: + 'export type OperationStartedResult =\n | { tag: "Started"; value: { operationId: string } }\n | { tag: "LimitReached"; value?: undefined }\n;', + variants: [ + { + name: "Started", + type: '{ tag: "Started"; value: { operationId: string } }', + }, + { + name: "LimitReached", + type: '{ tag: "LimitReached"; value?: undefined }', + }, + ], + }, + { + id: "payment-purse-id", + name: "PaymentPurseId", + category: "payment", + definition: "export type PaymentPurseId = number;", + description: "Identifier selecting a product payment purse.", + }, + { + id: "payment-top-up-source", + name: "PaymentTopUpSource", + category: "payment", + definition: + 'export type PaymentTopUpSource =\n | { tag: "ProductAccount"; value: { derivationIndex: number } }\n | { tag: "PrivateKey"; value: { sr25519SecretKey: HexString } }\n | { tag: "Coins"; value: { sr25519SecretKeys: Array } }\n;', + description: + "Source for a payment top-up operation.\n\nSee [RFC 0006].\n\n[RFC 0006]: https://github.com/paritytech/triangle-js-sdks/pull/94", + variants: [ + { + name: "ProductAccount", + type: '{ tag: "ProductAccount"; value: { derivationIndex: number } }', + description: "Fund from one of the calling product's scoped accounts.", + }, + { + name: "PrivateKey", + type: '{ tag: "PrivateKey"; value: { sr25519SecretKey: HexString } }', + description: + "Fund from a one-time account represented by its private key. This is a\nstandard account holding public funds, not a coin key.", + }, + { + name: "Coins", + type: '{ tag: "Coins"; value: { sr25519SecretKeys: Array } }', + description: + "Fund directly from coin secret keys. Each key is an sr25519 secret\ncontrolling a single coin.", + }, + ], + }, + { + id: "preimage-submit-error", + name: "PreimageSubmitError", + category: "preimage", + definition: + 'export type PreimageSubmitError =\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Preimage submission error.", + variants: [ + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "product-account", + name: "ProductAccount", + category: "account", + definition: "export interface ProductAccount {\n publicKey: HexString;\n}", + description: "A product account: public key only, no display name.", + fields: [ + { + name: "public_key", + type: "HexString", + description: "The account public key (variable-length bytes).", + }, + ], + }, + { + id: "product-account-id", + name: "ProductAccountId", + category: "account", + definition: + "export interface ProductAccountId {\n dotNsIdentifier: string;\n derivationIndex: number;\n}", + description: + "Identifies a product-specific account by combining a dotNS domain name with a\nderivation index.", + fields: [ + { + name: "dot_ns_identifier", + type: "string", + description: + 'A dotNS domain name identifier (e.g., `"my-product.dot"`).', + }, + { + name: "derivation_index", + type: "number", + description: + "Key derivation index for generating product-specific accounts.", + }, + ], + }, + { + id: "product-account-tx-payload", + name: "ProductAccountTxPayload", + category: "transaction", + definition: + "export interface ProductAccountTxPayload {\n signer: ProductAccountId;\n genesisHash: GenesisHash;\n callData: HexString;\n extensions: Array;\n txExtVersion: number;\n}", + description: + "Transaction payload for a product account.\n\nContains everything the host needs to construct a signed extrinsic.\nThe signer is a [`ProductAccountId`]; the host resolves the\ncorresponding key pair through its account management layer.", + fields: [ + { + name: "signer", + type: "ProductAccountId", + description: "Product account that will sign the transaction.", + }, + { + name: "genesis_hash", + type: "GenesisHash", + description: "Chain where the transaction will execute.", + }, + { + name: "call_data", + type: "HexString", + description: "SCALE-encoded Call data.", + }, + { + name: "extensions", + type: "Array", + description: "Transaction extensions supplied by the caller.", + }, + { + name: "tx_ext_version", + type: "number", + description: "0 for Extrinsic V4, runtime-supported value for V5.", + }, + ], + }, + { + id: "product-chat-custom-message-render-subscribe-request", + name: "ProductChatCustomMessageRenderSubscribeRequest", + category: "chat", + definition: + "export interface ProductChatCustomMessageRenderSubscribeRequest {\n messageId: string;\n messageType: string;\n payload: HexString;\n}", + description: + "Subscribe payload identifying the chat message to render. The host responds\nwith a stream of [`CustomRendererNode`] trees describing the rendered UI.", + fields: [ + { + name: "message_id", + type: "string", + description: "Message identifier.", + }, + { + name: "message_type", + type: "string", + description: "Application-defined message type.", + }, + { + name: "payload", + type: "HexString", + description: "Binary payload.", + }, + ], + }, + { + id: "raw-payload", + name: "RawPayload", + category: "signing", + definition: + 'export type RawPayload =\n | { tag: "Bytes"; value: { bytes: HexString } }\n | { tag: "Payload"; value: { payload: string } }\n;', + description: "Raw data to sign -- either binary bytes or a string message.", + variants: [ + { + name: "Bytes", + type: '{ tag: "Bytes"; value: { bytes: HexString } }', + description: "Raw binary data to sign.", + }, + { + name: "Payload", + type: '{ tag: "Payload"; value: { payload: string } }', + description: "String message to sign.", + }, + ], + }, + { + id: "remote-chain-head-body-request", + name: "RemoteChainHeadBodyRequest", + category: "chain", + definition: + "export interface RemoteChainHeadBodyRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hash: HexString;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "follow_subscription_id", + type: "string", + description: "Follow subscription identifier.", + }, + { + name: "hash", + type: "HexString", + description: "Block hash.", + }, + ], + }, + { + id: "remote-chain-head-body-response", + name: "RemoteChainHeadBodyResponse", + category: "chain", + definition: + "export interface RemoteChainHeadBodyResponse {\n operation: OperationStartedResult;\n}", + fields: [ + { + name: "operation", + type: "OperationStartedResult", + description: "Started operation result.", + }, + ], + }, + { + id: "remote-chain-head-call-request", + name: "RemoteChainHeadCallRequest", + category: "chain", + definition: + "export interface RemoteChainHeadCallRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hash: HexString;\n function: string;\n callParameters: HexString;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "follow_subscription_id", + type: "string", + description: "Follow subscription identifier.", + }, + { + name: "hash", + type: "HexString", + description: "Block hash.", + }, + { + name: "function", + type: "string", + description: "Runtime API function name.", + }, + { + name: "call_parameters", + type: "HexString", + description: "SCALE-encoded call parameters.", + }, + ], + }, + { + id: "remote-chain-head-call-response", + name: "RemoteChainHeadCallResponse", + category: "chain", + definition: + "export interface RemoteChainHeadCallResponse {\n operation: OperationStartedResult;\n}", + fields: [ + { + name: "operation", + type: "OperationStartedResult", + description: "Started operation result.", + }, + ], + }, + { + id: "remote-chain-head-continue-request", + name: "RemoteChainHeadContinueRequest", + category: "chain", + definition: + "export interface RemoteChainHeadContinueRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n operationId: string;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "follow_subscription_id", + type: "string", + description: "Follow subscription identifier.", + }, + { + name: "operation_id", + type: "string", + description: "Operation identifier.", + }, + ], + }, + { + id: "remote-chain-head-follow-item", + name: "RemoteChainHeadFollowItem", + category: "chain", + definition: + 'export type RemoteChainHeadFollowItem =\n | { tag: "Initialized"; value: { finalizedBlockHashes: Array; finalizedBlockRuntime?: RuntimeType } }\n | { tag: "NewBlock"; value: { blockHash: HexString; parentBlockHash: HexString; newRuntime?: RuntimeType } }\n | { tag: "BestBlockChanged"; value: { bestBlockHash: HexString } }\n | { tag: "Finalized"; value: { finalizedBlockHashes: Array; prunedBlockHashes: Array } }\n | { tag: "OperationBodyDone"; value: { operationId: string; value: Array } }\n | { tag: "OperationCallDone"; value: { operationId: string; output: HexString } }\n | { tag: "OperationStorageItems"; value: { operationId: string; items: Array } }\n | { tag: "OperationStorageDone"; value: { operationId: string } }\n | { tag: "OperationWaitingForContinue"; value: { operationId: string } }\n | { tag: "OperationInaccessible"; value: { operationId: string } }\n | { tag: "OperationError"; value: { operationId: string; error: string } }\n | { tag: "Stop"; value?: undefined }\n;', + variants: [ + { + name: "Initialized", + type: '{ tag: "Initialized"; value: { finalizedBlockHashes: Array; finalizedBlockRuntime?: RuntimeType } }', + }, + { + name: "NewBlock", + type: '{ tag: "NewBlock"; value: { blockHash: HexString; parentBlockHash: HexString; newRuntime?: RuntimeType } }', + }, + { + name: "BestBlockChanged", + type: '{ tag: "BestBlockChanged"; value: { bestBlockHash: HexString } }', + }, + { + name: "Finalized", + type: '{ tag: "Finalized"; value: { finalizedBlockHashes: Array; prunedBlockHashes: Array } }', + }, + { + name: "OperationBodyDone", + type: '{ tag: "OperationBodyDone"; value: { operationId: string; value: Array } }', + }, + { + name: "OperationCallDone", + type: '{ tag: "OperationCallDone"; value: { operationId: string; output: HexString } }', + }, + { + name: "OperationStorageItems", + type: '{ tag: "OperationStorageItems"; value: { operationId: string; items: Array } }', + }, + { + name: "OperationStorageDone", + type: '{ tag: "OperationStorageDone"; value: { operationId: string } }', + }, + { + name: "OperationWaitingForContinue", + type: '{ tag: "OperationWaitingForContinue"; value: { operationId: string } }', + }, + { + name: "OperationInaccessible", + type: '{ tag: "OperationInaccessible"; value: { operationId: string } }', + }, + { + name: "OperationError", + type: '{ tag: "OperationError"; value: { operationId: string; error: string } }', + }, + { + name: "Stop", + type: '{ tag: "Stop"; value?: undefined }', + }, + ], + }, + { + id: "remote-chain-head-follow-request", + name: "RemoteChainHeadFollowRequest", + category: "chain", + definition: + "export interface RemoteChainHeadFollowRequest {\n genesisHash: HexString;\n withRuntime: boolean;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "with_runtime", + type: "boolean", + description: "Whether to include runtime information in events.", + }, + ], + }, + { + id: "remote-chain-head-header-request", + name: "RemoteChainHeadHeaderRequest", + category: "chain", + definition: + "export interface RemoteChainHeadHeaderRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hash: HexString;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "follow_subscription_id", + type: "string", + description: "Follow subscription identifier.", + }, + { + name: "hash", + type: "HexString", + description: "Block hash.", + }, + ], + }, + { + id: "remote-chain-head-header-response", + name: "RemoteChainHeadHeaderResponse", + category: "chain", + definition: + "export interface RemoteChainHeadHeaderResponse {\n header?: HexString;\n}", + fields: [ + { + name: "header", + type: "HexString | undefined", + description: "SCALE-encoded block header.", + }, + ], + }, + { + id: "remote-chain-head-stop-operation-request", + name: "RemoteChainHeadStopOperationRequest", + category: "chain", + definition: + "export interface RemoteChainHeadStopOperationRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n operationId: string;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "follow_subscription_id", + type: "string", + description: "Follow subscription identifier.", + }, + { + name: "operation_id", + type: "string", + description: "Operation identifier.", + }, + ], + }, + { + id: "remote-chain-head-storage-request", + name: "RemoteChainHeadStorageRequest", + category: "chain", + definition: + "export interface RemoteChainHeadStorageRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hash: HexString;\n items: Array;\n childTrie?: HexString;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "follow_subscription_id", + type: "string", + description: "Follow subscription identifier.", + }, + { + name: "hash", + type: "HexString", + description: "Block hash.", + }, + { + name: "items", + type: "Array", + description: "Storage items to query.", + }, + { + name: "child_trie", + type: "HexString | undefined", + description: "Optional child trie.", + }, + ], + }, + { + id: "remote-chain-head-storage-response", + name: "RemoteChainHeadStorageResponse", + category: "chain", + definition: + "export interface RemoteChainHeadStorageResponse {\n operation: OperationStartedResult;\n}", + fields: [ + { + name: "operation", + type: "OperationStartedResult", + description: "Started operation result.", + }, + ], + }, + { + id: "remote-chain-head-unpin-request", + name: "RemoteChainHeadUnpinRequest", + category: "chain", + definition: + "export interface RemoteChainHeadUnpinRequest {\n genesisHash: HexString;\n followSubscriptionId: string;\n hashes: Array;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "follow_subscription_id", + type: "string", + description: "Follow subscription identifier.", + }, + { + name: "hashes", + type: "Array", + description: "Block hashes to unpin.", + }, + ], + }, + { + id: "remote-chain-spec-chain-name-request", + name: "RemoteChainSpecChainNameRequest", + category: "chain", + definition: + "export interface RemoteChainSpecChainNameRequest {\n genesisHash: HexString;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + ], + }, + { + id: "remote-chain-spec-chain-name-response", + name: "RemoteChainSpecChainNameResponse", + category: "chain", + definition: + "export interface RemoteChainSpecChainNameResponse {\n chainName: string;\n}", + fields: [ + { + name: "chain_name", + type: "string", + description: "Chain display name.", + }, + ], + }, + { + id: "remote-chain-spec-genesis-hash-request", + name: "RemoteChainSpecGenesisHashRequest", + category: "chain", + definition: + "export interface RemoteChainSpecGenesisHashRequest {\n genesisHash: HexString;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash requested by the product.", + }, + ], + }, + { + id: "remote-chain-spec-genesis-hash-response", + name: "RemoteChainSpecGenesisHashResponse", + category: "chain", + definition: + "export interface RemoteChainSpecGenesisHashResponse {\n genesisHash: HexString;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + ], + }, + { + id: "remote-chain-spec-properties-request", + name: "RemoteChainSpecPropertiesRequest", + category: "chain", + definition: + "export interface RemoteChainSpecPropertiesRequest {\n genesisHash: HexString;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + ], + }, + { + id: "remote-chain-spec-properties-response", + name: "RemoteChainSpecPropertiesResponse", + category: "chain", + definition: + "export interface RemoteChainSpecPropertiesResponse {\n properties: string;\n}", + fields: [ + { + name: "properties", + type: "string", + description: "JSON-encoded properties.", + }, + ], + }, + { + id: "remote-chain-transaction-broadcast-request", + name: "RemoteChainTransactionBroadcastRequest", + category: "chain", + definition: + "export interface RemoteChainTransactionBroadcastRequest {\n genesisHash: HexString;\n transaction: HexString;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "transaction", + type: "HexString", + description: "Signed transaction bytes.", + }, + ], + }, + { + id: "remote-chain-transaction-broadcast-response", + name: "RemoteChainTransactionBroadcastResponse", + category: "chain", + definition: + "export interface RemoteChainTransactionBroadcastResponse {\n operationId?: string;\n}", + fields: [ + { + name: "operation_id", + type: "string | undefined", + description: "Broadcast operation identifier, if available.", + }, + ], + }, + { + id: "remote-chain-transaction-stop-request", + name: "RemoteChainTransactionStopRequest", + category: "chain", + definition: + "export interface RemoteChainTransactionStopRequest {\n genesisHash: HexString;\n operationId: string;\n}", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "operation_id", + type: "string", + description: "Operation identifier of the broadcast to stop.", + }, + ], + }, + { + id: "remote-permission", + name: "RemotePermission", + category: "permissions", + definition: + 'export type RemotePermission =\n | { tag: "Remote"; value: { domains: Array } }\n | { tag: "WebRtc"; value?: undefined }\n | { tag: "ChainSubmit"; value?: undefined }\n | { tag: "PreimageSubmit"; value?: undefined }\n | { tag: "StatementSubmit"; value?: undefined }\n;', + description: + "One remote-operation permission requested by the product (RFC 0002).\n\n`ChainSubmit`, `PreimageSubmit`, and `StatementSubmit` are also triggered\nimplicitly by the corresponding business calls when not yet granted.", + variants: [ + { + name: "Remote", + type: '{ tag: "Remote"; value: { domains: Array } }', + description: "Outbound HTTP/WebSocket access to a set of domains.", + }, + { + name: "WebRtc", + type: '{ tag: "WebRtc"; value?: undefined }', + description: "WebRTC media access.", + }, + { + name: "ChainSubmit", + type: '{ tag: "ChainSubmit"; value?: undefined }', + description: + "Submitting transactions on behalf of the user via `remote_chain_transaction_broadcast`.", + }, + { + name: "PreimageSubmit", + type: '{ tag: "PreimageSubmit"; value?: undefined }', + description: + "Submitting preimages on behalf of the user via `remote_preimage_submit`.", + }, + { + name: "StatementSubmit", + type: '{ tag: "StatementSubmit"; value?: undefined }', + description: + "Submitting statements on behalf of the user via `remote_statement_store_submit`.", + }, + ], + }, + { + id: "remote-permission-request", + name: "RemotePermissionRequest", + category: "permissions", + definition: + "export interface RemotePermissionRequest {\n permission: RemotePermission;\n}", + description: "remote-permission request (RFC 0002).", + fields: [ + { + name: "permission", + type: "RemotePermission", + description: "Permission requested by the product.", + }, + ], + }, + { + id: "remote-permission-response", + name: "RemotePermissionResponse", + category: "permissions", + definition: + "export interface RemotePermissionResponse {\n granted: boolean;\n}", + description: "Outcome of a remote-permission request.", + fields: [ + { + name: "granted", + type: "boolean", + description: "Whether the permission was granted.", + }, + ], + }, + { + id: "remote-preimage-lookup-subscribe-item", + name: "RemotePreimageLookupSubscribeItem", + category: "preimage", + definition: + "export interface RemotePreimageLookupSubscribeItem {\n value?: HexString;\n}", + description: "Item containing an optional preimage lookup result.", + fields: [ + { + name: "value", + type: "HexString | undefined", + description: "Preimage data, if found.", + }, + ], + }, + { + id: "remote-preimage-lookup-subscribe-request", + name: "RemotePreimageLookupSubscribeRequest", + category: "preimage", + definition: + "export interface RemotePreimageLookupSubscribeRequest {\n key: HexString;\n}", + description: "Request to subscribe to preimage lookup results.", + fields: [ + { + name: "key", + type: "HexString", + description: "Hash of the preimage.", + }, + ], + }, + { + id: "remote-statement-store-create-proof-error", + name: "RemoteStatementStoreCreateProofError", + category: "statement_store", + definition: + 'export type RemoteStatementStoreCreateProofError =\n | { tag: "UnableToSign"; value?: undefined }\n | { tag: "UnknownAccount"; value?: undefined }\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Statement proof creation error.", + variants: [ + { + name: "UnableToSign", + type: '{ tag: "UnableToSign"; value?: undefined }', + description: "Signing operation failed.", + }, + { + name: "UnknownAccount", + type: '{ tag: "UnknownAccount"; value?: undefined }', + description: "Account not recognized.", + }, + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "remote-statement-store-create-proof-request", + name: "RemoteStatementStoreCreateProofRequest", + category: "statement_store", + definition: + "export interface RemoteStatementStoreCreateProofRequest {\n productAccountId: ProductAccountId;\n statement: Statement;\n}", + description: "Request to create a cryptographic proof for a statement.", + fields: [ + { + name: "product_account_id", + type: "ProductAccountId", + description: "Product account that should create the proof.", + }, + { + name: "statement", + type: "Statement", + description: "Statement to prove.", + }, + ], + }, + { + id: "remote-statement-store-create-proof-response", + name: "RemoteStatementStoreCreateProofResponse", + category: "statement_store", + definition: + "export interface RemoteStatementStoreCreateProofResponse {\n proof: StatementProof;\n}", + description: "Response containing a statement proof.", + fields: [ + { + name: "proof", + type: "StatementProof", + description: "Created statement proof.", + }, + ], + }, + { + id: "remote-statement-store-subscribe-item", + name: "RemoteStatementStoreSubscribeItem", + category: "statement_store", + definition: + "export interface RemoteStatementStoreSubscribeItem {\n statements: Array;\n isComplete: boolean;\n}", + description: + "Page of signed statements delivered by the statement store subscription\n(RFC 0008). The `is_complete` flag distinguishes the historical-dump phase\n(`false`) from the live-update phase (`true`).", + fields: [ + { + name: "statements", + type: "Array", + description: "Signed statements matching the subscription.", + }, + { + name: "is_complete", + type: "boolean", + description: + "`false` while the host is still streaming the historical dump (more\npages to follow). `true` once the dump is complete; all subsequent\npages are also `true` and carry only newly-arrived statements.", + }, + ], + }, + { + id: "remote-statement-store-subscribe-request", + name: "RemoteStatementStoreSubscribeRequest", + category: "statement_store", + definition: + 'export type RemoteStatementStoreSubscribeRequest =\n | { tag: "MatchAll"; value: Array }\n | { tag: "MatchAny"; value: Array }\n;', + description: + "Request to subscribe to statements via a topic filter (RFC 0008).", + variants: [ + { + name: "MatchAll", + type: '{ tag: "MatchAll"; value: Array }', + description: "AND: statement must contain every listed topic.", + }, + { + name: "MatchAny", + type: '{ tag: "MatchAny"; value: Array }', + description: "OR: statement must contain at least one listed topic.", + }, + ], + }, + { + id: "resource-allocation-error", + name: "ResourceAllocationError", + category: "resource_allocation", + definition: + 'export type ResourceAllocationError =\n | { tag: "Unknown"; value: { reason: string } }\n;', + description: "Error from [`crate::api::ResourceAllocation::request`].", + variants: [ + { + name: "Unknown", + type: '{ tag: "Unknown"; value: { reason: string } }', + description: "Catch-all.", + }, + ], + }, + { + id: "ring-location", + name: "RingLocation", + category: "account", + definition: + "export interface RingLocation {\n genesisHash: HexString;\n ringRootHash: HexString;\n hints?: RingLocationHint;\n}", + description: + "Locates a specific ring on a specific chain for ring VRF operations.", + fields: [ + { + name: "genesis_hash", + type: "HexString", + description: "Chain genesis hash.", + }, + { + name: "ring_root_hash", + type: "HexString", + description: "Root hash of the ring.", + }, + { + name: "hints", + type: "RingLocationHint | undefined", + description: "Optional location hints.", + }, + ], + }, + { + id: "ring-location-hint", + name: "RingLocationHint", + category: "account", + definition: + "export interface RingLocationHint {\n palletInstance?: number;\n}", + description: "Hints for locating a ring on-chain.", + fields: [ + { + name: "pallet_instance", + type: "number | undefined", + description: "Optional pallet instance index.", + }, + ], + }, + { + id: "row-props", + name: "RowProps", + category: "chat", + definition: + "export interface RowProps {\n verticalAlignment?: VerticalAlignment;\n horizontalArrangement?: Arrangement;\n}", + description: "Properties for a [`CustomRendererNode::Row`] layout.", + fields: [ + { + name: "vertical_alignment", + type: "VerticalAlignment | undefined", + description: "Vertical alignment of children.", + }, + { + name: "horizontal_arrangement", + type: "Arrangement | undefined", + description: "Horizontal arrangement of children.", + }, + ], + }, + { + id: "runtime-api", + name: "RuntimeApi", + category: "chain", + definition: + "export interface RuntimeApi {\n name: string;\n version: number;\n}", + fields: [ + { + name: "name", + type: "string", + description: "Runtime API name.", + }, + { + name: "version", + type: "number", + description: "Runtime API version.", + }, + ], + }, + { + id: "runtime-spec", + name: "RuntimeSpec", + category: "chain", + definition: + "export interface RuntimeSpec {\n specName: string;\n implName: string;\n specVersion: number;\n implVersion: number;\n transactionVersion?: number;\n apis: Array;\n}", + fields: [ + { + name: "spec_name", + type: "string", + description: "Specification name.", + }, + { + name: "impl_name", + type: "string", + description: "Implementation name.", + }, + { + name: "spec_version", + type: "number", + description: "Spec version number.", + }, + { + name: "impl_version", + type: "number", + description: "Implementation version.", + }, + { + name: "transaction_version", + type: "number | undefined", + description: "Transaction format version.", + }, + { + name: "apis", + type: "Array", + description: "Supported runtime APIs.", + }, + ], + }, + { + id: "runtime-type", + name: "RuntimeType", + category: "chain", + definition: + 'export type RuntimeType =\n | { tag: "Valid"; value: RuntimeSpec }\n | { tag: "Invalid"; value: { error: string } }\n;', + variants: [ + { + name: "Valid", + type: '{ tag: "Valid"; value: RuntimeSpec }', + }, + { + name: "Invalid", + type: '{ tag: "Invalid"; value: { error: string } }', + }, + ], + }, + { + id: "shape", + name: "Shape", + category: "chat", + definition: + 'export type Shape =\n | { tag: "Rounded"; value: { radius: Size } }\n | { tag: "Circle"; value?: undefined }\n;', + description: "Shape for borders and backgrounds.", + variants: [ + { + name: "Rounded", + type: '{ tag: "Rounded"; value: { radius: Size } }', + description: "Border radius value.", + }, + { + name: "Circle", + type: '{ tag: "Circle"; value?: undefined }', + description: "Circular shape.", + }, + ], + }, + { + id: "signed-statement", + name: "SignedStatement", + category: "statement_store", + definition: + "export interface SignedStatement {\n proof: StatementProof;\n decryptionKey?: HexString;\n expiry?: bigint;\n channel?: HexString;\n topics: Array;\n data?: HexString;\n}", + description: "A statement with a required (not optional) proof.", + fields: [ + { + name: "proof", + type: "StatementProof", + description: "Required cryptographic proof.", + }, + { + name: "decryption_key", + type: "HexString | undefined", + description: "Optional decryption key.", + }, + { + name: "expiry", + type: "bigint | undefined", + description: "Optional Unix timestamp expiry.", + }, + { + name: "channel", + type: "HexString | undefined", + description: "Optional channel.", + }, + { + name: "topics", + type: "Array", + description: "[u8; 32] tags.", + }, + { + name: "data", + type: "HexString | undefined", + description: "Optional data payload.", + }, + ], + }, + { + id: "size", + name: "Size", + category: "chat", + definition: "export type Size = number | bigint;", + description: + "A size/dimension value (logical pixels) used across the custom renderer.\n\nEncoded as a SCALE `Compact`: the common small values cost a single\nbyte on the wire instead of eight.", + }, + { + id: "statement", + name: "Statement", + category: "statement_store", + definition: + "export interface Statement {\n proof?: StatementProof;\n decryptionKey?: HexString;\n expiry?: bigint;\n channel?: HexString;\n topics: Array;\n data?: HexString;\n}", + description: "A statement with optional proof and metadata.", + fields: [ + { + name: "proof", + type: "StatementProof | undefined", + description: "Optional cryptographic proof.", + }, + { + name: "decryption_key", + type: "HexString | undefined", + description: "Optional decryption key.", + }, + { + name: "expiry", + type: "bigint | undefined", + description: "Optional Unix timestamp expiry.", + }, + { + name: "channel", + type: "HexString | undefined", + description: "Optional channel.", + }, + { + name: "topics", + type: "Array", + description: "[u8; 32] tags.", + }, + { + name: "data", + type: "HexString | undefined", + description: "Optional data payload.", + }, + ], + }, + { + id: "statement-proof", + name: "StatementProof", + category: "statement_store", + definition: + 'export type StatementProof =\n | { tag: "Sr25519"; value: { signature: HexString; signer: HexString } }\n | { tag: "Ed25519"; value: { signature: HexString; signer: HexString } }\n | { tag: "Ecdsa"; value: { signature: HexString; signer: HexString } }\n | { tag: "OnChain"; value: { who: HexString; blockHash: HexString; event: bigint } }\n;', + description: "Cryptographic proof for a statement.", + variants: [ + { + name: "Sr25519", + type: '{ tag: "Sr25519"; value: { signature: HexString; signer: HexString } }', + description: "Sr25519 signature proof.", + }, + { + name: "Ed25519", + type: '{ tag: "Ed25519"; value: { signature: HexString; signer: HexString } }', + description: "Ed25519 signature proof.", + }, + { + name: "Ecdsa", + type: '{ tag: "Ecdsa"; value: { signature: HexString; signer: HexString } }', + description: "ECDSA signature proof.", + }, + { + name: "OnChain", + type: '{ tag: "OnChain"; value: { who: HexString; blockHash: HexString; event: bigint } }', + description: "On-chain event proof.", + }, + ], + }, + { + id: "storage-query-item", + name: "StorageQueryItem", + category: "chain", + definition: + "export interface StorageQueryItem {\n key: HexString;\n queryType: StorageQueryType;\n}", + fields: [ + { + name: "key", + type: "HexString", + description: "Storage key to query.", + }, + { + name: "query_type", + type: "StorageQueryType", + description: "What to return.", + }, + ], + }, + { + id: "storage-query-type", + name: "StorageQueryType", + category: "chain", + definition: + 'export type StorageQueryType = "Value" | "Hash" | "ClosestDescendantMerkleValue" | "DescendantsValues" | "DescendantsHashes";', + variants: [ + { + name: "Value", + type: '{ tag: "Value"; value?: undefined }', + }, + { + name: "Hash", + type: '{ tag: "Hash"; value?: undefined }', + }, + { + name: "ClosestDescendantMerkleValue", + type: '{ tag: "ClosestDescendantMerkleValue"; value?: undefined }', + }, + { + name: "DescendantsValues", + type: '{ tag: "DescendantsValues"; value?: undefined }', + }, + { + name: "DescendantsHashes", + type: '{ tag: "DescendantsHashes"; value?: undefined }', + }, + ], + }, + { + id: "storage-result-item", + name: "StorageResultItem", + category: "chain", + definition: + "export interface StorageResultItem {\n key: HexString;\n value?: HexString;\n hash?: HexString;\n closestDescendantMerkleValue?: HexString;\n}", + fields: [ + { + name: "key", + type: "HexString", + description: "The queried key.", + }, + { + name: "value", + type: "HexString | undefined", + description: "Value, if requested.", + }, + { + name: "hash", + type: "HexString | undefined", + description: "Hash, if requested.", + }, + { + name: "closest_descendant_merkle_value", + type: "HexString | undefined", + description: "Merkle value, if requested.", + }, + ], + }, + { + id: "text-field-props", + name: "TextFieldProps", + category: "chat", + definition: + "export interface TextFieldProps {\n text: string;\n placeholder?: string;\n label?: string;\n enabled: boolean | undefined;\n valueChangeAction?: string;\n}", + description: "Properties for a [`CustomRendererNode::TextField`].", + fields: [ + { + name: "text", + type: "string", + description: "Current text value.", + }, + { + name: "placeholder", + type: "string | undefined", + description: "Placeholder text.", + }, + { + name: "label", + type: "string | undefined", + description: "Field label.", + }, + { + name: "enabled", + type: "boolean | undefined", + description: + "Whether the field is enabled. Absent leaves the default to the host.", + }, + { + name: "value_change_action", + type: "string | undefined", + description: "Action identifier triggered when the value changes.", + }, + ], + }, + { + id: "text-props", + name: "TextProps", + category: "chat", + definition: + "export interface TextProps {\n style?: TypographyStyle;\n color?: ColorToken;\n}", + description: "Properties for a [`CustomRendererNode::Text`] display.", + fields: [ + { + name: "style", + type: "TypographyStyle | undefined", + description: "Typography preset.", + }, + { + name: "color", + type: "ColorToken | undefined", + description: "Text color.", + }, + ], + }, + { + id: "theme-name", + name: "ThemeName", + category: "theme", + definition: + 'export type ThemeName =\n | { tag: "Custom"; value: string }\n | { tag: "Default"; value?: undefined }\n;', + description: "Identifies a named theme.", + variants: [ + { + name: "Custom", + type: '{ tag: "Custom"; value: string }', + description: "A custom named theme.", + }, + { + name: "Default", + type: '{ tag: "Default"; value?: undefined }', + description: "The host's default theme.", + }, + ], + }, + { + id: "theme-variant", + name: "ThemeVariant", + category: "theme", + definition: 'export type ThemeVariant = "Light" | "Dark";', + description: "Light or dark variant.", + variants: [ + { + name: "Light", + type: '{ tag: "Light"; value?: undefined }', + }, + { + name: "Dark", + type: '{ tag: "Dark"; value?: undefined }', + }, + ], + }, + { + id: "topic", + name: "Topic", + category: "statement_store", + definition: "export type Topic = HexString;", + description: "32-byte statement topic.", + }, + { + id: "tx-payload-extension", + name: "TxPayloadExtension", + category: "transaction", + definition: + "export interface TxPayloadExtension {\n id: string;\n extra: HexString;\n additionalSigned: HexString;\n}", + description: "A signed extension for a transaction payload.", + fields: [ + { + name: "id", + type: "string", + description: 'Extension name (e.g., `"CheckSpecVersion"`).', + }, + { + name: "extra", + type: "HexString", + description: "SCALE-encoded extra data (in extrinsic body).", + }, + { + name: "additional_signed", + type: "HexString", + description: "SCALE-encoded implicit data (signed, not in body).", + }, + ], + }, + { + id: "typography-style", + name: "TypographyStyle", + category: "chat", + definition: + 'export type TypographyStyle = "HeadlineLarge" | "TitleMediumRegular" | "BodyLargeRegular" | "BodyMediumRegular" | "BodySmallRegular";', + description: "Text typography presets.", + variants: [ + { + name: "HeadlineLarge", + type: '{ tag: "HeadlineLarge"; value?: undefined }', + }, + { + name: "TitleMediumRegular", + type: '{ tag: "TitleMediumRegular"; value?: undefined }', + }, + { + name: "BodyLargeRegular", + type: '{ tag: "BodyLargeRegular"; value?: undefined }', + }, + { + name: "BodyMediumRegular", + type: '{ tag: "BodyMediumRegular"; value?: undefined }', + }, + { + name: "BodySmallRegular", + type: '{ tag: "BodySmallRegular"; value?: undefined }', + }, + ], + }, + { + id: "vertical-alignment", + name: "VerticalAlignment", + category: "chat", + definition: 'export type VerticalAlignment = "Top" | "Center" | "Bottom";', + description: "Vertical alignment options.", + variants: [ + { + name: "Top", + type: '{ tag: "Top"; value?: undefined }', + }, + { + name: "Center", + type: '{ tag: "Center"; value?: undefined }', + }, + { + name: "Bottom", + type: '{ tag: "Bottom"; value?: undefined }', + }, + ], + }, +];