Files
pezkuwi-subxt/historic/examples/extrinsics.rs
T
James Wilson 8203679cbd Merge v0.50.x to master (#2127)
* v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100)

* WIP integrating new frame-decode and working out new storage APIS

* WIP: first pass adding new storage things to subxt-core

* Second pass over Address type and start impl in Subxt

* WIP new storage APIs

* WIP New storage APIs roughly completed, lots of errors still

* Remove PlainorMap enum; plain and map values now use same struct to simplify usage

* Begin 'fixing' errors

* WIP splitting errors and tidying payload/address traits

* Get subxt-core compiling

* Small fixes in subxt-core and remove metadata mod

* subxt-core: cargo check --all-targets passes

* Fix test

* WIP starting to update subxt from subxt-core changes

* WIP splitting up subxt errors into smaller variants

* WIP errors: add DispatchError errors

* Port new Storage APIs to subxt-core

* cargo check -p subxt passes

* Quick-fix errors in subxt-cli (explore subcommand)

* fmt

* Finish fixing codegen up and start fixing examples

* get Subxt examples compiling and bytes_at for constants

* Add some arcs to limit lifetimes in subxt/subxt-core storage APIs

* A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt

* Update codegen test

* cargo check --all-targets passing

* cargo check --features 'unstable-light-client' passing

* clippy

* Remove unused dep in subxt

* use published frame-decode

* fix wasm-example

* Add new tx extension to fix daily tests

* Remove unused subxt_core::dynamic::DecodedValue type

* Update book to match changes

* Update docs to fix more broken bits

* Add missing docs

* fmt

* allow larger result errs for now

* Add missing alloc imports in subxt-core

* Fix doc tests and fix bug getting constant info

* Fix V14 -> Metadata transform for storage & constants

* Fix parachain example

* Fix FFI example

* BlockLength decodes t ostruct, not u128

* use fetch/iter shorthands rather than entry in most storage tests

* Fix some integration tests

* Fix Runtime codegen tests

* Expose the dynamic custom_value selecter and use in a UI test

* Update codegen metadata

* Tidy CLI storage query and support (str,str) as a storage address

* Add (str,str) as valid constant address too

* Show string tuple in constants example

* Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them

* clippy

* [v0.50] update scale-info-legacy and frame-decode to latest (#2119)

* bump scale-info-legacy and frame-decode to latest

* Remove something we don't need in this PR

* Fully remove unused for now dep

* [v0.50] Convert historic metadata to subxt::Metadata (#2120)

* First pass converting historic metadatas to our subxt::Metadata type

* use published frame-decode

* fmt and rename legacy metadata macro

* Enable legacy feature where needed in subxt_metadata so it compiles on its own

* Use cargo hack more in CI and fix subxt-metadata features

* Add tests for metadata conversion (need to optimise; some too expensive right now

* Address performance and equality issues in metadata conversion testing

* fmt

* fmt all

* clippy

* Fix a doc link

* Test codegen and fixes to make it work

* Remove local frame-decode patch

* bump frame-decode to latest

* [v0.50.0] Allow visiting extrinsic fields in subxt_historic (#2124)

* Allow visiting extrinsic fields

* fmt

* Don't use local scale-decode dep

* Clippy and tidy

* Extend 'subxt codegen' CLI to work with legacy metadatas

* Simplify historic extrinsics example now that AccountId32s have paths/names

* clippy

* clippy

* clippy..

* Allow visiting storage values, too, and clean up extrinsic visiting a little by narrowing lifetime

* Try to fix flaky test

* Add custom value decode to extrinsics example

* Remove useless else branch ra thought I needed

* Simplify examples

* Prep to release v0.0.5 (#2126)
2025-11-22 10:44:03 +00:00

451 lines
15 KiB
Rust

#![allow(missing_docs)]
use subxt_historic::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn core::error::Error + Send + Sync + 'static>> {
// Configuration for the Polkadot relay chain.
let config = PolkadotConfig::new();
// Create an online client for the Polkadot relay chain, pointed at a Polkadot archive node.
let client = OnlineClient::from_url(config, "wss://rpc.polkadot.io").await?;
// Iterate through some randomly selected old blocks to show how to fetch and decode extrinsics.
for block_number in 1234567.. {
println!("=== Block {block_number} ===");
// Point the client at a specific block number. By default this will download and cache
// metadata for the required spec version (so it's cheaper to instantiate again), if it
// hasn't already, and borrow the relevant legacy types from the client.
let client_at_block = client.at(block_number).await?;
// Fetch the extrinsics at that block.
let extrinsics = client_at_block.extrinsics().fetch().await?;
// Now, we have various operations to work with them. Here we print out various details
// about each extrinsic.
for extrinsic in extrinsics.iter() {
println!(
"{}.{}",
extrinsic.call().pallet_name(),
extrinsic.call().name()
);
if let Some(signature) = extrinsic.signature_bytes() {
println!(" Signature: 0x{}", hex::encode(signature));
}
println!(" Call Data:");
// We can decode each of the fields (in this example we decode everything into a
// scale_value::Value type, which can represent any SCALE encoded data, but if you
// have an idea of the type then you can try to decode into that type instead):
for field in extrinsic.call().fields().iter() {
// We can visit fields, which gives us the ability to inspect and decode information
// from them selectively, returning whatever we like from it. Here we demo our
// type name visitor which is defined below:
let tn = field
.visit(type_name::GetTypeName::new())?
.unwrap_or_default();
// When visiting fields we can also decode into a custom shape like so:
let _custom_value = field.visit(value::GetValue::new())?;
// We can also obtain and decode things without the complexity of the above:
println!(
" {}: {} {}",
field.name(),
field.decode_as::<scale_value::Value>().unwrap(),
if tn.is_empty() {
String::new()
} else {
format!("(type name: {tn})")
},
);
}
// Or, all of them at once:
println!(
" All: {}",
extrinsic
.call()
.fields()
.decode_as::<scale_value::Composite<_>>()
.unwrap()
);
// We can also look at things like the transaction extensions:
if let Some(extensions) = extrinsic.transaction_extensions() {
println!(" Transaction Extensions:");
// We can decode each of them:
for extension in extensions.iter() {
println!(
" {}: {}",
extension.name(),
extension.decode_as::<scale_value::Value>().unwrap()
);
}
// Or all of them at once:
println!(
" All: {}",
extensions.decode_as::<scale_value::Composite<_>>().unwrap()
);
}
}
}
Ok(())
}
/// This module defines an example visitor which retrieves the name of a type.
/// This is a more advanced use case and can typically be avoided.
mod type_name {
use scale_decode::{
Visitor,
visitor::types::{Composite, Sequence, Variant},
visitor::{TypeIdFor, Unexpected},
};
use scale_type_resolver::TypeResolver;
/// This is a visitor which obtains type names.
pub struct GetTypeName<R> {
marker: core::marker::PhantomData<R>,
}
impl<R> GetTypeName<R> {
/// Construct our TypeName visitor.
pub fn new() -> Self {
GetTypeName {
marker: core::marker::PhantomData,
}
}
}
impl<R: TypeResolver> Visitor for GetTypeName<R> {
type Value<'scale, 'resolver> = Option<&'resolver str>;
type Error = scale_decode::Error;
type TypeResolver = R;
// Look at the path of types that have paths and return the ident from that.
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
fn visit_sequence<'scale, 'resolver>(
self,
value: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(value.path().last())
}
// Else, we return nothing as we can't find a name for the type.
fn visit_unexpected<'scale, 'resolver>(
self,
_unexpected: Unexpected,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(None)
}
}
}
/// This visitor demonstrates how to decode and return a custom Value shape
mod value {
use scale_decode::{
Visitor,
visitor::TypeIdFor,
visitor::types::{Array, BitSequence, Composite, Sequence, Str, Tuple, Variant},
};
use scale_type_resolver::TypeResolver;
use std::collections::HashMap;
/// A value type we're decoding into.
#[allow(dead_code)]
pub enum Value {
Number(f64),
BigNumber(String),
Bool(bool),
Char(char),
Array(Vec<Value>),
String(String),
Address(Vec<u8>),
I256([u8; 32]),
U256([u8; 32]),
Struct(HashMap<String, Value>),
Variant(String, VariantFields),
}
pub enum VariantFields {
Unnamed(Vec<Value>),
Named(HashMap<String, Value>),
}
/// An error we can encounter trying to decode things into a [`Value`]
#[derive(Debug, thiserror::Error)]
pub enum ValueError {
#[error("Decode error: {0}")]
Decode(#[from] scale_decode::visitor::DecodeError),
#[error("Cannot decode bit sequence: {0}")]
CannotDecodeBitSequence(codec::Error),
}
/// This is a visitor which obtains type names.
pub struct GetValue<R> {
marker: core::marker::PhantomData<R>,
}
impl<R> GetValue<R> {
/// Construct our TypeName visitor.
pub fn new() -> Self {
GetValue {
marker: core::marker::PhantomData,
}
}
}
impl<R: TypeResolver> Visitor for GetValue<R> {
type Value<'scale, 'resolver> = Value;
type Error = ValueError;
type TypeResolver = R;
fn visit_i256<'resolver>(
self,
value: &[u8; 32],
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'_, 'resolver>, Self::Error> {
Ok(Value::I256(*value))
}
fn visit_u256<'resolver>(
self,
value: &[u8; 32],
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'_, 'resolver>, Self::Error> {
Ok(Value::U256(*value))
}
fn visit_i128<'scale, 'resolver>(
self,
value: i128,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let attempt = value as f64;
if attempt as i128 == value {
Ok(Value::Number(attempt))
} else {
Ok(Value::BigNumber(value.to_string()))
}
}
fn visit_i64<'scale, 'resolver>(
self,
value: i64,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_i32<'scale, 'resolver>(
self,
value: i32,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_i16<'scale, 'resolver>(
self,
value: i16,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_i8<'scale, 'resolver>(
self,
value: i8,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_i128(value.into(), type_id)
}
fn visit_u128<'scale, 'resolver>(
self,
value: u128,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let attempt = value as f64;
if attempt as u128 == value {
Ok(Value::Number(attempt))
} else {
Ok(Value::BigNumber(value.to_string()))
}
}
fn visit_u64<'scale, 'resolver>(
self,
value: u64,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_u32<'scale, 'resolver>(
self,
value: u32,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_u16<'scale, 'resolver>(
self,
value: u16,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_u8<'scale, 'resolver>(
self,
value: u8,
type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
self.visit_u128(value.into(), type_id)
}
fn visit_bool<'scale, 'resolver>(
self,
value: bool,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Bool(value))
}
fn visit_char<'scale, 'resolver>(
self,
value: char,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Char(value))
}
fn visit_array<'scale, 'resolver>(
self,
values: &mut Array<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(values.remaining(), values)?))
}
fn visit_sequence<'scale, 'resolver>(
self,
values: &mut Sequence<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(values.remaining(), values)?))
}
fn visit_str<'scale, 'resolver>(
self,
value: &mut Str<'scale>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::String(value.as_str()?.to_owned()))
}
fn visit_tuple<'scale, 'resolver>(
self,
values: &mut Tuple<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
Ok(Value::Array(to_array(values.remaining(), values)?))
}
fn visit_bitsequence<'scale, 'resolver>(
self,
value: &mut BitSequence<'scale>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let bits = value.decode()?;
let mut out = Vec::with_capacity(bits.len());
for b in bits {
let b = b.map_err(ValueError::CannotDecodeBitSequence)?;
out.push(Value::Bool(b));
}
Ok(Value::Array(out))
}
fn visit_composite<'scale, 'resolver>(
self,
value: &mut Composite<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
// Special case for ss58 addresses:
if let Some(n) = value.name()
&& n == "AccountId32"
&& value.bytes_from_start().len() == 32
{
return Ok(Value::Address(value.bytes_from_start().to_vec()));
}
// Reuse logic for decoding variant fields:
match to_variant_fieldish(value)? {
VariantFields::Named(s) => Ok(Value::Struct(s)),
VariantFields::Unnamed(a) => Ok(Value::Array(a)),
}
}
fn visit_variant<'scale, 'resolver>(
self,
value: &mut Variant<'scale, 'resolver, Self::TypeResolver>,
_type_id: TypeIdFor<Self>,
) -> Result<Self::Value<'scale, 'resolver>, Self::Error> {
let name = value.name().to_owned();
let fields = to_variant_fieldish(value.fields())?;
Ok(Value::Variant(name, fields))
}
}
fn to_variant_fieldish<'scale, 'resolver, R: TypeResolver>(
value: &mut Composite<'scale, 'resolver, R>,
) -> Result<VariantFields, ValueError> {
// If fields are unnamed, treat as array:
if value.fields().iter().all(|f| f.name.is_none()) {
return Ok(VariantFields::Unnamed(to_array(value.remaining(), value)?));
}
// Otherwise object:
let mut out = HashMap::new();
for field in value {
let field = field?;
let name = field.name().unwrap().to_string();
let value = field.decode_with_visitor(GetValue::new())?;
out.insert(name, value);
}
Ok(VariantFields::Named(out))
}
fn to_array<'scale, 'resolver, R: TypeResolver>(
len: usize,
mut values: impl scale_decode::visitor::DecodeItemIterator<'scale, 'resolver, R>,
) -> Result<Vec<Value>, ValueError> {
let mut out = Vec::with_capacity(len);
while let Some(value) = values.decode_item(GetValue::new()) {
out.push(value?);
}
Ok(out)
}
}