Files
pezkuwi-subxt/examples/wasm-example/src/services.rs
T
James Wilson 72ac18491c Support V16 metadata and refactor metadata code (#1967)
* WIP integrate unstable v16 metadata into Subxt

* first pass moving retain to the CLI tool

* Remove otuer enum variant stripping and move now simpler strip_metadata to new crate. test it

* tidyup to use stripmetadata package etc

* Fix / comment out tests

* fmt

* clippy

* Fix wasm example

* wasm-example fix

* wasm-example fix

* Maske sure to move IDs around after types.retain()

* fmt

* Tweak comment

* Find dispatch error separately to avoid issues during mapping

* Expose associated type information in pallet metadata

* Hopefully fix flaky archive RPC

* remove unwanted temp file

* Address nits

* Add back commented-otu tests and address review comments

* use either, and simplify for_each
2025-03-28 15:35:55 +00:00

174 lines
6.4 KiB
Rust

use anyhow::anyhow;
use js_sys::Promise;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::fmt::Write;
use subxt::ext::codec::{Compact, Encode};
use subxt::{self, OnlineClient, PolkadotConfig};
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use yew::{AttrValue, Callback};
#[subxt::subxt(runtime_metadata_path = "../../artifacts/polkadot_metadata_small.scale")]
pub 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 extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
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(())
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = getAccounts)]
pub fn js_get_accounts() -> Promise;
#[wasm_bindgen(js_name = signPayload)]
pub fn js_sign_payload(payload: String, source: String, address: String) -> Promise;
}
/// DTO to communicate with JavaScript
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Account {
/// account name
pub name: String,
/// name of the browser extension
pub source: String,
/// the signature type, e.g. "sr25519" or "ed25519"
pub ty: String,
/// ss58 formatted address as string. Can be converted into AccountId32 via it's FromStr implementation.
pub address: String,
}
pub async fn get_accounts() -> Result<Vec<Account>, anyhow::Error> {
let result = JsFuture::from(js_get_accounts())
.await
.map_err(|js_err| anyhow!("{js_err:?}"))?;
let accounts_str = result
.as_string()
.ok_or(anyhow!("Error converting JsValue into String"))?;
let accounts: Vec<Account> = serde_json::from_str(&accounts_str)?;
Ok(accounts)
}
fn to_hex(bytes: impl AsRef<[u8]>) -> String {
format!("0x{}", hex::encode(bytes.as_ref()))
}
fn encode_then_hex<E: Encode>(input: &E) -> String {
format!("0x{}", hex::encode(input.encode()))
}
/// communicates with JavaScript to obtain a signature for the `partial_extrinsic` via a browser extension (e.g. polkadot-js or Talisman)
///
/// Some parameters are hard-coded here and not taken from the partial_extrinsic itself (mortality_checkpoint, era, tip).
pub async fn extension_signature_for_extrinsic(
call_data: &[u8],
api: &OnlineClient<PolkadotConfig>,
account_nonce: u64,
account_source: String,
account_address: String,
) -> Result<Vec<u8>, anyhow::Error> {
let genesis_hash = encode_then_hex(&api.genesis_hash());
// These numbers aren't SCALE encoded; their bytes are just converted to hex:
let spec_version = to_hex(&api.runtime_version().spec_version.to_be_bytes());
let transaction_version = to_hex(&api.runtime_version().transaction_version.to_be_bytes());
let nonce = to_hex(&account_nonce.to_be_bytes());
// If you construct a mortal transaction, then this block hash needs to correspond
// to the block number passed to `Era::mortal()`.
let mortality_checkpoint = encode_then_hex(&api.genesis_hash());
let era = encode_then_hex(&subxt::utils::Era::Immortal);
let method = to_hex(call_data);
let signed_extensions: Vec<String> = api
.metadata()
.extrinsic()
.transaction_extensions_by_version(0)
.unwrap()
.map(|e| e.identifier().to_string())
.collect();
let tip = encode_then_hex(&Compact(0u128));
let payload = json!({
"specVersion": spec_version,
"transactionVersion": transaction_version,
"address": account_address,
"blockHash": mortality_checkpoint,
"blockNumber": "0x00000000",
"era": era,
"genesisHash": genesis_hash,
"method": method,
"nonce": nonce,
"signedExtensions": signed_extensions,
"tip": tip,
"version": 4,
});
let payload = payload.to_string();
let result = JsFuture::from(js_sign_payload(payload, account_source, account_address))
.await
.map_err(|js_err| anyhow!("{js_err:?}"))?;
let signature = result
.as_string()
.ok_or(anyhow!("Error converting JsValue into String"))?;
let signature = hex::decode(&signature[2..])?;
Ok(signature)
}