Starting new subxt crate; configs implemented

This commit is contained in:
James Wilson
2025-11-25 12:29:13 +00:00
parent 0c6423bb48
commit 4c27bd8062
66 changed files with 6923 additions and 8 deletions
+43
View File
@@ -0,0 +1,43 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client that subscribes to blocks of the Polkadot network.
let api = OnlineClient::<PolkadotConfig>::from_url("wss://rpc.polkadot.io:443").await?;
// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
while let Some(block) = blocks_sub.next().await {
let block = block?;
let block_number = block.header().number;
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash})");
// Decode each signed extrinsic in the block dynamically
let extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
let Some(transaction_extensions) = ext.transaction_extensions() else {
continue; // we do not look at inherents in this example
};
// Decode the fields into our dynamic Value type to display:
let fields = ext.decode_as_fields::<scale_value::Value>()?;
println!(" {}/{}", ext.pallet_name(), ext.call_name());
println!(" Transaction Extensions:");
for signed_ext in transaction_extensions.iter() {
// We only want to take a look at these 3 signed extensions, because the others all just have unit fields.
if ["CheckMortality", "CheckNonce", "ChargeTransactionPayment"]
.contains(&signed_ext.name())
{
println!(" {}: {}", signed_ext.name(), signed_ext.value()?);
}
}
println!(" Fields:");
println!(" {fields}\n");
}
}
Ok(())
}
+64
View File
@@ -0,0 +1,64 @@
#![allow(missing_docs)]
use subxt::{
OnlineClient, PolkadotConfig,
utils::{AccountId32, MultiAddress},
};
use codec::Decode;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
use polkadot::balances::calls::types::TransferKeepAlive;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client that subscribes to blocks of the Polkadot network.
let api = OnlineClient::<PolkadotConfig>::from_url("wss://rpc.polkadot.io:443").await?;
// Subscribe to all finalized blocks:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
// For each block, print details about the `TransferKeepAlive` transactions we are interested in.
while let Some(block) = blocks_sub.next().await {
let block = block?;
let block_number = block.header().number;
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash}):");
let extrinsics = block.extrinsics().await?;
for transfer in extrinsics.find::<TransferKeepAlive>() {
let transfer = transfer?;
let Some(extensions) = transfer.details.transaction_extensions() else {
panic!("TransferKeepAlive should be signed")
};
let addr_bytes = transfer
.details
.address_bytes()
.expect("TransferKeepAlive should be signed");
let sender = MultiAddress::<AccountId32, ()>::decode(&mut &addr_bytes[..])
.expect("Decoding should work");
let sender = display_address(&sender);
let receiver = display_address(&transfer.value.dest);
let value = transfer.value.value;
let tip = extensions.tip().expect("Should have tip");
let nonce = extensions.nonce().expect("Should have nonce");
println!(
" Transfer of {value} DOT:\n {sender} (Tip: {tip}, Nonce: {nonce}) ---> {receiver}",
);
}
}
Ok(())
}
fn display_address(addr: &MultiAddress<AccountId32, ()>) -> String {
if let MultiAddress::Id(id32) = addr {
format!("{id32}")
} else {
"MultiAddress::...".into()
}
}
+63
View File
@@ -0,0 +1,63 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::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 extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
let idx = ext.index();
let events = ext.events().await?;
let bytes_hex = format!("0x{}", hex::encode(ext.bytes()));
// See the API docs for more ways to decode extrinsics:
let decoded_ext = ext.as_root_extrinsic::<polkadot::Call>();
println!(" Extrinsic #{idx}:");
println!(" Bytes: {bytes_hex}");
println!(" Decoded: {decoded_ext:?}");
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.decode_as_fields::<scale_value::Value>()?;
println!(" {pallet_name}_{event_name}");
println!(" {event_values}");
}
println!(" Transaction Extensions:");
if let Some(transaction_extensions) = ext.transaction_extensions() {
for transaction_extension in transaction_extensions.iter() {
let name = transaction_extension.name();
let value = transaction_extension.value()?.to_string();
println!(" {name}: {value}");
}
}
}
}
Ok(())
}
+26
View File
@@ -0,0 +1,26 @@
#![allow(missing_docs)]
use subxt::dynamic::Value;
use subxt::{OnlineClient, PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// We can query a constant by providing a tuple of the pallet and constant name. The return type
// will be `Value` if we pass this query:
let constant_query = ("System", "BlockLength");
let _value = api.constants().at(&constant_query)?;
// Or we can use the library function to query a constant, which allows us to pass a generic type
// that Subxt will attempt to decode the constant into:
let constant_query = subxt::dynamic::constant::<Value>("System", "BlockLength");
let value = api.constants().at(&constant_query)?;
// Or we can obtain the bytes for the constant, using either form of query.
let bytes = api.constants().bytes_at(&constant_query)?;
println!("Constant bytes: {:?}", bytes);
println!("Constant value: {}", value);
Ok(())
}
+24
View File
@@ -0,0 +1,24 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// A query to obtain some constant:
let constant_query = polkadot::constants().system().block_length();
// Obtain the value:
let value = api.constants().at(&constant_query)?;
// Or obtain the bytes:
let bytes = api.constants().bytes_at(&constant_query)?;
println!("Encoded block length: {bytes:?}");
println!("Block length: {value:?}");
Ok(())
}
+48
View File
@@ -0,0 +1,48 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Get events for the latest block:
let events = api.events().at_latest().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.decode_as_fields::<scale_value::Value>()?;
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::<polkadot::Event>() {
println!("{ev:?}");
} else {
println!("<Cannot decode event>");
}
}
// Or we can look for specific events which match our statically defined ones:
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(ev) = transfer_event {
println!(" - Balance transfer success: value: {:?}", ev.amount);
} else {
println!(" - No balance transfer event found in this block");
}
Ok(())
}
+47
View File
@@ -0,0 +1,47 @@
#![allow(missing_docs)]
use futures::StreamExt;
use subxt::{PolkadotConfig, client::OnlineClient, lightclient::LightClient};
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
const POLKADOT_SPEC: &str = include_str!("../../artifacts/demo_chain_specs/polkadot.json");
const ASSET_HUB_SPEC: &str =
include_str!("../../artifacts/demo_chain_specs/polkadot_asset_hub.json");
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// The lightclient logs are informative:
tracing_subscriber::fmt::init();
// Instantiate a light client with the Polkadot relay chain,
// and connect it to Asset Hub, too.
let (lightclient, polkadot_rpc) = LightClient::relay_chain(POLKADOT_SPEC)?;
let asset_hub_rpc = lightclient.parachain(ASSET_HUB_SPEC)?;
// Create Subxt clients from these Smoldot backed RPC clients.
let polkadot_api = OnlineClient::<PolkadotConfig>::from_rpc_client(polkadot_rpc).await?;
let asset_hub_api = OnlineClient::<PolkadotConfig>::from_rpc_client(asset_hub_rpc).await?;
// Use them!
let polkadot_sub = polkadot_api
.blocks()
.subscribe_finalized()
.await?
.map(|block| ("Polkadot", block));
let parachain_sub = asset_hub_api
.blocks()
.subscribe_finalized()
.await?
.map(|block| ("AssetHub", block));
let mut stream_combinator = futures::stream::select(polkadot_sub, parachain_sub);
while let Some((chain, block)) = stream_combinator.next().await {
let block = block?;
println!(" Chain {:?} hash={:?}", chain, block.hash());
}
Ok(())
}
+58
View File
@@ -0,0 +1,58 @@
#![allow(missing_docs)]
use subxt::utils::fetch_chainspec_from_rpc_node;
use subxt::{
PolkadotConfig,
client::OnlineClient,
lightclient::{ChainConfig, LightClient},
};
use subxt_signer::sr25519::dev;
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// The smoldot logs are informative:
tracing_subscriber::fmt::init();
// Use a utility function to obtain a chain spec from a locally running node:
let chain_spec = fetch_chainspec_from_rpc_node("ws://127.0.0.1:9944").await?;
// Configure the bootnodes of this chain spec. In this case, because we start one
// single node, the bootnodes must be overwritten for the light client to connect
// to the local node.
//
// The `12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp` is the P2P address
// from a local polkadot node starting with
// `--node-key 0000000000000000000000000000000000000000000000000000000000000001`
let chain_config = ChainConfig::chain_spec(chain_spec.get()).set_bootnodes([
"/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp",
])?;
// Start the light client up, establishing a connection to the local node.
let (_light_client, chain_rpc) = LightClient::relay_chain(chain_config)?;
let api = OnlineClient::<PolkadotConfig>::from_rpc_client(chain_rpc).await?;
// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(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 = dev::alice();
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::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
}
Ok(())
}
+61
View File
@@ -0,0 +1,61 @@
#![allow(missing_docs)]
use subxt::backend::{legacy::LegacyRpcMethods, rpc::RpcClient};
use subxt::config::DefaultExtrinsicParamsBuilder as Params;
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// First, create a raw RPC client:
let rpc_client = RpcClient::from_url("ws://127.0.0.1:9944").await?;
// Use this to construct our RPC methods:
let rpc = LegacyRpcMethods::<PolkadotConfig>::new(rpc_client.clone());
// We can use the same client to drive our full Subxt interface too:
let api = OnlineClient::<PolkadotConfig>::from_rpc_client(rpc_client.clone()).await?;
// Now, we can make some RPC calls using some legacy RPC methods.
println!(
"📛 System Name: {:?}\n🩺 Health: {:?}\n🖫 Properties: {:?}\n🔗 Chain: {:?}\n",
rpc.system_name().await?,
rpc.system_health().await?,
rpc.system_properties().await?,
rpc.system_chain().await?
);
// We can also interleave RPC calls and using the full Subxt client, here to submit multiple
// transactions using the legacy `system_account_next_index` RPC call, which returns a nonce
// that is adjusted for any transactions already in the pool:
let alice = dev::alice();
let bob = dev::bob();
loop {
let current_nonce = rpc
.system_account_next_index(&alice.public_key().into())
.await?;
let ext_params = Params::new().mortal(8).nonce(current_nonce).build();
let balance_transfer = polkadot::tx()
.balances()
.transfer_allow_death(bob.public_key().into(), 1_000_000);
let ext_hash = api
.tx()
.create_partial_offline(&balance_transfer, ext_params)?
.sign(&alice)
.submit()
.await?;
println!("Submitted ext {ext_hash} with nonce {current_nonce}");
// Sleep less than block time, but long enough to ensure
// not all transactions end up in the same block.
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
}
+30
View File
@@ -0,0 +1,30 @@
#![allow(missing_docs)]
use subxt::utils::AccountId32;
use subxt::{OnlineClient, config::PolkadotConfig};
use subxt_signer::sr25519::dev;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create a "dynamic" runtime API payload that calls the
// `AccountNonceApi_account_nonce` function. We could use the
// `scale_value::Value` type as output, and a vec of those as inputs,
// but since we know the input + return types we can pass them directly.
// There is one input argument, so the inputs are a tuple of one element.
let account: AccountId32 = dev::alice().public_key().into();
let runtime_api_call =
subxt::dynamic::runtime_api_call::<_, u64>("AccountNonceApi", "account_nonce", (account,));
// 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);
Ok(())
}
+23
View File
@@ -0,0 +1,23 @@
#![allow(missing_docs)]
use subxt::ext::codec::{Compact, Decode};
use subxt::ext::frame_metadata::RuntimeMetadataPrefixed;
use subxt::{OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Use runtime APIs at the latest block:
let runtime_apis = api.runtime_api().at_latest().await?;
// Ask for metadata and decode it:
let result_bytes = runtime_apis.call_raw("Metadata_metadata", None).await?;
let (_, meta): (Compact<u32>, RuntimeMetadataPrefixed) = Decode::decode(&mut &*result_bytes)?;
println!("{meta:?}");
Ok(())
}
+28
View File
@@ -0,0 +1,28 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, config::PolkadotConfig};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a client to use:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Create a runtime API payload that calls into
// `AccountNonceApi_account_nonce` function.
let account = dev::alice().public_key().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(())
}
+86
View File
@@ -0,0 +1,86 @@
#![allow(missing_docs)]
use std::{
fmt::Write,
pin::Pin,
sync::{Arc, Mutex},
};
use subxt::{
OnlineClient, PolkadotConfig,
backend::rpc::{RawRpcFuture, RawRpcSubscription, RawValue, RpcClient, RpcClientT},
};
// 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<Mutex<String>>,
}
// 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<Box<RawValue>>,
) -> RawRpcFuture<'a, Box<RawValue>> {
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<Box<RawValue>>,
unsub: &'a str,
) -> RawRpcFuture<'a, RawRpcSubscription> {
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<Box<dyn futures::Stream<Item = _> + Send>> = Box::pin(stream);
// This subscription does not provide an ID.
Box::pin(std::future::ready(Ok(RawRpcSubscription {
stream,
id: None,
})))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Instantiate our replacement RPC client.
let log = Arc::default();
let rpc_client = {
let inner = MyLoggingClient {
log: Arc::clone(&log),
};
RpcClient::new(inner)
};
// 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::<PolkadotConfig>::from_rpc_client(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(())
}
+35
View File
@@ -0,0 +1,35 @@
#![allow(missing_docs)]
use subxt::ext::codec::Decode;
use subxt::metadata::Metadata;
use subxt::utils::H256;
use subxt::{OfflineClient, config::PolkadotConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// We need to obtain the following 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::client::RuntimeVersion {
spec_version: 9370,
transaction_version: 20,
};
// 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_small.scale").unwrap();
Metadata::decode(&mut &*bytes).unwrap()
};
// Create an offline client using the details obtained above:
let _api = OfflineClient::<PolkadotConfig>::new(genesis_hash, runtime_version, metadata);
Ok(())
}
+54
View File
@@ -0,0 +1,54 @@
#![allow(missing_docs)]
use subxt::config::{
Config, DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder, PolkadotConfig, SubstrateConfig,
};
use subxt_signer::sr25519::dev;
#[subxt::subxt(
runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
derive_for_type(
path = "staging_xcm::v3::multilocation::MultiLocation",
derive = "Clone, codec::Encode",
recursive
)
)]
pub mod runtime {}
use runtime::runtime_types::staging_xcm::v3::multilocation::MultiLocation;
use runtime::runtime_types::xcm::v3::junctions::Junctions;
// We don't need to construct this at runtime, so an empty enum is appropriate.
pub enum AssetHubConfig {}
impl Config for AssetHubConfig {
type AccountId = <SubstrateConfig as Config>::AccountId;
type Address = <PolkadotConfig as Config>::Address;
type Signature = <SubstrateConfig as Config>::Signature;
type Hasher = <SubstrateConfig as Config>::Hasher;
type Header = <SubstrateConfig as Config>::Header;
type ExtrinsicParams = DefaultExtrinsicParams<AssetHubConfig>;
// Here we use the MultiLocation from the metadata as a part of the config:
// The `ChargeAssetTxPayment` signed extension that is part of the ExtrinsicParams above, now uses the type:
type AssetId = MultiLocation;
}
#[tokio::main]
async fn main() {
// With the config defined, we can create an extrinsic with subxt:
let client = subxt::OnlineClient::<AssetHubConfig>::new().await.unwrap();
let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());
// Build extrinsic params using an asset at this location as a tip:
let location: MultiLocation = MultiLocation {
parents: 3,
interior: Junctions::Here,
};
let tx_config = DefaultExtrinsicParamsBuilder::<AssetHubConfig>::new()
.tip_of(1234, location)
.build();
// And provide the extrinsic params including the tip when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config)
.await;
}
+97
View File
@@ -0,0 +1,97 @@
#![allow(missing_docs)]
use codec::Encode;
use subxt::client::ClientState;
use subxt::config::{
Config, ExtrinsicParams, ExtrinsicParamsEncoder, ExtrinsicParamsError, HashFor,
transaction_extensions::Params,
};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
pub mod runtime {}
// We don't need to construct this at runtime,
// so an empty enum is appropriate:
pub enum CustomConfig {}
impl Config for CustomConfig {
type AccountId = subxt::utils::AccountId32;
type Address = subxt::utils::MultiAddress<Self::AccountId, ()>;
type Signature = subxt::utils::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = CustomExtrinsicParams<Self>;
type AssetId = u32;
}
// This represents some arbitrary (and nonsensical) custom parameters that
// will be attached to transaction extra and additional payloads:
pub struct CustomExtrinsicParams<T: Config> {
genesis_hash: HashFor<T>,
tip: u128,
foo: bool,
}
// We can provide a "pretty" interface to allow users to provide these:
#[derive(Default)]
pub struct CustomExtrinsicParamsBuilder {
tip: u128,
foo: bool,
}
impl CustomExtrinsicParamsBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn tip(mut self, value: u128) -> Self {
self.tip = value;
self
}
pub fn enable_foo(mut self) -> Self {
self.foo = true;
self
}
}
impl<T: Config> Params<T> for CustomExtrinsicParamsBuilder {}
// Describe how to fetch and then encode the params:
impl<T: Config> ExtrinsicParams<T> for CustomExtrinsicParams<T> {
type Params = CustomExtrinsicParamsBuilder;
// Gather together all of the params we will need to encode:
fn new(client: &ClientState<T>, params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(Self {
genesis_hash: client.genesis_hash,
tip: params.tip,
foo: params.foo,
})
}
}
// Encode the relevant params when asked:
impl<T: Config> ExtrinsicParamsEncoder for CustomExtrinsicParams<T> {
fn encode_value_to(&self, v: &mut Vec<u8>) {
(self.tip, self.foo).encode_to(v);
}
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
self.genesis_hash.encode_to(v)
}
}
#[tokio::main]
async fn main() {
// With the config defined, it can be handed to Subxt as follows:
let client = subxt::OnlineClient::<CustomConfig>::new().await.unwrap();
let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());
// Build your custom "Params":
let tx_config = CustomExtrinsicParamsBuilder::new().tip(1234).enable_foo();
// And provide them when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), tx_config)
.await;
}
@@ -0,0 +1,106 @@
#![allow(missing_docs)]
use codec::Encode;
use scale_encode::EncodeAsType;
use scale_info::PortableRegistry;
use subxt::client::ClientState;
use subxt::config::transaction_extensions;
use subxt::config::{
Config, DefaultExtrinsicParamsBuilder, ExtrinsicParams, ExtrinsicParamsEncoder,
ExtrinsicParamsError,
};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod runtime {}
// We don't need to construct this at runtime,
// so an empty enum is appropriate:
#[derive(EncodeAsType)]
pub enum CustomConfig {}
impl Config for CustomConfig {
type AccountId = subxt::utils::AccountId32;
type Address = subxt::utils::MultiAddress<Self::AccountId, ()>;
type Signature = subxt::utils::MultiSignature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header = subxt::config::substrate::SubstrateHeader<u32, Self::Hasher>;
type ExtrinsicParams = transaction_extensions::AnyOf<
Self,
(
// Load in the existing signed extensions we're interested in
// (if the extension isn't actually needed it'll just be ignored):
transaction_extensions::VerifySignature<Self>,
transaction_extensions::CheckSpecVersion,
transaction_extensions::CheckTxVersion,
transaction_extensions::CheckNonce,
transaction_extensions::CheckGenesis<Self>,
transaction_extensions::CheckMortality<Self>,
transaction_extensions::ChargeAssetTxPayment<Self>,
transaction_extensions::ChargeTransactionPayment,
transaction_extensions::CheckMetadataHash,
// And add a new one of our own:
CustomTransactionExtension,
),
>;
type AssetId = u32;
}
// Our custom signed extension doesn't do much:
pub struct CustomTransactionExtension;
// Give the extension a name; this allows `AnyOf` to look it
// up in the chain metadata in order to know when and if to use it.
impl<T: Config> transaction_extensions::TransactionExtension<T> for CustomTransactionExtension {
type Decoded = ();
fn matches(identifier: &str, _type_id: u32, _types: &PortableRegistry) -> bool {
identifier == "CustomTransactionExtension"
}
}
// Gather together any params we need for our signed extension, here none.
impl<T: Config> ExtrinsicParams<T> for CustomTransactionExtension {
type Params = ();
fn new(_client: &ClientState<T>, _params: Self::Params) -> Result<Self, ExtrinsicParamsError> {
Ok(CustomTransactionExtension)
}
}
// Encode whatever the extension needs to provide when asked:
impl ExtrinsicParamsEncoder for CustomTransactionExtension {
fn encode_value_to(&self, v: &mut Vec<u8>) {
"Hello".encode_to(v);
}
fn encode_implicit_to(&self, v: &mut Vec<u8>) {
true.encode_to(v)
}
}
// When composing a tuple of signed extensions, the user parameters we need must
// be able to convert `Into` a tuple of corresponding `Params`. Here, we just
// "hijack" the default param builder, but add the `Params` (`()`) for our
// new signed extension at the end, to make the types line up. IN reality you may wish
// to construct an entirely new interface to provide the relevant `Params`.
pub fn custom(
params: DefaultExtrinsicParamsBuilder<CustomConfig>,
) -> <<CustomConfig as Config>::ExtrinsicParams as ExtrinsicParams<CustomConfig>>::Params {
let (a, b, c, d, e, f, g, h, i) = params.build();
(a, b, c, d, e, f, g, h, i, ())
}
#[tokio::main]
async fn main() {
// With the config defined, it can be handed to Subxt as follows:
let client = subxt::OnlineClient::<CustomConfig>::new().await.unwrap();
let tx_payload = runtime::tx().system().remark(b"Hello".to_vec());
// Configure the tx params:
let tx_config = DefaultExtrinsicParamsBuilder::new().tip(1234);
// And provide them when submitting a transaction:
let _ = client
.tx()
.sign_and_submit_then_watch(&tx_payload, &dev::alice(), custom(tx_config))
.await;
}
@@ -0,0 +1,77 @@
//! Example to utilize the `reconnecting rpc client` in subxt
//! which hidden behind behind `--feature reconnecting-rpc-client`
//!
//! To utilize full logs from the RPC client use:
//! `RUST_LOG="jsonrpsee=trace,subxt-reconnecting-rpc-client=trace"`
#![allow(missing_docs)]
use std::time::Duration;
use futures::StreamExt;
use subxt::backend::rpc::reconnecting_rpc_client::{ExponentialBackoff, RpcClient};
use subxt::{OnlineClient, PolkadotConfig};
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a new client with a reconnecting RPC client.
let rpc = RpcClient::builder()
// Reconnect with exponential backoff
//
// This API is "iterator-like" and we use `take` to limit the number of retries.
.retry_policy(
ExponentialBackoff::from_millis(100)
.max_delay(Duration::from_secs(10))
.take(3),
)
// There are other configurations as well that can be found at [`reconnecting_rpc_client::ClientBuilder`].
.build("ws://localhost:9944".to_string())
.await?;
// If you want to use the chainhead backend with the reconnecting RPC client, you can do so like this:
//
// ```
// use subxt::backend::chain_head:ChainHeadBackend;
// use subxt::OnlineClient;
//
// let backend = ChainHeadBackend::builder().build_with_background_task(RpcClient::new(rpc.clone()));
// let api: OnlineClient<PolkadotConfig> = OnlineClient::from_backend(Arc::new(backend)).await?;
// ```
let api: OnlineClient<PolkadotConfig> = OnlineClient::from_rpc_client(rpc.clone()).await?;
// Run for at most 100 blocks and print a bunch of information about it.
//
// The subscription is automatically re-started when the RPC client has reconnected.
// You can test that by stopping the polkadot node and restarting it.
let mut blocks_sub = api.blocks().subscribe_finalized().await?.take(100);
while let Some(block) = blocks_sub.next().await {
let block = match block {
Ok(b) => b,
Err(e) => {
// This can only happen on the legacy backend and the unstable backend
// will handle this internally.
if e.is_disconnected_will_reconnect() {
println!("The RPC connection was lost and we may have missed a few blocks");
continue;
}
return Err(e.into());
}
};
let block_number = block.number();
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash})");
}
Ok(())
}
@@ -0,0 +1,35 @@
//! Example to utilize the ChainHeadBackend rpc backend to subscribe to finalized blocks.
#![allow(missing_docs)]
use futures::StreamExt;
use subxt::backend::chain_head::{ChainHeadBackend, ChainHeadBackendBuilder};
use subxt::backend::rpc::RpcClient;
use subxt::{OnlineClient, PolkadotConfig};
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let rpc = RpcClient::from_url("ws://localhost:9944".to_string()).await?;
let backend: ChainHeadBackend<PolkadotConfig> =
ChainHeadBackendBuilder::default().build_with_background_driver(rpc.clone());
let api = OnlineClient::from_backend(std::sync::Arc::new(backend)).await?;
let mut blocks_sub = api.blocks().subscribe_finalized().await?.take(100);
while let Some(block) = blocks_sub.next().await {
let block = block?;
let block_number = block.number();
let block_hash = block.hash();
println!("Block #{block_number} ({block_hash})");
}
Ok(())
}
+32
View File
@@ -0,0 +1,32 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
let account = dev::alice().public_key().into();
// Build a storage query to access account information.
let storage_query = polkadot::storage().system().account();
// Use that query to access a storage entry, fetch a result and decode the value.
// The static address knows that fetching requires a tuple of one value, an
// AccountId32.
let client_at = api.storage().at_latest().await?;
let account_info = client_at
.entry(storage_query)?
.fetch((account,))
.await?
.decode()?;
// The static address that we got from the subxt macro knows the expected input
// and return types, so it is decoded into a static type for us.
println!("Alice: {account_info:?}");
Ok(())
}
+34
View File
@@ -0,0 +1,34 @@
#![allow(missing_docs)]
use subxt::dynamic::{At, Value};
use subxt::utils::AccountId32;
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a dynamic storage query to access account information.
// here, we assume that there is one value to provide at this entry
// to access a value; an AccountId32. In this example we don't know the
// return type and so we set it to `Value`, which anything can decode into.
let account: AccountId32 = dev::alice().public_key().into();
let storage_query = subxt::dynamic::storage::<(AccountId32,), Value>("System", "Account");
// Use that query to access a storage entry, fetch a result and decode the value.
let client_at = api.storage().at_latest().await?;
let account_info = client_at
.entry(storage_query)?
.fetch((account,))
.await?
.decode()?;
// With out `Value` type we can dig in to find what we want using the `At`
// trait and `.at()` method that this provides on the Value.
println!(
"Alice has free balance: {}",
account_info.at("data").at("free").unwrap()
);
Ok(())
}
+42
View File
@@ -0,0 +1,42 @@
#![allow(missing_docs)]
use subxt::ext::futures::StreamExt;
use subxt::{OnlineClient, PolkadotConfig};
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a storage query to access account information. Same as if we were
// fetching a single value from this entry.
let storage_query = polkadot::storage().system().account();
// Use that query to access a storage entry, iterate over it and decode values.
let client_at = api.storage().at_latest().await?;
// We provide an empty tuple when iterating. If the storage entry had been an N map with
// multiple keys, then we could provide any prefix of those keys to iterate over. This is
// statically type checked, so only a valid number/type of keys in the tuple is accepted.
let mut values = client_at.entry(storage_query)?.iter(()).await?;
while let Some(kv) = values.next().await {
let kv = kv?;
// The key decodes into the type that the static address knows about, in this case a
// tuple of one entry, because the only part of the key that we can decode is the
// AccountId32 for each user.
let (account_id32,) = kv.key()?.decode()?;
// The value decodes into a statically generated type which holds account information.
let value = kv.value().decode()?;
let value_data = value.data;
println!("{account_id32}:\n {value_data:?}");
}
Ok(())
}
+42
View File
@@ -0,0 +1,42 @@
#![allow(missing_docs)]
use subxt::ext::futures::StreamExt;
use subxt::utils::AccountId32;
use subxt::{
OnlineClient, PolkadotConfig,
dynamic::{At, Value},
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a dynamic storage query to access account information.
// here, we assume that there is one value to provide at this entry
// to access a value; an AccountId32. In this example we don't know the
// return type and so we set it to `Value`, which anything can decode into.
let storage_query = subxt::dynamic::storage::<(AccountId32,), Value>("System", "Account");
// Use that query to access a storage entry, iterate over it and decode values.
let client_at = api.storage().at_latest().await?;
let mut values = client_at.entry(storage_query)?.iter(()).await?;
while let Some(kv) = values.next().await {
let kv = kv?;
// The key decodes into the first type we provided in the address. Since there's just
// one key, it is a tuple of one entry, an AccountId32. If we didn't know how many
// keys or their type, we could set the key to `Vec<Value>` instead.
let (account_id32,) = kv.key()?.decode()?;
// The value decodes into the second type we provided in the address. In this example,
// we just decode it into our `Value` type and then look at the "data" field in this
// (which implicitly assumes we get a struct shaped thing back with such a field).
let value = kv.value().decode()?;
let value_data = value.at("data").unwrap();
println!("{account_id32}:\n {value_data}");
}
Ok(())
}
+117
View File
@@ -0,0 +1,117 @@
//! This example demonstrates how to use to add a custom signer implementation to `subxt`
//! by using the signer implementation from polkadot-sdk.
//!
//! Similar functionality was provided by the `substrate-compat` feature in the original `subxt` crate.
//! which is now removed.
#![allow(missing_docs, unused)]
use sp_core::{Pair as _, sr25519};
use subxt::config::substrate::MultiAddress;
use subxt::{Config, OnlineClient, PolkadotConfig};
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
/// A concrete PairSigner implementation which relies on `sr25519::Pair` for signing
/// and that PolkadotConfig is the runtime configuration.
mod pair_signer {
use super::*;
use sp_runtime::{
MultiSignature as SpMultiSignature,
traits::{IdentifyAccount, Verify},
};
use subxt::{
config::substrate::{AccountId32, MultiSignature},
tx::Signer,
};
/// A [`Signer`] implementation for [`sp_core::sr25519::Pair`].
#[derive(Clone)]
pub struct PairSigner {
account_id: <PolkadotConfig as Config>::AccountId,
signer: sr25519::Pair,
}
impl PairSigner {
/// Creates a new [`Signer`] from an [`sp_core::sr25519::Pair`].
pub fn new(signer: sr25519::Pair) -> Self {
let account_id =
<SpMultiSignature as Verify>::Signer::from(signer.public()).into_account();
Self {
// Convert `sp_core::AccountId32` to `subxt::config::substrate::AccountId32`.
//
// This is necessary because we use `subxt::config::substrate::AccountId32` and no
// From/Into impls are provided between `sp_core::AccountId32` because `polkadot-sdk` isn't a direct
// dependency in subxt.
//
// This can also be done by provided a wrapper type around `subxt::config::substrate::AccountId32` to implement
// such conversions but that also most likely requires a custom `Config` with a separate `AccountId` type to work
// properly without additional hacks.
account_id: AccountId32(account_id.into()),
signer,
}
}
/// Returns the [`sp_core::sr25519::Pair`] implementation used to construct this.
pub fn signer(&self) -> &sr25519::Pair {
&self.signer
}
/// Return the account ID.
pub fn account_id(&self) -> &AccountId32 {
&self.account_id
}
}
impl Signer<PolkadotConfig> for PairSigner {
fn account_id(&self) -> <PolkadotConfig as Config>::AccountId {
self.account_id.clone()
}
fn sign(&self, signer_payload: &[u8]) -> <PolkadotConfig as Config>::Signature {
let signature = self.signer.sign(signer_payload);
MultiSignature::Sr25519(signature.0)
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
let signer = {
let acc = sr25519::Pair::from_string("//Alice", None)?;
pair_signer::PairSigner::new(acc)
};
let dest = {
let acc = sr25519::Pair::from_string("//Bob", None)?;
MultiAddress::Address32(acc.public().0)
};
// Build a balance transfer extrinsic.
let balance_transfer_tx = polkadot::tx()
.balances()
.transfer_allow_death(dest, 100_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 events = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
.await?
.wait_for_finalized_success()
.await?;
// Find a Transfer event and print it.
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
}
Ok(())
}
+35
View File
@@ -0,0 +1,35 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(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 = dev::alice();
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::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
}
Ok(())
}
+56
View File
@@ -0,0 +1,56 @@
//! Example to use subxt to talk to substrate-based nodes with ethereum accounts
//! which is not the default for subxt which is why we need to provide a custom config.
//!
//! This example requires to run a local frontier/moonbeam node to work.
#![allow(missing_docs)]
use subxt::OnlineClient;
use subxt_core::utils::AccountId20;
use subxt_signer::eth::{Signature, dev};
#[subxt::subxt(runtime_metadata_path = "../artifacts/frontier_metadata_small.scale")]
mod eth_runtime {}
enum EthRuntimeConfig {}
impl subxt::Config for EthRuntimeConfig {
type AccountId = AccountId20;
type Address = AccountId20;
type Signature = Signature;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header =
subxt::config::substrate::SubstrateHeader<u32, subxt::config::substrate::BlakeTwo256>;
type ExtrinsicParams = subxt::config::SubstrateExtrinsicParams<Self>;
type AssetId = u32;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = OnlineClient::<EthRuntimeConfig>::from_insecure_url("ws://127.0.0.1:9944").await?;
let alith = dev::alith();
let baltathar = dev::baltathar();
let dest = baltathar.public_key().to_account_id();
println!("baltathar pub: {}", hex::encode(baltathar.public_key().0));
println!("baltathar addr: {}", hex::encode(dest));
let balance_transfer_tx = eth_runtime::tx()
.balances()
.transfer_allow_death(dest, 10_001);
let events = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &alith)
.await?
.wait_for_finalized_success()
.await?;
let transfer_event = events.find_first::<eth_runtime::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
println!("Balance transfer success: {event:?}");
}
Ok(())
}
+43
View File
@@ -0,0 +1,43 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Prepare some extrinsics. These are boxed so that they can live alongside each other.
let txs = [dynamic_remark(), balance_transfer(), remark()];
for tx in txs {
let from = dev::alice();
api.tx()
.sign_and_submit_then_watch_default(&tx, &from)
.await?
.wait_for_finalized_success()
.await?;
println!("Submitted tx");
}
Ok(())
}
fn balance_transfer() -> Box<dyn subxt::tx::Payload> {
let dest = dev::bob().public_key().into();
Box::new(polkadot::tx().balances().transfer_allow_death(dest, 10_000))
}
fn remark() -> Box<dyn subxt::tx::Payload> {
Box::new(polkadot::tx().system().remark(vec![1, 2, 3, 4, 5]))
}
fn dynamic_remark() -> Box<dyn subxt::tx::Payload> {
use subxt::dynamic::{Value, tx};
let tx_payload = tx("System", "remark", vec![Value::from_bytes("Hello")]);
Box::new(tx_payload)
}
+53
View File
@@ -0,0 +1,53 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), BoxedError> {
// Spawned tasks require things held across await points to impl Send,
// so we use one to demonstrate that this is possible with `PartialTransaction`
tokio::spawn(signing_example()).await??;
Ok(())
}
async fn signing_example() -> Result<(), BoxedError> {
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);
let alice = dev::alice();
// Create partial tx, ready to be signed.
let mut partial_tx = api
.tx()
.create_partial(
&balance_transfer_tx,
&alice.public_key().to_account_id(),
Default::default(),
)
.await?;
// Simulate taking some time to get a signature back, in part to
// show that the `PartialTransaction` can be held across await points.
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
let signature = alice.sign(&partial_tx.signer_payload());
// Sign the transaction.
let tx = partial_tx
.sign_with_account_and_signature(&alice.public_key().to_account_id(), &signature.into());
// Submit it.
tx.submit_and_watch()
.await?
.wait_for_finalized_success()
.await?;
Ok(())
}
+55
View File
@@ -0,0 +1,55 @@
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig, tx::TxStatus};
use subxt_signer::sr25519::dev;
// Generate an interface that we can use from the node's metadata.
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);
// Submit the balance transfer extrinsic from Alice, and then monitor the
// progress of it.
let from = dev::alice();
let mut balance_transfer_progress = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
.await?;
while let Some(status) = balance_transfer_progress.next().await {
match status? {
// It's finalized in a block!
TxStatus::InFinalizedBlock(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::<polkadot::balances::events::Transfer>()?;
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(())
}
+28
View File
@@ -0,0 +1,28 @@
#![allow(missing_docs)]
use subxt::config::polkadot::PolkadotExtrinsicParamsBuilder as Params;
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);
// Configure the transaction parameters; we give a small tip and set the
// transaction to live for 32 blocks from the `latest_block` above.
let tx_params = Params::new().tip(1_000).mortal(32).build();
// submit the transaction:
let from = dev::alice();
let hash = api.tx().sign_and_submit(&tx, &from, tx_params).await?;
println!("Balance transfer extrinsic submitted with hash : {hash}");
Ok(())
}