diff --git a/subxt/examples/block_decoding_dynamic.rs b/subxt/examples/block_decoding_dynamic.rs new file mode 100644 index 0000000000..8bd54d4afb --- /dev/null +++ b/subxt/examples/block_decoding_dynamic.rs @@ -0,0 +1,46 @@ +#![allow(missing_docs)] +use subxt::{OnlineClient, PolkadotConfig}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client that subscribes to blocks of the Polkadot network. + let api = OnlineClient::::from_url("wss://rpc.polkadot.io:443").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 block_number = block.header().number; + let block_hash = block.hash(); + println!("Block #{block_number} ({block_hash})"); + + // Decode each signed extrinsic in the block dynamically + let extrinsics = block.extrinsics().await?; + for ext in extrinsics.iter() { + let ext = ext?; + + let Some(signed_extensions) = ext.signed_extensions() else { + continue; // we do not look at inherents in this example + }; + + let meta = ext.extrinsic_metadata()?; + let fields = ext.field_values()?; + + println!(" {}/{}", meta.pallet.name(), meta.variant.name); + println!(" Signed Extensions:"); + for signed_ext in signed_extensions.iter() { + let signed_ext = signed_ext?; + // We only want to take a look at these 3 signed extensions, because the others all just have unit fields. + if ["CheckMortality", "CheckNonce", "ChargeTransactionPayment"] + .contains(&signed_ext.name()) + { + println!(" {}: {}", signed_ext.name(), signed_ext.value()?); + } + } + println!(" Fields:"); + println!(" {}\n", fields); + } + } + + Ok(()) +} diff --git a/subxt/examples/block_decoding_static.rs b/subxt/examples/block_decoding_static.rs new file mode 100644 index 0000000000..42eccc9c24 --- /dev/null +++ b/subxt/examples/block_decoding_static.rs @@ -0,0 +1,65 @@ +#![allow(missing_docs)] +use subxt::{ + utils::{AccountId32, MultiAddress}, + OnlineClient, PolkadotConfig, +}; + +use codec::Decode; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")] +pub mod polkadot {} + +use polkadot::balances::calls::types::TransferKeepAlive; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client that subscribes to blocks of the Polkadot network. + let api = OnlineClient::::from_url("wss://rpc.polkadot.io:443").await?; + + // Subscribe to all finalized blocks: + let mut blocks_sub = api.blocks().subscribe_finalized().await?; + + // For each block, print details about the `TransferKeepAlive` transactions we are interested in. + while let Some(block) = blocks_sub.next().await { + let block = block?; + let block_number = block.header().number; + let block_hash = block.hash(); + println!("Block #{block_number} ({block_hash}):"); + + let extrinsics = block.extrinsics().await?; + for ext in extrinsics.iter() { + let ext = ext?; + if let Ok(Some(transfer)) = ext.as_extrinsic::() { + let Some(extensions) = ext.signed_extensions() else { + panic!("TransferKeepAlive should be signed") + }; + + ext.address_bytes().unwrap(); + let addr_bytes = ext + .address_bytes() + .expect("TransferKeepAlive should be signed"); + let sender = MultiAddress::::decode(&mut &addr_bytes[..]) + .expect("Decoding should work"); + let sender = display_address(&sender); + let receiver = display_address(&transfer.dest); + let value = transfer.value; + let tip = extensions.tip().expect("Should have tip"); + let nonce = extensions.nonce().expect("Should have nonce"); + + println!( + " Transfer of {value} DOT:\n {sender} (Tip: {tip}, Nonce: {nonce}) ---> {receiver}", + ); + } + } + } + + Ok(()) +} + +fn display_address(addr: &MultiAddress) -> String { + if let MultiAddress::Id(id32) = addr { + format!("{id32}") + } else { + "MultiAddress::...".into() + } +} diff --git a/subxt/examples/blocks_subscribing.rs b/subxt/examples/blocks_subscribing.rs index 5511df990d..3402a08827 100644 --- a/subxt/examples/blocks_subscribing.rs +++ b/subxt/examples/blocks_subscribing.rs @@ -37,8 +37,8 @@ async fn main() -> Result<(), Box> { println!(" Extrinsic #{idx}:"); println!(" Bytes: {bytes_hex}"); println!(" Decoded: {decoded_ext:?}"); - println!(" Events:"); + println!(" Events:"); for evt in events.iter() { let evt = evt?; let pallet_name = evt.pallet_name(); diff --git a/subxt/examples/setup_config_assethub.rs b/subxt/examples/setup_config_assethub.rs new file mode 100644 index 0000000000..99750898ef --- /dev/null +++ b/subxt/examples/setup_config_assethub.rs @@ -0,0 +1,54 @@ +#![allow(missing_docs)] +use subxt::config::{ + Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, PolkadotConfig, SubstrateConfig, +}; +use subxt_signer::sr25519::dev; + +#[subxt::subxt( + runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale", + derive_for_type( + path = "xcm::v2::multilocation::MultiLocation", + derive = "Clone", + recursive + ) +)] +pub mod runtime {} +use runtime::runtime_types::xcm::v2::multilocation::{Junctions, MultiLocation}; + +// We don't need to construct this at runtime, so an empty enum is appropriate. +pub enum AssetHubConfig {} + +impl Config for AssetHubConfig { + type Hash = ::Hash; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + type Hasher = ::Hasher; + type Header = ::Header; + type ExtrinsicParams = DefaultExtrinsicParams; + // Here we use the MultiLocation from the metadata as a part of the config: + // The `ChargeAssetTxPayment` signed extension that is part of the ExtrinsicParams above, now uses the type: + type AssetId = MultiLocation; +} + +#[tokio::main] +async fn main() { + // With the config defined, we can create an extrinsic with subxt: + let client = subxt::OnlineClient::::new().await.unwrap(); + let tx_payload = runtime::tx().system().remark(b"Hello".to_vec()); + + // Build extrinsic params using an asset at this location as a tip: + let location: MultiLocation = MultiLocation { + parents: 3, + interior: Junctions::Here, + }; + let tx_config = DefaultExtrinsicParamsBuilder::::new() + .tip_of(1234, location) + .build(); + + // And provide the extrinsic params including the tip when submitting a transaction: + let _ = client + .tx() + .sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config) + .await; +} diff --git a/subxt/examples/setup_config_custom.rs b/subxt/examples/setup_config_custom.rs index c884324fc0..4748202e1f 100644 --- a/subxt/examples/setup_config_custom.rs +++ b/subxt/examples/setup_config_custom.rs @@ -4,16 +4,8 @@ use subxt::client::OfflineClientT; use subxt::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError}; use subxt_signer::sr25519::dev; -#[subxt::subxt( - runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale", - derive_for_type( - path = "xcm::v2::multilocation::MultiLocation", - derive = "Clone", - recursive - ) -)] +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")] pub mod runtime {} -use runtime::runtime_types::xcm::v2::multilocation::MultiLocation; // We don't need to construct this at runtime, // so an empty enum is appropriate: @@ -27,7 +19,7 @@ impl Config for CustomConfig { type Hasher = subxt::config::substrate::BlakeTwo256; type Header = subxt::config::substrate::SubstrateHeader; type ExtrinsicParams = CustomExtrinsicParams; - type AssetId = MultiLocation; + type AssetId = u32; } // This represents some arbitrary (and nonsensical) custom parameters that diff --git a/subxt/src/book/setup/config.rs b/subxt/src/book/setup/config.rs index dba7654d7b..6e6e811a55 100644 --- a/subxt/src/book/setup/config.rs +++ b/subxt/src/book/setup/config.rs @@ -152,3 +152,15 @@ //! ```rust,ignore #![doc = include_str ! ("../../../examples/setup_config_custom.rs")] //! ``` +//! +//! ### Using a type from the metadata as a config parameter +//! +//! You can also use types that are generated from chain metadata as type parameters of the Config trait. +//! Just make sure all trait bounds are satisfied. This can often be achieved by using custom derives with the subxt macro. +//! For example, the AssetHub Parachain expects tips to include a `MultiLocation`, which is a type we can draw from the metadata. +//! +//! This example shows what using the `MultiLocation` struct as part of your config would look like in subxt: +//! +//! ```rust,ignore +#![doc = include_str ! ("../../../examples/setup_config_assethub.rs")] +//! ``` diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs index c98e0298ca..95befa6818 100644 --- a/subxt/src/book/usage/blocks.rs +++ b/subxt/src/book/usage/blocks.rs @@ -27,13 +27,72 @@ //! Aside from these links to other Subxt APIs, the main thing that we can do here is iterate over and //! decode the extrinsics in a block body. //! -//! ## Example +//! ## Decoding Extrinsics //! -//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and iterate over -//! the extrinsics stored within it. From there, you can decode the extrinsics and access various details, -//! including the associated events: +//! Given a block, you can [download the block body](crate::blocks::Block::extrinsics()) and [iterate over +//! the extrinsics](crate::blocks::Extrinsics::iter()) stored within it. The extrinsics yielded are of type +//! [ExtrinsicDetails](crate::blocks::ExtrinsicDetails), which is just a blob of bytes that also stores which +//! pallet and call in that pallet it belongs to. It also contains information about signed extensions that +//! have been used for submitting this extrinsic. +//! +//! To use the extrinsic, you probably want to decode it into a concrete Rust type. These Rust types representing +//! extrinsics from different pallets can be generated from metadata using the subxt macro or the CLI tool. +//! +//! When decoding the extrinsic into a static type you have two options: +//! +//! ### Statically decode the extrinsics into [the root extrinsic type](crate::blocks::ExtrinsicDetails::as_root_extrinsic()) +//! +//! The root extrinsic type generated by subxt is a Rust enum with one variant for each pallet. Each of these +//! variants has a field that is another enum whose variants cover all calls of the respective pallet. +//! If the extrinsic bytes are valid and your metadata matches the chain's metadata, decoding the bytes of an extrinsic into +//! this root extrinsic type should always succeed. +//! +//! This example shows how to subscribe to blocks and decode the extrinsics in each block into the root extrinsic type. +//! Once we get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails), we can decode it statically or dynamically. +//! We can also access details about the extrinsic, including the associated events and signed extensions. //! //! ```rust,ignore #![doc = include_str!("../../../examples/blocks_subscribing.rs")] //! ``` //! +//! ### Statically decode the extrinsic into [a specific pallet call](crate::blocks::ExtrinsicDetails::as_extrinsic()) +//! +//! This is useful if you are expecting a specific extrinsic to be part of some block. If the extrinsic you try to decode +//! is a different extrinsic, an `Ok(None)` value is returned from [`as_extrinsic::()`](crate::blocks::ExtrinsicDetails::as_extrinsic()); +//! +//! If you are only interested in finding specific extrinsics in a block, you can also [iterate over all of them](crate::blocks::Extrinsics::find), +//! get only [the first one](crate::blocks::Extrinsics::find_first), or [the last one](crate::blocks::Extrinsics::find_last). +//! +//! The following example monitors `TransferKeepAlive` extrinsics on the Polkadot network. +//! We statically decode them and access the [tip](crate::blocks::ExtrinsicSignedExtensions::tip()) and [account nonce](crate::blocks::ExtrinsicSignedExtensions::nonce()) signed extensions. +//! +//! ```rust,ignore +#![doc = include_str!("../../../examples/block_decoding_static.rs")] +//! ``` +//! +//! ### Dynamically decode the extrinsic +//! +//! Sometimes you might use subxt with metadata that is not known at compile time. In this case, you do not have access to a statically generated +//! interface module that contains the relevant Rust types. You can [decode ExtrinsicDetails dynamically](crate::blocks::ExtrinsicDetails::field_values()), +//! which gives you access to it's fields as a [scale value composite](scale_value::Composite). +//! The following example looks for signed extrinsics on the Polkadot network and retrieves their pallet name, variant name, data fields and signed extensions dynamically. +//! Notice how we do not need to use code generation via the subxt macro. The only fixed component we provide is the [PolkadotConfig](crate::config::PolkadotConfig). +//! Other than that it works in a chain-agnostic way: +//! +//! ```rust,ignore +#![doc = include_str!("../../../examples/block_decoding_dynamic.rs")] +//! ``` +//! +//! ## Decoding signed extensions +//! +//! Extrinsics can contain signed extensions. The signed extensions can be different across chains. +//! The [Config](crate::Config) implementation for your chain defines which signed extensions you expect. +//! Once you get hold of the [ExtrinsicDetails](crate::blocks::ExtrinsicDetails) for an extrinsic you are interested in, +//! you can try to [get its signed extensions](crate::blocks::ExtrinsicDetails::signed_extensions()). +//! These are only available on signed extrinsics. You can try to [find a specific signed extension](crate::blocks::ExtrinsicSignedExtensions::find), +//! in the returned [signed extensions](crate::blocks::ExtrinsicSignedExtensions). +//! +//! Subxt also provides utility functions to get the [tip](crate::blocks::ExtrinsicSignedExtensions::tip()) and the +//! [account nonce](crate::blocks::ExtrinsicSignedExtensions::tip()) associated with an extrinsic, given its signed extensions. +//! If you prefer to do things dynamically you can get the data of the signed extension as a [scale value](crate::blocks::ExtrinsicSignedExtension::value()). +//!