mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-06 03:18:01 +00:00
Yew Subxt WASM examples (#968)
* add wasm examples * cargo fmt * change crate name * resolve workspace conflicts
This commit is contained in:
@@ -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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user