mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-06 03:18:01 +00:00
Starting new subxt crate; configs implemented
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user