The purpose of this project is to elucidate the details of interacting with Substrate- and FRAME-based blockchains via front-end JavaScript and WebAssembly (Wasm). The code in this repository is for educational and demonstrational purposes only - it is not intended for production. This project demonstrates the following capabilities:
- SCALE encoding and decoding
- Parsing FRAME metadata
- Account (i.e. public/private key) generation or recovery
- WebSocket interaction with the Substrate JSON-RPC server
- Signed extrinsic construction and submission
Those who are familiar with the above concepts may wish to skip ahead to the Usage section.
Substrate is a Rust framework for building blockchains; FRAME is Substrate's framework for building blockchain runtimes. A "runtime" is the component of a blockchain system that represents end-user, application capabilities. Blockchains that are built with Substrate and FRAME are self-describing systems that expose metadata, which enables the dynamic, programmatic creation of interfaces that allow users to interact with the blockchain runtime.
The following sections give a brief overview of the components of this project.
SCALE is Substrate's lightweight and efficient codec (i.e. data serialization algorithm). The SCALE encoding omits contextual information in pursuit of efficiency, which means that in order to decode SCALE data, it's necessary to independently posses a type registry that defines the structures/schemas of the objects in the data that is to be decoded. Further details of the SCALE codec are outlined in a separate document. Although it may have been possible to reuse existing Rust code and compile it to Wasm, fighting the Rust type system proved too challenging for an initial implementation - this is potentially an opportunity to improve this project. For now, this project uses a simple JavaScript SCALE implementation.
The metadata exposed by FRAME runtimes consists of three components: 1) a type
registry to enable the SCALE-encoding and -decoding of messages to and from the
runtime, 2) a list that describes the data that must be included with
signed extrinsics, and 3) an exhaustive description of the
runtime's public interface. Since Substrate and FRAME are Rust-based frameworks,
the metadata that is exposed by FRAME runtimes is defined as a Rust struct
, in
particular the
frame_metadata::v14::RuntimeMetadataV14
type has been used by FRAME since around the end of 2021. When a user requests
the metadata from a FRAME-based runtime, the runtime returns a hexadecimal
string that represents the SCALE-encoding of its
frame_metadata::v14::RuntimeMetadataV14
instance. Although it would be
possible to decode the hex string and parse the metadata in JavaScript (as in
this older code
that does so for version 11 of FRAME metadata), doing so in Rust and making the
resulting code available as Wasm has two primary benefits: 1) it relies on
existing, unit- and battle-tested libraries that are provided by the maintainers
of Substrate, 2) it allows for reliable, efficient transformation of the
metadata into a structure that is better suited for front-end/client-side use.
The Rust code that decodes and transforms the metadata can be found in
rs/metadata/src/lib.rs.
Further details about the
frame_metadata::v14::RuntimeMetadataV14
format and the transformations that
are applied to it are documented in a separate file.
Like the decoding and transformation of metadata, most of this project's account capabilities are implemented in Rust and made available to the front-end as Wasm. The code, which is located in the rs/account/src/lib.rs file, was more or less copied directly from Polkadot-JS and is fairly self-explanatory. This project supports importing an existing, known account from its 32-byte sr25519 private seed, or generating a new BIP39 seed phrase and using that to derive an sr25519 keypair. An sr25519 account can be used to sign a message, and a 32-byte sr25519 public key and an Ss58 network identifier can be used to derive an Ss58 address for that account.
Substrate uses a JSON-RPC server as
its primary mechanism for interacting with end-users. This project defines a
JavaScript class named Context
as an abstraction on top of a
WebSocket
connection to a Substrate node's JSON-RPC server. Substrate implements a
specification called
PSP-6 in order
to define its JSON-RPC interface; this project relies on a small subset of the
endpoints defined by this specification. The Context
class, which can be found
in the lib/context.js file, makes use of the following PSP-6
endpoints:
state_subscribeRuntimeVersion
is used to watch the node for runtime upgradesstate_getMetadata
is used to retrieve the runtime's metadata, which will only change when the runtime version changes by way of a runtime upgrade- The following endpoints are used to track various properties of the node
itself (as opposed to the runtime); these properties can be updated on-demand
by using the
update
method of theContext
class state_getStorage
for querying runtime storage itemsauthor_submitAndWatchExtrinsic
for submitting and tracking signed extrinsicssystem_accountNextIndex
is used to retrieve the nonce for an account before submitting an extrinsic that is signed by that account
The term "extrinsic"
is native to Substrate and is used to refer to any external data that is
included in a block of a blockchain. The most straightforward usage of this term
applies to
"signed extrinsics",
which are colloquially referred to as "transactions". Signed extrinsics allow
blockchain end-users to interact with the blockchain runtime; as the name
implies, requests of this type must be signed by the private key that is
associated with a blockchain account, which allows the blockchain
network to debit that user's account in order to pay the
fees associated with that
request. Other types of extrinsics, like
unsigned extrinsics
or
inherents,
are out of the scope of this project at this time. Extrinsic encoding is
implemented in the submitExtrinsic
method of the
Context
class. More details
about the encoding of signed extrinsics are documented in a separate file.
This project includes a simple browser front-end that is built with standard Web Components. The front-end uses the metadata from a Substrate- and FRAME-based blockchain to enumerate the modules ("pallets") that inform the blockchain runtime. Pallets may expose storage items or dispatchable calls, and the front-end can be used to query storage items and submit dispatchable calls in the form of signed extrinsics.
Other than a browser and an HTTP server, the only requirement for building and
using this project is wasm-pack
and its associated dependencies for building the Wasm libraries. First,
install Rust, then
install wasm-pack
. This
project includes a package.json
file that provides a command for launching a
simple HTTP server, which requires Node.js.
To build the Wasm libraries and their bindings, and install them in the expected
directory (lib/wasm
), execute the build:wasm
NPM script:
npm run build:wasm
Launch the web UI by executing the start
NPM script:
npm start