Yew Subxt WASM examples (#968)

* add wasm examples

* cargo fmt

* change crate name

* resolve workspace conflicts
This commit is contained in:
Tadeo Hepperle
2023-05-26 11:14:49 +02:00
committed by GitHub
parent b9f5419095
commit e8612ddae0
8 changed files with 2666 additions and 1 deletions
+152
View File
@@ -0,0 +1,152 @@
//! This is a small WASM app using the Yew UI framework showcasing how to use Subxt's features in a WASM environment.
//!
//! To run the app locally use Trunk, a WASM bundler:
//! ```
//! cargo install --locked trunk
//! ```
//! Run the app locally:
//! ```
//! trunk serve --open
//! ```
//! You need to have a local polkadot/substrate node with it's JSON-RPC HTTP server running at 127.0.0.1:9933 in order for the examples to be working.
//! Also make sure your browser supports WASM.
use futures::{self, FutureExt};
use yew::prelude::*;
mod services;
fn main() {
yew::Renderer::<SubxtExamplesComponent>::new().render();
}
struct SubxtExamplesComponent {
operation_title: Option<AttrValue>,
lines: Vec<AttrValue>,
}
enum Message {
Error(subxt::Error),
Reload,
Line(AttrValue),
Lines(Vec<AttrValue>),
ButtonClick(Button),
}
enum Button {
SubscribeFinalized,
FetchConstant,
FetchEvents,
}
impl Component for SubxtExamplesComponent {
type Message = Message;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
SubxtExamplesComponent {
lines: Vec::new(),
operation_title: None,
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Message::Error(err) => {
self.lines.push(err.to_string().into());
}
Message::Reload => {
let window = web_sys::window().expect("Failed to access the window object");
window
.location()
.reload()
.expect("Failed to reload the page");
}
Message::Line(line) => {
self.lines.push(line);
}
Message::Lines(mut lines) => {
self.lines.append(&mut lines);
}
Message::ButtonClick(button) => match button {
Button::SubscribeFinalized => {
self.operation_title = Some("Subscribe to finalized blocks:".into());
let cb: Callback<AttrValue> = ctx.link().callback(Message::Line);
ctx.link()
.send_future(services::subscribe_to_finalized_blocks(cb).map(|result| {
let err = result.unwrap_err();
Message::Error(err)
}));
}
Button::FetchConstant => {
self.operation_title =
Some("Fetch the constant \"block_length\" of \"System\" pallet:".into());
ctx.link()
.send_future(services::fetch_constant_block_length().map(|result| {
match result {
Ok(value) => Message::Line(
format!(
"constant \"block_length\" of \"System\" pallet:\n {value}"
)
.into(),
),
Err(err) => Message::Error(err),
}
}))
}
Button::FetchEvents => {
self.operation_title = Some("Fetch events:".into());
ctx.link()
.send_future(services::fetch_events_dynamically().map(
|result| match result {
Ok(value) => {
Message::Lines(value.into_iter().map(AttrValue::from).collect())
}
Err(err) => Message::Error(err),
},
))
}
},
}
true
}
fn view(&self, ctx: &Context<Self>) -> Html {
let reload: Callback<MouseEvent> = ctx.link().callback(|_| Message::Reload);
let subscribe_finalized = ctx
.link()
.callback(|_| Message::ButtonClick(Button::SubscribeFinalized));
let fetch_constant = ctx
.link()
.callback(|_| Message::ButtonClick(Button::FetchConstant));
let fetch_events = ctx
.link()
.callback(|_| Message::ButtonClick(Button::FetchEvents));
html! {
<div>
if let Some(operation_title) = &self.operation_title{
<button onclick={reload}>{"🡄 Back"}</button>
<h1>{operation_title}</h1>
if self.lines.is_empty(){
<p>{"Loading..."}</p>
}
else{
}
{ for self.lines.iter().map(|line| html! {<p> {line} </p>}) }
}
else{
<>
<h1>{"Subxt Examples"}</h1>
<button onclick={subscribe_finalized}>{"Example: Subscribe to Finalized blocks"}</button>
<button onclick={fetch_constant}>{"Example: Fetch constant value"}</button>
<button onclick={fetch_events}>{"Example: Fetch events"}</button>
</>
}
</div>
}
}
}
+73
View File
@@ -0,0 +1,73 @@
use futures::StreamExt;
use std::fmt::Write;
use subxt::{self, OnlineClient, PolkadotConfig};
use yew::{AttrValue, Callback};
#[subxt::subxt(runtime_metadata_path = "../../artifacts/polkadot_metadata_small.scale")]
mod polkadot {}
pub(crate) async fn fetch_constant_block_length() -> Result<String, subxt::Error> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
let constant_query = polkadot::constants().system().block_length();
let value = api.constants().at(&constant_query)?;
Ok(format!("{value:?}"))
}
pub(crate) async fn fetch_events_dynamically() -> Result<Vec<String>, subxt::Error> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
let events = api.events().at_latest().await?;
let mut event_strings = Vec::<String>::new();
for event in events.iter() {
let event = event?;
let pallet = event.pallet_name();
let variant = event.variant_name();
let field_values = event.field_values()?;
event_strings.push(format!("{pallet}::{variant}: {field_values}"));
}
Ok(event_strings)
}
/// subscribes to finalized blocks. When a block is received, it is formatted as a string and sent via the callback.
pub(crate) async fn subscribe_to_finalized_blocks(
cb: Callback<AttrValue>,
) -> Result<(), subxt::Error> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
while let Some(block) = blocks_sub.next().await {
let block = block?;
let mut output = String::new();
writeln!(output, "Block #{}:", block.header().number).ok();
writeln!(output, " Hash: {}", block.hash()).ok();
writeln!(output, " Extrinsics:").ok();
let body = block.body().await?;
for ext in body.extrinsics().iter() {
let ext = ext?;
let idx = ext.index();
let events = ext.events().await?;
let bytes_hex = format!("0x{}", hex::encode(ext.bytes()));
// See the API docs for more ways to decode extrinsics:
let decoded_ext = ext.as_root_extrinsic::<polkadot::Call>();
writeln!(output, " Extrinsic #{idx}:").ok();
writeln!(output, " Bytes: {bytes_hex}").ok();
writeln!(output, " Decoded: {decoded_ext:?}").ok();
writeln!(output, " Events:").ok();
for evt in events.iter() {
let evt = evt?;
let pallet_name = evt.pallet_name();
let event_name = evt.variant_name();
let event_values = evt.field_values()?;
writeln!(output, " {pallet_name}_{event_name}").ok();
writeln!(output, " {}", event_values).ok();
}
}
cb.emit(output.into())
}
Ok(())
}