NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
crates.io · #3717 most downloaded on crates.io
Generate an API for interacting with a substrate node from FRAME metadata
Last release 7 days ago
30 Sep 2026
Ships fairly regularly
a new release about every 6 weeks
Most releases are documented
notes for 42 of 50 stable releases
2 versions withdrawn
withdrawn after publishing
5 years old
56 releases · first in 2021
One column per quarter.
Upgrade jsonrpsee to the latest version by @bkchr in #2287
Full Changelog: v0.51.0...v0.51.1
Breaking change: DefaultTransactionExtensions<T> and DefaultExtrinsicParams<T> are no longer tuple type aliases; they are now structs with private fie…
This release adds support for providing custom transaction extension values without implementing a custom Config, which fixes signing on chains with bespoke transaction extensions (such as Paseo Asset Hub), and fixes decoding of V4 extrinsics on runtimes exposing more than one transaction extension version.
Breaking change: DefaultTransactionExtensions<T> and DefaultExtrinsicParams<T> are no longer tuple type aliases; they are now structs with private fields, so that custom extension values can travel alongside the known ones. The tuple forms live on as KnownDefaultTransactionExtensions<T> and KnownDefaultExtrinsicParams<T>, and the known values remain accessible via known() and known_mut().
DefaultExtrinsicParamsBuilder::custom_extension(name, value). The value is a scale_value::Value encoded against the extension type declared in the runtime metadata, and a name the runtime does not declare is rejected rather than silently ignored. When encoding a V5 extrinsic, the newest transaction extension version for which all required extension data is available is now selected, so a chain adding a new non-optional extension no longer breaks clients without a value for it. (#2273)[0, 1]) the extras were read against the wrong extension set and decoding failed with VariantNotFound. V5 extrinsics are unaffected: they carry an explicit version and it is used as given. Addresses the v4 half of #1998. (#2277)This release updates frame-decode to 0.18.1, which fixes V5 signer payload construction and improves how authorization extensions (like VerifyMultiSig
This release updates frame-decode to 0.18.1, which fixes V5 signer payload construction and improves how authorization extensions (like VerifyMultiSignature) are handled.
Option<T> transaction extensions are now encoded as None (0u8) by default, allowing transaction encoding to succeed on chains with optional extensions. (from frame-decode 0.17.2)This release fixes an issue whereby setting the genesis hash in the SubstrateConfigBuilder had no effect, and improves the reliability of tests agains
This release fixes an issue whereby setting the genesis hash in the SubstrateConfigBuilder had no effect, and improves the reliability of tests against public archival RPC endpoints.
SubstrateConfigBuilder::set_genesis_hash is a no-op (#2236)This release bumps the light-client smoldot crate to the latest version and adds several fixes.
This release bumps the light-client smoldot crate to the latest version and adds several fixes.
This changes have results in many breaking changes across APIs, which I will try my best to summarize below.
This release version is a deliberately large bump up from 0.44.0 to signify the extent of the changes in this release.
The headline changes are as follows:
subxt-historic crate has been removed, its functionality having been merged into Subxt.subxt-core crate has been removed for now to make way for the above. Subxt itself continues to support WASM use cases.
no-std functionality, the frame-decode crate now contains much of the underlying logic used throughout Subxt to encode and decode things, and we would like to expand the functionality here.subxt-core crate on how they use it, and will use this feedback to drive future work in this area.Error enum; instead different Subxt APIs return different errors, to limit the number of possible errors in any one place. These all convert into subxt::Error so this can continue to be used as a catch-all.There are also a couple of organizational changes which aren't visible:
name.rs + name/submodule.rs convention instead of the name/mod.rs + name/submodule.rs convention.This changes have results in many breaking changes across APIs, which I will try my best to summarize below.
A good place to look for a more holistic understanding of what's changes are the examples, both:
For the smaller examples, start with the basic transaction submission example and then have a look at the blocks example and the storage example to give the best broad overview of the changes. Pick and choose others next depending on what suits.
A breakdown of the significant changes follows, to aid migration efforts:
Before
Configuration (PolkadotConfig and SubstrateConfig) was type-only and didn't exist at the value level, and so you'd provide it to the client like so:
use subxt::{OnlineClient, PolkadotConfig};
let api = OnlineClient::<PolkadotConfig>::new().await?;After
Configuration now exists at the value level too. This is because it has been extended with support for historic types and working with historic metadatas and spec versions. The same code as above will continue to work, but it's now possible to instantiate and tweak the configuration and then use _with_config methods to provide it, like so:
use subxt::{OnlineClient, PolkadotConfig};
let config = PolkadotConfig::builder()
.use_historic_types(false)
.build();
let api = OnlineClient::<PolkadotConfig>::new_with_config(config).await?;The rules for when to use PolkadotConfig and SubstrateConfig remain the same:
PolkadotConfig for the Polkadot Relay Chain.SubstrateConfig by default with other chains.See the docs for PolkadotConfig and SubstrateConfig for more. One example to be aware of is that if you want to work with historic blocks with SubstrateConfig, you'll need to instantiate it yourself and provide historic type information before passing it to OnlineClient or OfflineClient.
ExtrinsicParamsAside from the new historic types support, a notable change to the configuration has been the simplification of transaction extensions, previously called ExtrinsicParams in our Config. Now, the type is called TransactionExtensions and the supporting traits have been simplified and named more appropriately (just TransactionExtensions and TransactionExtension), moving to rely more on frame-decode for the core logic. For any users that implement their own transaction extensions, migrating to the new traits is straightforward and I would encourage you to look at how the built-in transaction extensions are implemented for guidance here.
See (see #2177) for more details around this change.
Before
Previously, you'd be able to select (within a limited range) which block to work at with APIs like so:
let api = OnlineClient::<PolkadotConfig>::new().await?;
let constants = api.constants();
let storage = api.storage().at(block_hash).await?;
let storage = api.storage().at_latest().await?;
let events = api.events().at(block_hash).await?;
let events = api.events().at_latest().await?;
let runtime_apis = api.runtime_api().at(block_hash).await?;
let runtime_apis = api.runtime_api().at_latest().await?;After
Now, the block is selected first, like so:
let api = OnlineClient::<PolkadotConfig>::new().await?;
let constants = api.at_block(block_hash_or_number).await?.constants();
let constants = api.at_current_block().await?.constants();
let storage = api.at_block(block_hash_or_number).await?.storage();
let storage = api.at_current_block().await?.storage();
let events = api.at_block(block_hash_or_number).await?.events();
let events = api.at_current_block().await?.events();
let runtime_apis = api.at_block(block_hash_or_number).await?.runtime_apis();
let runtime_apis = api.at_current_block().await?.runtime_apis();Notes:
at_latest has been renamed to at_current_block and, like before, it fetches the current finalized block at the time of calling.at_current_block now accepts a block hash or block number, and returns a client that works in the context of that block.Metadata and so it was unnecessary. Now, constants are retrieved at a specific block like everything else (different blocks may have different Metadatas).runtime_api() was renamed to runtime_apis() to be consistent with other APIs names..tx() is now callable at a specific block, and uses this block for any account nonce and mortality configuration.Before
A .blocks() method accessed block-specific APIs for fetching and subscribing to blocks.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// fetching:
let block = api.blocks().at(block_hash).await?;
let block = api.blocks().at_latest().await?;
// subscribing:
let mut blocks = api.blocks().subscribe_finalized().await?;
while let Some(block) = blocks_sub.next().await {
let block = block?;
let extrinsics = block.extrinsics().await?;
for ext in extrinsics.iter() {
// See the blocks example for more.
}
}After
Now that APIs are largely block-specific up front, we don't need separate APIs for block fetching, and so we move streaming blocks up a level, removing the .blocks() APIs.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// fetching:
let block = api.at_block(block_hash_or_number).await?;
let block = api.at_current_block().await?;
// subscribing:
let mut blocks = api.stream_blocks().await?;
while let Some(block) = blocks_sub.next().await {
let block = block?;
// now, we instantiate a client at a given block, which gives back the
// same thing as api.at_block() and api.at_current_block() does:
let at_block = block.at().await?;
let extrinsics = at_block.extrinsics().fetch().await?;
for ext in extrinsics.iter() {
// See the blocks example for more.
}
}Notes:
.at() at a given block to hand back a full client which can do anything at that block.api.blocks().subscribe_finalized() => api.stream_blocks().api.blocks().subscribe_best() => api.stream_best_blocks().api.blocks().subscribe_all() => api.stream_all_blocks().Before
Transactions were implicitly created at the latest block, and the APIs were disconnected from any particular block:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Submit an extrinsic, waiting for success.
let events = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
.await?
.wait_for_finalized_success()
.await?;After
Transactions are anchored to a given block but we continue to provide a .tx() method on the client as a shorthand for "create transactions at the current block".
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Work at a specific block:
let at_block = api.at_current_block().await?;
// Submit the balance transfer extrinsic anchored at this block:
let events = at_block
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
.await?
.wait_for_finalized_success()
.await?;
// A shorthand for the above:
let events = api
.tx()
.await? // This is the minimal change from the old APIs.
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
.await?
.wait_for_finalized_success()
.await?;Notes:
SignableTransaction::signer_payload, SignableTransaction::sign and SignableTransaction::sign_with_account_and_signature now may return an error (which previously would have led to harder to diagnose issues), and the SignableTransaction type now has a lifetime (see #2177).transactions instead of tx everywhere to align better with other API names, but continue to provide tx as a shorthand.partial is changed to signable in transaction APIs. "partial" was always a confusing name, and "signable" makes it much clearer what is being created; something that can be signed.
tx().create_partial_offline(..) => tx().create_signable_offline(..)tx().create_v4_partial_offline(..) => tx().create_v4_signable_offline(..)tx().create_v5_partial_offline(..) => tx().create_v5_signable_offline(..)tx().create_partial(..) => tx().create_signable(..)tx().create_v4_partial(..) => tx().create_v4_signable(..)tx().create_v5_partial(..) => tx().create_v5_signable(..)tx().from_bytes(bytes) is added as an easy way to hand a pre-constructed transaction to Subxt to be submitted, removing the need for an ugly SubmittableTransaction::from_bytes method.Before
The codegen dealt with the heavy lifting of iterating storage maps at various depths (albeit with a bug), and on fetching an entry you had little control over how you handled the resulting bytes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
//// Fetching:
let result = api
.storage()
.at_latest()
.await?
.fetch(&storage_query)
.await?;
//// Iterating
let mut results = api
.storage()
.at_latest()
.await?
.iter(storage_query)
.await?;
while let Some(Ok(kv)) = results.next().await {
println!("Keys decoded: {:?}", kv.keys); // <- Broken in some cases
println!("Key: 0x{}", hex::encode(&kv.key_bytes));
println!("Value: {:?}", kv.value);
}After
A redesign of the Storage APIs makes everything more unified, and allows working at specific storage entries in a much more flexible way than before, while moving logic out of the codegen, simplifying it, and into Subxt proper.
let api = OnlineClient::<PolkadotConfig>::new().await?;
let at_block = api.at_current_block().await?;
let account_balances = at_block
.storage()
.entry(storage_query)?;
//// Fetching:
// We can fetch multiple values from an entry:
let value1 = account_balances.fetch((account_id1,)).await?;
let value2 = account_balances.fetch((account_id2,)).await?;
// Entries can be decoded into the static type given by the address:
let result = value1.decode()?;
// Or they can be decoded into any arbitrary shape:
let result = value1.decode_as::<scale_value::Value>()?;
// Or we can "visit" the entry for more control over decoding:
let result = value1.visit(my_visitor)?;
// Or we can just get the bytes out and do what we want:
let result_bytes = value1.bytes();
//// Iterating
// We can iterate over the same entry we fetched things from:
let mut balances = account_balances.iter(()).await?;
while let Some(Ok(entry)) = all_balances.next().await {
let key = entry.key()?;
let value = entry.value();
// Decode the keys that can be decoded:
let keys_tuple = key.decode()?;
// Value is as above:
let value = value.decode()?;
println!("Keys decoded: {:?}", keys_tuple);
println!("Key: 0x{}", hex::encode(key.bytes()));
println!("Value: {:?}", value);
}This is perhaps the largest change to any specific set of APIs in terms of differences. Take a look at the API docs and the storage example and PR for more on this.
Before
In previous versions of Subxt, you could subscribe to runtime updates and have Subxt update its internal metadata in response to them, allowing it to track the head of a chain over runtime changes, like so:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// The "easy" approach to ensuring Subxt remains up to date:
let updater = api.updater();
tokio::spawn(async move {
update_task.perform_runtime_updates().await;
});
// We can also do something lower level:
let updater = api.updater();
tokio::spawn(async move {
let mut update_stream = updater.runtime_updates().await.unwrap();
while let Ok(update) = update_stream.next().await {
let version = update.runtime_version().spec_version;
match updater.apply_update(update) {
Ok(()) => {
println!("Upgrade to version: {} successful", version)
}
Err(e) => {
println!("Upgrade to version {} failed {:?}", version, e);
}
};
}
});After
The way that Subxt works with metadata across runtimes has been overhauled, and so now Subxt can automatically store and use whichever metadata version is required for a given block, without any need to explicitly monitor for changes. Thus, this code can be safely removed as it is no longer needed.
If you'd like to keep track of when runtime updates occur, you can still do this by simply subscribing to blocks, like so:
let mut blocks = api.stream_blocks().await?;
while let Some(block) = blocks.next().await {
let block = block?;
let at_block = block.at().await?;
// If this changes, then it means that the runtime has updated.
let spec_version = at_block.spec_version();
}Before
Dynamic values were always constructed using and returning scale_value::Values, for instance:
let constant_query = subxt::dynamic::constant(
"System",
"BlockLength"
);
let runtime_api_payload = subxt::dynamic::runtime_api_call(
"AccountNonceApi",
"account_nonce",
vec![Value::from_bytes(account)],
);
let storage_query = subxt::dynamic::storage(
"System",
"Account",
vec![Value::from_bytes(account)]
);After
The dynamic methods have been made more generic, allowing more arbitrary types to be used in their construction, and allowing the return type to be set. This does however mean that types need to be provided sometimes:
let constant_query = subxt::dynamic::constant::<Value>(
"System",
"BlockLength"
);
let runtime_api_payload = subxt::dynamic::runtime_api_call::<_, Value>(
"AccountNonceApi",
"account_nonce",
vec![Value::from_bytes(account)],
);
// We can provide more generic input args now, negating the need
// to convert to Values unnecessarily:
let runtime_api_payload = subxt::dynamic::runtime_api_call::<_, Value>(
"AccountNonceApi",
"account_nonce",
(account,),
);
// We no longer provide the keys up front for storage; we just point
// to the _entry_ we want and provide the key and return types:
let storage_query = subxt::dynamic::storage::<Vec<Value>, Value>(
"System",
"Account",
);
// This allows us to set better key/value types if we know what to expect. Here
// we know what information we want from account info and the key format:
#[derive(scale_decode::DecodeAsType)]
struct MyAccountInfo {
nonce: u32,
data: MyAccountInfoData
}
#[derive(scale_decode::DecodeAsType)]
struct MyAccountInfoData {
free: u128,
reserved: u128
}
let storage_query = subxt::dynamic::storage::<(AccountId32,), MyAccountInfo>(
"System",
"Account",
);Notes:
scale_value::Value was used everywhere, the actual values provided are always checked at runtime against the API and invalid shapes/values will lead to an error.scale_value::Value. This makes it easier to work against historic blocks where you may not have or want to use the #[subxt] codegen, but still want to work with static types as much as possible.Subxt previously exposed subxt::Metadata, which was a wrapped version of subxt_metadata::Metadata. The wrapping was removed, and now we have only subxt_metadata::Metadata, which is exposed as subxt::Metadata. This metadata can be cloned but is not cheap to clone, and so we also expose subxt::ArcMetadata, which is used in many places and is the Arc-wrapped version of it, for cheap cloning.
subxt_metadata::Metadata now exposes helper functions to construct it from various frame_metadata versions, to support our historic decoding efforts:
Metadata::from_v16(..)Metadata::from_v15(..)Metadata::from_v14(..)Metadata::from_v13(..)Metadata::from_v12(..)Metadata::from_v11(..)Metadata::from_v10(..)Metadata::from_v9(..)Metadata::from_v8(..)Where the older versions require type information to be provided in addition to the corresponding frame_metadata version.
A list of the main change PRs follows:
system_chainType to legacy rpcs (#2116)Nothing published for this version
Update to frame-decode 0.17.1 and update CHANGELOG with frame-decode …
Update to frame-decode 0.17.1 and update CHANGELOG with frame-decode …
Update copyright headers to 2026
Update copyright headers to 2026
Nothing published for this version
Nothing published for this version
Nothing published for this version
This manually cherry-picks #2142 onto the 0.44 branch to allow using $OUT_DIR in a couple of Subxt macro attributes.
When using .tip_of(some_tip, optional_asset_id) to configure a tip for transactions, the actual tip was being set to 0. This is now fixed.
When using .tip_of(some_tip, optional_asset_id) to configure a tip for transactions, the actual tip was being set to 0. This is now fixed.
This is a minor version bump because, in theory at least, adding the Clone bound to block headers in ( #2047 ) is a breaking change, although I think…
This small release primarily fixes a few issues, but also adds the code for a prelease of subxt-historic, a new crate (at the moment) for working with historic blocks and state. Future releases will aim to stabilize this crate to the level of other subxt crates or otherwise merge the logic into subxt itself.
This is a minor version bump because, in theory at least, adding the Clone bound to block headers in (#2047) is a breaking change, although I think it is unlikely that this will impact any users.
subxt-historic crate for accessing historic (non head-of-chain) blocks (#2040)This is a reasonably small release which is mainly bug fixing, but has a couple of changes I'd like to elaborate on:
This is a reasonably small release which is mainly bug fixing, but has a couple of changes I'd like to elaborate on:
codec::Encode and codec::Decode derives from generated APIs by default (#2008)When generating an API using the #[subxt::subxt(...)] macro (or programatically via subxt-codegen), we had always previously added parity_scale_codec::Encode and parity_scale_codec::Decode derives to all of the generated types. Most places in Subxt have not made use of these for a long time (relying instead on scale_encode::EncodeAsType and scale_decode::DecodeAsType, since they allow encoding and encoding which takes the type information into account and can more gracefully handle incompatibilities).
We eventually hit an issue to which the most appropriate fix was just to remove these derives.
If you still need the parity_scale_codec::Encode or parity_scale_codec::Decode derives on certain types, you have two options:
derive_for_type attr to add them back where needed, eg:
#[subxt::subxt(
...
derive_for_type(
path = "staging_xcm::v3::multilocation::MultiLocation",
derive = "parity_scale_codec::Encode, parity_scale_codec::Decode",
recursive
)
)]derive_for_all_types attr to add them back everywhere, eg:
#[subxt::subxt(
...
derive_for_all_types = "parity_scale_codec::Encode, parity_scale_codec::Decode"
)]
Prefer (1) where possible to reduce the amount of generated code, and reduce the likelihood of running into issues around those derives in certain edge cases.
This PR changes some things around storage keys to remove one last requirement for Encode and Decode derives, and also as a side effect changes api.storage().call_raw() slightly to no longer also try to decode the resulting type via Decode, leaving this to the user (and also meaning it's much easier now for the user to obtain the raw bytes for some storage entry).
In other words, instead of doing something like:
let (compact_len, metadata) = rt
.call_raw::<(Compact<u32>, frame_metadata::RuntimeMetadataPrefixed)>(
"Metadata_metadata",
None,
)
.await?;You would now do:
let meta_bytes = rt.call_raw("Metadata_metadata", None).await?;
let (compact_len, metadata): (Compact<u32>, frame_metadata::RuntimeMetadataPrefixed) =
Decode::decode(&mut &*meta_bytes)?;Prior to this change, the intended behavior was that any transaction submitted via an OnlineClient would have a mortality of 32 blocks by default, and any transaction submitted via an OfflineClient would be immortal by default. A couple of issues were present or cropped up however:
PolkadotExtrinsicParamsBuilder::new().mortal(32).build(), the OfflineClient transaction would still be immortal, because it didn't have enough information to properly configure the mortality as asked for (by virtue of being offline and unable to fetch it).OnlineClient by default, unless mortality was explicitly configured.OfflineClient transaction; you'd have to do something like this:
let params = DefaultExtrinsicParamsBuilder::new();
params.5 = CheckMortalityParams::mortal_from_unchecked(for_n_blocks, from_block_n, from_block_hash);With this PR, transactions are now mortal by default using the OnlineClient, we now return an error if you try to construct a transaction with the OfflineClient and try to use params.mortal(..) when configuring it, and we expose params.mortal_from_unchecked(..) to allow configuration for offline transactions without the ugly code above.
In this PR, we also discovered an issue decoding Eras and fixed this, so that decoding the mortality of a transaction when it is mortal should now work.
I'd like to do a quick shoutout to @wassimans, who submitted an excellent example for how to interact with Subxt via the C FFI in Python and Node.JS. This is something I've wanted to add for a while, so it's lovely to see this new example which highlights one of the strengths of Subxt over Javascript based compatitors in the space.
All of the non-trivial changes in this release are listed below:
codec::Encode and codec::Decode derives from generated APIs by default (#2008)at_latest (#2035)This patch release reduces the rust-version to 1.85.0, given that we don't use any features newer than this at the moment.
This patch release reduces the rust-version to 1.85.0, given that we don't use any features newer than this at the moment.
The primary benefit of this release is introducing support for the _about-to-be-stabilised-in-polkadot-sdk_ V16 metadata, and with that, support for c
The primary benefit of this release is introducing support for the about-to-be-stabilised-in-polkadot-sdk V16 metadata, and with that, support for calling Pallet View Functions on runtimes which will support this. Pallet View Functions are used much like Runtime APIs, except that they are declared in specific pallets and not declared at the runtime-wide level, allowing pallets to carry their own APIs with them.
Calling a Pallet View Function in this Subxt release will look like:
use runtime::proxy::view_functions::check_permissions::{Call, ProxyType};
// Construct the call, providing the two arguments.
let view_function_call = runtime::view_functions()
.proxy()
.check_permissions(
Call::System(runtime::system::Call::remark { remark: b"hi".to_vec() }),
ProxyType::Any
);
// Submit the call and get back a result.
let _is_call_allowed = api
.view_functions()
.at_latest()
.await?
.call(view_function_call)
.await?;
Like Runtime APIs and others, the dynamic API can also be used to call into Pallet View Functions, which has the advantage of not needing the statically generated interface, but the downside of not being strongly typed. This looks like the following:
use scale_value::value;
let metadata = api.metadata();
// Look up the query ID for the View Function in the node metadata:
let query_id = metadata
.pallet_by_name("Proxy")
.unwrap()
.view_function_by_name("check_permissions")
.unwrap()
.query_id();
// Construct the call, providing the two arguments.
let view_function_call = subxt::dynamic::view_function_call(
*query_id,
vec![
value!(System(remark(b"hi".to_vec()))),
value!(Any())
],
);
// Submit the call and get back a result.
let _is_call_allowed = api
.view_functions()
.at_latest()
.await?
.call(view_function_call)
.await?;
Config traitAnother change to be aware of is that our Config trait has been tweaked. The Hash associated type is no longer needed, as it can be obtained via the Hasher associated type already, and PolkadotConfig/SubstrateConfig now set the hasher by default to be DynamicHasher256, which will (when V16 metadata is available for a runtime) automatically select between Keccak256 and BlakeTwo256 hashers depending on what the chain requires.
We also solidify our support for V1 archive RPCs, upgrade the codebase to Rust 2024 edition, and a bunch of other changes, the full list of which is here:
This release makes two main changes:
This release makes two main changes:
subxt-rpcs crate.Previously, if you wanted to make raw RPC calls but weren't otherwise interested in using the higher level Subxt interface, you still needed to include the entire Subxt crate.
Now, one can depend on subxt-rpcs directly. This crate implements the new RPC-V2 chainHead/transaction endpoints as well as the currently unstable archive endpoints. it also implements various legacy endpoints that Subxt uses as a fallback to the modern ones. It also provides several feature gated clients for interacting with them:
jsonrpsee based RPC client for connecting to individual RPC nodes.jsonrpsee based client which handles reconnecting automatically in the event of network issues.Custom clients can be implemented if preferred.
Example usage via jsonrpsee feature:
use subxt_rpcs::{RpcClient, ChainHeadRpcMethods};
// Connect to a local node:
let client = RpcClient::from_url("ws://127.0.0.1:9944").await?;
// Use chainHead/archive V2 methods:
let methods = ChainHeadRpcMethods::new(client);
// Call some RPC methods (in this case a subscription):
let mut follow_subscription = methods.chainhead_v1_follow(false).await.unwrap();
while let Some(follow_event) = follow_subscription.next().await {
// do something with events..
}
Subxt has supported decoding V5 transactions from blocks since 0.38.0, but now it also supports constructing V5 transactions where allowed. Some naming changes have also taken place to align with the Substrate terminology now around transactions (see #1931 for more!).
The main changes here are:
subxt_core now contains versioned methods for creating each of the possible types of transaction (V4 unsigned, V4 signed, V5 "bare" or V5 "general"), enabling the APIs to be tailored for each case.subxt exposes higher level wrappers these (ie api.tx().create_v4_unsigned(..), api.tx().create_v5_bare(..)), but also continues to expose the same standard APIs for creating transactions which will, under the hood, decide what to create based on the chain we're connected to.sign_and_submit now take a T::AccountId rather than a T::Address since it was found to not be useful to provide the latter, and V5 transactions only expect an T::AccountId.VerifySignature).A full list of the relevant changes is as follows:
runtime-wasm-path (#1936)Nothing published for this version
This release reverts the usage of the polkadot-sdk umbrella crate, which was causing issues such as an increased number of dependencies in Cargo.lock.
This release reverts the usage of the polkadot-sdk umbrella crate, which was causing issues such as an increased number of dependencies in Cargo.lock. For more details, see #1925.
Additionally, this update bumps the Polkadot SDK-related dependencies to their latest versions, ensuring compatibility and stability.
Full Changelog: https://github.com/paritytech/subxt/compare/v0.39.0...v0.40.0
This release is mostly bug fixes and changes. The only change that should be a breaking change is removing the substrate-compat feature flag (see #185…
This release is mostly bug fixes and changes. The only change that should be a breaking change is removing the substrate-compat feature flag (see #1850), which we'll go into more detail about.
substrate-compat feature flag has been removed.The substrate-compat feature flag essentially provided:
subxt::config::Header trait for anything implementing sp_runtime::traits::Header (here).subxt::config::Hasher and anything implementing sp_runtime::traits::Hasher (here).subxt_core::tx::PairSigner type which could be given something implementing sp_core::Pair and then be used to sign transactions (here).sp_runtime::AccountId32 and related for subxt::utils::AccountId32 (here).sp_runtime::MultiAddress and subxt::utils::MultiAddress (here).sp_runtime::MultiSignature and subxt::utils::MultiSignature (here).While useful, providing these features in Subxt is almost impossible to maintain: we can only support a single version of sp_runtime/sp_core at a time, but many versions are in use in the wild. This led to various issues regarding the mismatch between sp_* crates in use and a given version of Subxt. More generally, the goal of Subxt is to be independent from any specific version of Substrate, and communicate via the exposed RPC APIs in order to work across any compatible Substrate version (or indeed, alternative implementations that follow things like the RPC spec).
As a result, we've taken the decision to remove this compatibility layer from Subxt itself. To migrate away from this feature, we suggest:
subxt_signer instead, if it's a viable alternative in your case.// Wrap a substrate header type in this to impl the subxt Header trait:
struct SubxtHeader<T>(pub T);
// This basically copies the code removed from Subxt, but on a wrapper type:
impl <T> subxt::config::Header for SubxtHeader<T>
where
T: sp_runtime::traits::Header,
<T as sp_runtime::traits::Header>::Number: Into<u64>,
{
type Number = T::Number;
type Hasher = T::Hashing;
fn number(&self) -> Self::Number {
*self.0.number()
}
}
The hope is that this pattern is applicable to any such types that you find useful to share between Substrate and Subxt code. Please raise an issue if you can't find a solution in your case, and we'll endeavour to help!
The result of this is that your code will work against whichever Substrate crate versions you are using, at the cost of this code no longer being included behind the substrate-compat feature flag.
A full list of relevant changes and fixes (nothing was added in this release) is as follows:
thiserror (#1856)jsonrpsee in subxt::ext (#1843)Nothing published for this version
This release doesn't introduce any substantial breaking changes and focuses primarily on incremental improvements, testing and bug fixes. A few of the…
This release doesn't introduce any substantial breaking changes and focuses primarily on incremental improvements, testing and bug fixes. A few of the highlights include:
subxt::backend::unstable::UnstableBackend (it's now called subxt::backend::chain_head::ChainHeadBackend). This backend can be used to interact with the modern chainHead RPC methods exposed by Smoldot and compliant RPC nodes. See this example.reconnecting-rpc-client. See this example.#[subx(runtime_path = "path/to/runtime.wasm")]).subxt-core. See this example.The notable changes in this release are as follows:
secret_key method for ecdsa::Keypair and eth::Keypair (#1628)scale family crates, primitive-types and impl-serde (#1832)instant with web-time (#1830)?Sized bound and replace never type with () (#1758)Backend impl (#1751)unstable-reconnecting-rpc-client (#1711)reconnecting-jsonrpsee-ws-client with subxt-reconnecting-rpc-client (#1705)defalt-feature -> default-features Cargo.toml (#1828)#[codec(dumb_trait_bound)] (#1630)This release mainly adds support for the sign extension CheckMetadataHash and fixes a regression introduced in v0.36.0 where the type de-duplication w
This release mainly adds support for the sign extension CheckMetadataHash and fixes a regression introduced in v0.36.0
where the type de-duplication was too aggressive and lots of the same type such as BoundedVec was duplicated to
plenty of different types such as BoundedVec1, BoundedVec2, .. BoundedVec<N>.
Yanked because the typegen changed, it's a breaking change.
Yanked because the typegen changed, it's a breaking change.
A breaking change that comes from migrating a bunch of logic to this new crate is that the ExtrinsicParams trait is now handed &ClientState rather tha…
This release adds a few new features, which I'll go over below in more detail.
subxt-coreWe now have a brand new subxt-core crate, which is #[no-std] compatible, and contains a lot of the core logic that is needed in Subxt. Using this crate, you can do things in a no-std environment like:
blocks: decode and explore block bodies.constants: access and validate the constant addresses in some metadata.custom_values: access and validate the custom value addresses in some metadata.metadata: decode bytes into the metadata used throughout this library.storage: construct storage request payloads and decode the results you'd get back.tx: construct and sign transactions (extrinsics).runtime_api: construct runtime API request payloads and decode the results you'd get back.events: decode and explore events.Check out the docs for more, including examples of each case.
A breaking change that comes from migrating a bunch of logic to this new crate is that the ExtrinsicParams trait is now handed &ClientState<T> rather than a Client. ClientState is just a concrete struct containing the state that one needs for things like signed extensions.
We've baked in a bunch of support for automatically reconnecting after a connection loss into Subxt. This comes in three parts:
unstable-reconnecting-rpc-client feature flag at the moment, andDisconnectedWillReconnect error to the user where it cannot. Note that the individual LegacyRpcMethods and UnstableRpcMethods are not automatically retried on reconnection. Which leads us to..subxt::backend::retry and subxt::backend::retry_stream) which can be used in conjunction with a reconnecting RPC client to make it easy to automatically retry RPC method calls where needed.We'd love feedback on this reconnecting work! To try it out, enable the unstable-reconnecting-rpc-client feature flag and then you can make use of this like so:
use std::time::Duration;
use futures::StreamExt;
use subxt::backend::rpc::reconnecting_rpc_client::{Client, ExponentialBackoff};
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 client with a reconnecting RPC client.
let rpc = Client::builder()
// We can configure the retry policy; here to an exponential backoff.
// This API accepts an iterator of retry delays, and here we use `take`
// to limit the number of retries.
.retry_policy(
ExponentialBackoff::from_millis(100)
.max_delay(Duration::from_secs(10))
.take(3),
)
.build("ws://localhost:9944".to_string())
.await?;
// Use this reconnecting client when instantiating a Subxt client:
let api: OnlineClient<PolkadotConfig> = OnlineClient::from_rpc_client(rpc.clone()).await?;
Check out the full example here.
We've added built-in support for Ethereum style chains (eg Frontier and Moonbeam) in subxt-signer, making it easier to sign transactions for these chains now.
Check out a full example here.
We plan to improve on this in the future, baking in better Ethereum support if possible so that it's as seamless to use AccountId20 as it is AccountId32.
A bunch of the new RPCs are now stable in the spec, and have consequently been stabilized here, bringing the unstable-backend a step closer to being stabilized itself! We'll probably first remove the feature flag and next make it the default backend, in upcoming releases.
All of the notable changes in this release are as follows:
frontier/ethereum example (#1557)subxt-core crate (#1466)scale-type-resolver 0.2 (#1565)subxt-signer::eth (#1553)Nothing published for this version
Nothing published for this version
Nothing published for this version
This release contains several fixes, adds no_std support to a couple of crates (subxt-signer and subxt-metadata) and introduces a few quality of life
This release contains several fixes, adds no_std support to a couple of crates (subxt-signer and subxt-metadata) and introduces a few quality of life improvements, which I'll quickly cover:
This PR reworks the light client interface. The "basic" usage of connecting to a parachain now looks like this:
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
use subxt::lightclient::LightClient;
// Instantiate a light client with the Polkadot relay chain given its chain spec.
let (lightclient, polkadot_rpc) = LightClient::relay_chain(POLKADOT_SPEC)?;
// Connect the light client to some parachain by giving a chain spec for it.
let asset_hub_rpc = lightclient.parachain(ASSET_HUB_SPEC)?;
// Now, we can 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?;
This interface mirrors the requirement that we must connect to a relay chain before we can connect to a parachain. It also moves the light client specific logic into an RpcClientT implementation, rather than exposing it as a subxt::client::LightClient.
This PR changes the storage interface so that, where possible, we now also decode the storage keys as well as the values when iterating over storage entries:
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
pub mod polkadot {}
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build a storage query to iterate over account information.
let storage_query = polkadot::storage().system().account_iter();
// Get back an iterator of results (here, we are fetching 10 items at
// a time from the node, but we always iterate over one at a time).
let mut results = api.storage().at_latest().await?.iter(storage_query).await?;
while let Some(Ok(kv)) = results.next().await {
// We used to get a tuple of key bytes + value. Now we get back a
// `kv` struct containing the bytes and value as well as the actual
// decoded keys:
println!("Decoded key(s): {:?}", kv.keys);
println!("Key bytes: 0x{}", hex::encode(&kv.key_bytes));
println!("Value: {:?}", kv.value);
}
When using the static interface, keys come back as a tuple of values corresponding to the different hashers used in constructing the key. When using a dynamic interface, keys will be encoded/decoded from the type given so long as it implements subxt::storage::StorageKey, eg Vec<scale_value::Value>.
Prior to this PR, one could configure extrinsic signed extensions by providing some params like so:
// 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(latest_block.header(), 32)
.build();
let hash = api.tx().sign_and_submit(&tx, &from, tx_params).await?;
If you want to customize the account nonce, you'd use a different call like create_signed_with_nonce instead.
One of the downsides of the above approach is that, if you don't provide any explicit params, transactions will be immortal by default (because the signed extensions didn't have the information to do any better).
Now, with the help of a RefineParams trait, transactions will default to being mortal and living for 32 blocks unless an explicit mortality is provided as above.
One notable change is that the offline-only create_signed_with_nonce and create_partial_signed_with_nonce functions have lost the _with_nonce suffix. Since we can't discover nonce/mortality settings offline, you should now provide Params and set an explicit nonce (and mortality, if you like) when using these calls, otherwise the nonce will be set to 0 and the mortality to Immortal.
For a full list of changes, please see the following:
no_std compatibility for subxt-signer (#1477)no_std compatibility for subxt-metadata (#1401)reconnecting-rpc-client (#1396)scale-type-resolver integration (#1460)std::cmp traits for subxt payloads and addresses (#1429)FollowEvent::Initialized (#1476)This release introduces a bunch of features that make subxt easier to use. Let's look at a few of them.
This release introduces a bunch of features that make subxt easier to use. Let's look at a few of them.
scale-typegen and adding type aliases (#1249)We rewrote the code generation functionality of subxt and outsourced it to the new scale-typegen crate, which serves a more general purpose.
Since a lot of types used in substrate are rich with generics, this release introduces type aliases into the generated code. A type alias is generated for the arguments/keys or each call, storage entry, and runtime API method (#1249).
The subxt macro provides attributes to specify custom derives, attributes, and type substitutions on a per-type basis. Previously we did not verify that the provided type paths are part of the metadata. This is now fixed: If you provide an invalid type path, the macro will tell you so. It also suggests similar type paths, you might have meant instead.
#[subxt::subxt(
runtime_metadata_path = "metadata.scale",
derive_for_type(path = "Junctions", derive = "Clone")
)]
pub mod polkadot {}
This gives you a compile-time error like this:
Type `Junctions` does not exist at path `Junctions`
A type with the same name is present at:
xcm::v3::junctions::Junctions
xcm::v2::multilocation::Junctions
Previously adding derives on a type containing other types was also cumbersome, see this example:
#[subxt::subxt(
runtime_metadata_path = "metadata.scale",
derive_for_type(path = "xcm::v2::multilocation::MultiLocation", derive = "Clone"),
derive_for_type(path = "xcm::v2::multilocation::Junctions", derive = "Clone"),
derive_for_type(path = "xcm::v2::junction::Junction", derive = "Clone"),
derive_for_type(path = "xcm::v2::NetworkId", derive = "Clone"),
derive_for_type(path = "xcm::v2::BodyId", derive = "Clone"),
derive_for_type(path = "xcm::v2::BodyPart", derive = "Clone"),
derive_for_type(
path = "bounded_collections::weak_bounded_vec::WeakBoundedVec",
derive = "Clone"
)
)]
pub mod polkadot {}
We introduced a recursive flag for custom derives and attributes that automatically inserts the specified derives on all child types:
#[subxt::subxt(
runtime_metadata_path = "metadata.scale",
derive_for_type(path = "xcm::v2::multilocation::MultiLocation", derive = "Clone", recursive),
)]
pub mod polkadot {}
Our CLI tool now allows you to explore runtime APIs and events (#1290). We also fully integrated with scale-typegen-description, a crate that can describe types in a friendly way and provide type examples. The output is also color-coded to be easier on the eyes. Get started with these commands:
# Show details about a runtime API call:
subxt explore --url wss://westend-rpc.polkadot.io api StakingAPI nominations_quota
# Execute a runtime API call from the CLI:
subxt explore --url wss://westend-rpc.polkadot.io api core version -e
# Discover what events a pallet can emit:
subxt explore --url wss://westend-rpc.polkadot.io pallet Balances events
All CLI commands that take some metadata via --file or --url, can now also read the metadata directly from stdin with --file - (#1336).
This allows you to pipe in metadata from other processes like in this command chain:
parachain-node export-metadata | subxt codegen --file - | rustfmt > main.rs
Similar to the macro, the subxt codegen command can now also use recursive flags:
subxt codegen --derive-for-type xcm::v2::multilocation::MultiLocation=Clone,recursive
subxt codegen --attributes-for-type "xcm::v2::multilocation::MultiLocation=#[myerror],recursive"
We also made a few fixes and improvements around the unstable backend and the lightclient, preparing them for more stable usage in the future.
--file - to read metadata from stdin (#1336)ExtrinsicDetails alongside decoded static extrinsics (#1376)scale-typegen as a backend for the codegen (#1260)This release makes a bunch of small QoL improvements and changes. Let's look at the main ones.
This release makes a bunch of small QoL improvements and changes. Let's look at the main ones.
The light client support previously provided a high level interface for connecting to single chains (ie relay chains). This PR exposes a "low level" interface which allows smoldot (the light client implementation we use) to be configured somewhat more arbitrarily, and then converted into a valid subxt OnlineClient to be used.
See this example for more on how to do this.
We'll likely refine this over time and add a slightly higher level interface to make common operations much easier to do.
This PR makes it possible to decode the signed extensions in extrinsics. This looks something like:
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Get blocks; here we just subscribe to them:
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
while let Some(block) = blocks_sub.next().await {
let block = block?;
// Fetch the extrinsics in the block:
let extrinsics = block.extrinsics().await?;
// Iterate over them:
for extrinsic in extrinsics.iter() {
// Returns None if the extrinsic isn't signed, so no signed extensions:
let Some(signed_exts) = extrinsic.signed_extensions() else {
continue;
};
// We can ask for a couple of common values, None if not found:
println!("Tip: {:?}", signed_exts.tip());
println!("Nonce: {:?}", signed_exts.tip());
// Or we can find and decode into a static signed extension type
// (Err if we hit a decode error first, then None if it's not found):
if let Ok(Some(era)) = signed_exts.find::<CheckMortality<PolkadotConfig>>() {
println!("Era: {era:?}");
}
// Or we can iterate over the signed extensions to work with them:
for signed_ext in signed_exts {
println!("Signed Extension name: {}", signed_ext.name());
// We can try to statically decode each one:
if let Ok(Some(era)) = signed_ext.as_signed_extension::<CheckMortality<PolkadotConfig>>() {
println!("Era: {era:?}");
}
// Or we can dynamically decode it into a `scale_value::Value`:
if let Ok(value) = signed_ext.value() {
println!("Decoded extension: {value}");
}
}
}
}
See the API docs for more.
Still on the topic of signed extensions, the ChargeAssetTxPayment extension was previously not able to be used with a generic AssetId, which prohibited it from being used on the Asset Hub (which uses a MultiLocation instead). To address this, we added an AssetId type to our subxt::Config, which can now be configured.
One example of doing that can be found here.
This example uses a generated MultiLocation type to be used as the AssetId. Currently it requires a rather hideous set of manual clones like so:
#[subxt::subxt(
runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale",
derive_for_type(path = "xcm::v2::multilocation::MultiLocation", derive = "Clone"),
derive_for_type(path = "xcm::v2::multilocation::Junctions", derive = "Clone"),
derive_for_type(path = "xcm::v2::junction::Junction", derive = "Clone"),
derive_for_type(path = "xcm::v2::NetworkId", derive = "Clone"),
derive_for_type(path = "xcm::v2::BodyId", derive = "Clone"),
derive_for_type(path = "xcm::v2::BodyPart", derive = "Clone"),
derive_for_type(
path = "bounded_collections::weak_bounded_vec::WeakBoundedVec",
derive = "Clone"
)
)]
This is something we plan to address in the next version of Subxt.
Before this release, each signed extension had a unique name (SignedExtension::NAME). We'd use this name to figure out which signed extensions to apply for a given chain inside the signed_extensions::AnyOf type.
However, we recently ran into a new signed extension in Substrate called SkipCheckIfFeeless. This extension would wrap another signed extension, but maintained its own name. It has since been "hidden" from the public Substrate interface again, but a result of encountering this is that we have generalised the way that we "match" on signed extensions, so that we can be smarter about it going forwards.
So now, for a given signed extension, we go from:
impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment<T> {
const NAME: &'static str = "ChargeAssetTxPayment";
type Decoded = Self;
}
To:
impl<T: Config> SignedExtension<T> for ChargeAssetTxPayment<T> {
type Decoded = Self;
fn matches(identifier: &str, type_id: u32, types: &PortableRegistry) -> bool {
identifier == "ChargeAssetTxPayment"
}
}
On the whole, we continue matching by name, as in the example above, but this allows an author to inspect the type of the signed extension (and subtypes of it) too if they want the signed extension to match (and thus be used) only in certain cases.
wait_for_in_block helper method (#1237)One can no longer use tx.wait_for_in_block to wait for a transaction to enter a block. The reason for this removal is that, especially when we migrate to the new chainHead APIs, we will no longer be able to reliably obtain any details about the block that the transaction made it into.
In other words, the following sort of thing would often fail:
tx.wait_for_in_block()
.await?
.wait_for_success()
.await?;
The reason for this is that the block announced in the transaction status may not have been "pinned" yet in the new APIs. In the old APIs, errors would occasionally be encountered because the block announced may have been pruned by the time we ask for details for it. Overall; having an "unreliable" higher level API felt like a potential foot gun.
That said, you can still achieve the same via the lower level APIs like so:
while let Some(status) = tx.next().await {
match status? {
TxStatus::InBestBlock(tx_in_block) | TxStatus::InFinalizedBlock(tx_in_block) => {
// now, we can attempt to work with the block, eg:
tx_in_block.wait_for_success().await?;
},
TxStatus::Error { message } | TxStatus::Invalid { message } | TxStatus::Dropped { message } => {
// Handle any errors:
println!("Error submitting tx: {message}");
},
// Continue otherwise:
_ => continue,
}
}
The subxt-codegen crate has always been a bit of a mess because it wasn't really supposed to be used outside of the subxt crates, which had led to issues like https://github.com/paritytech/subxt/issues/1211.
This PR tidies up the interface to that crate so that it's much easier now to programmatically generate the Subxt interface. Now, we have three properly supported ways to do this, depending on your needs:
#[subxt] macro.subxt codegen CLI command.subxt-codegen crate.Each method aims to expose a similar and consistent set of options.
If you were previously looking to use parts of the type generation logic to, for instance, generate runtime types but not the rest of the Subxt interface, then the https://github.com/paritytech/scale-typegen crate will aim to fill this role eventually.
That sums up the most significant changes. A summary of all of the relevant changes is as follows:
Arc<T> and Box<T> (#1277)u32 or MultiLocation) (#1227)0.14.0 and smoldot-light to 0.12.0 (#1307)substrate-compat impls to accept any valid hasher/header impl (#1265)wait_for_in_block helper method (#1237)subxt feature in subxt-signer crate to default features (#1193)This is a patch release, mainly to deploy the fix #1191, which resolves an issue around codegen when runtime API definitions have an argument name "_"
This is a patch release, mainly to deploy the fix #1191, which resolves an issue around codegen when runtime API definitions have an argument name "_".
We also expose an API, api.blocks().at(block_hash).account_nonce(account_id), which allows one to obtain the account nonce for some account at any block hash, and not just at the latest finalized block hash as is possible via api.tx().account_nonce(..).
The main changes are:
This is a big release that adds quite a lot, and also introduces some slightly larger breaking changes. Let's look at the main changes:
This is a big release that adds quite a lot, and also introduces some slightly larger breaking changes. Let's look at the main changes:
Backend trait and the UnstableBackend and LegacyBackend impls.See #1126, #1137 and #1161 for more information.
The overarching idea here is that we want Subxt to be able to continue to support talking to nodes/light-clients using the "legacy" RPC APIs that are currently available, but we also want to be able to support using only the new RPC APIs once they are stabilized.
Until now, the higher level APIs in Subxt all had access to the RPCs and could call whatever they needed. Now, we've abstracted away which RPCs are called (or even that RPCs are used at all) behind a subxt::backend::Backend trait. Higher level APIs no longer have access to RPC methods and instead have access to the current Backend implementation. We then added two Backend implementations:
subxt::backend::legacy::LegacyBackend: This uses the "legacy" RPCs, as we've done to date, to obtain the information we need. This is still the default backend that Subxt will use.subxt::backend::unstable::UnstableBackend: This backend relies on the new (and currently still unstable) chainHead based RPC APIs to obtain the information we need. This could break at any time as the RPC methods update, until they are fully stabilized. One day, this will be the default backend.One of the significant differences between backends is that the UnstableBackend can only fetch further information about blocks that are "pinned", ie that we have signalled are still in use. To that end, the backend now hands back BlockRefs instead of plain block hashes. As long as a BlockRef exists for some block, the backend (and node) will attempt to keep it available. Thus, Subxt will keep hold of these internally as needed, and also allows you to obtain them from a Block with block.reference(), in case you need to try and hold on to any blocks for longer.
One of the main breaking changes here is in how you can access and call RPC methods.
Previously, you could access them directly from the Subxt client, since it exposed the RPC methods itself, eg:
let genesis_hash = client.rpc().genesis_hash().await?;
Now, the client only knows about a Backend (ie it has a .backend() method instead of .rpc()), and doesn't know about RPCs, but you can still manually create an RpcClient to call RPC methods like so:
use subxt::{
config::SubstrateConfig,
backend::rpc::RpcClient,
backend::legacy::LegacyRpcMethods,
};
// Instantiate an RPC client pointing at some URL.
let rpc_client = RpcClient::from_url("ws://localhost:9944").await?;
// We could also call unstable RPCs with `backend::unstable::UnstableRpcMethods`:
let rpc_methods = LegacyRpcMethods::<SubstrateConfig>::new(rpc_client);
// Use it to make RPC calls, here calling the legacy genesis_hash method.
let genesis_hash = rpc_methods.genesis_hash().await?
If you'd like to share a single client for RPCs and Subxt usage, you can clone this RPC client and run OnlineClient::<SubstrateConfig>::from_rpc_client(rpc_client) to create a Subxt client using it.
Another side effect of this change is that RPC related things have moved from subxt::rpc::* to subxt::backend::rpc::* and some renaming has happened along the way.
A number of smaller breaking changes have also been made in order to expose details that are compatible with both sets of RPCs, and to generally move Subxt towards working well with the new APIs and exposing things in a consistent way:
fetch_keys is renamed to fetch_raw_keys (this just for consistency with fetch_raw).iter no longer accepts a page_size argument, and each item returned is now an Option<Result<(key, val)>> instead of a Result<Option<(key, val)>> (we now return a valid Stream implementation for storage entry iteration). See this example.next_item => next. If you rely on higher level calls like sign_and_submit_then_watch, nothing has changed..at_latest() in various places would mean that calls would run against the latest best block. Now, all such calls will run against the latest finalized block. The latest best block is subject to changing or being pruned entirely, and can differ between nodes..at(block_hash) should continue to work as-is, but can now also accept a BlockRef, to keep the relevant block around while you're using the associated APIs.block.body().await?.extrinsics(). This has now been simplified to block.extrinsics().await?.ExtrinsicParams more flexible with SignedExtensions.See #1107 for more information.
When configuring Subxt to work against a given chain, you needed to configure the ExtrinsicParams associated type to encode exactly what was required by the chain when submitting transactions. This could be difficult to get right.
Now, we have "upgraded" the ExtrinsicParams trait to give it access to metadata, so that it can be smarter about how to encode the correct values. We've also added a subxt::config::SignedExtension trait, and provided implementations of it for all of the "standard" signed extensions (though we have a little work to do still).
How can you use SignedExtensions? Well, subxt::config::signed_extensions::AnyOf<T, Params> is a type which can accept any tuple of SignedExtensions, and itself implements ExtrinsicParams. It's smart, and will use the metadata to know which of the signed extensions that you provided to actually use on a given chain. So, AnyOf makes it easy to compose whichever SignedExtensions you need to work with a chain.
Finally, we expose subxt::config::{ DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder }; the former just uses AnyOf to automatically use any of the "standard" signed extensions as needed, and the latter provided a nice builder interface to configure any parameters for them. This is now the default type used in SubstrateConfig and PolkadotConfig, so long story short: those configurations (and particularly their ExtrinsicParams) are more likely to Just Work now across default chains.
See this example for how to create and use custom signed extensions, or this example for how to implement custom ExtrinsicParams if you'd prefer to ignore SignedExtensions entirely.
As a result of using the new DefaultExtrinsicParams in SubstrateConfig and PolkadotConfig, the interface to configure transactions has changed (and in fact been generally simplified). Configuring a mortal transaction with a small tip ƒor instance used to look like:
use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params};
let tx_params = Params::new()
.tip(PlainTip::new(1_000))
.era(Era::mortal(32, latest_block.header().number()), latest_block.header().hash());
let hash = api.tx().sign_and_submit(&tx, &from, tx_params).await?;
And now it will look like this:
use subxt::config::polkadot::PolkadotExtrinsicParamsBuilder as Params;
let tx_params = Params::new()
.tip(1_000)
.mortal(latest_block.header(), 32)
.build();
let hash = api.tx().sign_and_submit(&tx, &from, tx_params).await?;
Check the docs for PolkadotExtrinsicParamsBuilder and the ExtrinsicParams trait for more information.
See (#1079) for more information.
Previously, we could statically iterate over the root of some storage map using something like:
// Build a storage query to iterate over account information.
let storage_query = polkadot::storage().system().account_root();
// Get back an iterator of results (here, we are fetching 10 items at
// a time from the node, but we always iterate over one at a time).
let mut results = api.storage().at_latest().await?.iter(storage_query, 10).await?;
Now, the suffix _root has been renamed to _iter, and if the storage entry is for instance a double map (or greater depth), we'll also now generate _iter2, iter3 and so on, each accepting the keys needed to access the map at that depth to iterate the remainder. The above example now becomes:
// Build a storage query to iterate over account information.
let storage_query = polkadot::storage().system().account_iter();
// Get back an iterator of results
let mut results = api.storage().at_latest().await?.iter(storage_query).await?;
Note also that the pagination size no longer needs to be provided; that's handled internally by the relevant Backend.
This is not a breaking change, but just a noteworthy addition; see #1106, #1117 and #1147 for more information.
V15 metadata allows chains to insert arbitrary information into a new "custom values" hashmap (see this). Subxt has now added APIs to allow accessing these custom values a little like how constants can be accessed.
Dynamically accessing custom values looks a bit like this:
// Obtain the raw bytes for some entry:
let custom_value_bytes: Vec<u8> = client.custom_values().bytes_at("custom-value-name")?;
// Obtain a representation of the value that we can attempt to decode:
let custom_value = client.custom_values().at("custom-value-name")?;
// Decode it into a runtime Value if possible:
let value: Value = custom_value.to_value()?;
// Or attempt to decode it into a specific type:
let value: Foo = custom_value.as_type()?;
We can also use codegen to statically access values, which makes use of validation and returns a known type whenever possible, for the added compile time safety this brings:
#[subxt::subxt(runtime_metadata_path = "metadata.scale")]
pub mod runtime {}
// The generated interface also exposes any custom values with known types and sensible names:
let value_addr = runtime::custom().custom_value_name();
// We can use this address to access and decode the relevant value from metadata:
let static_value = client.custom_values().at(&value_addr)?;
// Or just ask for the bytes for it:
let static_value_bytes = client.custom_values().bytes_at(&value_addr)?;
That sums up the most significant changes. All of the key commits in this release can be found here:
UnstableBackend: Add a chainHead based backend implementation (#1161)UnstableBackend: Expose the chainHead RPCs (#1137)storage_version() and runtime_wasm_code() to storage (#1111)subxt-codegen: Add "web" feature for WASM compilation that works with jsonrpsee (#1175)subxt-codegen: support compiling to WASM (#1154)Debug impl for DispatchError to avoid huge metadata output (#1153)sp-arithmetic to help substrate compat. (#1155)chainHead_follow (#1116)This is a small release whose primary goal is to bump the versions of scale-encode, scale-decode and scale-value being used, to benefit from recent ch
This is a small release whose primary goal is to bump the versions of scale-encode, scale-decode and scale-value being used, to benefit from recent changes in those crates.
scale-decode changes how compact values are decoded as part of #1103. A compact encoded struct should now be properly decoded into a struct of matching shape (which implements DecodeAsType). This will hopefully resolve issues around structs like Perbill. When decoding the SCALE bytes for such types into scale_value::Value, the Value will now be a composite type wrapping a value, and not just the value.
We've also figured out how to sign extrinsics using browser wallets when a Subxt app is compiled to WASM; see #1067 for more on that!
The key commits:
rust-version to 1.70 (#1097)This patch release fixes a small issue whereby using runtime_metadata_url in the Subxt macro would still attempt to download unstable metadata, which
This patch release fixes a small issue whereby using runtime_metadata_url in the Subxt macro would still attempt to download unstable metadata, which can fail at the moment if the chain has not updated to stable V15 metadata yet (which has a couple of changes from the last unstable version). Note that you're generally encouraged to use runtime_metadata_path instead, which does not have this issue.
A few small breaking changes have occurred:
This release beings with it a number of exciting additions. Let's cover a few of the most significant ones:
This release adds support for light clients using Smoldot, both when compiling native binaries and when compiling to WASM to run in a browser environment. This is unstable for now while we continue testing it and work on making use of the new RPC APIs.
Here's how to use it:
use subxt::{
client::{LightClient, LightClientBuilder},
PolkadotConfig
};
use subxt_signer::sr25519::dev;
// Create a light client:
let api = LightClient::<PolkadotConfig>::builder()
// You can also pass a chain spec directly using `build`, which is preferred:
.build_from_url("ws://127.0.0.1:9944")
.await?;
// Working with the interface is then the same as before:
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
let events = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &dev::alice())
.await?
.wait_for_finalized_success()
.await?;
At the moment you may encounter certain things that don't work; please file an issue if you do!
This release stabilizes the metadata V15 interface, which brings a few changes but primarily allows you to interact with Runtime APIs via an ergonomic Subxt interface:
// We can use the static interface to interact in a type safe way:
#[subxt::subxt(runtime_metadata_path = "path/to/metadata.scale")]
pub mod polkadot {}
let runtime_call = polkadot::apis()
.metadata()
.metadata_versions();
// Or we can use the dynamic interface like so:
use subxt::dynamic::Value;
let runtime_call = subxt::dynamic::runtime_api_call(
"Metadata",
"metadata_versions",
Vec::<Value<()>>::new()
);
This is no longer behind a feature flag, but if the chain you're connecting to doesn't use V15 metadata yet then the above will be unavailable.
subxt-signerThe new subxt-signer crate provides the ability to sign transactions using either sr25519 or ECDSA. It's WASM compatible, and brings in fewer dependencies than using sp_core/sp_keyring does, while having an easy to use interface. Here's an example of signing a transaction using it:
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;
let api = OnlineClient::<PolkadotConfig>::new().await?;
// Build the extrinsic; a transfer to bob:
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
// Sign and submit the balance transfer extrinsic from Alice:
let from = dev::alice();
let events = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
.await?
.wait_for_finalized_success()
.await?;
Dev keys should only be used for tests since they are publicly known. Actual keys can be generated from URIs, phrases or raw entropy, and derived using soft/hard junctions:
use subxt_signer::{ SecretUri, sr25519::Keypair };
use std::str::FromStr;
// From a phrase (see `bip39` crate on generating phrases):
let phrase = bip39::Mnemonic::parse(phrase).unwrap();
let keypair = Keypair::from_phrase(&phrase, Some("Password")).unwrap();
// Or from a URI:
let uri = SecretUri::from_str("//Alice").unwrap();
let keypair = Keypair::from_uri(&uri).unwrap();
// Deriving a new key from an existing one:
let keypair = keypair.derive([
DeriveJunction::hard("Alice"),
DeriveJunction::soft("stash")
]);
A few small breaking changes have occurred:
Index associated type in your Config implementations; we now work it out dynamically where needed.subxt-signer added native signing support and can be used instead of bringing in Substrate dependencies to sign transactions now. You can still enable this feature flag as before to make use of them if needed.
For anything else that crops up, the compile errors and API docs will hopefully point you in the right direction, but please raise an issue if not.
For a full list of changes, see below:
subxt_signer crate for native & WASM compatible signing (#1016)diff command to CLI tool to visualize metadata changes (#1015)substrate-compat default feature flag (#1078)UncheckedExtrinsic with custom encoding (#1076)Index type from Config trait (#1074)#[allow(rustdoc::broken_intra_doc_links)] to subxt-codegen (#998)This is another big release for Subxt with a bunch of awesome changes. Let's talk about some of the notable ones:
This is another big release for Subxt with a bunch of awesome changes. Let's talk about some of the notable ones:
This release will come with overhauled documentation and examples which is much more comprehensive than before, and goes into much more detail on each of the main areas that Subxt can work in.
Check out the documentation for more. We'll continue to build on this with some larger examples, too, going forwards. (#968) is particularly cool as it's our first example showcasing Subxt working with Yew and WASM; it'll be extended with more documentation and things in the next release.
explore command.The CLI tool has grown a new command, explore. Point it at a node and use explore to get information about the calls, constants and storage of a node, with a helpful interface that allows you to progressively dig into each of these areas!
One of the biggest changes in this version is that, given (unstable) V15 metadata, Subxt can now generate a nice interface to make working with Runtime APIs as easy as building extrinsics or storage queries. This is currently unstable until the V15 metadata format is stabilised, and so will break as we introduce more tweaks to the metadata format. We hope to stabilise V15 metadata soon; see this for more information. At this point, we'll stabilize support in Subxt.
Up until now, you were able to retrieve the bytes for extrinsics, but weren't able to use Subxt to do much with those bytes.
Now, we expose several methods to decode extrinsics that work much like decoding events:
#[subxt::subxt(runtime_metadata_path = "polkadot_metadata.scale")]
pub mod polkadot {}
// Get some block:
let block = api.blocks().at_latest().await?;
// Find and decode a specific extrinsic in the block:
let remark = block.find::<polkadot::system::calls::Remark>()?;
// Iterate the extrinsics in the block:
for ext in block.iter() {
// Decode a specific extrinsic into the call data:
let remark = ext.as_extrinsic::<polkadot::system::calls::Remark>()?;
// Decode any extrinsic into an enum containing the call data:
let extrinsic = ext.as_root_extrinsic::<polkadot::Call>()?;
}
Previously, the subxt_metadata crate was simply a collection of functions that worked directly on frame_metadata types. Then, in subxt, we had a custom metadata type which wrapped this to provide the interface needed by various Subxt internals and traits.
Now, the subxt_metadata crate exposes our own Metadata type which can be decoded from the same wire format as the frame_metadata types we used to use. This type is now used throughout Subxt, as well as in the codegen stuff, and provides a single unified interface for working with metadata that is independent of the actual underlying metadata version we're using.
This shouldn't lead to breakages in most code, but if you need to load metadata for an OfflineClient you might previously have done this:
use subxt::ext::frame_metadata::RuntimeMetadataPrefixed;
use subxt::metadata::Metadata;
let metadata = RuntimeMetadataPrefixed::decode(&mut &*bytes).unwrap();
let metadata = Metadata::try_from(metadata).unwrap();
But now you'd do this:
use subxt::metadata::Metadata;
let metadata = Metadata::decode(&mut &*bytes).unwrap();
Otherwise, if you implement traits like TxPayload directly, you'll need to tweak the implementations to use the new Metadata type, which exposes everything you used to be able to get hold of but behind a slightly different interface.
as_pallet_event method (#953)In an effort to simplify the number of ways we have to decode events, as_pallet_event was removed. You can achieve a similar thing by calling as_root_event, which will decode any event that the static interface knows about into an outer enum of pallet names to event names. if you only care about a specific event, you can match on this enum to look for events from a specific pallet.
Another reason that as_pallet_event was removed was that it could potentially decode events from the wrong pallets into what you're looking for, if the event shapes happened to line up, which was a potential foot gun.
as_root_error for decoding errors.Much like we can call as_root_extrinsic or as_root_event to decode extrinsics and events into a top level enum, we've also added as_root_error to do the same for errors and help to make this interface consistent across the board.
Beyond these, there's a bunch more that's been added, fixed and changes. A full list of the notable changes in this release are as follows:
EventDetails (#989)as_root_error for helping to decode ModuleErrors (#930)syn 2.0 (#875)This is not a breaking change, but notable in that is adds create_partial_signed_with_nonce and create_partial_signed to the TxClient to allow you to…
This is a fairly significant change; what follows is a description of the main changes to be aware of:
Prior to this, static types generated by codegen (ie subxt macro) would implement Encode and Decode from the parity-scale-codec library. This meant that they woule be encoded-to and decoded-from based on their shape. Dynamic types (eg the subxt::dynamic::Value type) would be encoded and decoded based on the node metadata instead.
This change makes use of the new scale-encode and scale-decode crates to auto-implement EncodeAsType and DecodeAsType on all of our static types. These traits allow types to take the node metadata into account when working out how best to encode and decode into them. By using metadata, we can be much more flexible/robust about how to encode/decode various types (as an example, nested transactions will now be portable across runtimes). Additionally, we can merge our codepaths for static and dynamic encoding/decoding, since both static and dynamic types can implement these traits. Read the PR description for more info.
A notable impact of this is that any types you wish to substitute when performing codegen (via the CLI tool or #[subxt] macro) must also implement EncodeAsType and DecodeAsType too. Substrate types, for instance, generally do not. To work around this, #886 introduces a Static type and enhances the type substitution logic so that you're able to wrap any types which only implement Encode and Decode to work (note that you lose out on the improvements from EncodeAsType and DecodeAsType when you do this):
#[subxt::subxt(
runtime_metadata_path = "/path/to/metadata.scale",
substitute_type(
type = "sp_runtime::multiaddress::MultiAddress<A, B>",
with = "::subxt::utils::Static<::sp_runtime::multiaddress::MultiAddress<A, B>>"
)
)]
pub mod node_runtime {}
So, if you want to substitute in Substrate types, wrap them in ::subxt::utils::Static in the type substitution, as above. #886 also generally improves type substitution so that you can substitute the generic params in nested types, since it's required in the above.
Several types have been renamed as a result of this unification (though they aren't commonly made explicit use of). Additionally, to obtain the bytes from a storage address, instead of doing:
let addr_bytes = storage_address.to_bytes()
You must now do:
let addr_bytes = cxt.client().storage().address_bytes(&storage_address).unwrap();
This is because the address on it's own no longer requires as much static information, and relies more heavily now on the node metadata to encode it to bytes.
This is not a breaking change, but notable in that is adds create_partial_signed_with_nonce and create_partial_signed to the TxClient to allow you to break extrinsic creation into two steps:
This allows a signer payload to be obtained from Subxt, handed off to some external application, and then once a signature has been obtained, that can be passed back to Subxt to complete the creation of an extrinsic. This opens the door to using browser wallet extensions, for instance, to sign Subxt payloads.
This is not a breaking change, but adds the ability to use the Subxt CLI tool to strip out all but some named list of pallets from a metadata bundle. Aside from allowing you to store a significantly smaller metadata bundle with only the APIs you need in it, it will also lead to faster codegen, since there's much less of it to do.
Use a command like subxt metadata --pallets Balances,System to select specific pallets. You can provide an existing metadata file to take that and strip it, outputting a smaller bundle. Alternately it will grab the metadata from a local node and strip that before outputting.
The DispatchError returned from either attempting to submit an extrinsic, or from calling .dry_run() has changed. It's now far more complete with respect to the information it returns in each case, and the interface has been tidied up. Changes include:
ModuleError's, instead of err.pallet and err.error, you can obtain error details using let details = err.details()? and then details.pallet() and details.error().DryRunResult is now a custom enum with 3 states, Success, DispatchError or TransactionValidityError. The middle of these contains much more information than previously.#[non_exahustive] since they could grow and change at any time. (Owing to our use of scale-decode internally, we are not so contrained when it comes to having precise variant indexes or anything now, and can potentially deprecate rather than remove old variants as needed).rpc.dry_run() RPC call now returns the raw dry run bytes which can then be decoded with the help of metadata into our DryRunResult.It was found by @furoxr that Substrate nodes will stop sending transaction progress events under more circumstances than we originally expected. Thus, now calls like wait_for_finalized() and wait_for_in_block() will stop waiting for events when any of the following is sent from the node:
UsurpedFinalizedFinalityTimeoutInvalidDroppedPreviously we'd only close the subscription and stop waiting when we saw a Finalized or FinalityTimeout event. Thanks for digging into this @furoxr!
at_latest() method (#900 and #904)A small breaking change; previously we had .at(None) or .at(Some(block_hash)) methods in a few places to obtain things at either the latest block or some specific block hash.
This API has been clarified; we now have .at_latest() to obtain the thing at the latest block, or .at(block_hash) (note; no more option) to obtain the thing at some fixed block hash. In a few instances this has allowed us to ditch the async from the .at() call.
That covers the larger changes in this release. For more details, have a look at all of the notable PRs since the last release here:
subxt (#837)sp_core and sp_runtime (#853)### Added - Add find_last for block types
find_last for block types (#825)The main breaking change is fairly small: #804. Here, the BlockNumber associated type has been removed from Config entirely, since it wasn't actually…
This is a fairly small release, primarily to bump substrate dependencies to their latest versions.
The main breaking change is fairly small: #804. Here, the BlockNumber associated type has been removed from Config entirely, since it wasn't actually needed anywhere in Subxt. Additionally, the constraints on each of those associated types in Config were made more precise, primarily to tidy things up (but this should result in types more easily being able to meet the requirements here). If you use custom Config, the fix is simply to remove the BlockNumber type. If you also use the Config trait in your own functions and depend on those constraints, you may be able to define a custom MyConfig type which builds off Config and adds back any additional bounds that you want.
Note worthy PRs merged since the last release:
This release introduces a number of breaking changes that can be generally be fixed with mechanical tweaks to your code. The notable changes are descr…
This release adds a number of improvements, most notably:
AccountId32, MultiAddress and MultiSignature internally.chainHead style RPC interface.chainHead style interface (see https://paritytech.github.io/json-rpc-interface-spec/). These are currently unstable, but will allow users to start experimenting with this new API if their nodes support it.This release introduces a number of breaking changes that can be generally be fixed with mechanical tweaks to your code. The notable changes are described below.
See #774. This PR makes the Storage API more consistent with the Events API, and allows access to it from a given block as part of a push to provide a more block centric API that will hopefully be easier to understand, and will align with the new RPC chainHead style RPC interface.
Before, your code will look like:
let a = api.storage().fetch(&staking_bonded, None).await?;
After, it should look like:
let a = api.storage().at(None).await?.fetch(&staking_bonded).await?;
Essentially, the final parameter for choosing which block to call some method at has been moved out of the storage method itself and is now provided to instantiate the storage API, either explicitly via an .at(optional_block_hash) as above, or implicitly when calling block.storage() to access the same storage methods for some block.
An alternate way to access the same storage (primarily used if you have subscribed to blocks or otherwise are working with some block) now is:
let block = api.blocks().at(None).await?
let a = block.storage().fetch(&staking_bonded, None).await?;
See #735. Previously, you could perform basic type substitution like this:
#[subxt::subxt(runtime_metadata_path = "../polkadot_metadata.scale")]
pub mod node_runtime {
#[subxt::subxt(substitute_type = "sp_arithmetic::per_things::Foo")]
use crate::Foo;
}
This example would use crate::Foo every time an sp_arithmetic::per_things::Foo was encountered in the codegen. However, this was limited; the substitute type had to have the name number and order of generic parameters for this to work.
We've changed the interface above into:
#[subxt::subxt(
runtime_metadata_path = "../polkadot_metadata.scale",
substitute_type(
type = "sp_arithmetic::per_things::Foo<A, B, C>",
with = "crate::Foo<C>"
)
)]
pub mod node_runtime {}
In this example, we can (optionally) specify the generic parameters we expect to see on the original type ("type"), and then of those, decide which should be present on the substitute type ("with"). If no parameters are provided at all, we'll get the same behaviour as before. This allows much more flexibility when defining substitute types.
See #760. Subxt now has a "substrate-compat" feature (enabled by default, and disabled for WASM builds). At present, enabling this feature simply exposes the PairSigner (which was always available before), allowing transactions to be signed via Substrate signer logic (as before). When disabled, you (currently) must bring your own signer implementation, but in return we can avoid bringing in a substantial number of Substrate dependencies in the process.
Regardless, this change also tidied up and moved various bits and pieces around to be consistent with this goal. To address some common moves, previously we'd have:
use subxt::{
ext::{
sp_core::{ sr25519, Pair },
sp_runtime::{ AccountId32, generic::Header },
},
tx::{
Era,
PlainTip,
PolkadotExtrinsicParamsBuilder
}
};
And now this would look more like:
// `sp_core` and `sp_runtime` are no longer exposed via `ext`; add the crates yourself at matching versions to use:
use sp_core::{
sr25519,
Pair,
};
use subxt::{
// You'll often want to use the "built-in" `AccountId32` now instead of the `sp_runtime` version:
utils::AccountId32,
// traits used in our `Config` trait are now provided directly in this module:
config::Header,
// Polkadot and Substrate specific Config types are now in the relevant Config section:
config::polkadot::{
Era,
PlainTip,
PolkadotExtrinsicParamsBuilder
}
}
Additionally, the type Hashing in the Config trait is now called Hasher, to clarify what it is, and types returned directly from the RPC calls now all live in crate::rpc::types, rather than sometimes living in Substrate crates.
Some other note worthy PRs that were merged since the last release:
chainHead RPC methods (#766)Cargo.lock for deterministic builds (#795)--derive-for-type to cli (#708)Update (#791)subscription ID for RpcClientT (#733)This release resolves the parity-util-mem crate several version guard by updating substrate related dependencies which makes it possible to have other
This release resolves the parity-util-mem crate several version guard by updating substrate related dependencies which makes
it possible to have other substrate dependencies in tree again along with subxt.
In addition the release has several API improvements in the dynamic transaction API along with that subxt now compiles down to WASM.
Notable PRs merged:
Module (#697)api.blocks() to be the primary way to subscribe and fetch blocks/extrinsics/events (#691)BlocksClient for working with blocks (#671)subxt crate path for generated code (#664)RpcError with custom client error (#694)This release has a bunch of smaller changes and fixes. The breaking changes are fairly minor and should be easy to address if encountered. Notable add…
This release has a bunch of smaller changes and fixes. The breaking changes are fairly minor and should be easy to address if encountered. Notable additions are:
jsonrpsee an optional dependency, and opens the door for Subxt to be integrated into things like light clients, since we can decide how to handle RPC calls.scale-value and scale-decode dependencies are bumped. The main effect of this is that bitvec is no longer used under the hood in the core of Subxt, which helps to remove one hurdle on the way to being able to compile it to WASM.Notable PRs merged:
runtime upgrade API (#657)StaticTxPayload::call_data (#660)NumberOrHex (#636)async (#645)This is one of the most significant releases to date in Subxt, and carries with it a number of significant breaking changes, but in exchange, a number…
This is one of the most significant releases to date in Subxt, and carries with it a number of significant breaking changes, but in exchange, a number of significant improvements. The most significant PR is #593; the fundamental change that this makes is to separate creating a query/transaction/address from submitting it. This gives us flexibility when creating queries; they can be either dynamically or statically generated, but also flexibility in our client, enabling methods to be exposed for online or offline use.
The best place to look to get a feel for what's changed, aside from the documentation itself, is the examples folder. What follows are some examples of the changes you'll need to make, which all follow a similar pattern:
Previously, we'd build a client which is tied to the static codegen, and then use the client to build and submit a transaction like so:
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<_>>>();
let balance_transfer = api
.tx()
.balances()
.transfer(dest, 10_000)?
.sign_and_submit_then_watch_default(&signer)
.await?
.wait_for_finalized_success()
.await?;
Now, we build a transaction separately (in this case, using static codegen to guide us as before) and then submit it to a client like so:
let api = OnlineClient::<PolkadotConfig>::new().await?;
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
let balance_transfer = api
.tx()
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
.await?
.wait_for_finalized_success()
.await?;
See the examples/examples/submit_and_watch.rs example for more.
Previously, we build and submit a storage query in one step:
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let entry = api.storage().staking().bonded(&addr, None).await;
Now, we build the storage query separately and submit it to the client:
let api = OnlineClient::<PolkadotConfig>::new().await?;
let staking_bonded = polkadot::storage().staking().bonded(&addr);
let entry = api.storage().fetch(&staking_bonded, None).await;
Note that previously, the generated code would do the equivalent of fetch_or_default if possible, or fetch if no default existed. You must now decide whether to:
None if it's not found (api.storage().fetch(..)), orapi.storage().fetch_or_default(..)).The static types will protect you against using fetch_or_default when no such default exists, and so the recommendation is to try changing all storage requests to use fetch_or_default, falling back to using fetch where doing so leads to compile errors.
See examples/examples/concurrent_storage_requests.rs for an example of fetching entries.
Previously:
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let mut iter = api
.storage()
.xcm_pallet()
.version_notifiers_iter(None)
.await?;
while let Some((key, value)) = iter.next().await? {
// ...
}
Now, as before, building the storage query to iterate over is separate from using it:
let api = OnlineClient::<PolkadotConfig>::new().await?;
let key_addr = polkadot::storage()
.xcm_pallet()
.version_notifiers_root();
let mut iter = api
.storage()
.iter(key_addr, 10, None).await?;
while let Some((key, value)) = iter.next().await? {
// ...
}
Note that the _root() suffix on generated storage queries accesses the root entry at that address,
and is available when the address is a map that can be iterated over. By not appending _root(), you'll
be asked to provide the values needed to access a specific entry in the map.
See the examples/examples/storage_iterating.rs example for more.
Before, we'd build a client and use the client to select and query a constant:
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let existential_deposit = api
.constants()
.balances()
.existential_deposit()?;
Now, similar to the other examples, we separately build a constant address and provide that address to the client to look it up:
let api = OnlineClient::<PolkadotConfig>::new().await?;
let address = polkadot::constants()
.balances()
.existential_deposit();
let existential_deposit = api.constants().at(&address)?;
See the examples/examples/fetch_constants.rs example for more.
Event subscriptions themselves are relatively unchanged (although the data you can access/get back has changed a little). Before:
let api = ClientBuilder::new()
.build()
.await?
.to_runtime_api::<polkadot::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();
let mut event_sub = api.events().subscribe().await?;
while let Some(events) = event_sub.next().await {
// ...
}
Now, we simply swap the client out for our new one, and the rest is similar:
let api = OnlineClient::<PolkadotConfig>::new().await?;
let mut event_sub = api.events().subscribe().await?;
while let Some(events) = event_sub.next().await {
// ...
}
Note that when working with a single event, the method event.bytes() previously returned just the bytes associated with the event fields. Now, event.bytes() returns all of the bytes associated with the event. There is a separate method, event.field_bytes(), that returns the bytes for just the fields in the event. This change will not lead to a compile error, and so it's worth keeping an eye out for any uses of .bytes() to update them to .field_bytes().
See the examples/examples/subscribe_all_events.rs example for more.
The general pattern, as seen above, is that we break apart constructing a query/address and using it. You can now construct queries dynamically instead and forego all static codegen by using the functionality exposed in the subxt::dynamic module instead.
Other smaller breaking changes have happened, but they should be easier to address by following compile errors.
For more details about all of the changes, the full commit history since the last release is as follows:
ws in subxt-cli (#579)substitute_type (#577)field::type_name from metadata validation (#595)With this release, subxt can subscribe to the node's runtime upgrades to ensure that the metadata is updated and extrinsics are properly constructed.
With this release, subxt can subscribe to the node's runtime upgrades to ensure that the metadata is updated and extrinsics are properly constructed.
We have also made some slight API improvements to make in the area of storage keys, and thanks to an external contribution we now support dry running transactions before submitting them.
This release also improves the documentation, adds UI tests, and defaults the subxt-cli to return metadata
bytes instead of the JSON format.
StorageEntry empty keys (#565)Clone for the generated RuntimeApi (#544)log with tracing and record extrinsic info (#535)…target node's runtime metadata. This implies a breaking change in the subxt API, as the user receives an error when interacting with an incompatible A…
This release adds static metadata validation, via comparing the statically generated API with the target node's runtime metadata. This implies a breaking change in the subxt API, as the user receives an error when interacting with an incompatible API at the storage, call, and constant level.
The subxt-cli can check the compatibility of multiple runtime nodes, either full metadata compatibility or
compatibility at the pallet level.
Users can define custom derives for specific generated types of the API via adding the derive_for_type configuration
to the subxt attribute.
The metadata documentation is propagated to the statically generated API.
Previously developers wanting to build the subxt crate needed the substrate binary dependency in their local
environment. This restriction is removed via moving the integration tests to a dedicated crate.
The number of dependencies is reduced for individual subxt crates.
tidext in real world usage (#508)The most significant change in this release is how we create and sign extrinsics, and how we manage the "additional" and "extra" data that is attached
The most significant change in this release is how we create and sign extrinsics, and how we manage the "additional" and "extra" data that is attached to them. See https://github.com/paritytech/subxt/issues/477, and the associated PR https://github.com/paritytech/subxt/pull/490 for a more detailed look at the code changes.
If you're targeting a node with compatible additional and extra transaction data to Substrate or Polkadot, the main
change you'll have to make is to import and use subxt::PolkadotExtrinsicParams or subxt::SubstrateExtrinsicParams
instead of subxt::DefaultExtra (depending on what node you're compatible with), and then use sign_and_submit_default
instead of sign_and_submit when making a call. Now, sign_and_submit accepts a second argument which allows these
parameters (such as mortality and tip payment) to be customized. See examples/balance_transfer_with_params.rs for a
small usage example.
If you're targeting a node which involves custom additional and extra transaction data, you'll need to implement the
trait subxt::extrinsic::ExtrinsicParams, which determines the parameters that can be provided to sign_and_submit, as
well as how to encode these into the "additional" and "extra" data needed for a transaction. Have a look at
subxt/src/extrinsic/params.rs for the trait definition and Substrate/Polkadot implementations. The aim with this change
is to make it easier to customise this for your own chains, and provide a simple way to provide values at runtime.
sp_runtime::ModuleError substrate updates (#492)dev_getBlockStats RPC (#489)Return events from blocks skipped over during Finalization, too
Remove unused sp_version dependency to fix duplicate parity-scale-codec deps
sp_version dependency to fix duplicate parity-scale-codec deps (#466)Expose method to fetch nonce via Client
Client (#451)codegen::TypeGenerator (#444)Call struct names to UpperCamelCase (#441)introduce jsonrpsee client abstraction + kill HTTP support.
…and macro based codegen, as well as making many breaking changes to the API.
Note: This is a significant release which introduces support for V14 metadata and macro based codegen, as well as making many breaking changes to the API.
From<Arc<WsClient>> and From<Arc<HttpClient>> (#257)Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →