From ba43e7d154521e9738e559242332eed27978b791 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 31 Mar 2023 15:55:50 +0100 Subject: [PATCH 01/25] WIP Starting to write book; extrinsics first pass done --- Cargo.toml | 2 +- examples/examples/balance_transfer.rs | 40 ----- .../balance_transfer_status_stream.rs | 52 ++++++ .../examples/balance_transfer_with_params.rs | 46 ++--- examples/examples/basic_balance_transfer.rs | 34 ++++ .../examples/concurrent_storage_requests.rs | 39 ---- examples/examples/custom_config.rs | 59 ------- examples/examples/custom_metadata_url.rs | 12 -- examples/examples/custom_rpc_client.rs | 88 ---------- examples/examples/custom_type_derives.rs | 32 ---- examples/examples/dynamic_multisig.rs | 74 -------- examples/examples/dynamic_queries.rs | 85 --------- examples/examples/fetch_all_accounts.rs | 32 ---- examples/examples/fetch_constants.rs | 35 ---- examples/examples/fetch_staking_details.rs | 71 -------- examples/examples/metadata_compatibility.rs | 33 ---- examples/examples/multisig.rs | 72 -------- examples/examples/rpc_call.rs | 32 ---- .../examples/rpc_call_subscribe_blocks.rs | 36 ---- examples/examples/runtime_types_only.rs | 62 ------- examples/examples/storage_iterating.rs | 109 ------------ examples/examples/submit_and_watch.rs | 166 ------------------ examples/examples/subscribe_block_events.rs | 88 ---------- examples/examples/subscribe_blocks.rs | 61 ------- .../examples/subscribe_runtime_updates.rs | 36 ---- subxt/src/book/examples/mod.rs | 0 subxt/src/book/mod.rs | 88 ++++++++++ subxt/src/book/usage/blocks.rs | 7 + subxt/src/book/usage/constants.rs | 7 + subxt/src/book/usage/events.rs | 7 + subxt/src/book/usage/extrinsics.rs | 158 +++++++++++++++++ subxt/src/book/usage/mod.rs | 21 +++ subxt/src/book/usage/storage.rs | 7 + subxt/src/config/mod.rs | 2 +- subxt/src/lib.rs | 110 +----------- 35 files changed, 401 insertions(+), 1402 deletions(-) delete mode 100644 examples/examples/balance_transfer.rs create mode 100644 examples/examples/balance_transfer_status_stream.rs create mode 100644 examples/examples/basic_balance_transfer.rs delete mode 100644 examples/examples/concurrent_storage_requests.rs delete mode 100644 examples/examples/custom_config.rs delete mode 100644 examples/examples/custom_metadata_url.rs delete mode 100644 examples/examples/custom_rpc_client.rs delete mode 100644 examples/examples/custom_type_derives.rs delete mode 100644 examples/examples/dynamic_multisig.rs delete mode 100644 examples/examples/dynamic_queries.rs delete mode 100644 examples/examples/fetch_all_accounts.rs delete mode 100644 examples/examples/fetch_constants.rs delete mode 100644 examples/examples/fetch_staking_details.rs delete mode 100644 examples/examples/metadata_compatibility.rs delete mode 100644 examples/examples/multisig.rs delete mode 100644 examples/examples/rpc_call.rs delete mode 100644 examples/examples/rpc_call_subscribe_blocks.rs delete mode 100644 examples/examples/runtime_types_only.rs delete mode 100644 examples/examples/storage_iterating.rs delete mode 100644 examples/examples/submit_and_watch.rs delete mode 100644 examples/examples/subscribe_block_events.rs delete mode 100644 examples/examples/subscribe_blocks.rs delete mode 100644 examples/examples/subscribe_runtime_updates.rs create mode 100644 subxt/src/book/examples/mod.rs create mode 100644 subxt/src/book/mod.rs create mode 100644 subxt/src/book/usage/blocks.rs create mode 100644 subxt/src/book/usage/constants.rs create mode 100644 subxt/src/book/usage/events.rs create mode 100644 subxt/src/book/usage/extrinsics.rs create mode 100644 subxt/src/book/usage/mod.rs create mode 100644 subxt/src/book/usage/storage.rs diff --git a/Cargo.toml b/Cargo.toml index 1adcacd1dd..71e08cfeac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,4 @@ rust-version = "1.64.0" license = "Apache-2.0 OR GPL-3.0" repository = "https://github.com/paritytech/subxt" documentation = "https://docs.rs/subxt" -homepage = "https://www.parity.io/" +homepage = "https://www.parity.io/" \ No newline at end of file diff --git a/examples/examples/balance_transfer.rs b/examples/examples/balance_transfer.rs deleted file mode 100644 index c15e79b9b3..0000000000 --- a/examples/examples/balance_transfer.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use sp_keyring::AccountKeyring; -use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let dest = AccountKeyring::Bob.to_account_id().into(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - // Create a transaction to submit: - let tx = polkadot::tx() - .balances() - .transfer(dest, 123_456_789_012_345); - - // Submit the transaction with default params: - let hash = api.tx().sign_and_submit_default(&tx, &signer).await?; - - println!("Balance transfer extrinsic submitted: {hash}"); - - Ok(()) -} diff --git a/examples/examples/balance_transfer_status_stream.rs b/examples/examples/balance_transfer_status_stream.rs new file mode 100644 index 0000000000..d7c34cb4c8 --- /dev/null +++ b/examples/examples/balance_transfer_status_stream.rs @@ -0,0 +1,52 @@ +use futures::StreamExt; +use sp_keyring::AccountKeyring; +use subxt::{tx::{TxStatus, PairSigner}, OnlineClient, PolkadotConfig}; + +// Generate an interface that we can use from the node's metadata. +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let api = OnlineClient::::new().await?; + + let dest = AccountKeyring::Bob.to_account_id().into(); + let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000); + + let signer = PairSigner::new(AccountKeyring::Alice.pair()); + let mut balance_transfer_progress = api + .tx() + .sign_and_submit_then_watch_default(&balance_transfer_tx, &signer) + .await?; + + while let Some(status) = balance_transfer_progress.next().await { + match status? { + // It's finalized in a block! + TxStatus::Finalized(in_block) => { + println!( + "Transaction {:?} is finalized in block {:?}", + in_block.extrinsic_hash(), + in_block.block_hash() + ); + + // grab the events and fail if no ExtrinsicSuccess event seen: + let events = in_block.wait_for_success().await?; + // We can look for events (this uses the static interface; we can also iterate + // over them and dynamically decode them): + let transfer_event = events + .find_first::()?; + + if let Some(event) = transfer_event { + println!("Balance transfer success: {event:?}"); + } else { + println!("Failed to find Balances::Transfer Event"); + } + }, + // Just log any other status we encounter: + other => { + println!("Status: {other:?}"); + } + } + } + Ok(()) +} \ No newline at end of file diff --git a/examples/examples/balance_transfer_with_params.rs b/examples/examples/balance_transfer_with_params.rs index fd00ba48cc..d6cb06bae6 100644 --- a/examples/examples/balance_transfer_with_params.rs +++ b/examples/examples/balance_transfer_with_params.rs @@ -1,52 +1,28 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - use sp_keyring::AccountKeyring; -use subxt::{ - config::{ - polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params}, - PolkadotConfig, - }, - tx::PairSigner, - OnlineClient, -}; +use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; +use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params}; #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] pub mod polkadot {} #[tokio::main] async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let dest = AccountKeyring::Bob.to_account_id().into(); - - // Create a client to use: + // Create a new API client, configured to talk to Polkadot nodes. let api = OnlineClient::::new().await?; - // Create a transaction to submit: - let tx = polkadot::tx() - .balances() - .transfer(dest, 123_456_789_012_345); + // Build a balance transfer extrinsic. + let dest = AccountKeyring::Bob.to_account_id().into(); + let tx = polkadot::tx().balances().transfer(dest, 10_000); - // Configure the transaction tip and era: + // Configure the transaction parameters; for Polkadot the tip and era: let tx_params = Params::new() - .tip(PlainTip::new(20_000_000_000)) + .tip(PlainTip::new(1_000)) .era(Era::Immortal, api.genesis_hash()); // submit the transaction: - let hash = api.tx().sign_and_submit(&tx, &signer, tx_params).await?; - - println!("Balance transfer extrinsic submitted: {hash}"); + let from = PairSigner::new(AccountKeyring::Alice.pair()); + let hash = api.tx().sign_and_submit(&tx, &from, tx_params).await?; + println!("Balance transfer extrinsic submitted with hash : {hash}"); Ok(()) } diff --git a/examples/examples/basic_balance_transfer.rs b/examples/examples/basic_balance_transfer.rs new file mode 100644 index 0000000000..50afccfee9 --- /dev/null +++ b/examples/examples/basic_balance_transfer.rs @@ -0,0 +1,34 @@ +use sp_keyring::AccountKeyring; +use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; + +// Generate an interface that we can use from the node's metadata. +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a new API client, configured to talk to Polkadot nodes. + let api = OnlineClient::::new().await?; + + // Build a balance transfer extrinsic. + let dest = AccountKeyring::Bob.to_account_id().into(); + let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000); + + // Submit the balance transfer extrinsic from Alice, and wait for it to be successful + // and in a finalized block. We get back the extrinsic events if all is well. + let from = PairSigner::new(AccountKeyring::Alice.pair()); + let events = api + .tx() + .sign_and_submit_then_watch_default(&balance_transfer_tx, &from) + .await? + .wait_for_finalized_success() + .await?; + + // Find a Transfer event and print it. + let transfer_event = events.find_first::()?; + if let Some(event) = transfer_event { + println!("Balance transfer success: {event:?}"); + } + + Ok(()) +} \ No newline at end of file diff --git a/examples/examples/concurrent_storage_requests.rs b/examples/examples/concurrent_storage_requests.rs deleted file mode 100644 index 25c80c7244..0000000000 --- a/examples/examples/concurrent_storage_requests.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use futures::join; -use sp_keyring::AccountKeyring; -use subxt::{OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - let api = OnlineClient::::new().await?; - - let addr = AccountKeyring::Bob.to_account_id().into(); - - // Construct storage addresses to access: - let staking_bonded = polkadot::storage().staking().bonded(&addr); - let staking_ledger = polkadot::storage().staking().ledger(&addr); - - // For storage requests, we can join futures together to - // await multiple futures concurrently: - let a_fut = api.storage().at(None).await?.fetch(&staking_bonded); - let b_fut = api.storage().at(None).await?.fetch(&staking_ledger); - let (a, b) = join!(a_fut, b_fut); - - println!("{a:?}, {b:?}"); - - Ok(()) -} diff --git a/examples/examples/custom_config.rs b/examples/examples/custom_config.rs deleted file mode 100644 index f92ef059fc..0000000000 --- a/examples/examples/custom_config.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! This example should compile but should fail to work, since we've modified the -//! config to not align with a Polkadot node. - -use sp_keyring::AccountKeyring; -use subxt::{ - config::{substrate::SubstrateExtrinsicParams, Config, SubstrateConfig}, - tx::PairSigner, - OnlineClient, -}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -/// Custom [`Config`] impl where the default types for the target chain differ from the -/// [`DefaultConfig`] -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct MyConfig; -impl Config for MyConfig { - // This is different from the default `u32`. - // - // *Note* that in this example it does differ from the actual `Index` type in the - // polkadot runtime used, so some operations will fail. Normally when using a custom `Config` - // impl types MUST match exactly those used in the actual runtime. - type Index = u64; - type Hash = ::Hash; - type Hasher = ::Hasher; - type Header = ::Header; - type AccountId = ::AccountId; - type Address = ::Address; - type Signature = ::Signature; - // ExtrinsicParams makes use of the index type, so we need to adjust it - // too to align with our modified index type, above: - type ExtrinsicParams = SubstrateExtrinsicParams; -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let dest = AccountKeyring::Bob.to_account_id().into(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - // Create a transaction to submit: - let tx = polkadot::tx() - .balances() - .transfer(dest, 123_456_789_012_345); - - // submit the transaction with default params: - let hash = api.tx().sign_and_submit_default(&tx, &signer).await?; - - println!("Balance transfer extrinsic submitted: {hash}"); - - Ok(()) -} diff --git a/examples/examples/custom_metadata_url.rs b/examples/examples/custom_metadata_url.rs deleted file mode 100644 index 7123695c45..0000000000 --- a/examples/examples/custom_metadata_url.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -// If you'd like to use metadata directly from a running node, you -// can provide a URL to that node here. HTTP or WebSocket URLs can be -// provided. Note that if the metadata cannot be retrieved from this -// node URL at compile time, compilation will fail. -#[subxt::subxt(runtime_metadata_url = "wss://rpc.polkadot.io:443")] -pub mod polkadot {} - -fn main() {} diff --git a/examples/examples/custom_rpc_client.rs b/examples/examples/custom_rpc_client.rs deleted file mode 100644 index 5a463ab44e..0000000000 --- a/examples/examples/custom_rpc_client.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -use std::{ - fmt::Write, - pin::Pin, - sync::{Arc, Mutex}, -}; -use subxt::{ - rpc::{RawValue, RpcClientT, RpcFuture, RpcSubscription}, - OnlineClient, PolkadotConfig, -}; - -// A dummy RPC client that doesn't actually handle requests properly -// at all, but instead just logs what requests to it were made. -struct MyLoggingClient { - log: Arc>, -} - -// We have to implement this fairly low level trait to turn [`MyLoggingClient`] -// into an RPC client that we can make use of in Subxt. Here we just log the requests -// made but don't forward them to any real node, and instead just return nonsense. -impl RpcClientT for MyLoggingClient { - fn request_raw<'a>( - &'a self, - method: &'a str, - params: Option>, - ) -> RpcFuture<'a, Box> { - writeln!( - self.log.lock().unwrap(), - "{method}({})", - params.as_ref().map(|p| p.get()).unwrap_or("[]") - ) - .unwrap(); - - // We've logged the request; just return garbage. Because a boxed future is returned, - // you're able to run whatever async code you'd need to actually talk to a node. - let res = RawValue::from_string("[]".to_string()).unwrap(); - Box::pin(std::future::ready(Ok(res))) - } - - fn subscribe_raw<'a>( - &'a self, - sub: &'a str, - params: Option>, - unsub: &'a str, - ) -> RpcFuture<'a, RpcSubscription> { - writeln!( - self.log.lock().unwrap(), - "{sub}({}) (unsub: {unsub})", - params.as_ref().map(|p| p.get()).unwrap_or("[]") - ) - .unwrap(); - - // We've logged the request; just return garbage. Because a boxed future is returned, - // and that will return a boxed Stream impl, you have a bunch of flexibility to build - // and return whatever type of Stream you see fit. - let res = RawValue::from_string("[]".to_string()).unwrap(); - let stream = futures::stream::once(async move { Ok(res) }); - let stream: Pin + Send>> = Box::pin(stream); - // This subscription does not provide an ID. - Box::pin(std::future::ready(Ok(RpcSubscription { stream, id: None }))) - } -} - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // Instantiate our replacement RPC client. - let log = Arc::default(); - let rpc_client = MyLoggingClient { - log: Arc::clone(&log), - }; - - // Pass this into our OnlineClient to instantiate it. This will lead to some - // RPC calls being made to fetch chain details/metadata, which will immediately - // fail.. - let _ = OnlineClient::::from_rpc_client(Arc::new(rpc_client)).await; - - // But, we can see that the calls were made via our custom RPC client: - println!("Log of calls made:\n\n{}", log.lock().unwrap().as_str()); - Ok(()) -} diff --git a/examples/examples/custom_type_derives.rs b/examples/examples/custom_type_derives.rs deleted file mode 100644 index dbff1a62ff..0000000000 --- a/examples/examples/custom_type_derives.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! Example verified against polkadot polkadot 0.9.25-5174e9ae75b. -#![allow(clippy::redundant_clone)] - -#[subxt::subxt( - runtime_metadata_path = "../artifacts/polkadot_metadata.scale", - // We can add (certain) custom derives to the generated types by providing - // a comma separated list to the below attribute. Most useful for adding `Clone`. - // The derives that we can add ultimately is limited to the traits that the base - // types relied upon by the codegen implement. - derive_for_all_types = "Clone, PartialEq, Eq", - - // To apply derives to specific generated types, add a `derive_for_type` per type, - // mapping the type path to the derives which should be added for that type only. - // Note that these derives will be in addition to those specified above in - // `derive_for_all_types` - derive_for_type(type = "frame_support::PalletId", derive = "Eq, Ord, PartialOrd"), - derive_for_type(type = "sp_runtime::ModuleError", derive = "Eq, Hash"), -)] -pub mod polkadot {} - -use polkadot::runtime_types::frame_support::PalletId; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let pallet_id = PalletId([1u8; 8]); - let _ = pallet_id.clone(); - Ok(()) -} diff --git a/examples/examples/dynamic_multisig.rs b/examples/examples/dynamic_multisig.rs deleted file mode 100644 index 6c8e47341c..0000000000 --- a/examples/examples/dynamic_multisig.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.31-3711c6f9b2a. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.31/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use sp_keyring::AccountKeyring; -use subxt::{dynamic::Value, tx::PairSigner, OnlineClient, PolkadotConfig}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // My account. - let signer_account = AccountKeyring::Alice; - let signer_account_id = signer_account.to_account_id(); - let signer = PairSigner::new(signer_account.pair()); - - // Transfer balance to this destination: - let dest = AccountKeyring::Bob.to_account_id(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - // Create the inner balance transfer call. - let inner_tx = subxt::dynamic::tx( - "Balances", - "transfer", - vec![ - Value::unnamed_variant("Id", [Value::from_bytes(&dest)]), - Value::u128(123_456_789_012_345), - ], - ); - - // Now, build an outer call which this inner call will be a part of. - // This sets up the multisig arrangement. - // - // Note: Since this is a dynamic call, we can either use named or unnamed - // arguments (if unnamed, the order matters). - let tx = subxt::dynamic::tx( - "Multisig", - "as_multi", - vec![ - ("threshold", Value::u128(1)), - ( - "other_signatories", - Value::unnamed_composite([Value::from_bytes(&signer_account_id)]), - ), - ("maybe_timepoint", Value::unnamed_variant("None", [])), - ("call", inner_tx.into_value()), - ( - "max_weight", - Value::named_composite([ - ("ref_time", Value::u128(10000000000)), - ("proof_size", Value::u128(1)), - ]), - ), - ], - ); - - // Submit it: - let encoded = hex::encode(api.tx().call_data(&tx)?); - println!("Call data: {encoded}"); - let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?; - println!("Submitted tx with hash {tx_hash}"); - - Ok(()) -} diff --git a/examples/examples/dynamic_queries.rs b/examples/examples/dynamic_queries.rs deleted file mode 100644 index 61437ecfbd..0000000000 --- a/examples/examples/dynamic_queries.rs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -// This example showcases working with dynamic values rather than those that are generated via the subxt proc macro. - -use sp_keyring::AccountKeyring; -use subxt::{dynamic::Value, tx::PairSigner, OnlineClient, PolkadotConfig}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let api = OnlineClient::::new().await?; - - // 1. Dynamic Balance Transfer (the dynamic equivalent to the balance_transfer example). - - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let dest = AccountKeyring::Bob.to_account_id(); - - // Create a transaction to submit: - let tx = subxt::dynamic::tx( - "Balances", - "transfer", - vec![ - // A value representing a MultiAddress. We want the "Id" variant, and that - // will ultimately contain the bytes for our destination address (there is a new type wrapping - // the address, but our encoding will happily ignore such things and do it's best to line up what - // we provide with what it needs). - Value::unnamed_variant("Id", [Value::from_bytes(&dest)]), - // A value representing the amount we'd like to transfer. - Value::u128(123_456_789_012_345), - ], - ); - - // submit the transaction with default params: - let hash = api.tx().sign_and_submit_default(&tx, &signer).await?; - println!("Balance transfer extrinsic submitted: {hash}"); - - // 2. Dynamic constant access (the dynamic equivalent to the fetch_constants example). - - let constant_address = subxt::dynamic::constant("Balances", "ExistentialDeposit"); - let existential_deposit = api.constants().at(&constant_address)?.to_value()?; - println!("Existential Deposit: {existential_deposit}"); - - // 3. Dynamic storage access - - let storage_address = subxt::dynamic::storage( - "System", - "Account", - vec![ - // Something that encodes to an AccountId32 is what we need for the map key here: - Value::from_bytes(&dest), - ], - ); - let account = api - .storage() - .at(None) - .await? - .fetch_or_default(&storage_address) - .await? - .to_value()?; - println!("Bob's account details: {account}"); - - // 4. Dynamic storage iteration (the dynamic equivalent to the fetch_all_accounts example). - - let storage_address = subxt::dynamic::storage_root("System", "Account"); - let mut iter = api - .storage() - .at(None) - .await? - .iter(storage_address, 10) - .await?; - while let Some((key, account)) = iter.next().await? { - println!("{}: {}", hex::encode(key), account.to_value()?); - } - - Ok(()) -} diff --git a/examples/examples/fetch_all_accounts.rs b/examples/examples/fetch_all_accounts.rs deleted file mode 100644 index a578f13cb3..0000000000 --- a/examples/examples/fetch_all_accounts.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use subxt::{OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - let api = OnlineClient::::new().await?; - - let address = polkadot::storage().system().account_root(); - - let mut iter = api.storage().at(None).await?.iter(address, 10).await?; - - while let Some((key, account)) = iter.next().await? { - println!("{}: {}", hex::encode(key), account.data.free); - } - Ok(()) -} diff --git a/examples/examples/fetch_constants.rs b/examples/examples/fetch_constants.rs deleted file mode 100644 index 817893c0da..0000000000 --- a/examples/examples/fetch_constants.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use subxt::{OnlineClient, PolkadotConfig}; - -// Generate the API from a static metadata path. -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - // Build a constant address to query: - let address = polkadot::constants().balances().existential_deposit(); - - // Look it up: - let existential_deposit = api.constants().at(&address)?; - - println!("Existential Deposit: {existential_deposit}"); - - Ok(()) -} diff --git a/examples/examples/fetch_staking_details.rs b/examples/examples/fetch_staking_details.rs deleted file mode 100644 index cd0af22b54..0000000000 --- a/examples/examples/fetch_staking_details.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use sp_core::{sr25519, Pair}; -use sp_keyring::AccountKeyring; -use subxt::{utils::AccountId32, OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - let active_era_addr = polkadot::storage().staking().active_era(); - let era = api - .storage() - .at(None) - .await? - .fetch(&active_era_addr) - .await? - .unwrap(); - println!( - "Staking active era: index: {:?}, start: {:?}", - era.index, era.start - ); - - let alice_id = AccountKeyring::Alice.to_account_id(); - println!(" Alice account id: {alice_id:?}"); - - // Get Alice' Stash account ID - let alice_stash_id: AccountId32 = sr25519::Pair::from_string("//Alice//stash", None) - .expect("Could not obtain stash signer pair") - .public() - .into(); - println!(" Alice//stash account id: {alice_stash_id:?}"); - - // Map from all locked "stash" accounts to the controller account. - let controller_acc_addr = polkadot::storage().staking().bonded(&alice_stash_id); - let controller_acc = api - .storage() - .at(None) - .await? - .fetch(&controller_acc_addr) - .await? - .unwrap(); - println!(" account controlled by: {controller_acc:?}"); - - let era_reward_addr = polkadot::storage().staking().eras_reward_points(era.index); - let era_result = api - .storage() - .at(None) - .await? - .fetch(&era_reward_addr) - .await?; - println!("Era reward points: {era_result:?}"); - - Ok(()) -} diff --git a/examples/examples/metadata_compatibility.rs b/examples/examples/metadata_compatibility.rs deleted file mode 100644 index c961ed3133..0000000000 --- a/examples/examples/metadata_compatibility.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use subxt::{OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - let api = OnlineClient::::new().await?; - - // Each individual request will be validated against the static code that was - // used to construct it, if possible. We can also validate the entirety of the - // statically generated code against some client at a given point in time using - // this check. If it fails, then there is some breaking change between the metadata - // used to generate this static code, and the runtime metadata from a node that the - // client is using. - polkadot::validate_codegen(&api)?; - - Ok(()) -} diff --git a/examples/examples/multisig.rs b/examples/examples/multisig.rs deleted file mode 100644 index 9cf246dabe..0000000000 --- a/examples/examples/multisig.rs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.31-3711c6f9b2a. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.31/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use sp_keyring::AccountKeyring; -use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // My account. - let signer_account = AccountKeyring::Alice; - let signer_account_id = signer_account.to_account_id(); - let signer = PairSigner::new(signer_account.pair()); - - // Transfer balance to this destination: - let dest = AccountKeyring::Bob.to_account_id(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - // Create the inner balance transfer call. - // - // Note: This call, being manually constructed, will have a specific pallet and call index - // which is determined by the generated code. If you're trying to submit this to a node which - // has the pallets/calls at different indexes, it will fail. See `dynamic_multisig.rs` for a - // workaround in this case which will work regardless of pallet and call indexes. - let inner_tx = polkadot::runtime_types::polkadot_runtime::RuntimeCall::Balances( - polkadot::runtime_types::pallet_balances::pallet::Call::transfer { - dest: dest.into(), - value: 123_456_789_012_345, - }, - ); - - // Now, build an outer call which this inner call will be a part of. - // This sets up the multisig arrangement. - let tx = polkadot::tx().multisig().as_multi( - // threshold - 1, - // other signatories - vec![signer_account_id.into()], - // maybe timepoint - None, - // call - inner_tx, - // max weight - polkadot::runtime_types::sp_weights::weight_v2::Weight { - ref_time: 10000000000, - proof_size: 1, - }, - ); - - // Submit the extrinsic with default params: - let encoded = hex::encode(api.tx().call_data(&tx)?); - println!("Call data: {encoded}"); - let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?; - println!("Submitted tx with hash {tx_hash}"); - - Ok(()) -} diff --git a/examples/examples/rpc_call.rs b/examples/examples/rpc_call.rs deleted file mode 100644 index 75766b7eaf..0000000000 --- a/examples/examples/rpc_call.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use subxt::{OnlineClient, PolkadotConfig}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - let api = OnlineClient::::new().await?; - - let block_number = 1u32; - - let block_hash = api.rpc().block_hash(Some(block_number.into())).await?; - - if let Some(hash) = block_hash { - println!("Block hash for block number {block_number}: {hash}"); - } else { - println!("Block number {block_number} not found."); - } - - Ok(()) -} diff --git a/examples/examples/rpc_call_subscribe_blocks.rs b/examples/examples/rpc_call_subscribe_blocks.rs deleted file mode 100644 index 2a816d26aa..0000000000 --- a/examples/examples/rpc_call_subscribe_blocks.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use subxt::{config::Header, OnlineClient, PolkadotConfig}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - let api = OnlineClient::::from_url("wss://rpc.polkadot.io:443").await?; - - // For non-finalised blocks use `.subscribe_blocks()` - let mut blocks = api.rpc().subscribe_finalized_block_headers().await?; - - while let Some(Ok(block)) = blocks.next().await { - println!( - "block number: {} hash:{} parent:{} state root:{} extrinsics root:{}", - block.number, - block.hash(), - block.parent_hash, - block.state_root, - block.extrinsics_root - ); - } - - Ok(()) -} diff --git a/examples/examples/runtime_types_only.rs b/examples/examples/runtime_types_only.rs deleted file mode 100644 index e1270a4f81..0000000000 --- a/examples/examples/runtime_types_only.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! In some cases we are interested only in the `RuntimeCall` enum (or more generally, only in some -//! runtime types). We can ask `subxt` to generate only runtime types by passing a corresponding -//! flag. -//! -//! Here we present how to correctly create `Block` type for the Polkadot chain. - -use sp_core::H256; -use sp_runtime::{ - generic, - traits::{BlakeTwo256, Block as _, Header as _}, - Digest, -}; -use subxt::PolkadotConfig; - -#[subxt::subxt( - runtime_metadata_path = "../artifacts/polkadot_metadata.scale", - derive_for_all_types = "Clone, PartialEq, Eq", - runtime_types_only -)] -pub mod polkadot {} - -type RuntimeCall = polkadot::runtime_types::polkadot_runtime::RuntimeCall; - -type UncheckedExtrinsic = generic::UncheckedExtrinsic< - ::Address, - RuntimeCall, - ::Signature, - // Usually we are not interested in `SignedExtra`. - (), ->; - -type Header = generic::Header; -type Block = generic::Block; - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // Although we could build an online client, we do not have access to the full runtime API. For - // that, we would have to specify `runtime_types_only = false` (or just skipping it). - // - // let api = subxt::OnlineClient::::new().await?; - // let address = polkadot::constants().balances().existential_deposit(); <- this won't compile! - - let polkadot_header = Header::new( - 41, - H256::default(), - H256::default(), - H256::default(), - Digest::default(), - ); - - let polkadot_block = Block::new(polkadot_header, vec![]); - - println!("{polkadot_block:?}"); - - Ok(()) -} diff --git a/examples/examples/storage_iterating.rs b/examples/examples/storage_iterating.rs deleted file mode 100644 index 8fc8f65584..0000000000 --- a/examples/examples/storage_iterating.rs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use codec::{Decode, Encode}; -use subxt::{OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - // Example 1. Iterate over (keys, value) using the storage client. - // This is the standard and most ergonomic approach. - { - let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root(); - - let mut iter = api.storage().at(None).await?.iter(key_addr, 10).await?; - - println!("\nExample 1. Obtained keys:"); - while let Some((key, value)) = iter.next().await? { - println!("Key: 0x{}", hex::encode(key)); - println!(" Value: {value}"); - } - } - - // Example 2. Iterate over fetched keys manually. Here, you forgo any static type - // safety and work directly with the bytes on either side. - { - let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root(); - - // Fetch at most 10 keys from below the prefix XcmPallet' VersionNotifiers. - let keys = api - .storage() - .at(None) - .await? - .fetch_keys(&key_addr.to_root_bytes(), 10, None) - .await?; - - println!("Example 2. Obtained keys:"); - for key in keys.iter() { - println!("Key: 0x{}", hex::encode(key)); - - if let Some(storage_data) = api.storage().at(None).await?.fetch_raw(&key.0).await? { - // We know the return value to be `QueryId` (`u64`) from inspecting either: - // - polkadot code - // - polkadot.rs generated file under `version_notifiers()` fn - // - metadata in json format - let value = u64::decode(&mut &*storage_data)?; - println!(" Value: {value}"); - } - } - } - - // Example 3. Custom iteration over double maps. Here, we manually append one lookup - // key to the root and just iterate over the values underneath that. - { - let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root(); - - // Obtain the root bytes (`twox_128("XcmPallet") ++ twox_128("VersionNotifiers")`). - let mut query_key = key_addr.to_root_bytes(); - - // We know that the first key is a u32 (the `XcmVersion`) and is hashed by twox64_concat. - // twox64_concat is just the result of running the twox_64 hasher on some value and concatenating - // the value itself after it: - query_key.extend(subxt::ext::sp_core::twox_64(&2u32.encode())); - query_key.extend(&2u32.encode()); - - // The final query key is essentially the result of: - // `twox_128("XcmPallet") ++ twox_128("VersionNotifiers") ++ twox_64(scale_encode(2u32)) ++ scale_encode(2u32)` - println!("\nExample 3\nQuery key: 0x{}", hex::encode(&query_key)); - - let keys = api - .storage() - .at(None) - .await? - .fetch_keys(&query_key, 10, None) - .await?; - - println!("Obtained keys:"); - for key in keys.iter() { - println!("Key: 0x{}", hex::encode(key)); - - if let Some(storage_data) = api.storage().at(None).await?.fetch_raw(&key.0).await? { - // We know the return value to be `QueryId` (`u64`) from inspecting either: - // - polkadot code - // - polkadot.rs generated file under `version_notifiers()` fn - // - metadata in json format - let value = u64::decode(&mut &storage_data[..])?; - println!(" Value: {value}"); - } - } - } - - Ok(()) -} diff --git a/examples/examples/submit_and_watch.rs b/examples/examples/submit_and_watch.rs deleted file mode 100644 index 6e4b758ac2..0000000000 --- a/examples/examples/submit_and_watch.rs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use futures::StreamExt; -use sp_keyring::AccountKeyring; -use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - simple_transfer().await?; - simple_transfer_separate_events().await?; - handle_transfer_events().await?; - - Ok(()) -} - -/// This is the highest level approach to using this API. We use `wait_for_finalized_success` -/// to wait for the transaction to make it into a finalized block, and also ensure that the -/// transaction was successful according to the associated events. -async fn simple_transfer() -> Result<(), Box> { - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let dest = AccountKeyring::Bob.to_account_id().into(); - - let api = OnlineClient::::new().await?; - - let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000); - - let balance_transfer = api - .tx() - .sign_and_submit_then_watch_default(&balance_transfer_tx, &signer) - .await? - .wait_for_finalized_success() - .await?; - - let transfer_event = balance_transfer.find_first::()?; - - if let Some(event) = transfer_event { - println!("Balance transfer success: {event:?}"); - } else { - println!("Failed to find Balances::Transfer Event"); - } - Ok(()) -} - -/// This is very similar to `simple_transfer`, except to show that we can handle -/// waiting for the transaction to be finalized separately from obtaining and checking -/// for success on the events. -async fn simple_transfer_separate_events() -> Result<(), Box> { - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let dest = AccountKeyring::Bob.to_account_id().into(); - - let api = OnlineClient::::new().await?; - - let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000); - - let balance_transfer = api - .tx() - .sign_and_submit_then_watch_default(&balance_transfer_tx, &signer) - .await? - .wait_for_finalized() - .await?; - - // Now we know it's been finalized, we can get hold of a couple of - // details, including events. Calling `wait_for_finalized_success` is - // equivalent to calling `wait_for_finalized` and then `wait_for_success`: - let _events = balance_transfer.wait_for_success().await?; - - // Alternately, we could just `fetch_events`, which grabs all of the events like - // the above, but does not check for success, and leaves it up to you: - let events = balance_transfer.fetch_events().await?; - - let failed_event = events.find_first::()?; - - if let Some(_ev) = failed_event { - // We found a failed event; the transfer didn't succeed. - println!("Balance transfer failed"); - } else { - // We didn't find a failed event; the transfer succeeded. Find - // more details about it to report.. - let transfer_event = events.find_first::()?; - if let Some(event) = transfer_event { - println!("Balance transfer success: {event:?}"); - } else { - println!("Failed to find Balances::Transfer Event"); - } - } - - Ok(()) -} - -/// If we need more visibility into the state of the transaction, we can also ditch -/// `wait_for_finalized` entirely and stream the transaction progress events, handling -/// them more manually. -async fn handle_transfer_events() -> Result<(), Box> { - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let dest = AccountKeyring::Bob.to_account_id().into(); - - let api = OnlineClient::::new().await?; - - let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000); - - let mut balance_transfer_progress = api - .tx() - .sign_and_submit_then_watch_default(&balance_transfer_tx, &signer) - .await?; - - while let Some(ev) = balance_transfer_progress.next().await { - let ev = ev?; - use subxt::tx::TxStatus::*; - - // Made it into a block, but not finalized. - if let InBlock(details) = ev { - println!( - "Transaction {:?} made it into block {:?}", - details.extrinsic_hash(), - details.block_hash() - ); - - let events = details.wait_for_success().await?; - let transfer_event = events.find_first::()?; - - if let Some(event) = transfer_event { - println!("Balance transfer is now in block (but not finalized): {event:?}"); - } else { - println!("Failed to find Balances::Transfer Event"); - } - } - // Finalized! - else if let Finalized(details) = ev { - println!( - "Transaction {:?} is finalized in block {:?}", - details.extrinsic_hash(), - details.block_hash() - ); - - let events = details.wait_for_success().await?; - let transfer_event = events.find_first::()?; - - if let Some(event) = transfer_event { - println!("Balance transfer success: {event:?}"); - } else { - println!("Failed to find Balances::Transfer Event"); - } - } - // Report other statuses we see. - else { - println!("Current transaction status: {ev:?}"); - } - } - - Ok(()) -} diff --git a/examples/examples/subscribe_block_events.rs b/examples/examples/subscribe_block_events.rs deleted file mode 100644 index 1737fd1ff4..0000000000 --- a/examples/examples/subscribe_block_events.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use futures::StreamExt; -use sp_keyring::AccountKeyring; -use std::time::Duration; -use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -/// Subscribe to all events, and then manually look through them and -/// pluck out the events that we care about. -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - // Subscribe to (in this case, finalized) blocks. - let mut block_sub = api.blocks().subscribe_finalized().await?; - - // While this subscription is active, balance transfers are made somewhere: - tokio::task::spawn({ - let api = api.clone(); - async move { - let signer = PairSigner::new(AccountKeyring::Alice.pair()); - let mut transfer_amount = 1_000_000_000; - - // Make small balance transfers from Alice to Bob in a loop: - loop { - let transfer_tx = polkadot::tx() - .balances() - .transfer(AccountKeyring::Bob.to_account_id().into(), transfer_amount); - api.tx() - .sign_and_submit_default(&transfer_tx, &signer) - .await - .unwrap(); - - tokio::time::sleep(Duration::from_secs(10)).await; - transfer_amount += 100_000_000; - } - } - }); - - // Get each finalized block as it arrives. - while let Some(block) = block_sub.next().await { - let block = block?; - - // Ask for the events for this block. - let events = block.events().await?; - - let block_hash = block.hash(); - - // We can dynamically decode events: - println!(" Dynamic event details: {block_hash:?}:"); - for event in events.iter() { - let event = event?; - let is_balance_transfer = event - .as_event::()? - .is_some(); - let pallet = event.pallet_name(); - let variant = event.variant_name(); - println!(" {pallet}::{variant} (is balance transfer? {is_balance_transfer})"); - } - - // Or we can find the first transfer event, ignoring any others: - let transfer_event = events.find_first::()?; - - if let Some(ev) = transfer_event { - println!(" - Balance transfer success: value: {:?}", ev.amount); - } else { - println!(" - No balance transfer event found in this block"); - } - } - - Ok(()) -} diff --git a/examples/examples/subscribe_blocks.rs b/examples/examples/subscribe_blocks.rs deleted file mode 100644 index c9a277e32b..0000000000 --- a/examples/examples/subscribe_blocks.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.29-41a9d84b152. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.29/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use futures::StreamExt; -use subxt::{OnlineClient, PolkadotConfig}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // Create a client to use: - let api = OnlineClient::::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 block_number = block.header().number; - let block_hash = block.hash(); - - println!("Block #{block_number}:"); - println!(" Hash: {block_hash}"); - println!(" Extrinsics:"); - - let body = block.body().await?; - for ext in body.extrinsics() { - let idx = ext.index(); - let events = ext.events().await?; - let bytes_hex = format!("0x{}", hex::encode(ext.bytes())); - - println!(" Extrinsic #{idx}:"); - println!(" Bytes: {bytes_hex}"); - println!(" Events:"); - - for evt in events.iter() { - let evt = evt?; - - let pallet_name = evt.pallet_name(); - let event_name = evt.variant_name(); - - println!(" {pallet_name}_{event_name}"); - } - } - } - - Ok(()) -} diff --git a/examples/examples/subscribe_runtime_updates.rs b/examples/examples/subscribe_runtime_updates.rs deleted file mode 100644 index 6e097706ae..0000000000 --- a/examples/examples/subscribe_runtime_updates.rs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use std::time::Duration; -use subxt::{OnlineClient, PolkadotConfig}; - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - // Start a new tokio task to perform the runtime updates while - // utilizing the API for other use cases. - let update_client = api.updater(); - tokio::spawn(async move { - let result = update_client.perform_runtime_updates().await; - println!("Runtime update failed with result={result:?}"); - }); - - // If this client is kept in use a while, it'll update its metadata and such - // as needed when the node it's pointed at updates. - tokio::time::sleep(Duration::from_secs(10_000)).await; - - Ok(()) -} diff --git a/subxt/src/book/examples/mod.rs b/subxt/src/book/examples/mod.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/subxt/src/book/mod.rs b/subxt/src/book/mod.rs new file mode 100644 index 0000000000..384e09fb8e --- /dev/null +++ b/subxt/src/book/mod.rs @@ -0,0 +1,88 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! +# The Subxt Guide + +Subxt is a library for interacting with Substrate based nodes. It has a focus on **sub**mitting e**xt**rinsics, hence the name, however it's also capable of reading blocks, storage, events and constants from a node. The aim of this guide is to explain key concepts and get you started with using Subxt. + +1. [Features](#features-at-a-glance) +2. [Setup](#setup) + 1. [Metadata](#metadata) + 2. [Config](#config) +3. [Usage](#usage) +4. [Examples](#examples) + +## Features at a glance + +Here's a quick overview of the features that Subxt has to offer: + +- Subxt allows you to generate a static, type safe interface to a node given some metadata; this allows you to catch more errors at compile time rather than runtime. +- Subxt also makes heavy use of node metadata to encode/decode the data sent to/from it. This allows it to target almost any node which can output the correct metadata version, and allows it some flexibility in encoding and decoding things to account for cross-node differences. +- Subxt has a pallet-oriented interface, meaning that code you write to talk to some pallet on one node will often "Just Work" when pointed at different nodes that use the same pallet. +- Subxt can work offline; you can generate and sign transactions, access constants from node metadata and more, without a network connection. This is all checked at compile time, so you can be certain it won't try to establish a network connection if you don't want it to. +- Subxt can forego the statically generated interface and build transactions, storage queries and constant queries using data provided at runtime, rather than queries constructed statically. +- Subxt can be compiled to WASM to run in the browser, allowing it to back Rust based browser apps, or even bind to JS apps. + +## Setup + +Here is a simple but complete example of using Subxt to transfer some tokens from Alice to Bob: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../examples/examples/basic_balance_transfer.rs")] +//! ``` +/*! + +This example assumes that a Polkadot node at a supported version is running locally (use [OnlineClient::from_url()](crate::client::OnlineClient::from_url()) to instead interact with a node hosted elsewhere). + +Typically, to use Subxt to talk to some custom Substrate node (for example a parachain node), you'll need to: + +1. Acquire some metadata from that node to pass to the [`#[subxt]`](crate::subxt) macro (optional but recommended). +2. Configure the client used by providing an instance of the [`Config`](crate::config::Config) trait. + +### Acquiring metadata + +The simplest way to do acquire metadata is to use the `subxt` CLI tool to download it from your node. The tool can be installed via `cargo`: + +```shell +cargo install subxt-cli +``` + +Once installed, run `subxt metadata > metadata.scale` to download the node's metadata and save it to some file (again, this assumes that the node you'd like to talk to is running locally; run `subxt metadata --help` to see other options). + +Sometimes it's useful to be able to manually output the code that's being generated by the [`#[subxt]`](crate::subxt) macro to better understand what's going on. To do this, use `subxt codegen | rustfmt > generated_interface.rs`. + +### Configuring the client + +To use Subxt, you must instantiate the client (typically an [`OnlineClient`](crate::client::OnlineClient)) with some [`Config`](crate::config::Config). Subxt ships with these default configurations: + +- [`crate::config::SubstrateConfig`] to talk to generic Substrate nodes. +- [`crate::config::PolkadotConfig`] to talk to Polkadot nodes. + +The configuration defines the types used for things like the account nonce, account ID and block header, so that it can encode or decode data from the node properly. It also defines how transactions should be signed, how data should be hashed, and which data should be sent along with an extrinsic. Thus, if the configuration is incorrect, Subxt may run into errors performing various actions against a node. + +In many cases, using one of the provided configuration implementations will work. If the node you're talking to is not compatible with the selected configuration, then you'll run into errors (particularly when trying to submit transactions or downloading blocks), and will need to find out what is different between the configuration you've used and the node in question (perhaps it expects things to be signed differently, or has a different address format for instance). + +## Usage + +Once Subxt is configured, the next step is actually interacting with a node. Follow the links below to learn more about how to use Subxt for each of the following things: + +- [Extrinsics](usage::extrinsics): Subxt can build and submit extrinsics, wait until they are in blocks, and retrieve the associated events. +- [Storage](usage::storage): Subxt can query the node storage. +- [Events](usage::events): Subxt can read the events emitted for recent blocks. +- [Constants](usage::constants): Subxt can access the constant values stored in a node, which remain the same for a given runtime version. +- [Blocks](usage::blocks): Subxt can load recent blocks or subscribe to new/finalized blocks, reading the extrinsics, events and storage at these blocks. + +## Examples + +A set of examples to help showcase various Subxt features and functionality: + +- Building a WASM app with Subxt. +- Ditching the statically generated interface. +- Integrating with Substrate. +- Working offline. + +*/ +pub mod usage; \ No newline at end of file diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs new file mode 100644 index 0000000000..c8824fa37b --- /dev/null +++ b/subxt/src/book/usage/blocks.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! + +*/ \ No newline at end of file diff --git a/subxt/src/book/usage/constants.rs b/subxt/src/book/usage/constants.rs new file mode 100644 index 0000000000..c8824fa37b --- /dev/null +++ b/subxt/src/book/usage/constants.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! + +*/ \ No newline at end of file diff --git a/subxt/src/book/usage/events.rs b/subxt/src/book/usage/events.rs new file mode 100644 index 0000000000..c8824fa37b --- /dev/null +++ b/subxt/src/book/usage/events.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! + +*/ \ No newline at end of file diff --git a/subxt/src/book/usage/extrinsics.rs b/subxt/src/book/usage/extrinsics.rs new file mode 100644 index 0000000000..7f298def25 --- /dev/null +++ b/subxt/src/book/usage/extrinsics.rs @@ -0,0 +1,158 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! +# Extrinsics + +Submitting extrinsics to a node is one of the core features of Subxt, and generally consists of the following steps: + +1. [Constructing an extrinsic payload to submit](#constructing-an-extrinsic-payload). +2. [Signing it](#signing-it). +2. [Submitting it (optionally with some additional parameters)](#submitting-it). + Once submitted, you'll typically also: + 1. Watch the transaction progress and wait until it has made it into a block. + 2. Check the events emitted by it once it's in a block to see what happened and whether it succeeded. + +We'll look at each of these steps in turn. + +> As a side note, an _extrinsic_ is anything that can be added to a block, and a _transaction_ is an extrinsic that is submitted from a particular user (and is therefore also signed). Subxt can construct unsigned extrinsics, but overwhelmingly you'll need to sign them, and so the documentation tends to use the terms _extrinsic_ and _transaction_ interchangably. + +## Constructing an extrinsic payload + +We can use the statically generated interface to build extrinsic payloads: + +```rust,no_run +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +let remark = "Hello there".as_bytes().to_vec(); +let extrinsic_payload = polkadot::tx().system().remark(remark); +``` + +> If you're not sure what types to import and use to build a given payload, you can use the `subxt` CLI tool to generate the interface by using something like `subxt codegen | rustfmt > interface.rs`, to see what types and things are available (or even just to use directly instead of the [`#[subxt]`](crate::subxt) macro). + +Alternately, we can dynamically construct an extrinsic payload. This will not be type checked or validated until it's submitted: + +```rust,no_run +use subxt::dynamic::Value; + +let extrinsic_payload = subxt::dynamic::tx("System", "remark", vec![ + Value::from_bytes("Hello there") +]); +``` + +The [`crate::dynamic::Value`] type is a dynamic type much like a `serde_json::Value` but instead represents any type of data that can be SCALE encoded or decoded. It can be serialized, deserialized and parsed from/to strings. + +A valid extrinsic payload is just something that implements the [`crate::tx::TxPayload`] trait; you can implement this trait on your own custom types if the built-in ones are not suitable for your needs. + +## Signing it + +You'll normally need to sign an extrinsic to prove that it originated from an account that you control. To do this, you will typically first create an [`crate::tx::Signer`], which tells Subxt who the extrinsic is from, and takes care of signing the relevant details to prove this. + +Subxt provides a [`crate::tx::PairSigner`] which implements this trait (if the `substrate-compat` feature is enabled) which accepts any valid [`sp_core::Pair`] and uses that to sign transactions: + +```rust +use subxt::tx::PairSigner; +use sp_core::Pair; +use subxt::config::PolkadotConfig; + +// Get hold of a `Signer` given a test account: +let pair = sp_keyring::AccountKeyring::Alice.pair(); +let signer = PairSigner::::new(pair); + +// Or generate an `sr25519` keypair to use: +let (pair, _, _) = sp_core::sr25519::Pair::generate_with_phrase(Some("password")); +let signer = PairSigner::::new(pair); +``` + +See the [`sp_core::Pair`] docs for more ways to generate them. + +If this isn't suitable/available, you can either implement [`crate::tx::Signer`] yourself to use custom signing logic, or you can use some external signing logic, like so: + +```rust,no_run +# #[tokio::main] +# async fn main() -> Result<(), Box> { +use subxt::client::OnlineClient; +use subxt::config::PolkadotConfig; +use subxt::dynamic::Value; + +// Create client: +let client = OnlineClient::::new().await?; + +// Create a dummy extrinsic payload to sign: +let payload = subxt::dynamic::tx("System", "remark", vec![ + Value::from_bytes("Hello there") +]); + +// Construct the extrinsic but don't sign it. You need to provide the nonce +// here, or can use `create_partial_signed` to fetch the correct nonce. +let partial_extrinsic = client.tx().create_partial_signed_with_nonce( + &payload, + 0, + Default::default() +)?; + +// Fetch the payload that needs to be signed: +let signer_payload = partial_extrinsic.signer_payload(); + +// ... At this point, we can hand off the `signer_payload` to be signed externally. +// Ultimately we need to be given back a `signature` (or really, anything +// that can be SCALE encoded) and an `address`: +let signature; +let address; +# use subxt::tx::Signer; +# let pair = sp_keyring::AccountKeyring::Alice.pair(); +# let signer = subxt::tx::PairSigner::::new(pair); +# signature = signer.sign(&signer_payload); +# address = signer.address(); + +// Now we can build an extrinsic, which one can call `submit` or `submit_and_watch` +// on to submit to a node and optionally watch the status. +let extrinsic = partial_extrinsic.sign_with_address_and_signature( + &address, + &signature +); +# Ok(()) +# } +``` + +## Submitting it + +Once we are able to sign the extrinsic, we need to submit it. + +### The high level API + +The highest level approach to doing this is to call [`crate::tx::TxClient::sign_and_submit_then_watch_default`]. This hands back a [`crate::tx::TxProgress`] struct which will monitor the transaction status. We can then call [`crate::tx::TxProgress::wait_for_finalized_success()`] to wait for this transaction to make it into a finalized block, check for an `ExtrinsicSuccess` event, and then hand back the events for inspection. This looks like: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/basic_balance_transfer.rs")] +//! ``` +/*! + +### Providing extrinsic parameters + +If you'd like to provide extrinsic parameters (such as mortality), you can use [`crate::tx::TxClient::sign_and_submit_then_watch`] instead, and provide parameters for your chain: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/balance_transfer_with_params.rs")] +//! ``` +/*! + +This example doesn't wait for the extrinsic to be included in a block; it just submits it and hopes for the best! + +### Custom handling of transaction status updates + +If you'd like more control or visibility over exactly which status updates are being emitted for the transaction, you can monitor them as they are emitted and react however you choose: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/balance_transfer_status_stream.rs")] +//! ``` +/*! + +Take a look at the API docs for [`crate::tx::TxProgress`], [`crate::tx::TxStatus`] and [`crate::tx::TxInBlock`] for more options. + +*/ \ No newline at end of file diff --git a/subxt/src/book/usage/mod.rs b/subxt/src/book/usage/mod.rs new file mode 100644 index 0000000000..dc4db8c13a --- /dev/null +++ b/subxt/src/book/usage/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! +This modules contains examples of using Subxt; follow the links for more: + +- [Extrinsics](extrinsics) +- [Storage](storage) +- [Events](events) +- [Constants](constants) +- [Blocks](blocks) + +Alternately, [go back](super). +*/ + +pub mod extrinsics; +pub mod storage; +pub mod events; +pub mod constants; +pub mod blocks; diff --git a/subxt/src/book/usage/storage.rs b/subxt/src/book/usage/storage.rs new file mode 100644 index 0000000000..c8824fa37b --- /dev/null +++ b/subxt/src/book/usage/storage.rs @@ -0,0 +1,7 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! + +*/ \ No newline at end of file diff --git a/subxt/src/config/mod.rs b/subxt/src/config/mod.rs index 04251306e7..c70208dd8e 100644 --- a/subxt/src/config/mod.rs +++ b/subxt/src/config/mod.rs @@ -29,7 +29,7 @@ pub trait Config: 'static { /// transactions associated with a sender account. type Index: Debug + Copy + DeserializeOwned + Into; - /// The output of the `Hashing` function. + /// The output of the `Hasher` function. type Hash: Debug + Copy + Send diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index b442e3d7d5..d309b3eedd 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -2,112 +2,13 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -//! Subxt is a library to **sub**mit e**xt**rinsics to a [substrate](https://github.com/paritytech/substrate) node via RPC. +//! Subxt is a library for interacting with Substrate based nodes. Using it looks something like this: //! -//! The generated Subxt API exposes the ability to: -//! - [Submit extrinsics](https://docs.substrate.io/v3/concepts/extrinsics/) (Calls) -//! - [Query storage](https://docs.substrate.io/v3/runtime/storage/) (Storage) -//! - [Query constants](https://docs.substrate.io/how-to-guides/v3/basics/configurable-constants/) (Constants) -//! - [Subscribe to events](https://docs.substrate.io/v3/runtime/events-and-errors/) (Events) -//! -//! # Initializing the API client -//! -//! To interact with a node, you'll need to construct a client. -//! -//! ```no_run -//! use subxt::{OnlineClient, PolkadotConfig}; -//! -//! # #[tokio::main] -//! # async fn main() { -//! let api = OnlineClient::::new().await.unwrap(); -//! # } +//! ```rust,ignore +#![doc = include_str!("../../examples/examples/basic_balance_transfer.rs")] //! ``` //! -//! This default client connects to a locally running node, but can be configured to point anywhere. -//! Additionally, an [`crate::OfflineClient`] is available to perform operations that don't require a -//! network connection to a node. -//! -//! The client takes a type parameter, here [`crate::PolkadotConfig`], which bakes in assumptions about -//! the structure of extrinsics and the underlying types used by the node for things like block numbers. -//! If the node you'd like to interact with deviates from Polkadot or the default Substrate node in these -//! areas, you'll need to configure them by implementing the [`crate::config::Config`] type yourself. -//! -//! # Generating runtime types -//! -//! Subxt can optionally generate types at compile time to help you interact with a node. These types are -//! generated using metadata which can be downloaded from a node using the [subxt-cli](https://crates.io/crates/subxt-cli) -//! tool. These generated types provide a degree of type safety when interacting with a node that is compatible with -//! the metadata that they were generated using. We also do runtime checks in case the node you're talking to has -//! deviated from the types you're using to communicate with it (see below). -//! -//! To generate the types, use the `subxt` macro and point it at the metadata you've downloaded, like so: -//! -//! ```ignore -//! #[subxt::subxt(runtime_metadata_path = "metadata.scale")] -//! pub mod node_runtime { } -//! ``` -//! -//! For more information, please visit the [subxt-codegen](https://docs.rs/subxt-codegen/latest/subxt_codegen/) -//! documentation. -//! -//! You can opt to skip this step and use dynamic queries to talk to nodes instead, which can be useful in some cases, -//! but doesn't provide any type safety. -//! -//! # Interacting with the API -//! -//! Once instantiated, a client exposes four core functions: -//! - `.tx()` for submitting extrinsics/transactions. See [`crate::tx::TxClient`] for more details, or see -//! the [balance_transfer](../examples/examples/balance_transfer.rs) example. -//! - `.storage()` for fetching and iterating over storage entries. See [`crate::storage::StorageClient`] for more details, or see -//! the [fetch_staking_details](../examples/examples/fetch_staking_details.rs) example. -//! - `.constants()` for getting hold of constants. See [`crate::constants::ConstantsClient`] for more details, or see -//! the [fetch_constants](../examples/examples/fetch_constants.rs) example. -//! - `.events()` for subscribing/obtaining events. See [`crate::events::EventsClient`] for more details, or see: -//! - [subscribe_all_events](../examples/examples/subscribe_all_events.rs): Subscribe to events emitted from blocks. -//! - [subscribe_one_event](../examples/examples/subscribe_one_event.rs): Subscribe and filter by one event. -//! - [subscribe_some_events](../examples/examples/subscribe_some_events.rs): Subscribe and filter event. -//! -//! # Static Metadata Validation -//! -//! If you use types generated by the [`crate::subxt`] macro, there is a chance that they will fall out of sync -//! with the actual state of the node you're trying to interact with. -//! -//! When you attempt to use any of these static types to interact with a node, Subxt will validate that they are -//! still compatible and issue an error if they have deviated. -//! -//! Additionally, you can validate that the entirety of the statically generated code aligns with a node like so: -//! -//! ```no_run -//! use subxt::{OnlineClient, PolkadotConfig}; -//! -//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -//! pub mod polkadot {} -//! -//! # #[tokio::main] -//! # async fn main() { -//! let api = OnlineClient::::new().await.unwrap(); -//! -//! if let Err(_e) = polkadot::validate_codegen(&api) { -//! println!("Generated code is not up to date with node we're connected to"); -//! } -//! # } -//! ``` -//! ## Opting out of static validation -//! -//! The static types that are used to query/access information are validated by default, to make sure that they are -//! compatible with the node being queried. You can generally call `.unvalidated()` on these static types to -//! disable this validation. -//! -//! # Runtime Updates -//! -//! The node you're connected to may occasionally perform runtime updates while you're connected, which would ordinarily -//! leave the runtime state of the node out of sync with the information Subxt requires to do things like submit -//! transactions. -//! -//! If this is a concern, you can use the `UpdateClient` API to keep the `RuntimeVersion` and `Metadata` of the client -//! synced with the target node. -//! -//! Please visit the [subscribe_runtime_updates](../examples/examples/subscribe_runtime_updates.rs) example for more details. +//! Take a look at [the Subxt guide](book) to learn more about how to use Subxt. #![deny( bad_style, @@ -132,6 +33,9 @@ )] #![allow(clippy::type_complexity)] +// The guide is here. +pub mod book; + // Suppress an unused dependency warning because tokio is // only used in example code snippets at the time of writing. #[cfg(test)] From 058d892c9a31f96a4b2ce07fe7c14f8a119cbb3d Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 3 Apr 2023 13:00:31 +0100 Subject: [PATCH 02/25] cargo fmt --- examples/examples/balance_transfer_status_stream.rs | 12 +++++++----- examples/examples/balance_transfer_with_params.rs | 2 +- examples/examples/basic_balance_transfer.rs | 2 +- subxt/src/book/mod.rs | 2 +- subxt/src/book/usage/blocks.rs | 2 +- subxt/src/book/usage/constants.rs | 2 +- subxt/src/book/usage/events.rs | 2 +- subxt/src/book/usage/extrinsics.rs | 2 +- subxt/src/book/usage/mod.rs | 6 +++--- subxt/src/book/usage/storage.rs | 2 +- 10 files changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/examples/balance_transfer_status_stream.rs b/examples/examples/balance_transfer_status_stream.rs index d7c34cb4c8..f0ea802a17 100644 --- a/examples/examples/balance_transfer_status_stream.rs +++ b/examples/examples/balance_transfer_status_stream.rs @@ -1,6 +1,9 @@ use futures::StreamExt; use sp_keyring::AccountKeyring; -use subxt::{tx::{TxStatus, PairSigner}, OnlineClient, PolkadotConfig}; +use subxt::{ + tx::{PairSigner, TxStatus}, + OnlineClient, PolkadotConfig, +}; // Generate an interface that we can use from the node's metadata. #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] @@ -33,15 +36,14 @@ async fn main() -> Result<(), Box> { let events = in_block.wait_for_success().await?; // We can look for events (this uses the static interface; we can also iterate // over them and dynamically decode them): - let transfer_event = events - .find_first::()?; + let transfer_event = events.find_first::()?; if let Some(event) = transfer_event { println!("Balance transfer success: {event:?}"); } else { println!("Failed to find Balances::Transfer Event"); } - }, + } // Just log any other status we encounter: other => { println!("Status: {other:?}"); @@ -49,4 +51,4 @@ async fn main() -> Result<(), Box> { } } Ok(()) -} \ No newline at end of file +} diff --git a/examples/examples/balance_transfer_with_params.rs b/examples/examples/balance_transfer_with_params.rs index d6cb06bae6..c94140d421 100644 --- a/examples/examples/balance_transfer_with_params.rs +++ b/examples/examples/balance_transfer_with_params.rs @@ -1,6 +1,6 @@ use sp_keyring::AccountKeyring; -use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params}; +use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig}; #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] pub mod polkadot {} diff --git a/examples/examples/basic_balance_transfer.rs b/examples/examples/basic_balance_transfer.rs index 50afccfee9..787f1f9ef2 100644 --- a/examples/examples/basic_balance_transfer.rs +++ b/examples/examples/basic_balance_transfer.rs @@ -31,4 +31,4 @@ async fn main() -> Result<(), Box> { } Ok(()) -} \ No newline at end of file +} diff --git a/subxt/src/book/mod.rs b/subxt/src/book/mod.rs index 384e09fb8e..7b4681fa68 100644 --- a/subxt/src/book/mod.rs +++ b/subxt/src/book/mod.rs @@ -85,4 +85,4 @@ A set of examples to help showcase various Subxt features and functionality: - Working offline. */ -pub mod usage; \ No newline at end of file +pub mod usage; diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs index c8824fa37b..7c8f962585 100644 --- a/subxt/src/book/usage/blocks.rs +++ b/subxt/src/book/usage/blocks.rs @@ -4,4 +4,4 @@ /*! -*/ \ No newline at end of file +*/ diff --git a/subxt/src/book/usage/constants.rs b/subxt/src/book/usage/constants.rs index c8824fa37b..7c8f962585 100644 --- a/subxt/src/book/usage/constants.rs +++ b/subxt/src/book/usage/constants.rs @@ -4,4 +4,4 @@ /*! -*/ \ No newline at end of file +*/ diff --git a/subxt/src/book/usage/events.rs b/subxt/src/book/usage/events.rs index c8824fa37b..7c8f962585 100644 --- a/subxt/src/book/usage/events.rs +++ b/subxt/src/book/usage/events.rs @@ -4,4 +4,4 @@ /*! -*/ \ No newline at end of file +*/ diff --git a/subxt/src/book/usage/extrinsics.rs b/subxt/src/book/usage/extrinsics.rs index 7f298def25..7b0c6195f2 100644 --- a/subxt/src/book/usage/extrinsics.rs +++ b/subxt/src/book/usage/extrinsics.rs @@ -155,4 +155,4 @@ If you'd like more control or visibility over exactly which status updates are b Take a look at the API docs for [`crate::tx::TxProgress`], [`crate::tx::TxStatus`] and [`crate::tx::TxInBlock`] for more options. -*/ \ No newline at end of file +*/ diff --git a/subxt/src/book/usage/mod.rs b/subxt/src/book/usage/mod.rs index dc4db8c13a..74524fc1f2 100644 --- a/subxt/src/book/usage/mod.rs +++ b/subxt/src/book/usage/mod.rs @@ -14,8 +14,8 @@ This modules contains examples of using Subxt; follow the links for more: Alternately, [go back](super). */ +pub mod blocks; +pub mod constants; +pub mod events; pub mod extrinsics; pub mod storage; -pub mod events; -pub mod constants; -pub mod blocks; diff --git a/subxt/src/book/usage/storage.rs b/subxt/src/book/usage/storage.rs index c8824fa37b..7c8f962585 100644 --- a/subxt/src/book/usage/storage.rs +++ b/subxt/src/book/usage/storage.rs @@ -4,4 +4,4 @@ /*! -*/ \ No newline at end of file +*/ From 24c06a6506fac36e259774ab85670c6ea65fe5c9 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 11 Apr 2023 14:39:32 +0100 Subject: [PATCH 03/25] Ongoing work; events, constants, wip blocks --- artifacts/polkadot_metadata.scale | Bin 356565 -> 361757 bytes ..._transfer.rs => balance_transfer_basic.rs} | 0 examples/examples/constants_dynamic.rs | 17 ++++ examples/examples/constants_static.rs | 19 ++++ examples/examples/events.rs | 60 +++++++++++ examples/examples/storage_fetch.rs | 24 +++++ examples/examples/storage_fetch_dynamic.rs | 23 +++++ examples/examples/storage_iterating.rs | 24 +++++ .../examples/storage_iterating_dynamic.rs | 20 ++++ subxt/src/book/mod.rs | 23 ++++- subxt/src/book/usage/blocks.rs | 3 + subxt/src/book/usage/constants.rs | 50 ++++++++++ subxt/src/book/usage/events.rs | 34 +++++++ subxt/src/book/usage/extrinsics.rs | 9 +- subxt/src/book/usage/storage.rs | 93 ++++++++++++++++++ subxt/src/dynamic.rs | 2 +- subxt/src/lib.rs | 2 +- 17 files changed, 391 insertions(+), 12 deletions(-) rename examples/examples/{basic_balance_transfer.rs => balance_transfer_basic.rs} (100%) create mode 100644 examples/examples/constants_dynamic.rs create mode 100644 examples/examples/constants_static.rs create mode 100644 examples/examples/events.rs create mode 100644 examples/examples/storage_fetch.rs create mode 100644 examples/examples/storage_fetch_dynamic.rs create mode 100644 examples/examples/storage_iterating.rs create mode 100644 examples/examples/storage_iterating_dynamic.rs diff --git a/artifacts/polkadot_metadata.scale b/artifacts/polkadot_metadata.scale index b0d3e7de879b563b10e38c65ea021b0e15d015bc..84da3d41bf4e489bfed32e388ad0840bfa837ad8 100644 GIT binary patch delta 57177 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 delta 55397 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 z zE1{icM&qjQyZDyjY`75Q>>17q@i%;AIQoguLU7WS^0}M>3?aVXUB=S+>;g6*`qEaw z5)o6Tvw$sD2XtXUC4fw9l2wo9pL$qUYF9$^R(O((>r`Lko6B$|tXZ2DsVE^CmdHqZ zv5aLS7Sr8D?Bg_hNh=^6zkf8#Lg=91j%II?uJFMzY#77e=Sy*QDe2l##yY-odqer) za@N|{YW3<@-aUoxHz$hs|oOtz|B2E8<1n|B)c%T6R_^gWIkpFOE#ZJA*@*A3^qRe=mho<``qua ztEpj6F#1da|Metxeu9S-ENgBHJI`lX6r6InppLB&LWA%%Q*cwQSbRdaf*`H`L68e zGFy_PuUZ9K8QhaVR><8u00B`Pa00qnu;3Koxn+gZx}lK?`u-OxCQ9WZg#8g$!G9T0 z7oGuDs#MCmodK{kIYmSu#J~y&6++}zS7FO3za0R!NAmF-Ryt&Ea9R}QVGXc8~J6eMUOufG)67k{P8p&_2I4)2HnCq#PD_ZasS zNlH?{BO`LQ-ZBF*?O~>aXzY4F&|0exIC=wc(vO?>n*19PBD6-BVfVtw?n#qih@$KP zfuQ>mlzj=23&PX=qLb;etG&=ti`-yZhAR>T{(ONcH;2A2u-uCqxA_|@SyK1^&Xhpe zrBqJeXomK*0CR~O8v3n12Un%&=Erkt#j24rFN;l57?gEv_^i(>DA!Sc;~!gy@z^p+ zQTY{Kmi@ohQ(U?!mq37)q;3hVpUmy~|n09tek66d26Bi6KgI-GF zt}Aw%3j0WKL$uKpio8^=LBRb6T-G23CQ(jKT&cW2fh-b3Y+_(H;vu%_l4wqX#gwvN zVBqcqqN^0`#eto+kP=1lF43nuxqnxE@s&aq#vMU&Xc@>xs2 zT+uH`*iI5onsB@lok;KQpLpf|=ftZ_TptnWH1E)PB94mLh*+(lVowP4IGCV36{SZu zD&~w*vEDJ|f1jI6m;TEo|@?U?$H{8UKRJ z_|C?Y54|JRtwcj9Q3 zjZ_U1wCWv!9&S#^= zpOtL%&;pVp&xhi!gQ%FSoO1t4oJ0!vwJX`MFMjxgtd&0iISst<_}m3-1SDRs>CBTN z&qk&f|5sX=!U)#nUCwmMGoF2V6g@@wO%1F(&E7BT6vRJU2qA9)|93;&VQFZz^7vDh zvF@3X1B9+gic({TD_1msq~1uEf78hFtz(clC}D^by^uGXMa99$K0X~@$mzwWEPLv2 zy{w>L*rVr{3_iArrK21EB`k;Ja&HsMfekJ1Zh~kL81T{(HbVS;zKNX+2vVEb(44-9 zD!1ao>ApinG$;R3hMSqWubB;t9B3c7jAf=PB(bl@+@U{ab1lFUp<<1?%Q=98^eL(& zCE`l*>~Ni*{g^<>|3(L!t7Z@~k8fTF4bQ@Lz=u@6VI3Q{bUy<7&+QO#6-*lqoM%!K z?P^{W>+QXgflT5B;j778yeqFS#j@G7Kd9dXZMh_<$cFT4sYQ~Yj}0r2(3(X3O6xC`e-m?F7S0%vSBcG*nTA| z&GROboCT&`wxW3ZWc}-vtRPz#W4tm^@%7W}-G^CDfz|3dTdQq-s%qWDhNqR#94n9! zal<6R>1!$ByEd_+N$W%#ZHY=}?{aVkhm;)<>OqA#-Al?F${NaW3UM!~kWZM@u0&iq zoW*aviY2FO5PgZ;xK>-uVCZh;_g%$CW-XfK!#xtX^|cGkUMS!*ui);#Y5GB*}ubLuTy3<>c(udqeJ+*~K(_rYijSKpcTN#ZWzZdi*;gEeRkeC?%^WszWA zYXF1ZwNMXX&aO7o5Xey`$_p{ALQSGOrIm~P{@IAxZsr4tn2ZEmmxd_rvNh{i}vM5X~kM)Zz#2dBs&|LM_scJDSall+Eidt)jgAk1PO%@w=PZ z)Xd(&FN6t)2(7k`75 zSHDIg>sKMkxG4~uw1%q$O?N3}m%xdgiOTN26$`xPvwK+Lpe{tW5r;fnJF4tS4=DpA3Yu*Y>N&m>C*mujm1xfib8Q zJ%|h);5{L~U()@msPkl^f*%3dUgcDxv&y31u$UA!NH&64R%sH%y7p$DTv0Z<=w32T zQb>xlO8La_G+sWbc$y%e9C$icJ|*C(%KV&&$7-4ARC1P-gW`hbCW%Lr{0Pn1;s+CQ=T=0xJQ- z@S*JXgJsGmVjPGQL`#nOe5N?$Q<4*q@ba73u$0Lp=aSB<^R!BsRHTGg-o%;-rr_{) zwgVj5vYS~8$>fjT%tj3dBUE5fusLWW)7gjPlG|t6uT19TPz=3t!*vF`h^6I!PLb^A z%p!$QA4O2I5OC7xlp_9@9jx@65+|J2PlI48B~EdnA7UL0=WIPG=eKcoKFl+p=WKpj zdH?l~$+NLo^BoE4UvCCB+=jzF0TXv=0u%YKj8P@!Ade#I!V}Fg6Tu$p_JVW%>rddmwM#rtiL}M{;)u07 z{ENM8yPCGA|7Ik9#1Za&P7zTzi$AcRouK=j-1sTGF73eQl61f2fD$3@)J-VWF*wXO61F%_2d#3;9@=oKnJs`Yt z)HcsY#WG$(`F9?M zNmM?6<6$;3?bZHUn|jj1s#CVsPMRok5N<}IUA(>{Q{1OPO!}`!4)HMi68y0LjFUdG z8gwMZun1{xlSG1DBM3{}`(Q}mtOF_r^HFqgD4+Z&Tb$QVEF;B%eDo8il9a@6bWwNs zcaO3MS#AnaRcC6FTS-moLwp4#%E(AE$2`*pR0cnC7{}|0-1#`GR1s^Tz$kA_<4@kf zQbsD7qM?i=C0ACRkE-)Or|Lqh>LOd!h5YA_v$3R@zy3HZO^bQvFWIo9B2i^wl2Q_` z`z7OaK)I|Mh%lc25)|lSs295;BBq#`ieG4KDB(v%a)qm06$CD%xDwjR7OQTt zuAZ#5(sIjQ*`(B2-_?rUZ1XKD+t{MjjMUn;Y?sZp+!cNQX9kec_vJS~=G>Vx=leY8 z`FUZXQh zjb)y6=`PLm!7pY#=390=>tRZbTphK#f=kllo~BS`{k7{)sqbEK4SiNzz4e(7WSP<+ zOTzEXy0pt2CjO^g8>hpX68Z$tAroEiftVcep%yO9F(-VerK*iq#u4WB54G1cAp3-z|?D)D)&h(85t)hc5kN6@(yw!39r z*&!R}LDL?)@>gCvlX6rRH%ORgw0 zL><5OxDH>`+P%cut}(uC*BIYc9Ru7h2Uz}7PwEOTIly?)adjmYy(Xa!0+;sUI%oAO zwuGUg;WKC|-~G(i&$LkWb6tq9v||xo*}LrG?6UlzAD&A~JHZ0KPCU}7rHH)IRIYvg zy%L`Lr?yc&r;D*)Ynf`NE-Js4f^=OR{2Ji3^V*FX79-7!(U-_RbmTYJ&U-b%c|nJ@ zSfcpi5rE=foCnZTyL8bUMF|2XVrguz>HSvQ1e(@7^qsawm4w6hIjG~LP~WmCktRq% zfXw{=Xn}+U*ESrp^@GIW|7ah<1njYkKtA`1-(A!eMl&dRs@za;$vNoK72UXEsg~EJ zy%ia5Wq}!!yyt5e&3#dnC~_}pH>ohedhn7aJr(slGsKBYTGC)UI5oCy6;dDKY&fkaAXnnGL zKg|G})fxf3N7!~z(T}1;egws+*;Yg2(v7blrXu#B`6Mv2p1(p{%|$}eab6#k(X^2jV_9V$~^`$S-kC{u|3P7Uz%l6zsL^E z(6dHvk0c2=ABd!6y+Ss!+EA+Pk3Wbc@1Q;nfdy86`O->XG2D%U)!QJh?@y!j8u`%< zn3~uh)%K^yoa^z791RE#UN4^x5i_G`C=54NMo~)a56Jfh`Tb@?*(`G|HX7wme81Hi zWEc!)MAI;Jd(W@xXd29R*i{{lrlc#zKhnZS^ot>*-(C=z7`oXQHAUHJZ^UjhE?sQ= z46vdghGvnmTUNElQ1%$2IS#?dBexAC;7j-)Bhm-b^+uD-&}=BphS>K{*o9ROq-pLn zHeu?@Rb_n){cy-3qwH^*a>%X@22oB^9YnE=9g&Sq6q)ff+}VS8G1nrI8%fdPsRSA% zcEkg9Y>{=fFdRTuc0GX}RF4^AO9Exf`{x_9WTRE)Ic_M&?e=U-q$$j@_ls_mZ`ut7 z7aHQisyk^l#Dw|P7a>N6e7#Up&h`xB_92u4Y11Eu(B10ihDaJpTm3)DA;z z8cIXePDAV;N^ju31QKcK9t(i@i@_L?r#ueD|L=VB`;0P?yzJ*ogJFW|zgA zH{3`*c2`C&;26Zdz7=^e>IKvGonEH#6)RsE1;+}w8(fLNH3-~M6j%}{a`zqzsWbDy z+*skygX`qtskT9}5+b&HX|#0);;47sL<0p3(p+LpD&_V@RCER)eyRffkMdA^7kbn>|NNLG9(BquA`a+@v=T(p>0hs}b@hH>WKgR#nn&9<*j8Kv#NM0e9$3ja$5It;>&8+Y8*y+P!Jv7RXr6-Am~|@+=?6>N zO3UyA1`4Gr+md!RE&|GLHH-bQ{H_WtsTm{wd@IcWgh`x4Lwb3<41OdfL*H>lAbyKj zG>L8;H|Bet$-KHVnRaL7jT{azxttEE(Jp+GD7wzJ2!aVpnzRUl5XLe|!7w@IjUs4* z;yEmVdY^Y&0F6@dJiX6zaK9#7<-1B%Wtuf?9*;}Oc3wrkdM&G5n4I71FY-*;^49y| z!*ZN9jxxu21$oM`N-I@e${eIr{LHrpv3W8r0rM+%3T3DZonj;cq=QJ4;~UmTfRbUp zl0}o@xm*g9eB!xTG<|Z6X2mFAZEA9_X&BoY5(IrJrR1rrS6OasnkD?QT_y&%;@k}#SNEH{ z8N9!`XYD!8U;xbSKbH$3z4OUmzR|dVz4hjoNGp$gI}w3!lz+%A)$=|+@#33!KY`(W z@5d}ud2sHsDpkp~^I+FN&G~CC6c{D%pQ~e$X913)?4iHnyFL z-S_U>3z#?Qtb8udkvtA=#+eF+#MiqIglf znwCeiv#Ju7VO2U+`n`kH(<5GALw{o)^Ti)iEn_)iR4paK4mr1$ZpJFvR7>em;W(i( zasp+t-?<1?v9j1>9&Qcq8rvl9tfTA!`Jtd=ZN_tu^`@f5f7j70QMQh5 zXNvjKI!f0hZ~G@$nE`R%21<<@mj^`Bx70rmoDH;!LFz4jdYY0)KH1|N#S&;j&UbHm zYFcWV9TUyJ3I^m=t5#Mn52msNjXfM@M%800sMwrQ570!c_PGyGvSYoR0);(d-uD0< z(*}gaT5`o8Q|j@8;@$R7F>@mg^QBD<6horglVdM39Cpty@mImgM7N+6k=`o(R|{Gp z{In&lEC~Z<%V?!U)gI^|xE%NkdsD1m#jmSd4D)&!Kn~&Qt5+1uxL)u^*dvHNV|TjL z4>K4$R2y)eSg2JMb?W`k!>f(6r&V=+Y-Gx@m`_${c(LJirz1WY~An*W=IC6!l ztPx-(xRDSvPt4pzUosM7Hq#W*w3$ARlrwGlX$aag@vI1Yh*pBdvi2cb%tyeJ5{c6f z(fAmT6>L&ctbl%#nfFKpdMLG40lwa99w zrRx4Pb8j<^U~HfG@C6!;NsD-q0@$6aU!>Z(=Lq4GyCZu*piPNpiJn{ynBFV zCFf}Dw+ah)=J*Xt5^a-n@z<7NAvoJ=#b1xoGpcv5Xnc)s=WLJ0&YQzt zry&~H7ddZGk%r^A`7LsT$ba!28mD>NRPoh2G)46`h&gWq*T@vxkJDkt0gb`L1@zzA zcjzV<=KsH$TQtZ76zq$FchQyY;^*%IHQX-Fy$dKEX+HNSTEMiOjt#QmNn&Ul6}ygV zESx2Su#&1r*~KHwN84z)rbeF;>*{HMIQaqHusDXf=ld$lfG>{+5b(R9=wqqnU`c|L z32?9y{(Qh+@OB4tluJ_jBrHaSf=?3<+Xlch0 z=lagBJL*I7U^(ubQgU_a1lEQz~a+-dJ8T~O4Q;#~tzz$HAL&Vq)@`9^zRufA*r~|2Unz-!~%EGIM zKcTr1Z5m60DJB%vmIPL(iGO@TL!^*vz-e@$)gdxZ(`*&eDjY09>eO(CcAcjCBHJBt z@%dGO6!cg$C_(zMy_1UJAzWPgnwDXQ1kU3;&KA4RgBgL*{OCMQ3U4Kr!;+^;p+QxR z*mRi^&FpVTWpW5}{zZQ@jyR^m(TqgfMA9i*Bx=8<{upPo@mo47&-(cn=)Agmj>!2p zh>>iu_22X;Is_0h`akqLoX~&&4-HVg1;TNWPO9EC@!3Usf!c`Wn>)IwkpcS{d6{0& zeEJQ))#h&**O+v$ctqt7t8BgbBhGg*w%P0q;bjbn zd!d7G4{0a15aGivJNSLVg5?d?=J**OW4Pc_vgc~QD5uNUsgTa#RE~iB&LNkKckE(zNNiT zIk>~5XdZ|FL!@jlzaf047v;=?U|z0MG3QB&HeVRb-P%x#r<>z1U5vv3s#jduqvKSZ z1Xs;g3@~t==}d3utQs5k0zzx0^J&zjcd6F7D9m(Tu1nPSb%`0tXHnwT(R=}8<|Cu| zLXkFx0n}i z#%f`64&K853c;ND`9ywNWj?cYGM~q^9avREv-k@*>)NxhxC+GhseBjes}-lF@`*_7 z6z*w!vfjXUFzXQ6<^b;d(`o!}ZN0HWoSnuGBU8P2dOB~Kyk8#b%c0ij4pg~)wl6Md z-2qPmt_go|#ZY^N3Y&DERvAnM+ca2}1(!p)mDLhFGo{gNoWV2HQ1IwbSBmJol}CwF z+59V1u|vEwi?2gstC=^O-=>1GwcUWyb3iPZ&C|{H9Ny040dqUnd83$~%ilt0V&?E- z>)W++_`kpp6qnZWSaEJMkhLvyc^0~#BHHKjP04dYSQER(;p)EJ{ik3c8uIuEjJcV| zJDIxMFrUlka~U>u*F2s=4Xnu!!{_sPNO`+arKjg(x48-=F*QCczqFjmfAH78kjJ7` zjb{8p?qNu+HfP+)=c#D$<9G9mU`ak!z(3GD2MlrVJ-jw*!tKD3Yy*5NlCGp|au;E> z`9fGTR>L7f99zUw!dlo7j7aQZdCUo45swjf8Su2=Ddf+GwBU`hLfjfp*BN#p*kui7 zS0Nt^*FUCvF(1pc7WD4UB7P@}v$BtqUclzVSyQEqsbuZT2GLr~o2i8zHN^dXeh7^( zFefhID^!^hO(i@YFI&MzF5x>d5^aWA8Q?*t`$O0fH1(t*zFLMVT_SQh9{{eNIea-E z0(8<4b5`&#fi_Gm<%`8XOY!wL#sx!&GCtkq?$vQ*4)~uo#Qc@~`MPAuK+BWP^CZM@ z)nI)_!^JLSfI%!b3)hCqWO1Mf>b+Q(K46+fpo#2fAl0DcCXeweF`dhMICCp%Mp(SK zZi!2s9c2TGv|?#9aPqM$_T?q%)$umCp*2`E^Wq5^kMp$L{6K$$WO z_N(qCeo#PYj<3vJxVUDG4-CRG|Ds-1B%9*sW&Z_J6?m?Edq45~z_6+k=xjncw+Aih z?j?jX-5fKk_p+x}-rho_I;nixJyfZ&@G!E*4?+>nbt21oJktXq>?r1}%MdqJ@L`zX z22ohSJxDNfV+D_pKBFG32wXhNYx?iq&JXBY91m6 zujRwB=yKNb87#6kgmtj6z94*eEuSxm`L6r;fXHOqS%$UX{grax45{Ops%MlatONY? znm?=KI~kAx?|QyKo9y(62iEgKB&LbKt>;hT1mAc+KM#F}bwAmBIFn7o5?k5#aSDN*tTOqjG>9upf;VAs@0a2=P zdOcPS1luU`uXk?nTEA?rNWF7|I8i`xgBqM0W&#=Oo<#p-1V##01vfi4G&=tm=$}8C diff --git a/examples/examples/basic_balance_transfer.rs b/examples/examples/balance_transfer_basic.rs similarity index 100% rename from examples/examples/basic_balance_transfer.rs rename to examples/examples/balance_transfer_basic.rs diff --git a/examples/examples/constants_dynamic.rs b/examples/examples/constants_dynamic.rs new file mode 100644 index 0000000000..ba23a8415d --- /dev/null +++ b/examples/examples/constants_dynamic.rs @@ -0,0 +1,17 @@ +use subxt::{OnlineClient, PolkadotConfig}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client to use: + let api = OnlineClient::::new().await?; + + // A dynamic query to obtain some contant: + let constant_query = subxt::dynamic::constant("System", "BlockLength"); + + // Obtain the value: + let value = api.constants().at(&constant_query)?; + + println!("Constant bytes: {:?}", value.encoded()); + println!("Constant value: {}", value.to_value()?); + Ok(()) +} diff --git a/examples/examples/constants_static.rs b/examples/examples/constants_static.rs new file mode 100644 index 0000000000..1dd9409c0e --- /dev/null +++ b/examples/examples/constants_static.rs @@ -0,0 +1,19 @@ +use subxt::{OnlineClient, PolkadotConfig}; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client to use: + let api = OnlineClient::::new().await?; + + // A query to obtain some contant: + let constant_query = polkadot::constants().system().block_length(); + + // Obtain the value: + let value = api.constants().at(&constant_query)?; + + println!("Block length: {value:?}"); + Ok(()) +} diff --git a/examples/examples/events.rs b/examples/examples/events.rs new file mode 100644 index 0000000000..201e2a39a4 --- /dev/null +++ b/examples/examples/events.rs @@ -0,0 +1,60 @@ +use subxt::{OnlineClient, PolkadotConfig}; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client to use: + let api = OnlineClient::::new().await?; + + // Get events for the latest block: + let events = api.events().at(None).await?; + + // We can dynamically decode events: + println!("Dynamic event details:"); + for event in events.iter() { + let event = event?; + + let pallet = event.pallet_name(); + let variant = event.variant_name(); + let field_values = event.field_values()?; + + println!("{pallet}::{variant}: {field_values}"); + } + + // Or we can attempt to statically decode them into the root Event type: + println!("Static event details:"); + for event in events.iter() { + let event = event?; + + if let Ok(ev) = event.as_root_event::() { + println!("{ev:?}"); + } else { + println!(""); + } + } + + // Or we can attempt to decode them into a specific arbitraty pallet enum + // (We could also set the output type to Value to dynamically decode, here): + println!("Event details for Balances pallet:"); + for event in events.iter() { + let event = event?; + + if let Ok(ev) = event.as_pallet_event::() { + println!("{ev:?}"); + } else { + continue; + } + } + + // Or we can look for specific events which match our statically defined ones: + let transfer_event = events.find_first::()?; + if let Some(ev) = transfer_event { + println!(" - Balance transfer success: value: {:?}", ev.amount); + } else { + println!(" - No balance transfer event found in this block"); + } + + Ok(()) +} diff --git a/examples/examples/storage_fetch.rs b/examples/examples/storage_fetch.rs new file mode 100644 index 0000000000..28c380e162 --- /dev/null +++ b/examples/examples/storage_fetch.rs @@ -0,0 +1,24 @@ +use sp_keyring::AccountKeyring; +use subxt::{OnlineClient, PolkadotConfig}; + +// Generate an interface that we can use from the node's metadata. +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a new API client, configured to talk to Polkadot nodes. + let api = OnlineClient::::new().await?; + + // Build a storage query to access account information. + let account = AccountKeyring::Alice.to_account_id().into(); + let storage_query = polkadot::storage().system().account(&account); + + // Use that query to `fetch` a result. This returns an `Option<_>`, which will be + // `None` if no value exists at the given address. You can also use `fetch_default` + // where applicable, which will return the default value if none exists. + let result = api.storage().at(None).await?.fetch(&storage_query).await?; + + println!("Alice has free balance: {}", result.unwrap().data.free); + Ok(()) +} diff --git a/examples/examples/storage_fetch_dynamic.rs b/examples/examples/storage_fetch_dynamic.rs new file mode 100644 index 0000000000..04c6d78741 --- /dev/null +++ b/examples/examples/storage_fetch_dynamic.rs @@ -0,0 +1,23 @@ +use sp_keyring::AccountKeyring; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt::dynamic::{Value,At}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a new API client, configured to talk to Polkadot nodes. + let api = OnlineClient::::new().await?; + + // Build a dynamic storage query to access account information. + let account = AccountKeyring::Alice.to_account_id(); + let storage_query = subxt::dynamic::storage("System", "Account", vec![ + Value::from_bytes(account) + ]); + + // Use that query to `fetch` a result. Because the query is dynamic, we don't know what the result + // type will be either, and so we get a type back that can be decoded into a dynamic Value type. + let result = api.storage().at(None).await?.fetch(&storage_query).await?; + let value = result.unwrap().to_value()?; + + println!("Alice has free balance: {:?}", value.at("data").at("free")); + Ok(()) +} diff --git a/examples/examples/storage_iterating.rs b/examples/examples/storage_iterating.rs new file mode 100644 index 0000000000..31b7009221 --- /dev/null +++ b/examples/examples/storage_iterating.rs @@ -0,0 +1,24 @@ +use subxt::{OnlineClient, PolkadotConfig}; + +// Generate an interface that we can use from the node's metadata. +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a new API client, configured to talk to Polkadot nodes. + let api = OnlineClient::::new().await?; + + // Build a storage query to iterate over account information. + let storage_query = polkadot::storage().system().account_root(); + + // Get back an iterator of results (we acquire 10 at a time, here). + let mut results = api.storage().at(None).await?.iter(storage_query, 10).await?; + + while let Some((key, value)) = results.next().await? { + println!("Key: 0x{}", hex::encode(&key)); + println!("Value: {:?}", value); + } + + Ok(()) +} diff --git a/examples/examples/storage_iterating_dynamic.rs b/examples/examples/storage_iterating_dynamic.rs new file mode 100644 index 0000000000..0ffc569232 --- /dev/null +++ b/examples/examples/storage_iterating_dynamic.rs @@ -0,0 +1,20 @@ +use subxt::{OnlineClient, PolkadotConfig}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a new API client, configured to talk to Polkadot nodes. + let api = OnlineClient::::new().await?; + + // Build a dynamic storage query to iterate account information. + let storage_query = subxt::dynamic::storage_root("System", "Account"); + + // Use that query to return an iterator over the results. + let mut results = api.storage().at(None).await?.iter(storage_query, 10).await?; + + while let Some((key, value)) = results.next().await? { + println!("Key: 0x{}", hex::encode(&key)); + println!("Value: {:?}", value.to_value()?); + } + + Ok(()) +} diff --git a/subxt/src/book/mod.rs b/subxt/src/book/mod.rs index 7b4681fa68..d345db1842 100644 --- a/subxt/src/book/mod.rs +++ b/subxt/src/book/mod.rs @@ -8,11 +8,12 @@ Subxt is a library for interacting with Substrate based nodes. It has a focus on **sub**mitting e**xt**rinsics, hence the name, however it's also capable of reading blocks, storage, events and constants from a node. The aim of this guide is to explain key concepts and get you started with using Subxt. 1. [Features](#features-at-a-glance) -2. [Setup](#setup) +2. [Limitations](#limitations) +3. [Setup](#setup) 1. [Metadata](#metadata) 2. [Config](#config) -3. [Usage](#usage) -4. [Examples](#examples) +4. [Usage](#usage) +5. [Examples](#examples) ## Features at a glance @@ -25,13 +26,27 @@ Here's a quick overview of the features that Subxt has to offer: - Subxt can forego the statically generated interface and build transactions, storage queries and constant queries using data provided at runtime, rather than queries constructed statically. - Subxt can be compiled to WASM to run in the browser, allowing it to back Rust based browser apps, or even bind to JS apps. +## Limitations + +In various places, you can provide a block hash to access data at a particular block, for instance: + +- [`crate::storage::StorageClient::at`] +- [`crate::events::EventsClient::at`] +- [`crate::blocks::BlocksClient::at`] + +However, Subxt is (by default) only capable of properly working with blocks that were produced after the most recent runtime update. This is because it uses the current runtime's metadata to encode and decode things. + +Subxt currently supports V14 metadata, and so it's possible to decode older blocks produced with a runtime that emits V14 metadata by manually setting the metadata used by the client using [`crate::client::OnlineClient::set_metadata()`]. + +Subxt is currently unable to work with any blocks produced prior to the runtime update that introduces V14 metadata. + ## Setup Here is a simple but complete example of using Subxt to transfer some tokens from Alice to Bob: */ //! ```rust,ignore -#![doc = include_str!("../../../examples/examples/basic_balance_transfer.rs")] +#![doc = include_str!("../../../examples/examples/balance_transfer_basic.rs")] //! ``` /*! diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs index 7c8f962585..bd2db31f04 100644 --- a/subxt/src/book/usage/blocks.rs +++ b/subxt/src/book/usage/blocks.rs @@ -3,5 +3,8 @@ // see LICENSE for license details. /*! +# Blocks + +The [blocks API](crate::blocks::BlocksClient) in Subxt provides a way to access information about specific blocks, as well as subscribe to new blocks. */ diff --git a/subxt/src/book/usage/constants.rs b/subxt/src/book/usage/constants.rs index 7c8f962585..6e8b087380 100644 --- a/subxt/src/book/usage/constants.rs +++ b/subxt/src/book/usage/constants.rs @@ -2,6 +2,56 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +/*! +# Constants + +There are various constants stored in a node; these can only change when the runtime is updated. Much like [`super::storage`], we can query these using Subxt by taking the following steps: + +1. [Constructing a constant query](#constructing-a-query). +2. [Submitting the query to get back the associated value](#submitting-it). + +## Constructing a constant query + +We can use the statically generated interface to build constant queries: + +```rust,no_run +use sp_keyring::AccountKeyring; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +let constant_query = polkadot::constants().system().block_length(); +``` + +Alternately, we can dynamically construct a constant query: + +```rust,no_run +use subxt::dynamic::Value; + +let account = AccountKeyring::Alice.to_account_id(); +let storage_query = subxt::dynamic::constant("System", "BlockLength"); +``` + +Static queries also have a static return type, so the constant is decoded appropriately. In addition, they are validated at runtime to ensure that they align with the current node state. Dynamic queries must be decoded into some static type manually, or into the dynamic [`crate::dynamic::Value`] type. + +## Submitting it + +Constant queries are submitted via [`crate::constants::ConstantsClient::at()`]. It's worth noting that constant values are pulled directly out of the node metadata which the client has already acquired, and so this function is available from a [`crate::OfflineClient`]. + +Here's an example using a static query: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/constants_static.rs")] +//! ``` +/*! + +And here's one using a dynamic query: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/constants_dynamic.rs")] +//! ``` /*! */ diff --git a/subxt/src/book/usage/events.rs b/subxt/src/book/usage/events.rs index 7c8f962585..a6c06d16c4 100644 --- a/subxt/src/book/usage/events.rs +++ b/subxt/src/book/usage/events.rs @@ -2,6 +2,40 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +/*! +# Events + +In the process of adding extrinsics to a block, they are executed. When extrinsics are executed, they may produce events describing what's happening, but additionally the node may add emit some events of its own as the blook is processed. Events live in a single location in node storage which is overwritten at each block. + +When we submit extrinsics using Subxt, methods like [`crate::tx::TxProgress::wait_for_finalized_success()`] return [`crate::blocks::ExtrinsicEvents`], which can be used to iterate and inspect the events produced for a specific extrinsic. We can also access _all_ of the events produced in a single block using one of these two interfaces: + +```rust,no_run +# #[tokio::main] +# async fn main() -> Result<(), Box> { +use subxt::client::OnlineClient; +use subxt::config::PolkadotConfig; + +// Create client: +let client = OnlineClient::::new().await?; + +// Get events from the latest block: +let events = client.blocks().at(None).await?.events().await?; +// We can use this shorthand too: +let events = client.events().at(None).await?; +# Ok(()) +# } +``` + +Once we've loaded our events, we can iterate all events or search for specific events via methods like [`crate::events::Events::iter()`] and [`crate::events::Events::find()`]. See [`crate::events::Events`] and [`crate::events::EventDetails`] for more information. + +## Example + +Here's an example which puts this all together: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/events.rs")] +//! ``` /*! */ diff --git a/subxt/src/book/usage/extrinsics.rs b/subxt/src/book/usage/extrinsics.rs index 7b0c6195f2..a7932cf98e 100644 --- a/subxt/src/book/usage/extrinsics.rs +++ b/subxt/src/book/usage/extrinsics.rs @@ -5,14 +5,11 @@ /*! # Extrinsics -Submitting extrinsics to a node is one of the core features of Subxt, and generally consists of the following steps: +Extrinsics define function calls and their parameters, and are the only way that you can change the state of the blockchain. Submitting extrinsics to a node is one of the core features of Subxt, and generally consists of the following steps: 1. [Constructing an extrinsic payload to submit](#constructing-an-extrinsic-payload). 2. [Signing it](#signing-it). -2. [Submitting it (optionally with some additional parameters)](#submitting-it). - Once submitted, you'll typically also: - 1. Watch the transaction progress and wait until it has made it into a block. - 2. Check the events emitted by it once it's in a block to see what happened and whether it succeeded. +3. [Submitting it (optionally with some additional parameters)](#submitting-it). We'll look at each of these steps in turn. @@ -127,7 +124,7 @@ The highest level approach to doing this is to call [`crate::tx::TxClient::sign_ */ //! ```rust,ignore -#![doc = include_str!("../../../../examples/examples/basic_balance_transfer.rs")] +#![doc = include_str!("../../../../examples/examples/balance_transfer_basic.rs")] //! ``` /*! diff --git a/subxt/src/book/usage/storage.rs b/subxt/src/book/usage/storage.rs index 7c8f962585..f35e6b9e35 100644 --- a/subxt/src/book/usage/storage.rs +++ b/subxt/src/book/usage/storage.rs @@ -3,5 +3,98 @@ // see LICENSE for license details. /*! +# Storage + +A Substrate based chain has storage, whose values are determined by the extrinsics added to past blocks. Subxt allows you to query the storage of a node, which consists of the following steps: + +1. [Constructing a storage query](#constructing-a-storage-query). +2. [Submitting the query to get back the associated values](#submitting-it). + +## Constructing a storage query + +We can use the statically generated interface to build storage queries: + +```rust,no_run +use sp_keyring::AccountKeyring; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +let account = AccountKeyring::Alice.to_account_id().into(); +let storage_query = polkadot::storage().system().account(&account); +``` + +Alternately, we can dynamically construct a storage query. This will not be type checked or validated until it's submitted: + +```rust,no_run +use subxt::dynamic::Value; + +let account = AccountKeyring::Alice.to_account_id(); +let storage_query = subxt::dynamic::storage("System", "Account", vec![ + Value::from_bytes(account) +]); +``` + +As well as accessing specific entries, some storage locations can also be iterated over (such as the map of account information). Do do this, suffix `_root` onto the query constructor (this will only be available on static constructors when iteration is actually possible): + +```rust,no_run +use sp_keyring::AccountKeyring; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +// A static query capable of iterating over accounts: +let storage_query = polkadot::storage().system().account_root(); +// A dynamic query to do the same: +let storage_query = subxt::dynamic::storage_root("System", "Account"); +``` + +All valid storage queries implement [`crate::storage::StorageAddress`]. As well as describing how to build a valid storage query, this trait also has some associated types that determine the shape of the result you'll get back, and determine what you can do with it (ie, can you iterate over storage entries using it). + +Static queries set appropriate values for these associated types, and can therefore only be used where it makes sense. Dyanmic queries don't know any better and can be used in more places, but may fail at runtime instead if they are invalid in those places. + +## Submitting it + +Storage queries can be handed to various functions in [`crate::storage::Storage`] in order to obtain the associated values (also referred to as storage entries) back. + +### Fetching storage entries + +The simplest way to access storage entries is to construct a query and then call either [`crate::storage::Storage::fetch()`] or [`crate::storage::Storage::fetch_or_default()`] (the latter will only work for storage queries that have a default value when empty): + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/storage_fetch.rs")] +//! ``` +/*! + +For completeness, below is an example using a dynamic query instead. The return type from a dynamic query is a [`crate::dynamic::DecodedValueThunk`], which can be decoded into a [`crate::dynamic::Value`], or else the raw bytes can be accessed instead. + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/storage_fetch_dynamic.rs")] +//! ``` +/*! + +### Iterating storage entries + +Many storage entries are maps of values; as well as fetching individual values, it's possible to iterate over all of the values stored at that location: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/storage_iterating.rs")] +//! ``` +/*! + +Here's the same logic but using dynamically constructed values instead: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/storage_iterating_dynamic.rs")] +//! ``` +/*! + +### Advanced + +For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and [`crate::storage::Storage::fetch_keys`]. Both of these take raw bytes as arguments, which can be obtained from a [`crate::storage::StorageAddress`] by using [`crate::storage::StorageClient::address_bytes()`] or [`crate::storage::StorageClient::address_root_bytes()`]. */ diff --git a/subxt/src/dynamic.rs b/subxt/src/dynamic.rs index a16611b6f4..04f5653e82 100644 --- a/subxt/src/dynamic.rs +++ b/subxt/src/dynamic.rs @@ -11,7 +11,7 @@ use crate::{ }; use scale_decode::DecodeAsType; -pub use scale_value::Value; +pub use scale_value::{ Value, At }; /// A [`scale_value::Value`] type endowed with contextual information /// regarding what type was used to decode each part of it. This implements diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 097bf2fc10..4117baa324 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -5,7 +5,7 @@ //! Subxt is a library for interacting with Substrate based nodes. Using it looks something like this: //! //! ```rust,ignore -#![doc = include_str!("../../examples/examples/basic_balance_transfer.rs")] +#![doc = include_str!("../../examples/examples/balance_transfer_basic.rs")] //! ``` //! //! Take a look at [the Subxt guide](book) to learn more about how to use Subxt. From 99f1a8fe91a82854ebecadd75c1c1a3bccd3f037 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 11 Apr 2023 14:59:27 +0100 Subject: [PATCH 04/25] at_latest() and wip blocks --- examples/examples/events.rs | 2 +- examples/examples/storage_fetch.rs | 2 +- examples/examples/storage_fetch_dynamic.rs | 2 +- examples/examples/storage_iterating_dynamic.rs | 2 +- subxt/src/book/usage/blocks.rs | 7 ++++++- subxt/src/book/usage/events.rs | 4 ++-- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/examples/examples/events.rs b/examples/examples/events.rs index 201e2a39a4..4c35398349 100644 --- a/examples/examples/events.rs +++ b/examples/examples/events.rs @@ -9,7 +9,7 @@ async fn main() -> Result<(), Box> { let api = OnlineClient::::new().await?; // Get events for the latest block: - let events = api.events().at(None).await?; + let events = api.events().at_latest().await?; // We can dynamically decode events: println!("Dynamic event details:"); diff --git a/examples/examples/storage_fetch.rs b/examples/examples/storage_fetch.rs index 28c380e162..2adc27a4bd 100644 --- a/examples/examples/storage_fetch.rs +++ b/examples/examples/storage_fetch.rs @@ -17,7 +17,7 @@ async fn main() -> Result<(), Box> { // Use that query to `fetch` a result. This returns an `Option<_>`, which will be // `None` if no value exists at the given address. You can also use `fetch_default` // where applicable, which will return the default value if none exists. - let result = api.storage().at(None).await?.fetch(&storage_query).await?; + let result = api.storage().at_latest().await?.fetch(&storage_query).await?; println!("Alice has free balance: {}", result.unwrap().data.free); Ok(()) diff --git a/examples/examples/storage_fetch_dynamic.rs b/examples/examples/storage_fetch_dynamic.rs index 04c6d78741..8ae54cb928 100644 --- a/examples/examples/storage_fetch_dynamic.rs +++ b/examples/examples/storage_fetch_dynamic.rs @@ -15,7 +15,7 @@ async fn main() -> Result<(), Box> { // Use that query to `fetch` a result. Because the query is dynamic, we don't know what the result // type will be either, and so we get a type back that can be decoded into a dynamic Value type. - let result = api.storage().at(None).await?.fetch(&storage_query).await?; + let result = api.storage().at_latest().await?.fetch(&storage_query).await?; let value = result.unwrap().to_value()?; println!("Alice has free balance: {:?}", value.at("data").at("free")); diff --git a/examples/examples/storage_iterating_dynamic.rs b/examples/examples/storage_iterating_dynamic.rs index 0ffc569232..43039b1c58 100644 --- a/examples/examples/storage_iterating_dynamic.rs +++ b/examples/examples/storage_iterating_dynamic.rs @@ -9,7 +9,7 @@ async fn main() -> Result<(), Box> { let storage_query = subxt::dynamic::storage_root("System", "Account"); // Use that query to return an iterator over the results. - let mut results = api.storage().at(None).await?.iter(storage_query, 10).await?; + let mut results = api.storage().at_latest().await?.iter(storage_query, 10).await?; while let Some((key, value)) = results.next().await? { println!("Key: 0x{}", hex::encode(&key)); diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs index bd2db31f04..2fef79b121 100644 --- a/subxt/src/book/usage/blocks.rs +++ b/subxt/src/book/usage/blocks.rs @@ -5,6 +5,11 @@ /*! # Blocks -The [blocks API](crate::blocks::BlocksClient) in Subxt provides a way to access information about specific blocks, as well as subscribe to new blocks. +The [blocks API](crate::blocks::BlocksClient) in Subxt provides a way to: + +- Access information about specific blocks (see [`crate::blocks::BlocksClient::at()`] and [`crate::blocks::BlocksClient::at_latest()`]). +- Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()), [best](crate::blocks::BlocksClient::subscribe_best()) or [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced. Prefer to subscribe to finalized blocks unless you know what you're doing. + +In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access the [storage](crate::blocks::Block::storage()), [events](crate::blocks::Block::events()) and [runtime APIs](crate::blocks::Block::runtime_api()) at that block, as well as acquire and iterate through the extrinsics in the block via [`crate::blocks::Block::extrinsics()`]. */ diff --git a/subxt/src/book/usage/events.rs b/subxt/src/book/usage/events.rs index a6c06d16c4..d4c4fe1256 100644 --- a/subxt/src/book/usage/events.rs +++ b/subxt/src/book/usage/events.rs @@ -19,9 +19,9 @@ use subxt::config::PolkadotConfig; let client = OnlineClient::::new().await?; // Get events from the latest block: -let events = client.blocks().at(None).await?.events().await?; +let events = client.blocks().at_latest().await?.events().await?; // We can use this shorthand too: -let events = client.events().at(None).await?; +let events = client.events().at_latest().await?; # Ok(()) # } ``` From 61538f4dad6a0d66935290d9886f0d9dba6ca724 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 12 Apr 2023 16:45:20 +0100 Subject: [PATCH 05/25] remove need to import parity-scale-codec crate with Subxt for macro to work --- codegen/src/types/derives.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/codegen/src/types/derives.rs b/codegen/src/types/derives.rs index 1f84bcd8ab..0775bae982 100644 --- a/codegen/src/types/derives.rs +++ b/codegen/src/types/derives.rs @@ -93,12 +93,17 @@ impl Derives { derives.insert(syn::parse_quote!(#crate_path::ext::scale_encode::EncodeAsType)); let encode_crate_path = quote::quote! { #crate_path::ext::scale_encode }.to_string(); attributes.insert(syn::parse_quote!(#[encode_as_type(crate_path = #encode_crate_path)])); + derives.insert(syn::parse_quote!(#crate_path::ext::scale_decode::DecodeAsType)); let decode_crate_path = quote::quote! { #crate_path::ext::scale_decode }.to_string(); attributes.insert(syn::parse_quote!(#[decode_as_type(crate_path = #decode_crate_path)])); derives.insert(syn::parse_quote!(#crate_path::ext::codec::Encode)); + attributes.insert(syn::parse_quote!(#[codec(crate = #crate_path::ext::codec)])); + derives.insert(syn::parse_quote!(#crate_path::ext::codec::Decode)); + attributes.insert(syn::parse_quote!(#[codec(crate = #crate_path::ext::codec)])); + derives.insert(syn::parse_quote!(Debug)); Self { From 4860cd35748eb5be4064121ab8b24b4011d73ad7 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 12 Apr 2023 16:46:06 +0100 Subject: [PATCH 06/25] More docs; expanding on setup guide and finish pass of main sections --- Cargo.lock | 4 - examples/Cargo.toml | 4 - examples/examples/blocks_subscribing.rs | 51 ++++++++++++ examples/examples/runtime_apis_raw.rs | 26 ++++++ .../examples/setup_client_custom_config.rs | 29 +++++++ examples/examples/setup_client_custom_rpc.rs | 79 +++++++++++++++++++ examples/examples/setup_client_offline.rs | 41 ++++++++++ subxt/src/book/mod.rs | 29 ++++--- subxt/src/book/setup/client.rs | 51 ++++++++++++ subxt/src/book/setup/codegen.rs | 54 +++++++++++++ subxt/src/book/setup/mod.rs | 15 ++++ subxt/src/book/usage/blocks.rs | 22 +++++- subxt/src/book/usage/mod.rs | 2 + subxt/src/book/usage/runtime_apis.rs | 22 ++++++ 14 files changed, 404 insertions(+), 25 deletions(-) create mode 100644 examples/examples/blocks_subscribing.rs create mode 100644 examples/examples/runtime_apis_raw.rs create mode 100644 examples/examples/setup_client_custom_config.rs create mode 100644 examples/examples/setup_client_custom_rpc.rs create mode 100644 examples/examples/setup_client_offline.rs create mode 100644 subxt/src/book/setup/client.rs create mode 100644 subxt/src/book/setup/codegen.rs create mode 100644 subxt/src/book/setup/mod.rs create mode 100644 subxt/src/book/usage/runtime_apis.rs diff --git a/Cargo.lock b/Cargo.lock index 970c2c424c..fc0a66913f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3568,13 +3568,9 @@ version = "0.28.0" dependencies = [ "futures", "hex", - "parity-scale-codec", - "sp-core", "sp-keyring", - "sp-runtime", "subxt", "tokio", - "tracing-subscriber 0.3.16", ] [[package]] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 66491ba71f..5e9c286523 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -16,9 +16,5 @@ description = "Subxt example usage" subxt = { path = "../subxt" } tokio = { version = "1.27", features = ["rt-multi-thread", "macros", "time"] } futures = "0.3.27" -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] } hex = "0.4.3" -tracing-subscriber = "0.3.11" sp-keyring = "23.0.0" -sp-core = { version = "20.0.0", default-features = false } -sp-runtime = "23.0.0" diff --git a/examples/examples/blocks_subscribing.rs b/examples/examples/blocks_subscribing.rs new file mode 100644 index 0000000000..a6bff69909 --- /dev/null +++ b/examples/examples/blocks_subscribing.rs @@ -0,0 +1,51 @@ +use futures::StreamExt; +use subxt::{OnlineClient, PolkadotConfig}; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client to use: + let api = OnlineClient::::new().await?; + + // Subscribe to all finalized blocks: + let mut blocks_sub = api.blocks().subscribe_finalized().await?; + + // For each block, print a bunch of information about it: + 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}:"); + println!(" Hash: {block_hash}"); + println!(" Extrinsics:"); + + // Log each of the extrinsic with it's associated events: + let body = block.body().await?; + for ext in body.extrinsics() { + let idx = ext.index(); + let events = ext.events().await?; + let bytes_hex = format!("0x{}", hex::encode(ext.bytes())); + + println!(" Extrinsic #{idx}:"); + println!(" Bytes: {bytes_hex}"); + println!(" Events:"); + + 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()?; + + println!(" {pallet_name}_{event_name}"); + println!(" {}", event_values); + } + } + } + + Ok(()) +} \ No newline at end of file diff --git a/examples/examples/runtime_apis_raw.rs b/examples/examples/runtime_apis_raw.rs new file mode 100644 index 0000000000..5f92d5ae6e --- /dev/null +++ b/examples/examples/runtime_apis_raw.rs @@ -0,0 +1,26 @@ +use subxt::{OnlineClient, PolkadotConfig}; +use subxt::ext::frame_metadata::RuntimeMetadataPrefixed; +use subxt::ext::codec::{Decode, Compact}; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client to use: + let api = OnlineClient::::new().await?; + + // Use runtime APIs at the latest block: + let runtime_apis = api.runtime_api().at_latest().await?; + + // Ask for metadata: + let bytes = runtime_apis.call_raw("Metadata_metadata", None).await?; + + // Decode it: + let cursor = &mut &*bytes; + let _ = >::decode(cursor)?; + let meta = RuntimeMetadataPrefixed::decode(cursor)?; + + println!("{meta:?}"); + Ok(()) +} diff --git a/examples/examples/setup_client_custom_config.rs b/examples/examples/setup_client_custom_config.rs new file mode 100644 index 0000000000..518a0dceb0 --- /dev/null +++ b/examples/examples/setup_client_custom_config.rs @@ -0,0 +1,29 @@ +use subxt::{ + config::{substrate::SubstrateExtrinsicParams, Config, SubstrateConfig}, + OnlineClient, +}; + +/// Defing a custom config type: +enum MyConfig {} +impl Config for MyConfig { + // This is different from the default `u32`: + type Index = u64; + // We can point to the default types if we don't need to change things: + type Hash = ::Hash; + type Hasher = ::Hasher; + type Header = ::Header; + type AccountId = ::AccountId; + type Address = ::Address; + type Signature = ::Signature; + // ExtrinsicParams makes use of the index type, so we need to tweak it + // too to align with our modified index type, above: + type ExtrinsicParams = SubstrateExtrinsicParams; +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client which uses the custom config: + let _api = OnlineClient::::new().await?; + + Ok(()) +} diff --git a/examples/examples/setup_client_custom_rpc.rs b/examples/examples/setup_client_custom_rpc.rs new file mode 100644 index 0000000000..ed04f1f4fa --- /dev/null +++ b/examples/examples/setup_client_custom_rpc.rs @@ -0,0 +1,79 @@ +use std::{ + fmt::Write, + pin::Pin, + sync::{Arc, Mutex}, +}; +use subxt::{ + rpc::{RawValue, RpcClientT, RpcFuture, RpcSubscription}, + OnlineClient, PolkadotConfig, +}; + +// A dummy RPC client that doesn't actually handle requests properly +// at all, but instead just logs what requests to it were made. +struct MyLoggingClient { + log: Arc>, +} + +// We have to implement this fairly low level trait to turn [`MyLoggingClient`] +// into an RPC client that we can make use of in Subxt. Here we just log the requests +// made but don't forward them to any real node, and instead just return nonsense. +impl RpcClientT for MyLoggingClient { + fn request_raw<'a>( + &'a self, + method: &'a str, + params: Option>, + ) -> RpcFuture<'a, Box> { + writeln!( + self.log.lock().unwrap(), + "{method}({})", + params.as_ref().map(|p| p.get()).unwrap_or("[]") + ) + .unwrap(); + + // We've logged the request; just return garbage. Because a boxed future is returned, + // you're able to run whatever async code you'd need to actually talk to a node. + let res = RawValue::from_string("[]".to_string()).unwrap(); + Box::pin(std::future::ready(Ok(res))) + } + + fn subscribe_raw<'a>( + &'a self, + sub: &'a str, + params: Option>, + unsub: &'a str, + ) -> RpcFuture<'a, RpcSubscription> { + writeln!( + self.log.lock().unwrap(), + "{sub}({}) (unsub: {unsub})", + params.as_ref().map(|p| p.get()).unwrap_or("[]") + ) + .unwrap(); + + // We've logged the request; just return garbage. Because a boxed future is returned, + // and that will return a boxed Stream impl, you have a bunch of flexibility to build + // and return whatever type of Stream you see fit. + let res = RawValue::from_string("[]".to_string()).unwrap(); + let stream = futures::stream::once(async move { Ok(res) }); + let stream: Pin + Send>> = Box::pin(stream); + // This subscription does not provide an ID. + Box::pin(std::future::ready(Ok(RpcSubscription { stream, id: None }))) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Instantiate our replacement RPC client. + let log = Arc::default(); + let rpc_client = MyLoggingClient { + log: Arc::clone(&log), + }; + + // Pass this into our OnlineClient to instantiate it. This will lead to some + // RPC calls being made to fetch chain details/metadata, which will immediately + // fail.. + let _ = OnlineClient::::from_rpc_client(Arc::new(rpc_client)).await; + + // But, we can see that the calls were made via our custom RPC client: + println!("Log of calls made:\n\n{}", log.lock().unwrap().as_str()); + Ok(()) +} diff --git a/examples/examples/setup_client_offline.rs b/examples/examples/setup_client_offline.rs new file mode 100644 index 0000000000..baa058be17 --- /dev/null +++ b/examples/examples/setup_client_offline.rs @@ -0,0 +1,41 @@ +use subxt::{ config::PolkadotConfig, OfflineClient}; +use subxt::ext::frame_metadata::RuntimeMetadataPrefixed; +use subxt::ext::codec::Decode; +use subxt::metadata::Metadata; +use subxt::utils::H256; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // We need to obtain these details for an OfflineClient to be instantiated.. + + // 1. Genesis hash (RPC call: chain_getBlockHash(0)): + let genesis_hash = { + let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"; + let bytes = hex::decode(h).unwrap(); + H256::from_slice(&bytes) + }; + + // 2. A runtime version (system_version constant on a Substrate node has these): + let runtime_version = subxt::rpc::types::RuntimeVersion { + spec_version: 9370, + transaction_version: 20, + other: Default::default() + }; + + // 3. Metadata (I'll load it from the downloaded metadata, but you can use + // `subxt metadata > file.scale` to download it): + let metadata = { + let bytes = std::fs::read("./artifacts/polkadot_metadata.scale").unwrap(); + let metadata = RuntimeMetadataPrefixed::decode(&mut &*bytes).unwrap(); + Metadata::try_from(metadata).unwrap() + }; + + // Create an offline client using the details obtained above: + let _api = OfflineClient::::new( + genesis_hash, + runtime_version, + metadata + ); + + Ok(()) +} diff --git a/subxt/src/book/mod.rs b/subxt/src/book/mod.rs index d345db1842..870ade078b 100644 --- a/subxt/src/book/mod.rs +++ b/subxt/src/book/mod.rs @@ -9,9 +9,7 @@ Subxt is a library for interacting with Substrate based nodes. It has a focus on 1. [Features](#features-at-a-glance) 2. [Limitations](#limitations) -3. [Setup](#setup) - 1. [Metadata](#metadata) - 2. [Config](#config) +3. [Quick start](#quick-start) 4. [Usage](#usage) 5. [Examples](#examples) @@ -19,8 +17,8 @@ Subxt is a library for interacting with Substrate based nodes. It has a focus on Here's a quick overview of the features that Subxt has to offer: -- Subxt allows you to generate a static, type safe interface to a node given some metadata; this allows you to catch more errors at compile time rather than runtime. -- Subxt also makes heavy use of node metadata to encode/decode the data sent to/from it. This allows it to target almost any node which can output the correct metadata version, and allows it some flexibility in encoding and decoding things to account for cross-node differences. +- Subxt allows you to generate a static, type safe interface to a node given some metadata; this allows you to catch many errors at compile time rather than runtime. +- Subxt also makes heavy use of node metadata to encode/decode the data sent to/from it. This allows it to target almost any node which can output the correct metadata, and allows it some flexibility in encoding and decoding things to account for cross-node differences. - Subxt has a pallet-oriented interface, meaning that code you write to talk to some pallet on one node will often "Just Work" when pointed at different nodes that use the same pallet. - Subxt can work offline; you can generate and sign transactions, access constants from node metadata and more, without a network connection. This is all checked at compile time, so you can be certain it won't try to establish a network connection if you don't want it to. - Subxt can forego the statically generated interface and build transactions, storage queries and constant queries using data provided at runtime, rather than queries constructed statically. @@ -33,14 +31,13 @@ In various places, you can provide a block hash to access data at a particular b - [`crate::storage::StorageClient::at`] - [`crate::events::EventsClient::at`] - [`crate::blocks::BlocksClient::at`] +- [`crate::runtime_api::RuntimeApiClient::at`] -However, Subxt is (by default) only capable of properly working with blocks that were produced after the most recent runtime update. This is because it uses the current runtime's metadata to encode and decode things. +However, Subxt is (by default) only capable of properly working with blocks that were produced after the most recent runtime update. This is because it uses the most recent metadata given back by a node to encode and decode things. It's possible to decode older blocks produced by a runtime that emits compatible (currently, V14) metadata by manually setting the metadata used by the client using [`crate::client::OnlineClient::set_metadata()`]. -Subxt currently supports V14 metadata, and so it's possible to decode older blocks produced with a runtime that emits V14 metadata by manually setting the metadata used by the client using [`crate::client::OnlineClient::set_metadata()`]. +Subxt does not support working with blocks produced prior to the runtime update that introduces V14 metadata. It may have some success decoding older blocks using newer metadata, but may also completely fail to do so. -Subxt is currently unable to work with any blocks produced prior to the runtime update that introduces V14 metadata. - -## Setup +## Quick start Here is a simple but complete example of using Subxt to transfer some tokens from Alice to Bob: @@ -50,12 +47,12 @@ Here is a simple but complete example of using Subxt to transfer some tokens fro //! ``` /*! -This example assumes that a Polkadot node at a supported version is running locally (use [OnlineClient::from_url()](crate::client::OnlineClient::from_url()) to instead interact with a node hosted elsewhere). +This example assumes that a Polkadot node is running locally (Subxt endeavours to support all recent releases). Typically, to use Subxt to talk to some custom Substrate node (for example a parachain node), you'll want to: -Typically, to use Subxt to talk to some custom Substrate node (for example a parachain node), you'll need to: +1. [Generate an interface](setup::codegen). +2. [Configure and instantiate the client](setup::client). -1. Acquire some metadata from that node to pass to the [`#[subxt]`](crate::subxt) macro (optional but recommended). -2. Configure the client used by providing an instance of the [`Config`](crate::config::Config) trait. +Follow the above links to learn more about each step. ### Acquiring metadata @@ -89,6 +86,7 @@ Once Subxt is configured, the next step is actually interacting with a node. Fol - [Events](usage::events): Subxt can read the events emitted for recent blocks. - [Constants](usage::constants): Subxt can access the constant values stored in a node, which remain the same for a given runtime version. - [Blocks](usage::blocks): Subxt can load recent blocks or subscribe to new/finalized blocks, reading the extrinsics, events and storage at these blocks. +- [Runtime APIs](usage::runtime_apis): Subxt can make calls into pallet runtime APIs to retrieve data. ## Examples @@ -100,4 +98,5 @@ A set of examples to help showcase various Subxt features and functionality: - Working offline. */ -pub mod usage; +pub mod setup; +pub mod usage; \ No newline at end of file diff --git a/subxt/src/book/setup/client.rs b/subxt/src/book/setup/client.rs new file mode 100644 index 0000000000..44e0794a54 --- /dev/null +++ b/subxt/src/book/setup/client.rs @@ -0,0 +1,51 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! +# Configuring the Subxt client + +Subxt ships with two clients, an [offline client](crate::client::OfflineClient) and an [online client](crate::client::OnlineClient). These are backed by the traits [`crate::client::OfflineClientT`] and [`crate::client::OnlineClientT`], so in theory it's possible for users to implement their own clients, although this isn't generally expected. + +Both clients are generic over a [`crate::config::Config`] trait, which is the way that we give the client certain information about how to interact with a node that isn't otherwise available or possible to include in the node metadata. Subxt ships out of the box with two default implementations: + +- [`crate::config::PolkadotConfig`] for talking to Polkadot nodes, and +- [`crate::config::SubstrateConfig`] for talking to generic nodes built with Substrate. + +The latter will generally work in many cases, but will need modifying if the chain you'd like to connect to has altered any of the details mentioned in [the trait](`crate::config::Config`). + +In the case of the [`crate::OnlineClient`], we have a few options to instantiate it: + +- [`crate::OnlineClient::new()`] to connect to a node running locally. +- [`crate::OnlineClient::from_url()`] to connect to a node at a specific URL. +- [`crate::OnlineClient::from_rpc_client()`] to instantiate the client with a custom RPC implementation. + +The latter accepts anything that implements the low level [`crate::rpc::RpcClientT`] trait; this allows you to decide how Subxt will attempt to talk to a node if you'd prefer something other than the provided interfaces. + +## Examples + +Defining some custom config based off the default Substrate config: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/setup_client_custom_config.rs")] +//! ``` +/*! + +Writing a custom [`crate::rpc::RpcClientT`] implementation: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/setup_client_custom_rpc.rs")] +//! ``` +/*! + +Creating an [`crate::OfflineClient`]: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/setup_client_offline.rs")] +//! ``` +/*! + +*/ \ No newline at end of file diff --git a/subxt/src/book/setup/codegen.rs b/subxt/src/book/setup/codegen.rs new file mode 100644 index 0000000000..f114fc4f0b --- /dev/null +++ b/subxt/src/book/setup/codegen.rs @@ -0,0 +1,54 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! +# Generating an interface + +The simplest way to use Subxt is to generate an interface to a chain that you'd like to interact with. This generated interface allows you to build transactions and construct queries to access data while leveraging the full type safety of the Rust compiler. + +## The `#[subxt]` macro + +The simplest way to generate an interface to use is via the [`#[subxt]`](crate::subxt!) macro. Using this macro looks something like: + +```rust,no_run +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} +``` + +The macro takes a path to some node metadata, and uses that to generate the interface you'll use to talk to it. + +The simplest way to do obtain this metadata is to use the `subxt` CLI tool to download it from a node. The tool can be installed via `cargo`: + +```shell +cargo install subxt-cli +``` + +And then, use it to fetch metadata and save it to a file: + +```shell +# Download and save all of the metadata: +subxt metadata > metadata.scale +# Download and save only the pallets you want to generate an interface for: +subxt metadata --pallets Balances,System > metadata.scale +``` + +Explicitly specifying pallets will cause the tool to strip out all unnecessary metadata and type information, making the bundle much smaller in the event that you only need to generate an interface for a subset of the available pallets on the node. + + +## The CLI tool + +Using the [`#[subxt]`](crate::subxt!) macro carries some downsides, notably that using it to generate an interface will have an impact on compile times (though much less of one if you only need a few pallets), and that editor looking tends to not be very good at autocompleting and providing documentation for the generated interface. Additionally, you can't peer into the generated code and see what's going on if you use the macro. + +If you'd like to manually generate the same code that the macro generates under the hood, you can use the `subxt codegen` command: + +```rust +# Install the CLI tool if you haven't already: +cargo install subxt-cli +# Generate and format rust code, saving it to `interface.rs`: +subxt codegen | rustfmt > interface.rs +``` + +Use `subxt codegen --help` for more options; many of the options available via the macro are also available via the CLI tool, such as the abliity to substitute generated types for others, or strip out docs from the generated code. + +*/ \ No newline at end of file diff --git a/subxt/src/book/setup/mod.rs b/subxt/src/book/setup/mod.rs new file mode 100644 index 0000000000..4376529edf --- /dev/null +++ b/subxt/src/book/setup/mod.rs @@ -0,0 +1,15 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! +This modules contains details on setting up Subxt: + +- [Codegen](codegen) +- [Client](client) + +Alternately, [go back](super). +*/ + +mod codegen; +mod client; \ No newline at end of file diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs index 2fef79b121..9fda69ae08 100644 --- a/subxt/src/book/usage/blocks.rs +++ b/subxt/src/book/usage/blocks.rs @@ -5,11 +5,29 @@ /*! # Blocks -The [blocks API](crate::blocks::BlocksClient) in Subxt provides a way to: +The [blocks API](crate::blocks::BlocksClient) in Subxt unifies many of the other interfaces, and allows you to: - Access information about specific blocks (see [`crate::blocks::BlocksClient::at()`] and [`crate::blocks::BlocksClient::at_latest()`]). - Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()), [best](crate::blocks::BlocksClient::subscribe_best()) or [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced. Prefer to subscribe to finalized blocks unless you know what you're doing. -In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access the [storage](crate::blocks::Block::storage()), [events](crate::blocks::Block::events()) and [runtime APIs](crate::blocks::Block::runtime_api()) at that block, as well as acquire and iterate through the extrinsics in the block via [`crate::blocks::Block::extrinsics()`]. +In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various information about the block, such a the [header](crate::blocks::Block::header()), [block number](crate::blocks::Block::number()) and [body](crate::blocks::Block::body()). It also provides shortcuts to other Subxt APIs that work at a given block: + +- [storage](crate::blocks::Block::storage()), +- [events](crate::blocks::Block::events()) +- [runtime APIs](crate::blocks::Block::runtime_api()) + +Taken together, this means that you can subsscribe to blocks and then easily make use of the other Subxt APIs at each block. + +Given a block, you can also [download the block body](crate::blocks::Block::body()) and iterate over the extrinsics stored within it using [`crate::blocks::BlockBody::extrinsics()`]. + +## Example + +To put this together, here's an example of subscribing to blocks and printing a bunch of information about each one: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/blocks_subscribing.rs")] +//! ``` +/*! */ diff --git a/subxt/src/book/usage/mod.rs b/subxt/src/book/usage/mod.rs index 74524fc1f2..89d86d984c 100644 --- a/subxt/src/book/usage/mod.rs +++ b/subxt/src/book/usage/mod.rs @@ -10,6 +10,7 @@ This modules contains examples of using Subxt; follow the links for more: - [Events](events) - [Constants](constants) - [Blocks](blocks) +- [Runtime APIs](runtime_apis) Alternately, [go back](super). */ @@ -19,3 +20,4 @@ pub mod constants; pub mod events; pub mod extrinsics; pub mod storage; +pub mod runtime_apis; \ No newline at end of file diff --git a/subxt/src/book/usage/runtime_apis.rs b/subxt/src/book/usage/runtime_apis.rs new file mode 100644 index 0000000000..24671f2994 --- /dev/null +++ b/subxt/src/book/usage/runtime_apis.rs @@ -0,0 +1,22 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +/*! +# Runtime API interface + +The Runtime API interface allows Subxt to call runtime APIs exposed by certain pallets in order to obtain information. + +At the moment, this interface is simply a wrapper around the `state_call` RPC method. This means that you need to know which runtime calls are available and how to encode their parameters (if needed). Eventually, Subxt will be able to generate an interface to the Runtime APIs exposed here to make this as easy to do as constructing extrinsics or storage queries. + +## Example + +Downloading node metadata via the Metadata runtime API interface: + +*/ +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/runtime_apis_raw.rs")] +//! ``` +/*! + +*/ From 24849bc9876b650d408c6da32936895b17d31863 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 21 Apr 2023 15:15:56 +0100 Subject: [PATCH 07/25] Tidy and remove example section for now --- examples/README.md | 4 +++- subxt/src/book/examples/mod.rs | 0 subxt/src/book/mod.rs | 12 +----------- subxt/src/book/setup/client.rs | 2 +- subxt/src/book/setup/codegen.rs | 2 +- subxt/src/book/setup/mod.rs | 2 +- subxt/src/book/usage/blocks.rs | 10 ++++------ subxt/src/book/usage/mod.rs | 2 +- 8 files changed, 12 insertions(+), 22 deletions(-) delete mode 100644 subxt/src/book/examples/mod.rs diff --git a/examples/README.md b/examples/README.md index 30ae9c8b65..a7c3c186ab 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,5 @@ # Subxt Examples -Take a look in the [examples](./examples) subfolder for various `subxt` usage examples. \ No newline at end of file +Take a look in the [examples](./examples) subfolder for various `subxt` usage examples. + +All examples form part of the `subxt` documentation; there should be no examples without corresponding links from the docs. \ No newline at end of file diff --git a/subxt/src/book/examples/mod.rs b/subxt/src/book/examples/mod.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/subxt/src/book/mod.rs b/subxt/src/book/mod.rs index 870ade078b..e152c17cc7 100644 --- a/subxt/src/book/mod.rs +++ b/subxt/src/book/mod.rs @@ -11,7 +11,6 @@ Subxt is a library for interacting with Substrate based nodes. It has a focus on 2. [Limitations](#limitations) 3. [Quick start](#quick-start) 4. [Usage](#usage) -5. [Examples](#examples) ## Features at a glance @@ -88,15 +87,6 @@ Once Subxt is configured, the next step is actually interacting with a node. Fol - [Blocks](usage::blocks): Subxt can load recent blocks or subscribe to new/finalized blocks, reading the extrinsics, events and storage at these blocks. - [Runtime APIs](usage::runtime_apis): Subxt can make calls into pallet runtime APIs to retrieve data. -## Examples - -A set of examples to help showcase various Subxt features and functionality: - -- Building a WASM app with Subxt. -- Ditching the statically generated interface. -- Integrating with Substrate. -- Working offline. - */ pub mod setup; -pub mod usage; \ No newline at end of file +pub mod usage; diff --git a/subxt/src/book/setup/client.rs b/subxt/src/book/setup/client.rs index 44e0794a54..dc68e4dbf9 100644 --- a/subxt/src/book/setup/client.rs +++ b/subxt/src/book/setup/client.rs @@ -48,4 +48,4 @@ Creating an [`crate::OfflineClient`]: //! ``` /*! -*/ \ No newline at end of file +*/ diff --git a/subxt/src/book/setup/codegen.rs b/subxt/src/book/setup/codegen.rs index f114fc4f0b..9c6b7e8b92 100644 --- a/subxt/src/book/setup/codegen.rs +++ b/subxt/src/book/setup/codegen.rs @@ -51,4 +51,4 @@ subxt codegen | rustfmt > interface.rs Use `subxt codegen --help` for more options; many of the options available via the macro are also available via the CLI tool, such as the abliity to substitute generated types for others, or strip out docs from the generated code. -*/ \ No newline at end of file +*/ diff --git a/subxt/src/book/setup/mod.rs b/subxt/src/book/setup/mod.rs index 4376529edf..f6bd4fd8eb 100644 --- a/subxt/src/book/setup/mod.rs +++ b/subxt/src/book/setup/mod.rs @@ -11,5 +11,5 @@ This modules contains details on setting up Subxt: Alternately, [go back](super). */ +mod client; mod codegen; -mod client; \ No newline at end of file diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs index 9fda69ae08..f87760c912 100644 --- a/subxt/src/book/usage/blocks.rs +++ b/subxt/src/book/usage/blocks.rs @@ -10,19 +10,17 @@ The [blocks API](crate::blocks::BlocksClient) in Subxt unifies many of the other - Access information about specific blocks (see [`crate::blocks::BlocksClient::at()`] and [`crate::blocks::BlocksClient::at_latest()`]). - Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()), [best](crate::blocks::BlocksClient::subscribe_best()) or [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced. Prefer to subscribe to finalized blocks unless you know what you're doing. -In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various information about the block, such a the [header](crate::blocks::Block::header()), [block number](crate::blocks::Block::number()) and [body](crate::blocks::Block::body()). It also provides shortcuts to other Subxt APIs that work at a given block: +In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various information about the block, such a the [header](crate::blocks::Block::header()), [block number](crate::blocks::Block::number()) and [body](crate::blocks::Block::body()). [`crate::blocks::Block`]'s also provide shortcuts to other Subxt APIs that will operate at the given block: - [storage](crate::blocks::Block::storage()), - [events](crate::blocks::Block::events()) - [runtime APIs](crate::blocks::Block::runtime_api()) -Taken together, this means that you can subsscribe to blocks and then easily make use of the other Subxt APIs at each block. - -Given a block, you can also [download the block body](crate::blocks::Block::body()) and iterate over the extrinsics stored within it using [`crate::blocks::BlockBody::extrinsics()`]. - ## Example -To put this together, here's an example of subscribing to blocks and printing a bunch of information about each one: +Given a block, you can [download the block body](crate::blocks::Block::body()) and iterate over the extrinsics stored within it using [`crate::blocks::BlockBody::extrinsics()`]. + +Here's an example in which we subscribe to blocks and print a bunch of information about each one: */ //! ```rust,ignore diff --git a/subxt/src/book/usage/mod.rs b/subxt/src/book/usage/mod.rs index 89d86d984c..d6198e9048 100644 --- a/subxt/src/book/usage/mod.rs +++ b/subxt/src/book/usage/mod.rs @@ -19,5 +19,5 @@ pub mod blocks; pub mod constants; pub mod events; pub mod extrinsics; +pub mod runtime_apis; pub mod storage; -pub mod runtime_apis; \ No newline at end of file From 75a0ad1ff8efdb1bf1b42ec0a644fc9b0c616987 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 21 Apr 2023 15:34:28 +0100 Subject: [PATCH 08/25] format book lines to 100chars --- subxt/src/book/mod.rs | 208 ++++++++++-------- subxt/src/book/setup/client.rs | 79 +++---- subxt/src/book/setup/codegen.rs | 112 +++++----- subxt/src/book/setup/mod.rs | 14 +- subxt/src/book/usage/blocks.rs | 56 ++--- subxt/src/book/usage/constants.rs | 95 ++++----- subxt/src/book/usage/events.rs | 74 ++++--- subxt/src/book/usage/extrinsics.rs | 304 ++++++++++++++------------- subxt/src/book/usage/mod.rs | 22 +- subxt/src/book/usage/runtime_apis.rs | 31 +-- subxt/src/book/usage/storage.rs | 177 ++++++++-------- 11 files changed, 635 insertions(+), 537 deletions(-) diff --git a/subxt/src/book/mod.rs b/subxt/src/book/mod.rs index e152c17cc7..e872bb7aa3 100644 --- a/subxt/src/book/mod.rs +++ b/subxt/src/book/mod.rs @@ -2,91 +2,133 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -# The Subxt Guide - -Subxt is a library for interacting with Substrate based nodes. It has a focus on **sub**mitting e**xt**rinsics, hence the name, however it's also capable of reading blocks, storage, events and constants from a node. The aim of this guide is to explain key concepts and get you started with using Subxt. - -1. [Features](#features-at-a-glance) -2. [Limitations](#limitations) -3. [Quick start](#quick-start) -4. [Usage](#usage) - -## Features at a glance - -Here's a quick overview of the features that Subxt has to offer: - -- Subxt allows you to generate a static, type safe interface to a node given some metadata; this allows you to catch many errors at compile time rather than runtime. -- Subxt also makes heavy use of node metadata to encode/decode the data sent to/from it. This allows it to target almost any node which can output the correct metadata, and allows it some flexibility in encoding and decoding things to account for cross-node differences. -- Subxt has a pallet-oriented interface, meaning that code you write to talk to some pallet on one node will often "Just Work" when pointed at different nodes that use the same pallet. -- Subxt can work offline; you can generate and sign transactions, access constants from node metadata and more, without a network connection. This is all checked at compile time, so you can be certain it won't try to establish a network connection if you don't want it to. -- Subxt can forego the statically generated interface and build transactions, storage queries and constant queries using data provided at runtime, rather than queries constructed statically. -- Subxt can be compiled to WASM to run in the browser, allowing it to back Rust based browser apps, or even bind to JS apps. - -## Limitations - -In various places, you can provide a block hash to access data at a particular block, for instance: - -- [`crate::storage::StorageClient::at`] -- [`crate::events::EventsClient::at`] -- [`crate::blocks::BlocksClient::at`] -- [`crate::runtime_api::RuntimeApiClient::at`] - -However, Subxt is (by default) only capable of properly working with blocks that were produced after the most recent runtime update. This is because it uses the most recent metadata given back by a node to encode and decode things. It's possible to decode older blocks produced by a runtime that emits compatible (currently, V14) metadata by manually setting the metadata used by the client using [`crate::client::OnlineClient::set_metadata()`]. - -Subxt does not support working with blocks produced prior to the runtime update that introduces V14 metadata. It may have some success decoding older blocks using newer metadata, but may also completely fail to do so. - -## Quick start - -Here is a simple but complete example of using Subxt to transfer some tokens from Alice to Bob: - -*/ +// Dev note; I used the following command to normalize and wrap comments: +// rustfmt +nightly --config wrap_comments=true,comment_width=100,normalize_comments=true subxt/src/book/mod.rs +// It messed up comments in code blocks though, so be prepared to go and fix those. + +//! # The Subxt Guide +//! +//! Subxt is a library for interacting with Substrate based nodes. It has a focus on **sub**mitting +//! e**xt**rinsics, hence the name, however it's also capable of reading blocks, storage, events and +//! constants from a node. The aim of this guide is to explain key concepts and get you started with +//! using Subxt. +//! +//! 1. [Features](#features-at-a-glance) +//! 2. [Limitations](#limitations) +//! 3. [Quick start](#quick-start) +//! 4. [Usage](#usage) +//! +//! ## Features at a glance +//! +//! Here's a quick overview of the features that Subxt has to offer: +//! +//! - Subxt allows you to generate a static, type safe interface to a node given some metadata; this +//! allows you to catch many errors at compile time rather than runtime. +//! - Subxt also makes heavy use of node metadata to encode/decode the data sent to/from it. This +//! allows it to target almost any node which can output the correct metadata, and allows it some +//! flexibility in encoding and decoding things to account for cross-node differences. +//! - Subxt has a pallet-oriented interface, meaning that code you write to talk to some pallet on +//! one node will often "Just Work" when pointed at different nodes that use the same pallet. +//! - Subxt can work offline; you can generate and sign transactions, access constants from node +//! metadata and more, without a network connection. This is all checked at compile time, so you +//! can be certain it won't try to establish a network connection if you don't want it to. +//! - Subxt can forego the statically generated interface and build transactions, storage queries +//! and constant queries using data provided at runtime, rather than queries constructed +//! statically. +//! - Subxt can be compiled to WASM to run in the browser, allowing it to back Rust based browser +//! apps, or even bind to JS apps. +//! +//! ## Limitations +//! +//! In various places, you can provide a block hash to access data at a particular block, for +//! instance: +//! +//! - [`crate::storage::StorageClient::at`] +//! - [`crate::events::EventsClient::at`] +//! - [`crate::blocks::BlocksClient::at`] +//! - [`crate::runtime_api::RuntimeApiClient::at`] +//! +//! However, Subxt is (by default) only capable of properly working with blocks that were produced +//! after the most recent runtime update. This is because it uses the most recent metadata given +//! back by a node to encode and decode things. It's possible to decode older blocks produced by a +//! runtime that emits compatible (currently, V14) metadata by manually setting the metadata used by +//! the client using [`crate::client::OnlineClient::set_metadata()`]. +//! +//! Subxt does not support working with blocks produced prior to the runtime update that introduces +//! V14 metadata. It may have some success decoding older blocks using newer metadata, but may also +//! completely fail to do so. +//! +//! ## Quick start +//! +//! Here is a simple but complete example of using Subxt to transfer some tokens from Alice to Bob: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../examples/examples/balance_transfer_basic.rs")] //! ``` -/*! - -This example assumes that a Polkadot node is running locally (Subxt endeavours to support all recent releases). Typically, to use Subxt to talk to some custom Substrate node (for example a parachain node), you'll want to: - -1. [Generate an interface](setup::codegen). -2. [Configure and instantiate the client](setup::client). - -Follow the above links to learn more about each step. - -### Acquiring metadata - -The simplest way to do acquire metadata is to use the `subxt` CLI tool to download it from your node. The tool can be installed via `cargo`: - -```shell -cargo install subxt-cli -``` - -Once installed, run `subxt metadata > metadata.scale` to download the node's metadata and save it to some file (again, this assumes that the node you'd like to talk to is running locally; run `subxt metadata --help` to see other options). - -Sometimes it's useful to be able to manually output the code that's being generated by the [`#[subxt]`](crate::subxt) macro to better understand what's going on. To do this, use `subxt codegen | rustfmt > generated_interface.rs`. - -### Configuring the client - -To use Subxt, you must instantiate the client (typically an [`OnlineClient`](crate::client::OnlineClient)) with some [`Config`](crate::config::Config). Subxt ships with these default configurations: - -- [`crate::config::SubstrateConfig`] to talk to generic Substrate nodes. -- [`crate::config::PolkadotConfig`] to talk to Polkadot nodes. - -The configuration defines the types used for things like the account nonce, account ID and block header, so that it can encode or decode data from the node properly. It also defines how transactions should be signed, how data should be hashed, and which data should be sent along with an extrinsic. Thus, if the configuration is incorrect, Subxt may run into errors performing various actions against a node. - -In many cases, using one of the provided configuration implementations will work. If the node you're talking to is not compatible with the selected configuration, then you'll run into errors (particularly when trying to submit transactions or downloading blocks), and will need to find out what is different between the configuration you've used and the node in question (perhaps it expects things to be signed differently, or has a different address format for instance). - -## Usage - -Once Subxt is configured, the next step is actually interacting with a node. Follow the links below to learn more about how to use Subxt for each of the following things: - -- [Extrinsics](usage::extrinsics): Subxt can build and submit extrinsics, wait until they are in blocks, and retrieve the associated events. -- [Storage](usage::storage): Subxt can query the node storage. -- [Events](usage::events): Subxt can read the events emitted for recent blocks. -- [Constants](usage::constants): Subxt can access the constant values stored in a node, which remain the same for a given runtime version. -- [Blocks](usage::blocks): Subxt can load recent blocks or subscribe to new/finalized blocks, reading the extrinsics, events and storage at these blocks. -- [Runtime APIs](usage::runtime_apis): Subxt can make calls into pallet runtime APIs to retrieve data. - -*/ +//! +//! This example assumes that a Polkadot node is running locally (Subxt endeavours to support all +//! recent releases). Typically, to use Subxt to talk to some custom Substrate node (for example a +//! parachain node), you'll want to: +//! +//! 1. [Generate an interface](setup::codegen). +//! 2. [Configure and instantiate the client](setup::client). +//! +//! Follow the above links to learn more about each step. +//! +//! ### Acquiring metadata +//! +//! The simplest way to do acquire metadata is to use the `subxt` CLI tool to download it from your +//! node. The tool can be installed via `cargo`: +//! +//! ```shell +//! cargo install subxt-cli +//! ``` +//! +//! Once installed, run `subxt metadata > metadata.scale` to download the node's metadata and save +//! it to some file (again, this assumes that the node you'd like to talk to is running locally; run +//! `subxt metadata --help` to see other options). +//! +//! Sometimes it's useful to be able to manually output the code that's being generated by the +//! [`#[subxt]`](crate::subxt) macro to better understand what's going on. To do this, use `subxt +//! codegen | rustfmt > generated_interface.rs`. +//! +//! ### Configuring the client +//! +//! To use Subxt, you must instantiate the client (typically an +//! [`OnlineClient`](crate::client::OnlineClient)) with some [`Config`](crate::config::Config). +//! Subxt ships with these default configurations: +//! +//! - [`crate::config::SubstrateConfig`] to talk to generic Substrate nodes. +//! - [`crate::config::PolkadotConfig`] to talk to Polkadot nodes. +//! +//! The configuration defines the types used for things like the account nonce, account ID and block +//! header, so that it can encode or decode data from the node properly. It also defines how +//! transactions should be signed, how data should be hashed, and which data should be sent along +//! with an extrinsic. Thus, if the configuration is incorrect, Subxt may run into errors performing +//! various actions against a node. +//! +//! In many cases, using one of the provided configuration implementations will work. If the node +//! you're talking to is not compatible with the selected configuration, then you'll run into errors +//! (particularly when trying to submit transactions or downloading blocks), and will need to find +//! out what is different between the configuration you've used and the node in question (perhaps it +//! expects things to be signed differently, or has a different address format for instance). +//! +//! ## Usage +//! +//! Once Subxt is configured, the next step is actually interacting with a node. Follow the links +//! below to learn more about how to use Subxt for each of the following things: +//! +//! - [Extrinsics](usage::extrinsics): Subxt can build and submit extrinsics, wait until they are in +//! blocks, and retrieve the associated events. +//! - [Storage](usage::storage): Subxt can query the node storage. +//! - [Events](usage::events): Subxt can read the events emitted for recent blocks. +//! - [Constants](usage::constants): Subxt can access the constant values stored in a node, which +//! remain the same for a given runtime version. +//! - [Blocks](usage::blocks): Subxt can load recent blocks or subscribe to new/finalized blocks, +//! reading the extrinsics, events and storage at these blocks. +//! - [Runtime APIs](usage::runtime_apis): Subxt can make calls into pallet runtime APIs to retrieve +//! data. +//! pub mod setup; pub mod usage; diff --git a/subxt/src/book/setup/client.rs b/subxt/src/book/setup/client.rs index dc68e4dbf9..412ab95626 100644 --- a/subxt/src/book/setup/client.rs +++ b/subxt/src/book/setup/client.rs @@ -2,50 +2,53 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -# Configuring the Subxt client - -Subxt ships with two clients, an [offline client](crate::client::OfflineClient) and an [online client](crate::client::OnlineClient). These are backed by the traits [`crate::client::OfflineClientT`] and [`crate::client::OnlineClientT`], so in theory it's possible for users to implement their own clients, although this isn't generally expected. - -Both clients are generic over a [`crate::config::Config`] trait, which is the way that we give the client certain information about how to interact with a node that isn't otherwise available or possible to include in the node metadata. Subxt ships out of the box with two default implementations: - -- [`crate::config::PolkadotConfig`] for talking to Polkadot nodes, and -- [`crate::config::SubstrateConfig`] for talking to generic nodes built with Substrate. - -The latter will generally work in many cases, but will need modifying if the chain you'd like to connect to has altered any of the details mentioned in [the trait](`crate::config::Config`). - -In the case of the [`crate::OnlineClient`], we have a few options to instantiate it: - -- [`crate::OnlineClient::new()`] to connect to a node running locally. -- [`crate::OnlineClient::from_url()`] to connect to a node at a specific URL. -- [`crate::OnlineClient::from_rpc_client()`] to instantiate the client with a custom RPC implementation. - -The latter accepts anything that implements the low level [`crate::rpc::RpcClientT`] trait; this allows you to decide how Subxt will attempt to talk to a node if you'd prefer something other than the provided interfaces. - -## Examples - -Defining some custom config based off the default Substrate config: - -*/ +//! # Configuring the Subxt client +//! +//! Subxt ships with two clients, an [offline client](crate::client::OfflineClient) and an [online +//! client](crate::client::OnlineClient). These are backed by the traits +//! [`crate::client::OfflineClientT`] and [`crate::client::OnlineClientT`], so in theory it's +//! possible for users to implement their own clients, although this isn't generally expected. +//! +//! Both clients are generic over a [`crate::config::Config`] trait, which is the way that we give +//! the client certain information about how to interact with a node that isn't otherwise available +//! or possible to include in the node metadata. Subxt ships out of the box with two default +//! implementations: +//! +//! - [`crate::config::PolkadotConfig`] for talking to Polkadot nodes, and +//! - [`crate::config::SubstrateConfig`] for talking to generic nodes built with Substrate. +//! +//! The latter will generally work in many cases, but will need modifying if the chain you'd like to +//! connect to has altered any of the details mentioned in [the trait](`crate::config::Config`). +//! +//! In the case of the [`crate::OnlineClient`], we have a few options to instantiate it: +//! +//! - [`crate::OnlineClient::new()`] to connect to a node running locally. +//! - [`crate::OnlineClient::from_url()`] to connect to a node at a specific URL. +//! - [`crate::OnlineClient::from_rpc_client()`] to instantiate the client with a custom RPC +//! implementation. +//! +//! The latter accepts anything that implements the low level [`crate::rpc::RpcClientT`] trait; this +//! allows you to decide how Subxt will attempt to talk to a node if you'd prefer something other +//! than the provided interfaces. +//! +//! ## Examples +//! +//! Defining some custom config based off the default Substrate config: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/setup_client_custom_config.rs")] //! ``` -/*! - -Writing a custom [`crate::rpc::RpcClientT`] implementation: - -*/ +//! Writing a custom [`crate::rpc::RpcClientT`] implementation: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/setup_client_custom_rpc.rs")] //! ``` -/*! - -Creating an [`crate::OfflineClient`]: - -*/ +//! Creating an [`crate::OfflineClient`]: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/setup_client_offline.rs")] //! ``` -/*! - -*/ +//! diff --git a/subxt/src/book/setup/codegen.rs b/subxt/src/book/setup/codegen.rs index 9c6b7e8b92..c510e63513 100644 --- a/subxt/src/book/setup/codegen.rs +++ b/subxt/src/book/setup/codegen.rs @@ -2,53 +2,65 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -# Generating an interface - -The simplest way to use Subxt is to generate an interface to a chain that you'd like to interact with. This generated interface allows you to build transactions and construct queries to access data while leveraging the full type safety of the Rust compiler. - -## The `#[subxt]` macro - -The simplest way to generate an interface to use is via the [`#[subxt]`](crate::subxt!) macro. Using this macro looks something like: - -```rust,no_run -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} -``` - -The macro takes a path to some node metadata, and uses that to generate the interface you'll use to talk to it. - -The simplest way to do obtain this metadata is to use the `subxt` CLI tool to download it from a node. The tool can be installed via `cargo`: - -```shell -cargo install subxt-cli -``` - -And then, use it to fetch metadata and save it to a file: - -```shell -# Download and save all of the metadata: -subxt metadata > metadata.scale -# Download and save only the pallets you want to generate an interface for: -subxt metadata --pallets Balances,System > metadata.scale -``` - -Explicitly specifying pallets will cause the tool to strip out all unnecessary metadata and type information, making the bundle much smaller in the event that you only need to generate an interface for a subset of the available pallets on the node. - - -## The CLI tool - -Using the [`#[subxt]`](crate::subxt!) macro carries some downsides, notably that using it to generate an interface will have an impact on compile times (though much less of one if you only need a few pallets), and that editor looking tends to not be very good at autocompleting and providing documentation for the generated interface. Additionally, you can't peer into the generated code and see what's going on if you use the macro. - -If you'd like to manually generate the same code that the macro generates under the hood, you can use the `subxt codegen` command: - -```rust -# Install the CLI tool if you haven't already: -cargo install subxt-cli -# Generate and format rust code, saving it to `interface.rs`: -subxt codegen | rustfmt > interface.rs -``` - -Use `subxt codegen --help` for more options; many of the options available via the macro are also available via the CLI tool, such as the abliity to substitute generated types for others, or strip out docs from the generated code. - -*/ +//! # Generating an interface +//! +//! The simplest way to use Subxt is to generate an interface to a chain that you'd like to interact +//! with. This generated interface allows you to build transactions and construct queries to access +//! data while leveraging the full type safety of the Rust compiler. +//! +//! ## The `#[subxt]` macro +//! +//! The simplest way to generate an interface to use is via the [`#[subxt]`](crate::subxt!) macro. +//! Using this macro looks something like: +//! +//! ```rust,no_run +//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +//! pub mod polkadot {} +//! ``` +//! +//! The macro takes a path to some node metadata, and uses that to generate the interface you'll use +//! to talk to it. +//! +//! The simplest way to do obtain this metadata is to use the `subxt` CLI tool to download it from a +//! node. The tool can be installed via `cargo`: +//! +//! ```shell +//! cargo install subxt-cli +//! ``` +//! +//! And then, use it to fetch metadata and save it to a file: +//! +//! ```shell +//! # Download and save all of the metadata: +//! subxt metadata > metadata.scale +//! # Download and save only the pallets you want to generate an interface for: +//! subxt metadata --pallets Balances,System > metadata.scale +//! ``` +//! +//! Explicitly specifying pallets will cause the tool to strip out all unnecessary metadata and type +//! information, making the bundle much smaller in the event that you only need to generate an +//! interface for a subset of the available pallets on the node. +//! +//! +//! ## The CLI tool +//! +//! Using the [`#[subxt]`](crate::subxt!) macro carries some downsides, notably that using it to +//! generate an interface will have an impact on compile times (though much less of one if you only +//! need a few pallets), and that editor looking tends to not be very good at autocompleting and +//! providing documentation for the generated interface. Additionally, you can't peer into the +//! generated code and see what's going on if you use the macro. +//! +//! If you'd like to manually generate the same code that the macro generates under the hood, you +//! can use the `subxt codegen` command: +//! +//! ```rust +//! # Install the CLI tool if you haven't already: +//! cargo install subxt-cli +//! # Generate and format rust code, saving it to `interface.rs`: +//! subxt codegen | rustfmt > interface.rs +//! ``` +//! +//! Use `subxt codegen --help` for more options; many of the options available via the macro are +//! also available via the CLI tool, such as the abliity to substitute generated types for others, +//! or strip out docs from the generated code. +//! diff --git a/subxt/src/book/setup/mod.rs b/subxt/src/book/setup/mod.rs index f6bd4fd8eb..5d0bafdfd9 100644 --- a/subxt/src/book/setup/mod.rs +++ b/subxt/src/book/setup/mod.rs @@ -2,14 +2,12 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -This modules contains details on setting up Subxt: - -- [Codegen](codegen) -- [Client](client) - -Alternately, [go back](super). -*/ +//! This modules contains details on setting up Subxt: +//! +//! - [Codegen](codegen) +//! - [Client](client) +//! +//! Alternately, [go back](super). mod client; mod codegen; diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs index f87760c912..406391010c 100644 --- a/subxt/src/book/usage/blocks.rs +++ b/subxt/src/book/usage/blocks.rs @@ -2,30 +2,38 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -# Blocks - -The [blocks API](crate::blocks::BlocksClient) in Subxt unifies many of the other interfaces, and allows you to: - -- Access information about specific blocks (see [`crate::blocks::BlocksClient::at()`] and [`crate::blocks::BlocksClient::at_latest()`]). -- Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()), [best](crate::blocks::BlocksClient::subscribe_best()) or [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced. Prefer to subscribe to finalized blocks unless you know what you're doing. - -In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various information about the block, such a the [header](crate::blocks::Block::header()), [block number](crate::blocks::Block::number()) and [body](crate::blocks::Block::body()). [`crate::blocks::Block`]'s also provide shortcuts to other Subxt APIs that will operate at the given block: - -- [storage](crate::blocks::Block::storage()), -- [events](crate::blocks::Block::events()) -- [runtime APIs](crate::blocks::Block::runtime_api()) - -## Example - -Given a block, you can [download the block body](crate::blocks::Block::body()) and iterate over the extrinsics stored within it using [`crate::blocks::BlockBody::extrinsics()`]. - -Here's an example in which we subscribe to blocks and print a bunch of information about each one: - -*/ +//! # Blocks +//! +//! The [blocks API](crate::blocks::BlocksClient) in Subxt unifies many of the other interfaces, and +//! allows you to: +//! +//! - Access information about specific blocks (see [`crate::blocks::BlocksClient::at()`] and +//! [`crate::blocks::BlocksClient::at_latest()`]). +//! - Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()), +//! [best](crate::blocks::BlocksClient::subscribe_best()) or +//! [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced. +//! Prefer to subscribe to finalized blocks unless you know what you're doing. +//! +//! In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various +//! information about the block, such a the [header](crate::blocks::Block::header()), [block +//! number](crate::blocks::Block::number()) and [body](crate::blocks::Block::body()). +//! [`crate::blocks::Block`]'s also provide shortcuts to other Subxt APIs that will operate at the +//! given block: +//! +//! - [storage](crate::blocks::Block::storage()), +//! - [events](crate::blocks::Block::events()) +//! - [runtime APIs](crate::blocks::Block::runtime_api()) +//! +//! ## Example +//! +//! Given a block, you can [download the block body](crate::blocks::Block::body()) and iterate over +//! the extrinsics stored within it using [`crate::blocks::BlockBody::extrinsics()`]. +//! +//! Here's an example in which we subscribe to blocks and print a bunch of information about each +//! one: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/blocks_subscribing.rs")] //! ``` -/*! - -*/ +//! diff --git a/subxt/src/book/usage/constants.rs b/subxt/src/book/usage/constants.rs index 6e8b087380..b3869f9176 100644 --- a/subxt/src/book/usage/constants.rs +++ b/subxt/src/book/usage/constants.rs @@ -2,56 +2,57 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -# Constants - -There are various constants stored in a node; these can only change when the runtime is updated. Much like [`super::storage`], we can query these using Subxt by taking the following steps: - -1. [Constructing a constant query](#constructing-a-query). -2. [Submitting the query to get back the associated value](#submitting-it). - -## Constructing a constant query - -We can use the statically generated interface to build constant queries: - -```rust,no_run -use sp_keyring::AccountKeyring; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -let constant_query = polkadot::constants().system().block_length(); -``` - -Alternately, we can dynamically construct a constant query: - -```rust,no_run -use subxt::dynamic::Value; - -let account = AccountKeyring::Alice.to_account_id(); -let storage_query = subxt::dynamic::constant("System", "BlockLength"); -``` - -Static queries also have a static return type, so the constant is decoded appropriately. In addition, they are validated at runtime to ensure that they align with the current node state. Dynamic queries must be decoded into some static type manually, or into the dynamic [`crate::dynamic::Value`] type. - -## Submitting it - -Constant queries are submitted via [`crate::constants::ConstantsClient::at()`]. It's worth noting that constant values are pulled directly out of the node metadata which the client has already acquired, and so this function is available from a [`crate::OfflineClient`]. - -Here's an example using a static query: - -*/ +//! # Constants +//! +//! There are various constants stored in a node; these can only change when the runtime is updated. +//! Much like [`super::storage`], we can query these using Subxt by taking the following steps: +//! +//! 1. [Constructing a constant query](#constructing-a-query). +//! 2. [Submitting the query to get back the associated value](#submitting-it). +//! +//! ## Constructing a constant query +//! +//! We can use the statically generated interface to build constant queries: +//! +//! ```rust,no_run +//! use sp_keyring::AccountKeyring; +//! +//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +//! pub mod polkadot {} +//! +//! let constant_query = polkadot::constants().system().block_length(); +//! ``` +//! +//! Alternately, we can dynamically construct a constant query: +//! +//! ```rust,no_run +//! use subxt::dynamic::Value; +//! +//! let account = AccountKeyring::Alice.to_account_id(); +//! let storage_query = subxt::dynamic::constant("System", "BlockLength"); +//! ``` +//! +//! Static queries also have a static return type, so the constant is decoded appropriately. In +//! addition, they are validated at runtime to ensure that they align with the current node state. +//! Dynamic queries must be decoded into some static type manually, or into the dynamic +//! [`crate::dynamic::Value`] type. +//! +//! ## Submitting it +//! +//! Constant queries are submitted via [`crate::constants::ConstantsClient::at()`]. It's worth +//! noting that constant values are pulled directly out of the node metadata which the client has +//! already acquired, and so this function is available from a [`crate::OfflineClient`]. +//! +//! Here's an example using a static query: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/constants_static.rs")] //! ``` -/*! - -And here's one using a dynamic query: - -*/ +//! And here's one using a dynamic query: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/constants_dynamic.rs")] //! ``` -/*! - -*/ +//! diff --git a/subxt/src/book/usage/events.rs b/subxt/src/book/usage/events.rs index d4c4fe1256..7e122803a7 100644 --- a/subxt/src/book/usage/events.rs +++ b/subxt/src/book/usage/events.rs @@ -2,40 +2,46 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -# Events - -In the process of adding extrinsics to a block, they are executed. When extrinsics are executed, they may produce events describing what's happening, but additionally the node may add emit some events of its own as the blook is processed. Events live in a single location in node storage which is overwritten at each block. - -When we submit extrinsics using Subxt, methods like [`crate::tx::TxProgress::wait_for_finalized_success()`] return [`crate::blocks::ExtrinsicEvents`], which can be used to iterate and inspect the events produced for a specific extrinsic. We can also access _all_ of the events produced in a single block using one of these two interfaces: - -```rust,no_run -# #[tokio::main] -# async fn main() -> Result<(), Box> { -use subxt::client::OnlineClient; -use subxt::config::PolkadotConfig; - -// Create client: -let client = OnlineClient::::new().await?; - -// Get events from the latest block: -let events = client.blocks().at_latest().await?.events().await?; -// We can use this shorthand too: -let events = client.events().at_latest().await?; -# Ok(()) -# } -``` - -Once we've loaded our events, we can iterate all events or search for specific events via methods like [`crate::events::Events::iter()`] and [`crate::events::Events::find()`]. See [`crate::events::Events`] and [`crate::events::EventDetails`] for more information. - -## Example - -Here's an example which puts this all together: - -*/ +//! # Events +//! +//! In the process of adding extrinsics to a block, they are executed. When extrinsics are executed, +//! they may produce events describing what's happening, but additionally the node may add emit some +//! events of its own as the blook is processed. Events live in a single location in node storage +//! which is overwritten at each block. +//! +//! When we submit extrinsics using Subxt, methods like +//! [`crate::tx::TxProgress::wait_for_finalized_success()`] return +//! [`crate::blocks::ExtrinsicEvents`], which can be used to iterate and inspect the events produced +//! for a specific extrinsic. We can also access _all_ of the events produced in a single block +//! using one of these two interfaces: +//! +//! ```rust,no_run +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! use subxt::client::OnlineClient; +//! use subxt::config::PolkadotConfig; +//! +//! // Create client: +//! let client = OnlineClient::::new().await?; +//! +//! // Get events from the latest block: +//! let events = client.blocks().at_latest().await?.events().await?; +//! // We can use this shorthand too: +//! let events = client.events().at_latest().await?; +//! # Ok(()) +//! # } +//! ``` +//! +//! Once we've loaded our events, we can iterate all events or search for specific events via +//! methods like [`crate::events::Events::iter()`] and [`crate::events::Events::find()`]. See +//! [`crate::events::Events`] and [`crate::events::EventDetails`] for more information. +//! +//! ## Example +//! +//! Here's an example which puts this all together: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/events.rs")] //! ``` -/*! - -*/ +//! diff --git a/subxt/src/book/usage/extrinsics.rs b/subxt/src/book/usage/extrinsics.rs index a7932cf98e..7a2d32ad08 100644 --- a/subxt/src/book/usage/extrinsics.rs +++ b/subxt/src/book/usage/extrinsics.rs @@ -2,154 +2,174 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -# Extrinsics - -Extrinsics define function calls and their parameters, and are the only way that you can change the state of the blockchain. Submitting extrinsics to a node is one of the core features of Subxt, and generally consists of the following steps: - -1. [Constructing an extrinsic payload to submit](#constructing-an-extrinsic-payload). -2. [Signing it](#signing-it). -3. [Submitting it (optionally with some additional parameters)](#submitting-it). - -We'll look at each of these steps in turn. - -> As a side note, an _extrinsic_ is anything that can be added to a block, and a _transaction_ is an extrinsic that is submitted from a particular user (and is therefore also signed). Subxt can construct unsigned extrinsics, but overwhelmingly you'll need to sign them, and so the documentation tends to use the terms _extrinsic_ and _transaction_ interchangably. - -## Constructing an extrinsic payload - -We can use the statically generated interface to build extrinsic payloads: - -```rust,no_run -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -let remark = "Hello there".as_bytes().to_vec(); -let extrinsic_payload = polkadot::tx().system().remark(remark); -``` - -> If you're not sure what types to import and use to build a given payload, you can use the `subxt` CLI tool to generate the interface by using something like `subxt codegen | rustfmt > interface.rs`, to see what types and things are available (or even just to use directly instead of the [`#[subxt]`](crate::subxt) macro). - -Alternately, we can dynamically construct an extrinsic payload. This will not be type checked or validated until it's submitted: - -```rust,no_run -use subxt::dynamic::Value; - -let extrinsic_payload = subxt::dynamic::tx("System", "remark", vec![ - Value::from_bytes("Hello there") -]); -``` - -The [`crate::dynamic::Value`] type is a dynamic type much like a `serde_json::Value` but instead represents any type of data that can be SCALE encoded or decoded. It can be serialized, deserialized and parsed from/to strings. - -A valid extrinsic payload is just something that implements the [`crate::tx::TxPayload`] trait; you can implement this trait on your own custom types if the built-in ones are not suitable for your needs. - -## Signing it - -You'll normally need to sign an extrinsic to prove that it originated from an account that you control. To do this, you will typically first create an [`crate::tx::Signer`], which tells Subxt who the extrinsic is from, and takes care of signing the relevant details to prove this. - -Subxt provides a [`crate::tx::PairSigner`] which implements this trait (if the `substrate-compat` feature is enabled) which accepts any valid [`sp_core::Pair`] and uses that to sign transactions: - -```rust -use subxt::tx::PairSigner; -use sp_core::Pair; -use subxt::config::PolkadotConfig; - -// Get hold of a `Signer` given a test account: -let pair = sp_keyring::AccountKeyring::Alice.pair(); -let signer = PairSigner::::new(pair); - -// Or generate an `sr25519` keypair to use: -let (pair, _, _) = sp_core::sr25519::Pair::generate_with_phrase(Some("password")); -let signer = PairSigner::::new(pair); -``` - -See the [`sp_core::Pair`] docs for more ways to generate them. - -If this isn't suitable/available, you can either implement [`crate::tx::Signer`] yourself to use custom signing logic, or you can use some external signing logic, like so: - -```rust,no_run -# #[tokio::main] -# async fn main() -> Result<(), Box> { -use subxt::client::OnlineClient; -use subxt::config::PolkadotConfig; -use subxt::dynamic::Value; - -// Create client: -let client = OnlineClient::::new().await?; - -// Create a dummy extrinsic payload to sign: -let payload = subxt::dynamic::tx("System", "remark", vec![ - Value::from_bytes("Hello there") -]); - -// Construct the extrinsic but don't sign it. You need to provide the nonce -// here, or can use `create_partial_signed` to fetch the correct nonce. -let partial_extrinsic = client.tx().create_partial_signed_with_nonce( - &payload, - 0, - Default::default() -)?; - -// Fetch the payload that needs to be signed: -let signer_payload = partial_extrinsic.signer_payload(); - -// ... At this point, we can hand off the `signer_payload` to be signed externally. -// Ultimately we need to be given back a `signature` (or really, anything -// that can be SCALE encoded) and an `address`: -let signature; -let address; -# use subxt::tx::Signer; -# let pair = sp_keyring::AccountKeyring::Alice.pair(); -# let signer = subxt::tx::PairSigner::::new(pair); -# signature = signer.sign(&signer_payload); -# address = signer.address(); - -// Now we can build an extrinsic, which one can call `submit` or `submit_and_watch` -// on to submit to a node and optionally watch the status. -let extrinsic = partial_extrinsic.sign_with_address_and_signature( - &address, - &signature -); -# Ok(()) -# } -``` - -## Submitting it - -Once we are able to sign the extrinsic, we need to submit it. - -### The high level API - -The highest level approach to doing this is to call [`crate::tx::TxClient::sign_and_submit_then_watch_default`]. This hands back a [`crate::tx::TxProgress`] struct which will monitor the transaction status. We can then call [`crate::tx::TxProgress::wait_for_finalized_success()`] to wait for this transaction to make it into a finalized block, check for an `ExtrinsicSuccess` event, and then hand back the events for inspection. This looks like: - -*/ +//! # Extrinsics +//! +//! Extrinsics define function calls and their parameters, and are the only way that you can change +//! the state of the blockchain. Submitting extrinsics to a node is one of the core features of +//! Subxt, and generally consists of the following steps: +//! +//! 1. [Constructing an extrinsic payload to submit](#constructing-an-extrinsic-payload). +//! 2. [Signing it](#signing-it). +//! 3. [Submitting it (optionally with some additional parameters)](#submitting-it). +//! +//! We'll look at each of these steps in turn. +//! +//! > As a side note, an _extrinsic_ is anything that can be added to a block, and a _transaction_ +//! > is an extrinsic that is submitted from a particular user (and is therefore also signed). Subxt +//! > can construct unsigned extrinsics, but overwhelmingly you'll need to sign them, and so the +//! > documentation tends to use the terms _extrinsic_ and _transaction_ interchangably. +//! +//! ## Constructing an extrinsic payload +//! +//! We can use the statically generated interface to build extrinsic payloads: +//! +//! ```rust,no_run +//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +//! pub mod polkadot {} +//! +//! let remark = "Hello there".as_bytes().to_vec(); +//! let extrinsic_payload = polkadot::tx().system().remark(remark); +//! ``` +//! +//! > If you're not sure what types to import and use to build a given payload, you can use the +//! > `subxt` CLI tool to generate the interface by using something like `subxt codegen | rustfmt > +//! > interface.rs`, to see what types and things are available (or even just to use directly +//! > instead of the [`#[subxt]`](crate::subxt) macro). +//! +//! Alternately, we can dynamically construct an extrinsic payload. This will not be type checked or +//! validated until it's submitted: +//! +//! ```rust,no_run +//! use subxt::dynamic::Value; +//! +//! let extrinsic_payload = subxt::dynamic::tx("System", "remark", vec![ +//! Value::from_bytes("Hello there") +//! ]); +//! ``` +//! +//! The [`crate::dynamic::Value`] type is a dynamic type much like a `serde_json::Value` but instead +//! represents any type of data that can be SCALE encoded or decoded. It can be serialized, +//! deserialized and parsed from/to strings. +//! +//! A valid extrinsic payload is just something that implements the [`crate::tx::TxPayload`] trait; +//! you can implement this trait on your own custom types if the built-in ones are not suitable for +//! your needs. +//! +//! ## Signing it +//! +//! You'll normally need to sign an extrinsic to prove that it originated from an account that you +//! control. To do this, you will typically first create an [`crate::tx::Signer`], which tells Subxt +//! who the extrinsic is from, and takes care of signing the relevant details to prove this. +//! +//! Subxt provides a [`crate::tx::PairSigner`] which implements this trait (if the +//! `substrate-compat` feature is enabled) which accepts any valid [`sp_core::Pair`] and uses that +//! to sign transactions: +//! +//! ```rust +//! use subxt::tx::PairSigner; +//! use sp_core::Pair; +//! use subxt::config::PolkadotConfig; +//! +//! // Get hold of a `Signer` given a test account: +//! let pair = sp_keyring::AccountKeyring::Alice.pair(); +//! let signer = PairSigner::::new(pair); +//! +//! // Or generate an `sr25519` keypair to use: +//! let (pair, _, _) = sp_core::sr25519::Pair::generate_with_phrase(Some("password")); +//! let signer = PairSigner::::new(pair); +//! ``` +//! +//! See the [`sp_core::Pair`] docs for more ways to generate them. +//! +//! If this isn't suitable/available, you can either implement [`crate::tx::Signer`] yourself to use +//! custom signing logic, or you can use some external signing logic, like so: +//! +//! ```rust,no_run +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! use subxt::client::OnlineClient; +//! use subxt::config::PolkadotConfig; +//! use subxt::dynamic::Value; +//! +//! // Create client: +//! let client = OnlineClient::::new().await?; +//! +//! // Create a dummy extrinsic payload to sign: +//! let payload = subxt::dynamic::tx("System", "remark", vec![ +//! Value::from_bytes("Hello there") +//! ]); +//! +//! // Construct the extrinsic but don't sign it. You need to provide the nonce +//! // here, or can use `create_partial_signed` to fetch the correct nonce. +//! let partial_extrinsic = client.tx().create_partial_signed_with_nonce( +//! &payload, +//! 0, +//! Default::default() +//! )?; +//! +//! // Fetch the payload that needs to be signed: +//! let signer_payload = partial_extrinsic.signer_payload(); +//! +//! // ... At this point, we can hand off the `signer_payload` to be signed externally. +//! // Ultimately we need to be given back a `signature` (or really, anything +//! // that can be SCALE encoded) and an `address`: +//! let signature; +//! let address; +//! # use subxt::tx::Signer; +//! # let pair = sp_keyring::AccountKeyring::Alice.pair(); +//! # let signer = subxt::tx::PairSigner::::new(pair); +//! # signature = signer.sign(&signer_payload); +//! # address = signer.address(); +//! +//! // Now we can build an extrinsic, which one can call `submit` or `submit_and_watch` +//! // on to submit to a node and optionally watch the status. +//! let extrinsic = partial_extrinsic.sign_with_address_and_signature( +//! &address, +//! &signature +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Submitting it +//! +//! Once we are able to sign the extrinsic, we need to submit it. +//! +//! ### The high level API +//! +//! The highest level approach to doing this is to call +//! [`crate::tx::TxClient::sign_and_submit_then_watch_default`]. This hands back a +//! [`crate::tx::TxProgress`] struct which will monitor the transaction status. We can then call +//! [`crate::tx::TxProgress::wait_for_finalized_success()`] to wait for this transaction to make it +//! into a finalized block, check for an `ExtrinsicSuccess` event, and then hand back the events for +//! inspection. This looks like: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/balance_transfer_basic.rs")] //! ``` -/*! - -### Providing extrinsic parameters - -If you'd like to provide extrinsic parameters (such as mortality), you can use [`crate::tx::TxClient::sign_and_submit_then_watch`] instead, and provide parameters for your chain: - -*/ +//! ### Providing extrinsic parameters +//! +//! If you'd like to provide extrinsic parameters (such as mortality), you can use +//! [`crate::tx::TxClient::sign_and_submit_then_watch`] instead, and provide parameters for your +//! chain: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/balance_transfer_with_params.rs")] //! ``` -/*! - -This example doesn't wait for the extrinsic to be included in a block; it just submits it and hopes for the best! - -### Custom handling of transaction status updates - -If you'd like more control or visibility over exactly which status updates are being emitted for the transaction, you can monitor them as they are emitted and react however you choose: - -*/ +//! This example doesn't wait for the extrinsic to be included in a block; it just submits it and +//! hopes for the best! +//! +//! ### Custom handling of transaction status updates +//! +//! If you'd like more control or visibility over exactly which status updates are being emitted for +//! the transaction, you can monitor them as they are emitted and react however you choose: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/balance_transfer_status_stream.rs")] //! ``` -/*! - -Take a look at the API docs for [`crate::tx::TxProgress`], [`crate::tx::TxStatus`] and [`crate::tx::TxInBlock`] for more options. - -*/ +//! Take a look at the API docs for [`crate::tx::TxProgress`], [`crate::tx::TxStatus`] and +//! [`crate::tx::TxInBlock`] for more options. +//! diff --git a/subxt/src/book/usage/mod.rs b/subxt/src/book/usage/mod.rs index d6198e9048..1493756d17 100644 --- a/subxt/src/book/usage/mod.rs +++ b/subxt/src/book/usage/mod.rs @@ -2,18 +2,16 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -This modules contains examples of using Subxt; follow the links for more: - -- [Extrinsics](extrinsics) -- [Storage](storage) -- [Events](events) -- [Constants](constants) -- [Blocks](blocks) -- [Runtime APIs](runtime_apis) - -Alternately, [go back](super). -*/ +//! This modules contains examples of using Subxt; follow the links for more: +//! +//! - [Extrinsics](extrinsics) +//! - [Storage](storage) +//! - [Events](events) +//! - [Constants](constants) +//! - [Blocks](blocks) +//! - [Runtime APIs](runtime_apis) +//! +//! Alternately, [go back](super). pub mod blocks; pub mod constants; diff --git a/subxt/src/book/usage/runtime_apis.rs b/subxt/src/book/usage/runtime_apis.rs index 24671f2994..d1b8b0c631 100644 --- a/subxt/src/book/usage/runtime_apis.rs +++ b/subxt/src/book/usage/runtime_apis.rs @@ -2,21 +2,22 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -# Runtime API interface - -The Runtime API interface allows Subxt to call runtime APIs exposed by certain pallets in order to obtain information. - -At the moment, this interface is simply a wrapper around the `state_call` RPC method. This means that you need to know which runtime calls are available and how to encode their parameters (if needed). Eventually, Subxt will be able to generate an interface to the Runtime APIs exposed here to make this as easy to do as constructing extrinsics or storage queries. - -## Example - -Downloading node metadata via the Metadata runtime API interface: - -*/ +//! # Runtime API interface +//! +//! The Runtime API interface allows Subxt to call runtime APIs exposed by certain pallets in order +//! to obtain information. +//! +//! At the moment, this interface is simply a wrapper around the `state_call` RPC method. This means +//! that you need to know which runtime calls are available and how to encode their parameters (if +//! needed). Eventually, Subxt will be able to generate an interface to the Runtime APIs exposed +//! here to make this as easy to do as constructing extrinsics or storage queries. +//! +//! ## Example +//! +//! Downloading node metadata via the Metadata runtime API interface: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/runtime_apis_raw.rs")] //! ``` -/*! - -*/ +//! diff --git a/subxt/src/book/usage/storage.rs b/subxt/src/book/usage/storage.rs index f35e6b9e35..2723c56732 100644 --- a/subxt/src/book/usage/storage.rs +++ b/subxt/src/book/usage/storage.rs @@ -2,99 +2,108 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -/*! -# Storage - -A Substrate based chain has storage, whose values are determined by the extrinsics added to past blocks. Subxt allows you to query the storage of a node, which consists of the following steps: - -1. [Constructing a storage query](#constructing-a-storage-query). -2. [Submitting the query to get back the associated values](#submitting-it). - -## Constructing a storage query - -We can use the statically generated interface to build storage queries: - -```rust,no_run -use sp_keyring::AccountKeyring; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -let account = AccountKeyring::Alice.to_account_id().into(); -let storage_query = polkadot::storage().system().account(&account); -``` - -Alternately, we can dynamically construct a storage query. This will not be type checked or validated until it's submitted: - -```rust,no_run -use subxt::dynamic::Value; - -let account = AccountKeyring::Alice.to_account_id(); -let storage_query = subxt::dynamic::storage("System", "Account", vec![ - Value::from_bytes(account) -]); -``` - -As well as accessing specific entries, some storage locations can also be iterated over (such as the map of account information). Do do this, suffix `_root` onto the query constructor (this will only be available on static constructors when iteration is actually possible): - -```rust,no_run -use sp_keyring::AccountKeyring; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -// A static query capable of iterating over accounts: -let storage_query = polkadot::storage().system().account_root(); -// A dynamic query to do the same: -let storage_query = subxt::dynamic::storage_root("System", "Account"); -``` - -All valid storage queries implement [`crate::storage::StorageAddress`]. As well as describing how to build a valid storage query, this trait also has some associated types that determine the shape of the result you'll get back, and determine what you can do with it (ie, can you iterate over storage entries using it). - -Static queries set appropriate values for these associated types, and can therefore only be used where it makes sense. Dyanmic queries don't know any better and can be used in more places, but may fail at runtime instead if they are invalid in those places. - -## Submitting it - -Storage queries can be handed to various functions in [`crate::storage::Storage`] in order to obtain the associated values (also referred to as storage entries) back. - -### Fetching storage entries - -The simplest way to access storage entries is to construct a query and then call either [`crate::storage::Storage::fetch()`] or [`crate::storage::Storage::fetch_or_default()`] (the latter will only work for storage queries that have a default value when empty): - -*/ +//! # Storage +//! +//! A Substrate based chain has storage, whose values are determined by the extrinsics added to past +//! blocks. Subxt allows you to query the storage of a node, which consists of the following steps: +//! +//! 1. [Constructing a storage query](#constructing-a-storage-query). +//! 2. [Submitting the query to get back the associated values](#submitting-it). +//! +//! ## Constructing a storage query +//! +//! We can use the statically generated interface to build storage queries: +//! +//! ```rust,no_run +//! use sp_keyring::AccountKeyring; +//! +//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +//! pub mod polkadot {} +//! +//! let account = AccountKeyring::Alice.to_account_id().into(); +//! let storage_query = polkadot::storage().system().account(&account); +//! ``` +//! +//! Alternately, we can dynamically construct a storage query. This will not be type checked or +//! validated until it's submitted: +//! +//! ```rust,no_run +//! use subxt::dynamic::Value; +//! +//! let account = AccountKeyring::Alice.to_account_id(); +//! let storage_query = subxt::dynamic::storage("System", "Account", vec![ +//! Value::from_bytes(account) +//! ]); +//! ``` +//! +//! As well as accessing specific entries, some storage locations can also be iterated over (such as +//! the map of account information). Do do this, suffix `_root` onto the query constructor (this +//! will only be available on static constructors when iteration is actually possible): +//! +//! ```rust,no_run +//! use sp_keyring::AccountKeyring; +//! +//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +//! pub mod polkadot {} +//! +//! // A static query capable of iterating over accounts: +//! let storage_query = polkadot::storage().system().account_root(); +//! // A dynamic query to do the same: +//! let storage_query = subxt::dynamic::storage_root("System", "Account"); +//! ``` +//! +//! All valid storage queries implement [`crate::storage::StorageAddress`]. As well as describing +//! how to build a valid storage query, this trait also has some associated types that determine the +//! shape of the result you'll get back, and determine what you can do with it (ie, can you iterate +//! over storage entries using it). +//! +//! Static queries set appropriate values for these associated types, and can therefore only be used +//! where it makes sense. Dyanmic queries don't know any better and can be used in more places, but +//! may fail at runtime instead if they are invalid in those places. +//! +//! ## Submitting it +//! +//! Storage queries can be handed to various functions in [`crate::storage::Storage`] in order to +//! obtain the associated values (also referred to as storage entries) back. +//! +//! ### Fetching storage entries +//! +//! The simplest way to access storage entries is to construct a query and then call either +//! [`crate::storage::Storage::fetch()`] or [`crate::storage::Storage::fetch_or_default()`] (the +//! latter will only work for storage queries that have a default value when empty): +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/storage_fetch.rs")] //! ``` -/*! - -For completeness, below is an example using a dynamic query instead. The return type from a dynamic query is a [`crate::dynamic::DecodedValueThunk`], which can be decoded into a [`crate::dynamic::Value`], or else the raw bytes can be accessed instead. - -*/ +//! For completeness, below is an example using a dynamic query instead. The return type from a +//! dynamic query is a [`crate::dynamic::DecodedValueThunk`], which can be decoded into a +//! [`crate::dynamic::Value`], or else the raw bytes can be accessed instead. +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/storage_fetch_dynamic.rs")] //! ``` -/*! - -### Iterating storage entries - -Many storage entries are maps of values; as well as fetching individual values, it's possible to iterate over all of the values stored at that location: - -*/ +//! ### Iterating storage entries +//! +//! Many storage entries are maps of values; as well as fetching individual values, it's possible to +//! iterate over all of the values stored at that location: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/storage_iterating.rs")] //! ``` -/*! - -Here's the same logic but using dynamically constructed values instead: - -*/ +//! Here's the same logic but using dynamically constructed values instead: +//! +//! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/storage_iterating_dynamic.rs")] //! ``` -/*! - -### Advanced - -For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and [`crate::storage::Storage::fetch_keys`]. Both of these take raw bytes as arguments, which can be obtained from a [`crate::storage::StorageAddress`] by using [`crate::storage::StorageClient::address_bytes()`] or [`crate::storage::StorageClient::address_root_bytes()`]. - -*/ +//! ### Advanced +//! +//! For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and +//! [`crate::storage::Storage::fetch_keys`]. Both of these take raw bytes as arguments, which can be +//! obtained from a [`crate::storage::StorageAddress`] by using +//! [`crate::storage::StorageClient::address_bytes()`] or +//! [`crate::storage::StorageClient::address_root_bytes()`]. +//! From 45b56d848812ae3b690f46603a2cce8cbe3f31e0 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 21 Apr 2023 15:41:06 +0100 Subject: [PATCH 09/25] Fix example code --- subxt/src/book/setup/codegen.rs | 2 +- subxt/src/book/usage/constants.rs | 1 + subxt/src/book/usage/storage.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/subxt/src/book/setup/codegen.rs b/subxt/src/book/setup/codegen.rs index c510e63513..a8f3a85e48 100644 --- a/subxt/src/book/setup/codegen.rs +++ b/subxt/src/book/setup/codegen.rs @@ -53,7 +53,7 @@ //! If you'd like to manually generate the same code that the macro generates under the hood, you //! can use the `subxt codegen` command: //! -//! ```rust +//! ```shell //! # Install the CLI tool if you haven't already: //! cargo install subxt-cli //! # Generate and format rust code, saving it to `interface.rs`: diff --git a/subxt/src/book/usage/constants.rs b/subxt/src/book/usage/constants.rs index b3869f9176..dad24c1ca7 100644 --- a/subxt/src/book/usage/constants.rs +++ b/subxt/src/book/usage/constants.rs @@ -26,6 +26,7 @@ //! Alternately, we can dynamically construct a constant query: //! //! ```rust,no_run +//! use sp_keyring::AccountKeyring; //! use subxt::dynamic::Value; //! //! let account = AccountKeyring::Alice.to_account_id(); diff --git a/subxt/src/book/usage/storage.rs b/subxt/src/book/usage/storage.rs index 2723c56732..e9ddd7aeb7 100644 --- a/subxt/src/book/usage/storage.rs +++ b/subxt/src/book/usage/storage.rs @@ -28,6 +28,7 @@ //! validated until it's submitted: //! //! ```rust,no_run +//! use sp_keyring::AccountKeyring; //! use subxt::dynamic::Value; //! //! let account = AccountKeyring::Alice.to_account_id(); From 1d7812954c7a2e254ca321e821650874952ac0b0 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 21 Apr 2023 15:41:35 +0100 Subject: [PATCH 10/25] cargo fmt --- subxt/src/dynamic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subxt/src/dynamic.rs b/subxt/src/dynamic.rs index 04f5653e82..85ee375fe0 100644 --- a/subxt/src/dynamic.rs +++ b/subxt/src/dynamic.rs @@ -11,7 +11,7 @@ use crate::{ }; use scale_decode::DecodeAsType; -pub use scale_value::{ Value, At }; +pub use scale_value::{At, Value}; /// A [`scale_value::Value`] type endowed with contextual information /// regarding what type was used to decode each part of it. This implements From 074444cd39fd2ccba07219ccb4836e13c9f9505e Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 21 Apr 2023 15:43:29 +0100 Subject: [PATCH 11/25] cargo fmt --- examples/examples/blocks_subscribing.rs | 2 +- examples/examples/runtime_apis_raw.rs | 4 ++-- examples/examples/setup_client_offline.rs | 12 ++++-------- examples/examples/storage_fetch.rs | 7 ++++++- examples/examples/storage_fetch_dynamic.rs | 14 +++++++++----- examples/examples/storage_iterating.rs | 7 ++++++- examples/examples/storage_iterating_dynamic.rs | 7 ++++++- 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/examples/examples/blocks_subscribing.rs b/examples/examples/blocks_subscribing.rs index a6bff69909..4c8cdb6cff 100644 --- a/examples/examples/blocks_subscribing.rs +++ b/examples/examples/blocks_subscribing.rs @@ -48,4 +48,4 @@ async fn main() -> Result<(), Box> { } Ok(()) -} \ No newline at end of file +} diff --git a/examples/examples/runtime_apis_raw.rs b/examples/examples/runtime_apis_raw.rs index 5f92d5ae6e..1cbac989dd 100644 --- a/examples/examples/runtime_apis_raw.rs +++ b/examples/examples/runtime_apis_raw.rs @@ -1,6 +1,6 @@ -use subxt::{OnlineClient, PolkadotConfig}; +use subxt::ext::codec::{Compact, Decode}; use subxt::ext::frame_metadata::RuntimeMetadataPrefixed; -use subxt::ext::codec::{Decode, Compact}; +use subxt::{OnlineClient, PolkadotConfig}; #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] pub mod polkadot {} diff --git a/examples/examples/setup_client_offline.rs b/examples/examples/setup_client_offline.rs index baa058be17..b50defb2f7 100644 --- a/examples/examples/setup_client_offline.rs +++ b/examples/examples/setup_client_offline.rs @@ -1,8 +1,8 @@ -use subxt::{ config::PolkadotConfig, OfflineClient}; -use subxt::ext::frame_metadata::RuntimeMetadataPrefixed; use subxt::ext::codec::Decode; +use subxt::ext::frame_metadata::RuntimeMetadataPrefixed; use subxt::metadata::Metadata; use subxt::utils::H256; +use subxt::{config::PolkadotConfig, OfflineClient}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -19,7 +19,7 @@ async fn main() -> Result<(), Box> { let runtime_version = subxt::rpc::types::RuntimeVersion { spec_version: 9370, transaction_version: 20, - other: Default::default() + other: Default::default(), }; // 3. Metadata (I'll load it from the downloaded metadata, but you can use @@ -31,11 +31,7 @@ async fn main() -> Result<(), Box> { }; // Create an offline client using the details obtained above: - let _api = OfflineClient::::new( - genesis_hash, - runtime_version, - metadata - ); + let _api = OfflineClient::::new(genesis_hash, runtime_version, metadata); Ok(()) } diff --git a/examples/examples/storage_fetch.rs b/examples/examples/storage_fetch.rs index 2adc27a4bd..86c7c2a059 100644 --- a/examples/examples/storage_fetch.rs +++ b/examples/examples/storage_fetch.rs @@ -17,7 +17,12 @@ async fn main() -> Result<(), Box> { // Use that query to `fetch` a result. This returns an `Option<_>`, which will be // `None` if no value exists at the given address. You can also use `fetch_default` // where applicable, which will return the default value if none exists. - let result = api.storage().at_latest().await?.fetch(&storage_query).await?; + let result = api + .storage() + .at_latest() + .await? + .fetch(&storage_query) + .await?; println!("Alice has free balance: {}", result.unwrap().data.free); Ok(()) diff --git a/examples/examples/storage_fetch_dynamic.rs b/examples/examples/storage_fetch_dynamic.rs index 8ae54cb928..43325770ea 100644 --- a/examples/examples/storage_fetch_dynamic.rs +++ b/examples/examples/storage_fetch_dynamic.rs @@ -1,6 +1,6 @@ use sp_keyring::AccountKeyring; +use subxt::dynamic::{At, Value}; use subxt::{OnlineClient, PolkadotConfig}; -use subxt::dynamic::{Value,At}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -9,13 +9,17 @@ async fn main() -> Result<(), Box> { // Build a dynamic storage query to access account information. let account = AccountKeyring::Alice.to_account_id(); - let storage_query = subxt::dynamic::storage("System", "Account", vec![ - Value::from_bytes(account) - ]); + let storage_query = + subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]); // Use that query to `fetch` a result. Because the query is dynamic, we don't know what the result // type will be either, and so we get a type back that can be decoded into a dynamic Value type. - let result = api.storage().at_latest().await?.fetch(&storage_query).await?; + let result = api + .storage() + .at_latest() + .await? + .fetch(&storage_query) + .await?; let value = result.unwrap().to_value()?; println!("Alice has free balance: {:?}", value.at("data").at("free")); diff --git a/examples/examples/storage_iterating.rs b/examples/examples/storage_iterating.rs index 610298d370..a3f69ca5ea 100644 --- a/examples/examples/storage_iterating.rs +++ b/examples/examples/storage_iterating.rs @@ -13,7 +13,12 @@ async fn main() -> Result<(), Box> { let storage_query = polkadot::storage().system().account_root(); // Get back an iterator of results (we acquire 10 at a time, here). - let mut results = api.storage().at_latest().await?.iter(storage_query, 10).await?; + let mut results = api + .storage() + .at_latest() + .await? + .iter(storage_query, 10) + .await?; while let Some((key, value)) = results.next().await? { println!("Key: 0x{}", hex::encode(&key)); diff --git a/examples/examples/storage_iterating_dynamic.rs b/examples/examples/storage_iterating_dynamic.rs index 43039b1c58..9deca9ad8a 100644 --- a/examples/examples/storage_iterating_dynamic.rs +++ b/examples/examples/storage_iterating_dynamic.rs @@ -9,7 +9,12 @@ async fn main() -> Result<(), Box> { let storage_query = subxt::dynamic::storage_root("System", "Account"); // Use that query to return an iterator over the results. - let mut results = api.storage().at_latest().await?.iter(storage_query, 10).await?; + let mut results = api + .storage() + .at_latest() + .await? + .iter(storage_query, 10) + .await?; while let Some((key, value)) = results.next().await? { println!("Key: 0x{}", hex::encode(&key)); From 18c50be63322af9198ab9f70a60a3b6d536d3587 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 21 Apr 2023 16:16:23 +0100 Subject: [PATCH 12/25] fix example --- examples/examples/runtime_apis_raw.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/examples/runtime_apis_raw.rs b/examples/examples/runtime_apis_raw.rs index 1cbac989dd..fc61ff4b3b 100644 --- a/examples/examples/runtime_apis_raw.rs +++ b/examples/examples/runtime_apis_raw.rs @@ -1,4 +1,4 @@ -use subxt::ext::codec::{Compact, Decode}; +use subxt::ext::codec::Compact; use subxt::ext::frame_metadata::RuntimeMetadataPrefixed; use subxt::{OnlineClient, PolkadotConfig}; @@ -13,13 +13,9 @@ async fn main() -> Result<(), Box> { // Use runtime APIs at the latest block: let runtime_apis = api.runtime_api().at_latest().await?; - // Ask for metadata: - let bytes = runtime_apis.call_raw("Metadata_metadata", None).await?; - - // Decode it: - let cursor = &mut &*bytes; - let _ = >::decode(cursor)?; - let meta = RuntimeMetadataPrefixed::decode(cursor)?; + // Ask for metadata and decode it: + let (_, meta): (Compact, RuntimeMetadataPrefixed) = + runtime_apis.call_raw("Metadata_metadata", None).await?; println!("{meta:?}"); Ok(()) From 7f9df9979fe4029de28633abdd06a752c5659438 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 21 Apr 2023 16:23:07 +0100 Subject: [PATCH 13/25] Fix typos --- subxt/src/book/mod.rs | 2 +- subxt/src/book/setup/codegen.rs | 4 ++-- subxt/src/book/usage/events.rs | 2 +- subxt/src/book/usage/extrinsics.rs | 2 +- subxt/src/book/usage/storage.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/subxt/src/book/mod.rs b/subxt/src/book/mod.rs index e872bb7aa3..6373406a2d 100644 --- a/subxt/src/book/mod.rs +++ b/subxt/src/book/mod.rs @@ -67,7 +67,7 @@ #![doc = include_str!("../../../examples/examples/balance_transfer_basic.rs")] //! ``` //! -//! This example assumes that a Polkadot node is running locally (Subxt endeavours to support all +//! This example assumes that a Polkadot node is running locally (Subxt endeavors to support all //! recent releases). Typically, to use Subxt to talk to some custom Substrate node (for example a //! parachain node), you'll want to: //! diff --git a/subxt/src/book/setup/codegen.rs b/subxt/src/book/setup/codegen.rs index a8f3a85e48..55ca4b8d7a 100644 --- a/subxt/src/book/setup/codegen.rs +++ b/subxt/src/book/setup/codegen.rs @@ -46,7 +46,7 @@ //! //! Using the [`#[subxt]`](crate::subxt!) macro carries some downsides, notably that using it to //! generate an interface will have an impact on compile times (though much less of one if you only -//! need a few pallets), and that editor looking tends to not be very good at autocompleting and +//! need a few pallets), and that editor looking tends to not be very good at auto-completing and //! providing documentation for the generated interface. Additionally, you can't peer into the //! generated code and see what's going on if you use the macro. //! @@ -61,6 +61,6 @@ //! ``` //! //! Use `subxt codegen --help` for more options; many of the options available via the macro are -//! also available via the CLI tool, such as the abliity to substitute generated types for others, +//! also available via the CLI tool, such as the ability to substitute generated types for others, //! or strip out docs from the generated code. //! diff --git a/subxt/src/book/usage/events.rs b/subxt/src/book/usage/events.rs index 7e122803a7..90794bd180 100644 --- a/subxt/src/book/usage/events.rs +++ b/subxt/src/book/usage/events.rs @@ -6,7 +6,7 @@ //! //! In the process of adding extrinsics to a block, they are executed. When extrinsics are executed, //! they may produce events describing what's happening, but additionally the node may add emit some -//! events of its own as the blook is processed. Events live in a single location in node storage +//! events of its own as the block is processed. Events live in a single location in node storage //! which is overwritten at each block. //! //! When we submit extrinsics using Subxt, methods like diff --git a/subxt/src/book/usage/extrinsics.rs b/subxt/src/book/usage/extrinsics.rs index 7a2d32ad08..f22801c0ae 100644 --- a/subxt/src/book/usage/extrinsics.rs +++ b/subxt/src/book/usage/extrinsics.rs @@ -17,7 +17,7 @@ //! > As a side note, an _extrinsic_ is anything that can be added to a block, and a _transaction_ //! > is an extrinsic that is submitted from a particular user (and is therefore also signed). Subxt //! > can construct unsigned extrinsics, but overwhelmingly you'll need to sign them, and so the -//! > documentation tends to use the terms _extrinsic_ and _transaction_ interchangably. +//! > documentation tends to use the terms _extrinsic_ and _transaction_ interchangeably. //! //! ## Constructing an extrinsic payload //! diff --git a/subxt/src/book/usage/storage.rs b/subxt/src/book/usage/storage.rs index e9ddd7aeb7..f308f6b48d 100644 --- a/subxt/src/book/usage/storage.rs +++ b/subxt/src/book/usage/storage.rs @@ -59,7 +59,7 @@ //! over storage entries using it). //! //! Static queries set appropriate values for these associated types, and can therefore only be used -//! where it makes sense. Dyanmic queries don't know any better and can be used in more places, but +//! where it makes sense. Dynamic queries don't know any better and can be used in more places, but //! may fail at runtime instead if they are invalid in those places. //! //! ## Submitting it From 52fd3c19f5d3355837cfc30e0821226057647a3e Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 21 Apr 2023 16:50:22 +0100 Subject: [PATCH 14/25] fix broken doc links, pub mods --- subxt/src/blocks/mod.rs | 2 +- subxt/src/book/setup/codegen.rs | 4 ++-- subxt/src/book/setup/mod.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/subxt/src/blocks/mod.rs b/subxt/src/blocks/mod.rs index d60df23d99..9d6e62d8b3 100644 --- a/subxt/src/blocks/mod.rs +++ b/subxt/src/blocks/mod.rs @@ -7,5 +7,5 @@ mod block_types; mod blocks_client; -pub use block_types::{Block, Extrinsic, ExtrinsicEvents}; +pub use block_types::{Block, BlockBody, Extrinsic, ExtrinsicEvents}; pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient}; diff --git a/subxt/src/book/setup/codegen.rs b/subxt/src/book/setup/codegen.rs index 55ca4b8d7a..ee1431f201 100644 --- a/subxt/src/book/setup/codegen.rs +++ b/subxt/src/book/setup/codegen.rs @@ -10,7 +10,7 @@ //! //! ## The `#[subxt]` macro //! -//! The simplest way to generate an interface to use is via the [`#[subxt]`](crate::subxt!) macro. +//! The simplest way to generate an interface to use is via the [`#[subxt]`](crate::subxt) macro. //! Using this macro looks something like: //! //! ```rust,no_run @@ -44,7 +44,7 @@ //! //! ## The CLI tool //! -//! Using the [`#[subxt]`](crate::subxt!) macro carries some downsides, notably that using it to +//! Using the [`#[subxt]`](crate::subxt) macro carries some downsides, notably that using it to //! generate an interface will have an impact on compile times (though much less of one if you only //! need a few pallets), and that editor looking tends to not be very good at auto-completing and //! providing documentation for the generated interface. Additionally, you can't peer into the diff --git a/subxt/src/book/setup/mod.rs b/subxt/src/book/setup/mod.rs index 5d0bafdfd9..511b6ae0f1 100644 --- a/subxt/src/book/setup/mod.rs +++ b/subxt/src/book/setup/mod.rs @@ -9,5 +9,5 @@ //! //! Alternately, [go back](super). -mod client; -mod codegen; +pub mod client; +pub mod codegen; From 88ddc525aef4e5cdcc40bee2ed95eb3ad734d20e Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 21 Apr 2023 17:55:07 +0100 Subject: [PATCH 15/25] Update Subxt macro docs --- Cargo.lock | 2 + codegen/src/api/mod.rs | 9 +- macro/Cargo.toml | 6 + macro/src/lib.rs | 269 +++++++++++++++++++++++++---------------- 4 files changed, 177 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a11fceb08b..a33dd94b92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3585,6 +3585,8 @@ version = "0.28.0" dependencies = [ "darling", "proc-macro-error", + "sp-runtime", + "subxt", "subxt-codegen", "syn 1.0.109", ] diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index ae52fb3f2a..5139773ce6 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -219,8 +219,13 @@ impl RuntimeGenerator { // Preserve any Rust items that were previously defined in the adorned module #( #rust_items ) * - // Make it easy to access the root via `root_mod` at different levels: - use super::#mod_ident as root_mod; + // Make it easy to access the root items via `root_mod` at different levels + // without reaching out of this module. + #[allow(unused_imports)] + mod root_mod { + pub use super::*; + } + #types_mod } }) diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 6a3c8c3e54..45f4269136 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -22,3 +22,9 @@ proc-macro-error = "1.0.4" syn = "1.0.109" subxt-codegen = { path = "../codegen", version = "0.28.0" } + +[dev-dependencies] + +# Just so that we can reference these in the macro docs: +subxt = { path = "../subxt" } +sp-runtime = "23.0.0" \ No newline at end of file diff --git a/macro/src/lib.rs b/macro/src/lib.rs index b82e1589e2..6645e1e3e3 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -2,113 +2,6 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -//! Generate a strongly typed API for interacting with a Substrate runtime from its metadata. -//! -//! Usage: -//! -//! Download metadata from a running Substrate node using `subxt-cli`: -//! -//! ```bash -//! subxt metadata > polkadot_metadata.scale -//! ``` -//! -//! Annotate a Rust module with the `subxt` attribute referencing the aforementioned metadata file. -//! -//! ```ignore -//! #[subxt::subxt( -//! runtime_metadata_path = "polkadot_metadata.scale", -//! )] -//! pub mod polkadot {} -//! ``` -//! -//! The `subxt` macro will populate the annotated module with all of the methods and types required -//! for submitting extrinsics and reading from storage for the given runtime. -//! -//! ## Substituting types -//! -//! In order to replace a generated type by a user-defined type, use `substitute_type`: -//! -//! ```ignore -//! #[subxt::subxt( -//! runtime_metadata_path = "polkadot_metadata.scale", -//! substitute_type(type = "sp_arithmetic::per_things::Perbill", with = "sp_runtime::Perbill") -//! )] -//! pub mod polkadot {} -//! ``` -//! -//! This will replace the generated type and any usages with the specified type at the `use` import. -//! It is useful for using custom decoding for specific types, or to provide a type with foreign -//! trait implementations, or other specialized functionality. - -//! ## Custom Derives -//! -//! By default all generated types are annotated with `scale::Encode` and `scale::Decode` derives. -//! However when using the generated types in the client, they may require additional derives to be -//! useful. -//! -//! ### Adding derives for all types -//! -//! Add `derive_for_all_types` with a comma separated list of the derives to apply to *all* types -//! -//! ```ignore -//! #[subxt::subxt( -//! runtime_metadata_path = "polkadot_metadata.scale", -//! derive_for_all_types = "Eq, PartialEq" -//! )] -//! pub mod polkadot {} -//! ``` -//! -//! ### Adding derives for specific types -//! -//! Add `derive_for_type` for each specific type with a comma separated list of the derives to -//! apply for that type only. -//! -//! ```ignore -//! #[subxt::subxt( -//! runtime_metadata_path = "polkadot_metadata.scale", -//! derive_for_all_types = "Eq, PartialEq", -//! derive_for_type(type = "frame_support::PalletId", derive = "Ord, PartialOrd"), -//! derive_for_type(type = "sp_runtime::ModuleError", derive = "Hash"), -//! )] -//! pub mod polkadot {} -//! ``` -//! -//! ### Custom crate path -//! -//! In order to specify a custom crate path to be used for the code generation: -//! -//! ```ignore -//! #[subxt::subxt(crate = "crate::path::to::subxt")] -//! pub mod polkadot {} -//! ``` -//! -//! By default the path `::subxt` is used. -//! -//! ### Expose documentation -//! -//! In order to expose the documentation from the runtime metadata on the generated -//! code, users must specify the `generate_docs` flag: -//! -//! ```ignore -//! #[subxt::subxt(generate_docs)] -//! pub mod polkadot {} -//! ``` -//! -//! By default the documentation is not generated. -//! -//! ### Runtime types generation -//! -//! In some cases, you may be interested only in the runtime types, like `RuntimeCall` enum. You can -//! limit code generation to just `runtime_types` module with `runtime_types_only` flag: -//! -//! ```ignore -//! #[subxt::subxt(runtime_types_only)] -//! // or equivalently -//! #[subxt::subxt(runtime_types_only = true)] -//! ``` - -#![deny(unused_crate_dependencies)] - extern crate proc_macro; use std::str::FromStr; @@ -153,6 +46,168 @@ struct SubstituteType { with: syn::Path, } +/// Generate a strongly typed API for interacting with a Substrate runtime from its metadata. +/// +/// # Metadata +/// +/// First, you'll need to get hold of some metadata for the node you'd like to interact with. One +/// way to do this is by using the `subxt` CLI tool: +/// +/// ```bash +/// # Install the CLI tool: +/// cargo install subxt-cli +/// # Use it to download metadata (in this case, from a node running locally) +/// subxt metadata > polkadot_metadata.scale +/// ``` +/// +/// Run `subxt metadata --help` for more options. +/// +/// # Basic usage +/// +/// Annotate a Rust module with the `subxt` attribute referencing the aforementioned metadata file. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// )] +/// mod polkadot {} +/// ``` +/// +/// The `subxt` macro will populate the annotated module with all of the methods and types required +/// for interacting with the runtime that the metadata came from via Subxt. +/// +/// # Configuration +/// +/// This macro supports a number of attributes to configure what is generated: +/// +/// ## `crate = "..."` +/// +/// Use this attribute to specify a custom path to the `subxt` crate: +/// +/// ```ignore +/// #[subxt::subxt(crate = "crate::path::to::subxt")] +/// mod polkadot {} +/// ``` +/// +/// This is useful if you write a library which uses this macro, but don't want to force users to depend on `subxt` +/// at the top level too. By default the path `::subxt` is used. +/// +/// ## `substitute_type(type = "...", with = "...")` +/// +/// This attribute replaces any reference to the generated type at the path given by `type` with a +/// reference to the path given by `with`. +/// +/// ```rust,ignore +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// substitute_type(type = "sp_arithmetic::per_things::Perbill", with = "crate::Foo") +/// )] +/// mod polkadot {} +/// +/// // For this substitute type to work, it needs to EncodeAsType/DecodeAsType in a +/// // compatible way with Perbill. +/// #[derive( +/// subxt::ext::scale_encode::EncodeAsType, +/// subxt::ext::scale_decode::DecodeAsType, +/// subxt::ext::codec::Encode, +/// subxt::ext::codec::Decode, +/// Clone, +/// Debug, +/// )] +/// #[codec(crate = ::subxt::ext::codec)] +/// #[encode_as_type(crate_path = "::subxt::ext::scale_encode")] +/// #[decode_as_type(crate_path = "::subxt::ext::scale_decode")] +/// struct Foo(u32); +/// ``` +/// +/// If the type you're substituting contains generic parameters, you can "pattern match" on those, and +/// make use of them in the substituted type, like so: +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// substitute_type( +/// type = "sp_runtime::multiaddress::MultiAddress", +/// with = "::subxt::utils::Static<::sp_runtime::MultiAddress>" +/// ) +/// )] +/// mod polkadot {} +/// ``` +/// +/// The above is also an example of using the [`subxt::utils::Static`] type to wrap some type which doesn't +/// on it's own implement [`subxt::ext::scale_encode::EncodeAsType`] or [`subxt::ext::scale_decode::DecodeAsType`], +/// which are required traits for any substitute type to implement by default. +/// +/// ## `derive_for_all_types = "..."` +/// +/// By default, all generated types derive a small set of traits. This attribute allows you to derive additional +/// traits on all generated types: +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// derive_for_all_types = "Eq, PartialEq" +/// )] +/// mod polkadot {} +/// ``` +/// +/// Any substituted types (including the default substitutes) must also implement these traits in order to avoid errors +/// here. +/// +/// ## `derive_for_type(type = "...", derive = "...")` +/// +/// Unlike the above, which derives some trait on every generated type, this attribute allows you to derive traits only +/// for specific types. Note that any types which are used inside the specified type may also need to derive the same traits. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// derive_for_all_types = "Eq, PartialEq", +/// derive_for_type(type = "frame_support::PalletId", derive = "Ord, PartialOrd"), +/// derive_for_type(type = "sp_runtime::ModuleError", derive = "Hash"), +/// )] +/// mod polkadot {} +/// ``` +/// +/// ## `runtime_metadata_url = "..."` +/// +/// This attribute can be used instead of `runtime_metadata_path` and will tell the macro to download metadata from a node running +/// at the provided URL, rather than a node running locally. This can be useful in CI, but is **not recommended** in production code, +/// since it runs at compile time and will cause compilation to fail if the node at the given address is unavailable or unresponsive. +/// +/// ```rust,ignore +/// #[subxt::subxt( +/// runtime_metadata_url = "wss://rpc.polkadot.io:443" +/// )] +/// mod polkadot {} +/// ``` +/// +/// ## `generate_docs` +/// +/// By default, documentation is not generated via the macro, since IDEs do not typically make use of it. This attribute +/// forces documentation to be generated, too. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// generate_docs +/// )] +/// mod polkadot {} +/// ``` +/// +/// ## `runtime_types_only` +/// +/// By default, the macro will generate various interfaces to make using Subxt simpler in addition with any types that need +/// generating to make this possible. This attribute makes the codegen only generate the types and not the Subxt interface. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// runtime_types_only +/// )] +/// mod polkadot {} +/// ``` +/// #[proc_macro_attribute] #[proc_macro_error] pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { From 241033b4662a12afd2358fc0bd71401b75b7c9de Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 24 Apr 2023 10:35:24 +0100 Subject: [PATCH 16/25] can't link to Subxt here --- macro/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 6645e1e3e3..55c19a032c 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -134,9 +134,9 @@ struct SubstituteType { /// mod polkadot {} /// ``` /// -/// The above is also an example of using the [`subxt::utils::Static`] type to wrap some type which doesn't -/// on it's own implement [`subxt::ext::scale_encode::EncodeAsType`] or [`subxt::ext::scale_decode::DecodeAsType`], -/// which are required traits for any substitute type to implement by default. +/// The above is also an example of using the `subxt::utils::Static` type to wrap some type which doesn't +/// on it's own implement `EncodeAsType` or `DecodeAsType`, which are required traits for any substitute +/// type to implement by default. /// /// ## `derive_for_all_types = "..."` /// From 71e23a467c6c8d0a87676fddc0280c1df0c125d3 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 24 Apr 2023 11:39:42 +0100 Subject: [PATCH 17/25] move macro docs to Subxt to make linking better and fix example code --- Cargo.lock | 2 - macro/Cargo.toml | 6 -- macro/src/lib.rs | 162 ----------------------------------------- subxt/src/lib.rs | 184 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 182 insertions(+), 172 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a33dd94b92..a11fceb08b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3585,8 +3585,6 @@ version = "0.28.0" dependencies = [ "darling", "proc-macro-error", - "sp-runtime", - "subxt", "subxt-codegen", "syn 1.0.109", ] diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 45f4269136..6a3c8c3e54 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -22,9 +22,3 @@ proc-macro-error = "1.0.4" syn = "1.0.109" subxt-codegen = { path = "../codegen", version = "0.28.0" } - -[dev-dependencies] - -# Just so that we can reference these in the macro docs: -subxt = { path = "../subxt" } -sp-runtime = "23.0.0" \ No newline at end of file diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 55c19a032c..35d3413106 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -46,168 +46,6 @@ struct SubstituteType { with: syn::Path, } -/// Generate a strongly typed API for interacting with a Substrate runtime from its metadata. -/// -/// # Metadata -/// -/// First, you'll need to get hold of some metadata for the node you'd like to interact with. One -/// way to do this is by using the `subxt` CLI tool: -/// -/// ```bash -/// # Install the CLI tool: -/// cargo install subxt-cli -/// # Use it to download metadata (in this case, from a node running locally) -/// subxt metadata > polkadot_metadata.scale -/// ``` -/// -/// Run `subxt metadata --help` for more options. -/// -/// # Basic usage -/// -/// Annotate a Rust module with the `subxt` attribute referencing the aforementioned metadata file. -/// -/// ```rust,no_run -/// #[subxt::subxt( -/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", -/// )] -/// mod polkadot {} -/// ``` -/// -/// The `subxt` macro will populate the annotated module with all of the methods and types required -/// for interacting with the runtime that the metadata came from via Subxt. -/// -/// # Configuration -/// -/// This macro supports a number of attributes to configure what is generated: -/// -/// ## `crate = "..."` -/// -/// Use this attribute to specify a custom path to the `subxt` crate: -/// -/// ```ignore -/// #[subxt::subxt(crate = "crate::path::to::subxt")] -/// mod polkadot {} -/// ``` -/// -/// This is useful if you write a library which uses this macro, but don't want to force users to depend on `subxt` -/// at the top level too. By default the path `::subxt` is used. -/// -/// ## `substitute_type(type = "...", with = "...")` -/// -/// This attribute replaces any reference to the generated type at the path given by `type` with a -/// reference to the path given by `with`. -/// -/// ```rust,ignore -/// #[subxt::subxt( -/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", -/// substitute_type(type = "sp_arithmetic::per_things::Perbill", with = "crate::Foo") -/// )] -/// mod polkadot {} -/// -/// // For this substitute type to work, it needs to EncodeAsType/DecodeAsType in a -/// // compatible way with Perbill. -/// #[derive( -/// subxt::ext::scale_encode::EncodeAsType, -/// subxt::ext::scale_decode::DecodeAsType, -/// subxt::ext::codec::Encode, -/// subxt::ext::codec::Decode, -/// Clone, -/// Debug, -/// )] -/// #[codec(crate = ::subxt::ext::codec)] -/// #[encode_as_type(crate_path = "::subxt::ext::scale_encode")] -/// #[decode_as_type(crate_path = "::subxt::ext::scale_decode")] -/// struct Foo(u32); -/// ``` -/// -/// If the type you're substituting contains generic parameters, you can "pattern match" on those, and -/// make use of them in the substituted type, like so: -/// -/// ```rust,no_run -/// #[subxt::subxt( -/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", -/// substitute_type( -/// type = "sp_runtime::multiaddress::MultiAddress", -/// with = "::subxt::utils::Static<::sp_runtime::MultiAddress>" -/// ) -/// )] -/// mod polkadot {} -/// ``` -/// -/// The above is also an example of using the `subxt::utils::Static` type to wrap some type which doesn't -/// on it's own implement `EncodeAsType` or `DecodeAsType`, which are required traits for any substitute -/// type to implement by default. -/// -/// ## `derive_for_all_types = "..."` -/// -/// By default, all generated types derive a small set of traits. This attribute allows you to derive additional -/// traits on all generated types: -/// -/// ```rust,no_run -/// #[subxt::subxt( -/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", -/// derive_for_all_types = "Eq, PartialEq" -/// )] -/// mod polkadot {} -/// ``` -/// -/// Any substituted types (including the default substitutes) must also implement these traits in order to avoid errors -/// here. -/// -/// ## `derive_for_type(type = "...", derive = "...")` -/// -/// Unlike the above, which derives some trait on every generated type, this attribute allows you to derive traits only -/// for specific types. Note that any types which are used inside the specified type may also need to derive the same traits. -/// -/// ```rust,no_run -/// #[subxt::subxt( -/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", -/// derive_for_all_types = "Eq, PartialEq", -/// derive_for_type(type = "frame_support::PalletId", derive = "Ord, PartialOrd"), -/// derive_for_type(type = "sp_runtime::ModuleError", derive = "Hash"), -/// )] -/// mod polkadot {} -/// ``` -/// -/// ## `runtime_metadata_url = "..."` -/// -/// This attribute can be used instead of `runtime_metadata_path` and will tell the macro to download metadata from a node running -/// at the provided URL, rather than a node running locally. This can be useful in CI, but is **not recommended** in production code, -/// since it runs at compile time and will cause compilation to fail if the node at the given address is unavailable or unresponsive. -/// -/// ```rust,ignore -/// #[subxt::subxt( -/// runtime_metadata_url = "wss://rpc.polkadot.io:443" -/// )] -/// mod polkadot {} -/// ``` -/// -/// ## `generate_docs` -/// -/// By default, documentation is not generated via the macro, since IDEs do not typically make use of it. This attribute -/// forces documentation to be generated, too. -/// -/// ```rust,no_run -/// #[subxt::subxt( -/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", -/// generate_docs -/// )] -/// mod polkadot {} -/// ``` -/// -/// ## `runtime_types_only` -/// -/// By default, the macro will generate various interfaces to make using Subxt simpler in addition with any types that need -/// generating to make this possible. This attribute makes the codegen only generate the types and not the Subxt interface. -/// -/// ```rust,no_run -/// #[subxt::subxt( -/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", -/// runtime_types_only -/// )] -/// mod polkadot {} -/// ``` -/// #[proc_macro_attribute] #[proc_macro_error] pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 4117baa324..5b6af0f2c5 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -41,8 +41,6 @@ pub mod book; #[cfg(test)] use tokio as _; -pub use subxt_macro::subxt; - // Used to enable the js feature for wasm. #[cfg(target_arch = "wasm32")] pub use getrandom as _; @@ -88,3 +86,185 @@ pub mod ext { #[cfg(feature = "substrate-compat")] pub use sp_runtime; } + +/// Generate a strongly typed API for interacting with a Substrate runtime from its metadata. +/// +/// # Metadata +/// +/// First, you'll need to get hold of some metadata for the node you'd like to interact with. One +/// way to do this is by using the `subxt` CLI tool: +/// +/// ```bash +/// # Install the CLI tool: +/// cargo install subxt-cli +/// # Use it to download metadata (in this case, from a node running locally) +/// subxt metadata > polkadot_metadata.scale +/// ``` +/// +/// Run `subxt metadata --help` for more options. +/// +/// # Basic usage +/// +/// Annotate a Rust module with the `subxt` attribute referencing the aforementioned metadata file. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// )] +/// mod polkadot {} +/// ``` +/// +/// The `subxt` macro will populate the annotated module with all of the methods and types required +/// for interacting with the runtime that the metadata came from via Subxt. +/// +/// # Configuration +/// +/// This macro supports a number of attributes to configure what is generated: +/// +/// ## `crate = "..."` +/// +/// Use this attribute to specify a custom path to the `subxt` crate: +/// +/// ```rust +/// # pub extern crate subxt; +/// # pub mod path { pub mod to { pub use subxt; } } +/// # fn main() {} +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// crate = "crate::path::to::subxt" +/// )] +/// mod polkadot {} +/// ``` +/// +/// This is useful if you write a library which uses this macro, but don't want to force users to depend on `subxt` +/// at the top level too. By default the path `::subxt` is used. +/// +/// ## `substitute_type(type = "...", with = "...")` +/// +/// This attribute replaces any reference to the generated type at the path given by `type` with a +/// reference to the path given by `with`. +/// +/// ```rust +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// substitute_type(type = "sp_arithmetic::per_things::Perbill", with = "crate::Foo") +/// )] +/// mod polkadot {} +/// +/// # #[derive( +/// # scale_encode::EncodeAsType, +/// # scale_decode::DecodeAsType, +/// # codec::Encode, +/// # codec::Decode, +/// # Clone, +/// # Debug, +/// # )] +/// // In reality this needs some traits implementing on +/// // it to allow it to be used in place of Perbill: +/// pub struct Foo(u32); +/// # impl codec::CompactAs for Foo { +/// # type As = u32; +/// # fn encode_as(&self) -> &Self::As { +/// # &self.0 +/// # } +/// # fn decode_from(x: Self::As) -> Result { +/// # Ok(Foo(x)) +/// # } +/// # } +/// # impl From> for Foo { +/// # fn from(v: codec::Compact) -> Foo { +/// # v.0 +/// # } +/// # } +/// # fn main() {} +/// ``` +/// +/// If the type you're substituting contains generic parameters, you can "pattern match" on those, and +/// make use of them in the substituted type, like so: +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// substitute_type( +/// type = "sp_runtime::multiaddress::MultiAddress", +/// with = "::subxt::utils::Static<::sp_runtime::MultiAddress>" +/// ) +/// )] +/// mod polkadot {} +/// ``` +/// +/// The above is also an example of using the [`crate::utils::Static`] type to wrap some type which doesn't +/// on it's own implement [`scale_encode::EncodeAsType`] or [`scale_decode::DecodeAsType`], which are required traits +/// for any substitute type to implement by default. +/// +/// ## `derive_for_all_types = "..."` +/// +/// By default, all generated types derive a small set of traits. This attribute allows you to derive additional +/// traits on all generated types: +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// derive_for_all_types = "Eq, PartialEq" +/// )] +/// mod polkadot {} +/// ``` +/// +/// Any substituted types (including the default substitutes) must also implement these traits in order to avoid errors +/// here. +/// +/// ## `derive_for_type(type = "...", derive = "...")` +/// +/// Unlike the above, which derives some trait on every generated type, this attribute allows you to derive traits only +/// for specific types. Note that any types which are used inside the specified type may also need to derive the same traits. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// derive_for_all_types = "Eq, PartialEq", +/// derive_for_type(type = "frame_support::PalletId", derive = "Ord, PartialOrd"), +/// derive_for_type(type = "sp_runtime::ModuleError", derive = "Hash"), +/// )] +/// mod polkadot {} +/// ``` +/// +/// ## `runtime_metadata_url = "..."` +/// +/// This attribute can be used instead of `runtime_metadata_path` and will tell the macro to download metadata from a node running +/// at the provided URL, rather than a node running locally. This can be useful in CI, but is **not recommended** in production code, +/// since it runs at compile time and will cause compilation to fail if the node at the given address is unavailable or unresponsive. +/// +/// ```rust,ignore +/// #[subxt::subxt( +/// runtime_metadata_url = "wss://rpc.polkadot.io:443" +/// )] +/// mod polkadot {} +/// ``` +/// +/// ## `generate_docs` +/// +/// By default, documentation is not generated via the macro, since IDEs do not typically make use of it. This attribute +/// forces documentation to be generated, too. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// generate_docs +/// )] +/// mod polkadot {} +/// ``` +/// +/// ## `runtime_types_only` +/// +/// By default, the macro will generate various interfaces to make using Subxt simpler in addition with any types that need +/// generating to make this possible. This attribute makes the codegen only generate the types and not the Subxt interface. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// runtime_types_only +/// )] +/// mod polkadot {} +/// ``` +/// +pub use subxt_macro::subxt; \ No newline at end of file From 6213091bf71e556b7fca7047651ecd20929475ee Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 24 Apr 2023 11:40:30 +0100 Subject: [PATCH 18/25] note on macro about docs --- macro/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 35d3413106..95ff823d06 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -46,6 +46,7 @@ struct SubstituteType { with: syn::Path, } +// Note: docs for this are in the subxt library; don't add any here as they will be appended. #[proc_macro_attribute] #[proc_macro_error] pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { From 5ccba644a3d8855821da582ce6a79f941dcec8a3 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 24 Apr 2023 12:04:35 +0100 Subject: [PATCH 19/25] cargo fmt --- subxt/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 5b6af0f2c5..86642002ef 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -267,4 +267,4 @@ pub mod ext { /// mod polkadot {} /// ``` /// -pub use subxt_macro::subxt; \ No newline at end of file +pub use subxt_macro::subxt; From a10c1c1df1b20a09fb1bf3f226746207682311ed Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 24 Apr 2023 14:25:48 +0100 Subject: [PATCH 20/25] document the no_default_derives macro feature --- subxt/src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 86642002ef..48320c868d 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -266,5 +266,23 @@ pub mod ext { /// )] /// mod polkadot {} /// ``` +/// ## `no_default_derives` /// +/// By default, the macro will add all derives necessary for the generated code to play nicely with Subxt. Adding this attribute +/// removes all default derives. +/// +/// ```rust,no_run +/// #[subxt::subxt( +/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", +/// runtime_types_only, +/// no_default_derives, +/// derive_for_all_types="codec::Encode, codec::Decode" +/// )] +/// mod polkadot {} +/// ``` +/// +/// **Note**: At the moment, you must derive at least one of `codec::Encode` or `codec::Decode` or `scale_encode::EncodeAsType` or +/// `scale_decode::DecodeAsType` (because we add `#[codec(..)]` attributes on some fields/types during codegen), and you must use this +/// feature in conjunction with `runtime_types_only` (or manually specify a bunch of defaults to make codegen work properly when +/// generating the subxt interfaces). pub use subxt_macro::subxt; From 2a2cdd59aa6bba65268a3f4d77cf43258c53b12e Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 2 May 2023 14:22:00 +0100 Subject: [PATCH 21/25] Address feedback and remove redundant text --- .../balance_transfer_status_stream.rs | 4 ++ .../examples/setup_client_custom_config.rs | 3 +- examples/examples/storage_iterating.rs | 3 +- subxt/src/book/mod.rs | 44 ++----------------- subxt/src/book/setup/codegen.rs | 26 +++++------ subxt/src/book/usage/constants.rs | 12 ++--- subxt/src/book/usage/events.rs | 21 +++++---- subxt/src/book/usage/storage.rs | 2 +- 8 files changed, 44 insertions(+), 71 deletions(-) diff --git a/examples/examples/balance_transfer_status_stream.rs b/examples/examples/balance_transfer_status_stream.rs index f0ea802a17..a390f9e3d4 100644 --- a/examples/examples/balance_transfer_status_stream.rs +++ b/examples/examples/balance_transfer_status_stream.rs @@ -11,11 +11,15 @@ pub mod polkadot {} #[tokio::main] async fn main() -> Result<(), Box> { + // Create a new API client, configured to talk to Polkadot nodes. let api = OnlineClient::::new().await?; + // Build a balance transfer extrinsic. let dest = AccountKeyring::Bob.to_account_id().into(); let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000); + // Submit the balance transfer extrinsic from Alice, and then monitor the + // progress of it. let signer = PairSigner::new(AccountKeyring::Alice.pair()); let mut balance_transfer_progress = api .tx() diff --git a/examples/examples/setup_client_custom_config.rs b/examples/examples/setup_client_custom_config.rs index 518a0dceb0..0acf71ae4a 100644 --- a/examples/examples/setup_client_custom_config.rs +++ b/examples/examples/setup_client_custom_config.rs @@ -3,7 +3,8 @@ use subxt::{ OnlineClient, }; -/// Defing a custom config type: +/// Define a custom config type (see the `subxt::config::Config` docs for +/// more information about each type): enum MyConfig {} impl Config for MyConfig { // This is different from the default `u32`: diff --git a/examples/examples/storage_iterating.rs b/examples/examples/storage_iterating.rs index a3f69ca5ea..4a6a0159a6 100644 --- a/examples/examples/storage_iterating.rs +++ b/examples/examples/storage_iterating.rs @@ -12,7 +12,8 @@ async fn main() -> Result<(), Box> { // Build a storage query to iterate over account information. let storage_query = polkadot::storage().system().account_root(); - // Get back an iterator of results (we acquire 10 at a time, here). + // Get back an iterator of results (here, we are fetching 10 items at + // a time from the node, but we always iterate over oen at a time). let mut results = api .storage() .at_latest() diff --git a/subxt/src/book/mod.rs b/subxt/src/book/mod.rs index 6373406a2d..e7106b5cd4 100644 --- a/subxt/src/book/mod.rs +++ b/subxt/src/book/mod.rs @@ -60,8 +60,8 @@ //! //! ## Quick start //! -//! Here is a simple but complete example of using Subxt to transfer some tokens from Alice to Bob: -//! +//! Here is a simple but complete example of using Subxt to transfer some tokens from the example +//! accounts, Alice to Bob: //! //! ```rust,ignore #![doc = include_str!("../../../examples/examples/balance_transfer_basic.rs")] @@ -76,47 +76,9 @@ //! //! Follow the above links to learn more about each step. //! -//! ### Acquiring metadata -//! -//! The simplest way to do acquire metadata is to use the `subxt` CLI tool to download it from your -//! node. The tool can be installed via `cargo`: -//! -//! ```shell -//! cargo install subxt-cli -//! ``` -//! -//! Once installed, run `subxt metadata > metadata.scale` to download the node's metadata and save -//! it to some file (again, this assumes that the node you'd like to talk to is running locally; run -//! `subxt metadata --help` to see other options). -//! -//! Sometimes it's useful to be able to manually output the code that's being generated by the -//! [`#[subxt]`](crate::subxt) macro to better understand what's going on. To do this, use `subxt -//! codegen | rustfmt > generated_interface.rs`. -//! -//! ### Configuring the client -//! -//! To use Subxt, you must instantiate the client (typically an -//! [`OnlineClient`](crate::client::OnlineClient)) with some [`Config`](crate::config::Config). -//! Subxt ships with these default configurations: -//! -//! - [`crate::config::SubstrateConfig`] to talk to generic Substrate nodes. -//! - [`crate::config::PolkadotConfig`] to talk to Polkadot nodes. -//! -//! The configuration defines the types used for things like the account nonce, account ID and block -//! header, so that it can encode or decode data from the node properly. It also defines how -//! transactions should be signed, how data should be hashed, and which data should be sent along -//! with an extrinsic. Thus, if the configuration is incorrect, Subxt may run into errors performing -//! various actions against a node. -//! -//! In many cases, using one of the provided configuration implementations will work. If the node -//! you're talking to is not compatible with the selected configuration, then you'll run into errors -//! (particularly when trying to submit transactions or downloading blocks), and will need to find -//! out what is different between the configuration you've used and the node in question (perhaps it -//! expects things to be signed differently, or has a different address format for instance). -//! //! ## Usage //! -//! Once Subxt is configured, the next step is actually interacting with a node. Follow the links +//! Once Subxt is configured, the next step is interacting with a node. Follow the links //! below to learn more about how to use Subxt for each of the following things: //! //! - [Extrinsics](usage::extrinsics): Subxt can build and submit extrinsics, wait until they are in diff --git a/subxt/src/book/setup/codegen.rs b/subxt/src/book/setup/codegen.rs index ee1431f201..f6f6e6b5ad 100644 --- a/subxt/src/book/setup/codegen.rs +++ b/subxt/src/book/setup/codegen.rs @@ -10,7 +10,7 @@ //! //! ## The `#[subxt]` macro //! -//! The simplest way to generate an interface to use is via the [`#[subxt]`](crate::subxt) macro. +//! The most common way to generate the interface is to use the [`#[subxt]`](crate::subxt) macro. //! Using this macro looks something like: //! //! ```rust,no_run @@ -19,16 +19,16 @@ //! ``` //! //! The macro takes a path to some node metadata, and uses that to generate the interface you'll use -//! to talk to it. +//! to talk to it. [Go here](crate::subxt) to learn more about the options available to the macro. //! -//! The simplest way to do obtain this metadata is to use the `subxt` CLI tool to download it from a -//! node. The tool can be installed via `cargo`: +//! To obtain this metadata you'll need for the above, you can use the `subxt` CLI tool to download it +//! from a node. The tool can be installed via `cargo`: //! //! ```shell //! cargo install subxt-cli //! ``` //! -//! And then, use it to fetch metadata and save it to a file: +//! And then it can be used to fetch metadata and save it to a file: //! //! ```shell //! # Download and save all of the metadata: @@ -41,17 +41,17 @@ //! information, making the bundle much smaller in the event that you only need to generate an //! interface for a subset of the available pallets on the node. //! -//! //! ## The CLI tool //! -//! Using the [`#[subxt]`](crate::subxt) macro carries some downsides, notably that using it to -//! generate an interface will have an impact on compile times (though much less of one if you only -//! need a few pallets), and that editor looking tends to not be very good at auto-completing and -//! providing documentation for the generated interface. Additionally, you can't peer into the -//! generated code and see what's going on if you use the macro. +//! Using the [`#[subxt]`](crate::subxt) macro carries some downsides: +//! +//! - Using it to generate an interface will have a small impact on compile times (though much less of +//! one if you only need a few pallets). +//! - IDE support for autocompletion and documentation when using the macro interface can be poor. +//! - It's impossible to manually look at the generated code to understand and debug things. //! -//! If you'd like to manually generate the same code that the macro generates under the hood, you -//! can use the `subxt codegen` command: +//! If these are an issue, you can manually generate the same code that the macro generates under the hood +//! by using the `subxt codegen` command: //! //! ```shell //! # Install the CLI tool if you haven't already: diff --git a/subxt/src/book/usage/constants.rs b/subxt/src/book/usage/constants.rs index dad24c1ca7..a6e7a52da7 100644 --- a/subxt/src/book/usage/constants.rs +++ b/subxt/src/book/usage/constants.rs @@ -4,8 +4,9 @@ //! # Constants //! -//! There are various constants stored in a node; these can only change when the runtime is updated. -//! Much like [`super::storage`], we can query these using Subxt by taking the following steps: +//! There are various constants stored in a node; the types and values of these are defined in a +//! runtime, and can only change when the runtime is updated. Much like [`super::storage`], we can +//! query these using Subxt by taking the following steps: //! //! 1. [Constructing a constant query](#constructing-a-query). //! 2. [Submitting the query to get back the associated value](#submitting-it). @@ -40,9 +41,10 @@ //! //! ## Submitting it //! -//! Constant queries are submitted via [`crate::constants::ConstantsClient::at()`]. It's worth -//! noting that constant values are pulled directly out of the node metadata which the client has -//! already acquired, and so this function is available from a [`crate::OfflineClient`]. +//! Constant queries are handed to Subxt via [`crate::constants::ConstantsClient::at()`]. It's worth +//! noting that constant values are pulled directly out of the node metadata which Subxt has +//! already acquired, and so this function requires no network access and is available from a +//! [`crate::OfflineClient`]. //! //! Here's an example using a static query: //! diff --git a/subxt/src/book/usage/events.rs b/subxt/src/book/usage/events.rs index 90794bd180..2d04d7f629 100644 --- a/subxt/src/book/usage/events.rs +++ b/subxt/src/book/usage/events.rs @@ -5,15 +5,18 @@ //! # Events //! //! In the process of adding extrinsics to a block, they are executed. When extrinsics are executed, -//! they may produce events describing what's happening, but additionally the node may add emit some -//! events of its own as the block is processed. Events live in a single location in node storage -//! which is overwritten at each block. +//! they normally produce events describing what's happening (at the very least, an event dictating whether +//! the extrinsic has succeeded or failed). The node may also emit some events of its own as the block is +//! processed. //! -//! When we submit extrinsics using Subxt, methods like -//! [`crate::tx::TxProgress::wait_for_finalized_success()`] return -//! [`crate::blocks::ExtrinsicEvents`], which can be used to iterate and inspect the events produced -//! for a specific extrinsic. We can also access _all_ of the events produced in a single block -//! using one of these two interfaces: +//! Events live in a single location in node storage which is overwritten at each block. Normal nodes tend to +//! keep a snapshot of the state at a small number of previous blocks, so you can sometimes access +//! older events by using [`crate::events::EventsClient::at()`] and providing an older block hash. +//! +//! When we submit extrinsics using Subxt, methods like [`crate::tx::TxProgress::wait_for_finalized_success()`] +//! return [`crate::blocks::ExtrinsicEvents`], which can be used to iterate and inspect the events produced +//! for a specific extrinsic. We can also access _all_ of the events produced in a single block using one of these +//! two interfaces: //! //! ```rust,no_run //! # #[tokio::main] @@ -24,7 +27,7 @@ //! // Create client: //! let client = OnlineClient::::new().await?; //! -//! // Get events from the latest block: +//! // Get events from the latest block (use .at() to specify a block hash): //! let events = client.blocks().at_latest().await?.events().await?; //! // We can use this shorthand too: //! let events = client.events().at_latest().await?; diff --git a/subxt/src/book/usage/storage.rs b/subxt/src/book/usage/storage.rs index f308f6b48d..a92ef292c1 100644 --- a/subxt/src/book/usage/storage.rs +++ b/subxt/src/book/usage/storage.rs @@ -38,7 +38,7 @@ //! ``` //! //! As well as accessing specific entries, some storage locations can also be iterated over (such as -//! the map of account information). Do do this, suffix `_root` onto the query constructor (this +//! the map of account information). To do this, suffix `_root` onto the query constructor (this //! will only be available on static constructors when iteration is actually possible): //! //! ```rust,no_run From 8924c3029fd6a4e9c3cd0b30a89665b8204aca47 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 3 May 2023 16:56:01 +0100 Subject: [PATCH 22/25] address review comments; minor tweaks --- examples/examples/setup_client_offline.rs | 2 +- examples/examples/storage_fetch.rs | 2 +- subxt/src/book/mod.rs | 2 +- subxt/src/book/setup/client.rs | 2 +- subxt/src/book/setup/codegen.rs | 2 +- subxt/src/book/setup/mod.rs | 2 +- subxt/src/book/usage/blocks.rs | 2 +- subxt/src/book/usage/constants.rs | 2 +- subxt/src/book/usage/events.rs | 2 +- subxt/src/book/usage/extrinsics.rs | 2 +- subxt/src/book/usage/mod.rs | 2 +- subxt/src/book/usage/runtime_apis.rs | 2 +- subxt/src/book/usage/storage.rs | 2 +- subxt/src/config/mod.rs | 1 - 14 files changed, 13 insertions(+), 14 deletions(-) diff --git a/examples/examples/setup_client_offline.rs b/examples/examples/setup_client_offline.rs index b50defb2f7..ba5d9b31ab 100644 --- a/examples/examples/setup_client_offline.rs +++ b/examples/examples/setup_client_offline.rs @@ -6,7 +6,7 @@ use subxt::{config::PolkadotConfig, OfflineClient}; #[tokio::main] async fn main() -> Result<(), Box> { - // We need to obtain these details for an OfflineClient to be instantiated.. + // We need to obtain the following details for an OfflineClient to be instantiated: // 1. Genesis hash (RPC call: chain_getBlockHash(0)): let genesis_hash = { diff --git a/examples/examples/storage_fetch.rs b/examples/examples/storage_fetch.rs index 86c7c2a059..1a5b034be5 100644 --- a/examples/examples/storage_fetch.rs +++ b/examples/examples/storage_fetch.rs @@ -15,7 +15,7 @@ async fn main() -> Result<(), Box> { let storage_query = polkadot::storage().system().account(&account); // Use that query to `fetch` a result. This returns an `Option<_>`, which will be - // `None` if no value exists at the given address. You can also use `fetch_default` + // `None` if no value exists at the given address. You can also use `fetch_default` // where applicable, which will return the default value if none exists. let result = api .storage() diff --git a/subxt/src/book/mod.rs b/subxt/src/book/mod.rs index e7106b5cd4..47124df1ab 100644 --- a/subxt/src/book/mod.rs +++ b/subxt/src/book/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/setup/client.rs b/subxt/src/book/setup/client.rs index 412ab95626..92fd621769 100644 --- a/subxt/src/book/setup/client.rs +++ b/subxt/src/book/setup/client.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/setup/codegen.rs b/subxt/src/book/setup/codegen.rs index f6f6e6b5ad..5cf0d8032d 100644 --- a/subxt/src/book/setup/codegen.rs +++ b/subxt/src/book/setup/codegen.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/setup/mod.rs b/subxt/src/book/setup/mod.rs index 511b6ae0f1..f3f3c8e109 100644 --- a/subxt/src/book/setup/mod.rs +++ b/subxt/src/book/setup/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/usage/blocks.rs b/subxt/src/book/usage/blocks.rs index 406391010c..b991500f86 100644 --- a/subxt/src/book/usage/blocks.rs +++ b/subxt/src/book/usage/blocks.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/usage/constants.rs b/subxt/src/book/usage/constants.rs index a6e7a52da7..94074113ee 100644 --- a/subxt/src/book/usage/constants.rs +++ b/subxt/src/book/usage/constants.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/usage/events.rs b/subxt/src/book/usage/events.rs index 2d04d7f629..b64382731e 100644 --- a/subxt/src/book/usage/events.rs +++ b/subxt/src/book/usage/events.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/usage/extrinsics.rs b/subxt/src/book/usage/extrinsics.rs index f22801c0ae..3f5abe2546 100644 --- a/subxt/src/book/usage/extrinsics.rs +++ b/subxt/src/book/usage/extrinsics.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/usage/mod.rs b/subxt/src/book/usage/mod.rs index 1493756d17..8243cf2ce2 100644 --- a/subxt/src/book/usage/mod.rs +++ b/subxt/src/book/usage/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/usage/runtime_apis.rs b/subxt/src/book/usage/runtime_apis.rs index d1b8b0c631..c6a96a257b 100644 --- a/subxt/src/book/usage/runtime_apis.rs +++ b/subxt/src/book/usage/runtime_apis.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/book/usage/storage.rs b/subxt/src/book/usage/storage.rs index a92ef292c1..92efb13890 100644 --- a/subxt/src/book/usage/storage.rs +++ b/subxt/src/book/usage/storage.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/config/mod.rs b/subxt/src/config/mod.rs index 7c0f5233f4..702a90eb07 100644 --- a/subxt/src/config/mod.rs +++ b/subxt/src/config/mod.rs @@ -95,7 +95,6 @@ pub trait Header: Sized + Encode { /// Take a type implementing [`Config`] (eg [`SubstrateConfig`]), and some type which describes the /// additional and extra parameters to pass to an extrinsic (see [`ExtrinsicParams`]), /// and returns a type implementing [`Config`] with those new [`ExtrinsicParams`]. -/// ``` pub struct WithExtrinsicParams> { _marker: std::marker::PhantomData<(T, E)>, } From 4b3531158d084fba49ae878a5a3f57f5efa101c1 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 4 May 2023 11:51:53 +0100 Subject: [PATCH 23/25] WIP add Runtime calls to book --- Cargo.lock | 74 +++++++++--------- artifacts/polkadot_metadata.scale | Bin 361757 -> 404080 bytes examples/examples/runtime_apis_dynamic.rs | 32 ++++++++ examples/examples/runtime_apis_static.rs | 27 +++++++ examples/examples/runtime_calls.rs | 88 ---------------------- subxt/src/book/usage/extrinsics.rs | 2 +- subxt/src/book/usage/runtime_apis.rs | 29 ++++++- 7 files changed, 125 insertions(+), 127 deletions(-) create mode 100644 examples/examples/runtime_apis_dynamic.rs create mode 100644 examples/examples/runtime_apis_static.rs delete mode 100644 examples/examples/runtime_calls.rs diff --git a/Cargo.lock b/Cargo.lock index da506778fd..f1bbec594c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" dependencies = [ "anstyle", "anstyle-parse", @@ -143,9 +143,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "array-bytes" @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "bounded-collections" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a071c348a5ef6da1d3a87166b408170b46002382b1dda83992b5c2208cefb370" +checksum = "e3888522b497857eb606bf51695988dba7096941822c1bcf676e3a929a9ae7a0" dependencies = [ "log", "parity-scale-codec", @@ -458,9 +458,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags", "clap_lex 0.2.4", @@ -470,9 +470,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.5" +version = "4.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a1f23fa97e1d1641371b51f35535cb26959b8e27ab50d167a8b996b5bada819" +checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" dependencies = [ "clap_builder", "clap_derive", @@ -481,9 +481,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.5" +version = "4.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdc5d93c358224b4d6867ef1356d740de2303e9892edc06c5340daeccd96bab" +checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" dependencies = [ "anstream", "anstyle", @@ -640,7 +640,7 @@ dependencies = [ "atty", "cast", "ciborium", - "clap 3.2.23", + "clap 3.2.25", "criterion-plot", "itertools", "lazy_static", @@ -837,12 +837,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c99d16b88c92aef47e58dadd53e87b4bd234c29934947a6cec8b466300f99b" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" dependencies = [ - "darling_core 0.20.0", - "darling_macro 0.20.0", + "darling_core 0.20.1", + "darling_macro 0.20.1", ] [[package]] @@ -861,9 +861,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ea05d2fcb27b53f7a98faddaf5f2914760330ab7703adfc9df13332b42189f9" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" dependencies = [ "fnv", "ident_case", @@ -886,11 +886,11 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bfb82b62b1b8a2a9808fb4caf844ede819a76cfc23b2827d7f94eefb49551eb" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ - "darling_core 0.20.0", + "darling_core 0.20.1", "quote", "syn 2.0.15", ] @@ -1691,7 +1691,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", - "rustix 0.37.14", + "rustix 0.37.19", "windows-sys 0.48.0", ] @@ -1905,9 +1905,9 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "linux-raw-sys" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" [[package]] name = "lock_api" @@ -1958,7 +1958,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" dependencies = [ - "rustix 0.37.14", + "rustix 0.37.19", ] [[package]] @@ -2630,15 +2630,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.14" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", - "linux-raw-sys 0.3.4", + "linux-raw-sys 0.3.7", "windows-sys 0.48.0", ] @@ -3457,9 +3457,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "ss58-registry" -version = "1.39.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecf0bd63593ef78eca595a7fc25e9a443ca46fe69fd472f8f09f5245cdcd769d" +checksum = "eb47a8ad42e5fc72d5b1eb104a5546937eaf39843499948bb666d6e93c62423b" dependencies = [ "Inflector", "num-format", @@ -3574,7 +3574,7 @@ dependencies = [ name = "subxt-cli" version = "0.28.0" dependencies = [ - "clap 4.2.5", + "clap 4.2.7", "color-eyre", "frame-metadata", "hex", @@ -3623,7 +3623,7 @@ dependencies = [ name = "subxt-macro" version = "0.28.0" dependencies = [ - "darling 0.20.0", + "darling 0.20.1", "proc-macro-error", "subxt-codegen", "syn 2.0.15", @@ -3820,9 +3820,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -4634,9 +4634,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.1" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" dependencies = [ "memchr", ] diff --git a/artifacts/polkadot_metadata.scale b/artifacts/polkadot_metadata.scale index 84da3d41bf4e489bfed32e388ad0840bfa837ad8..d42d495410ded81cd5cc8866f1ef68f43357662f 100644 GIT binary patch delta 62749 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 Oey@hBQLa1UT>l>vKcFrE diff --git a/examples/examples/runtime_apis_dynamic.rs b/examples/examples/runtime_apis_dynamic.rs new file mode 100644 index 0000000000..e866cd7fc8 --- /dev/null +++ b/examples/examples/runtime_apis_dynamic.rs @@ -0,0 +1,32 @@ +use sp_keyring::AccountKeyring; +use subxt::dynamic::Value; +use subxt::{config::PolkadotConfig, OnlineClient}; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client to use: + let api = OnlineClient::::new().await?; + + // Create a dynamically runtime API payload that calls the + // `AccountNonceApi_account_nonce` function. + let account = AccountKeyring::Alice.to_account_id(); + let runtime_api_call = subxt::dynamic::runtime_api_call( + "AccountNonceApi_account_nonce", + vec![Value::from_bytes(account)], + None, + ); + + // Submit the call to get back a result. + let nonce = api + .runtime_api() + .at_latest() + .await? + .call(runtime_api_call) + .await?; + + println!("Account nonce: {:#?}", nonce.to_value()); + Ok(()) +} diff --git a/examples/examples/runtime_apis_static.rs b/examples/examples/runtime_apis_static.rs new file mode 100644 index 0000000000..5e0d256bde --- /dev/null +++ b/examples/examples/runtime_apis_static.rs @@ -0,0 +1,27 @@ +use sp_keyring::AccountKeyring; +use subxt::{config::PolkadotConfig, OnlineClient}; + +#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +pub mod polkadot {} + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a client to use: + let api = OnlineClient::::new().await?; + + // Create a runtime API payload that calls into + // `AccountNonceApi_account_nonce` function. + let account = AccountKeyring::Alice.to_account_id().into(); + let runtime_api_call = polkadot::apis().account_nonce_api().account_nonce(account); + + // Submit the call and get back a result. + let nonce = api + .runtime_api() + .at_latest() + .await? + .call(runtime_api_call) + .await; + + println!("AccountNonceApi_account_nonce for Alice: {:?}", nonce); + Ok(()) +} diff --git a/examples/examples/runtime_calls.rs b/examples/examples/runtime_calls.rs deleted file mode 100644 index f2d52766fd..0000000000 --- a/examples/examples/runtime_calls.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. -// This file is dual-licensed as Apache-2.0 or GPL-3.0. -// see LICENSE for license details. - -//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da. -//! -//! E.g. -//! ```bash -//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location -//! polkadot --dev --tmp -//! ``` - -use sp_keyring::AccountKeyring; -use subxt::dynamic::Value; -use subxt::{config::PolkadotConfig, OnlineClient}; - -#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] -pub mod polkadot {} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt::init(); - - // Create a client to use: - let api = OnlineClient::::new().await?; - - // In the first part of the example calls are made using the static generated code - // and as a result the returned values are strongly typed. - - // Create a runtime API payload that calls into - // `Core_version` function. - let runtime_api_call = polkadot::apis().core().version(); - - // Submit the runtime API call. - let version = api - .runtime_api() - .at_latest() - .await? - .call(runtime_api_call) - .await; - println!("Core_version: {:?}", version); - - // Show the supported metadata versions of the node. - // Calls into `Metadata_metadata_versions` runtime function. - let runtime_api_call = polkadot::apis().metadata().metadata_versions(); - - // Submit the runtime API call. - let versions = api - .runtime_api() - .at_latest() - .await? - .call(runtime_api_call) - .await?; - println!("Metadata_metadata_versions: {:?}", versions); - - // Create a runtime API payload that calls into - // `AccountNonceApi_account_nonce` function. - let account = AccountKeyring::Alice.to_account_id().into(); - let runtime_api_call = polkadot::apis().account_nonce_api().account_nonce(account); - - // Submit the runtime API call. - let nonce = api - .runtime_api() - .at_latest() - .await? - .call(runtime_api_call) - .await; - println!("AccountNonceApi_account_nonce for Alice: {:?}", nonce); - - // Dynamic calls. - let runtime_api_call = subxt::dynamic::runtime_api_call( - "Metadata_metadata_versions", - Vec::>::new(), - None, - ); - let versions = api - .runtime_api() - .at_latest() - .await? - .call(runtime_api_call) - .await?; - println!( - " dynamic Metadata_metadata_versions: {:#?}", - versions.to_value() - ); - - Ok(()) -} diff --git a/subxt/src/book/usage/extrinsics.rs b/subxt/src/book/usage/extrinsics.rs index 3f5abe2546..3d409f5772 100644 --- a/subxt/src/book/usage/extrinsics.rs +++ b/subxt/src/book/usage/extrinsics.rs @@ -43,7 +43,7 @@ //! use subxt::dynamic::Value; //! //! let extrinsic_payload = subxt::dynamic::tx("System", "remark", vec![ -//! Value::from_bytes("Hello there") +//! Value::from_bytes("Hello there") //! ]); //! ``` //! diff --git a/subxt/src/book/usage/runtime_apis.rs b/subxt/src/book/usage/runtime_apis.rs index c6a96a257b..cb5dd40574 100644 --- a/subxt/src/book/usage/runtime_apis.rs +++ b/subxt/src/book/usage/runtime_apis.rs @@ -5,7 +5,34 @@ //! # Runtime API interface //! //! The Runtime API interface allows Subxt to call runtime APIs exposed by certain pallets in order -//! to obtain information. +//! to obtain information. Much like [`super::storage`] and [`super::extrinsics`], Making a runtime +//! call to a node and getting the response back takes the following steps: +//! +//! 1. [Constructing a runtime call](#constructing-a-runtime-call) +//! 2. [Submitting it to get back the response](#submitting-it) +//! +//! ## Constructing a runtime call +//! +//! We can use the statically generated interface to build runtime calls: +//! +//! ```rust,no_run +//! use sp_keyring::AccountKeyring; +//! +//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")] +//! pub mod polkadot {} +//! +//! let runtime_call = polkadot::apis().metadata().metadata_versions(); +//! ``` +//! +//! Alternately, we can dynamically construct a runtime call: +//! +//! ```rust,no_run +//! use sp_keyring::AccountKeyring; +//! use subxt::dynamic::Value; +//! +//! let account = AccountKeyring::Alice.to_account_id(); +//! let storage_query = subxt::dynamic::runtime_api_call("Metadata_metadata_versions", vec![], None) +//! ``` //! //! At the moment, this interface is simply a wrapper around the `state_call` RPC method. This means //! that you need to know which runtime calls are available and how to encode their parameters (if From f0cbfc394720ed0a40f431fcec81908f253173f8 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 4 May 2023 13:54:32 +0100 Subject: [PATCH 24/25] Improve Runtime API docs --- examples/examples/runtime_apis_dynamic.rs | 3 +- subxt/src/book/usage/runtime_apis.rs | 45 +++++++++++++++++++---- subxt/src/lib.rs | 14 +++---- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/examples/examples/runtime_apis_dynamic.rs b/examples/examples/runtime_apis_dynamic.rs index e866cd7fc8..aa276bdbda 100644 --- a/examples/examples/runtime_apis_dynamic.rs +++ b/examples/examples/runtime_apis_dynamic.rs @@ -15,8 +15,7 @@ async fn main() -> Result<(), Box> { let account = AccountKeyring::Alice.to_account_id(); let runtime_api_call = subxt::dynamic::runtime_api_call( "AccountNonceApi_account_nonce", - vec![Value::from_bytes(account)], - None, + vec![Value::from_bytes(account)] ); // Submit the call to get back a result. diff --git a/subxt/src/book/usage/runtime_apis.rs b/subxt/src/book/usage/runtime_apis.rs index cb5dd40574..27774e3a68 100644 --- a/subxt/src/book/usage/runtime_apis.rs +++ b/subxt/src/book/usage/runtime_apis.rs @@ -11,6 +11,12 @@ //! 1. [Constructing a runtime call](#constructing-a-runtime-call) //! 2. [Submitting it to get back the response](#submitting-it) //! +//! **Note:** Runtime APIs are only available when using V15 metadata, which is currently unstable. +//! You'll need to use `subxt metadata --version unstable` command to download the unstable V15 metadata, +//! and activate the `unstable-metadata` feature in Subxt for it to also use this metadata from a node. The +//! metadata format is unstable because it may change and break compatibility with Subxt at any moment, so +//! use at your own risk. +//! //! ## Constructing a runtime call //! //! We can use the statically generated interface to build runtime calls: @@ -31,18 +37,43 @@ //! use subxt::dynamic::Value; //! //! let account = AccountKeyring::Alice.to_account_id(); -//! let storage_query = subxt::dynamic::runtime_api_call("Metadata_metadata_versions", vec![], None) +//! let runtime_call = subxt::dynamic::runtime_api_call( +//! "Metadata_metadata_versions", +//! Vec::>::new() +//! ); +//! ``` +//! +//! All valid runtime calls implement [`subxt::runtime_api::RuntimeApiPayload`], a trait which +//! describes how to encode the runtime call arguments and what return type to decode from the +//! response. +//! +//! ## Submitting it +//! +//! Runtime calls can be handed to [`subxt::runtime_api::RuntimeApi::call()`], which will submit +//! them and hand back the associated response. +//! +//! ### Making a static Runtime API call +//! +//! The easiest way to make a runtime API call is to use the statically generated interface. +//! +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/runtime_apis_static.rs")] //! ``` //! -//! At the moment, this interface is simply a wrapper around the `state_call` RPC method. This means -//! that you need to know which runtime calls are available and how to encode their parameters (if -//! needed). Eventually, Subxt will be able to generate an interface to the Runtime APIs exposed -//! here to make this as easy to do as constructing extrinsics or storage queries. +//! ### Making a dynamic Runtime API call //! -//! ## Example +//! If you'd prefer to construct the call at runtime, you can do this using the +//! [`subxt::dynamic::runtime_api_call`] method. +//! +//! ```rust,ignore +#![doc = include_str!("../../../../examples/examples/runtime_apis_dynamic.rs")] +//! ``` //! -//! Downloading node metadata via the Metadata runtime API interface: +//! ### Making a raw call //! +//! This is generally discouraged in favour of one of the above, but may be necessary (especially if +//! the node you're talking to does not yet serve V15 metadata). Here, you must manually encode +//! the argument bytes and manually provide a type for the response bytes to be decoded into. //! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/runtime_apis_raw.rs")] diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index 48320c868d..f7eeca7bc3 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -139,15 +139,15 @@ pub mod ext { /// This is useful if you write a library which uses this macro, but don't want to force users to depend on `subxt` /// at the top level too. By default the path `::subxt` is used. /// -/// ## `substitute_type(type = "...", with = "...")` +/// ## `substitute_type(path = "...", with = "...")` /// -/// This attribute replaces any reference to the generated type at the path given by `type` with a +/// This attribute replaces any reference to the generated type at the path given by `path` with a /// reference to the path given by `with`. /// /// ```rust /// #[subxt::subxt( /// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", -/// substitute_type(type = "sp_arithmetic::per_things::Perbill", with = "crate::Foo") +/// substitute_type(path = "sp_arithmetic::per_things::Perbill", with = "crate::Foo") /// )] /// mod polkadot {} /// @@ -186,7 +186,7 @@ pub mod ext { /// #[subxt::subxt( /// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", /// substitute_type( -/// type = "sp_runtime::multiaddress::MultiAddress", +/// path = "sp_runtime::multiaddress::MultiAddress", /// with = "::subxt::utils::Static<::sp_runtime::MultiAddress>" /// ) /// )] @@ -213,7 +213,7 @@ pub mod ext { /// Any substituted types (including the default substitutes) must also implement these traits in order to avoid errors /// here. /// -/// ## `derive_for_type(type = "...", derive = "...")` +/// ## `derive_for_type(path = "...", derive = "...")` /// /// Unlike the above, which derives some trait on every generated type, this attribute allows you to derive traits only /// for specific types. Note that any types which are used inside the specified type may also need to derive the same traits. @@ -222,8 +222,8 @@ pub mod ext { /// #[subxt::subxt( /// runtime_metadata_path = "../artifacts/polkadot_metadata.scale", /// derive_for_all_types = "Eq, PartialEq", -/// derive_for_type(type = "frame_support::PalletId", derive = "Ord, PartialOrd"), -/// derive_for_type(type = "sp_runtime::ModuleError", derive = "Hash"), +/// derive_for_type(path = "frame_support::PalletId", derive = "Ord, PartialOrd"), +/// derive_for_type(path = "sp_runtime::ModuleError", derive = "Hash"), /// )] /// mod polkadot {} /// ``` From e5990ea0a0692d232148658081ca89357cf5630e Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 4 May 2023 14:00:59 +0100 Subject: [PATCH 25/25] expose thing we forgot to expose and doc link fixes --- examples/examples/runtime_apis_dynamic.rs | 2 +- subxt/src/book/usage/runtime_apis.rs | 6 +++--- subxt/src/metadata/mod.rs | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/examples/runtime_apis_dynamic.rs b/examples/examples/runtime_apis_dynamic.rs index aa276bdbda..c861c53357 100644 --- a/examples/examples/runtime_apis_dynamic.rs +++ b/examples/examples/runtime_apis_dynamic.rs @@ -15,7 +15,7 @@ async fn main() -> Result<(), Box> { let account = AccountKeyring::Alice.to_account_id(); let runtime_api_call = subxt::dynamic::runtime_api_call( "AccountNonceApi_account_nonce", - vec![Value::from_bytes(account)] + vec![Value::from_bytes(account)], ); // Submit the call to get back a result. diff --git a/subxt/src/book/usage/runtime_apis.rs b/subxt/src/book/usage/runtime_apis.rs index 27774e3a68..f5b4dd0799 100644 --- a/subxt/src/book/usage/runtime_apis.rs +++ b/subxt/src/book/usage/runtime_apis.rs @@ -43,13 +43,13 @@ //! ); //! ``` //! -//! All valid runtime calls implement [`subxt::runtime_api::RuntimeApiPayload`], a trait which +//! All valid runtime calls implement [`crate::runtime_api::RuntimeApiPayload`], a trait which //! describes how to encode the runtime call arguments and what return type to decode from the //! response. //! //! ## Submitting it //! -//! Runtime calls can be handed to [`subxt::runtime_api::RuntimeApi::call()`], which will submit +//! Runtime calls can be handed to [`crate::runtime_api::RuntimeApi::call()`], which will submit //! them and hand back the associated response. //! //! ### Making a static Runtime API call @@ -63,7 +63,7 @@ //! ### Making a dynamic Runtime API call //! //! If you'd prefer to construct the call at runtime, you can do this using the -//! [`subxt::dynamic::runtime_api_call`] method. +//! [`crate::dynamic::runtime_api_call`] method. //! //! ```rust,ignore #![doc = include_str!("../../../../examples/examples/runtime_apis_dynamic.rs")] diff --git a/subxt/src/metadata/mod.rs b/subxt/src/metadata/mod.rs index 2e80ac9c15..7ada3e62b7 100644 --- a/subxt/src/metadata/mod.rs +++ b/subxt/src/metadata/mod.rs @@ -13,6 +13,7 @@ pub use metadata_location::MetadataLocation; pub use metadata_type::{ ErrorMetadata, EventMetadata, InvalidMetadataError, Metadata, MetadataError, PalletMetadata, + RuntimeFnMetadata, }; pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};