subxt-signer
Sign extrinsics to be submitted by Subxt
0.50.3
6.8M downloads/mo
#3864 most downloaded on crates.io
paritytech/subxt
What this package is like to depend on
Last release 17 days ago
06 Aug 2026
Ships fairly regularly
a new release about every 6 weeks
Most releases are documented
notes for 24 of 31 stable releases
1 version withdrawn
withdrawn after publishing
3 years old
36 releases · first in 2023
12 releases in the last 12 months
see the full history below
Release timeline
36 releases · Jul 2023 to Aug 2026Releases
latest 36-
0.50.306 Aug 2026Release notes
Open source →This release updates
frame-decodeto 0.18.1, which fixes V5 signer payload construction and improves how authorization extensions (likeVerifyMultiSignature) are handled.Fixed
- V5 signer payloads now correctly include the transaction extension version and call data as an immutable base implication, matching FRAME's transaction extension pipeline semantics. (paritytech/frame-decode#104)
- Unknown
Option<T>transaction extensions are now encoded asNone(0u8) by default, allowing transaction encoding to succeed on chains with optional extensions. (from frame-decode 0.17.2)
Changed
Release notes
Open source →This release updates
frame-decodeto 0.18.1, which fixes V5 signer payload construction and improves how authorization extensions (likeVerifyMultiSignature) are handled.Fixed
- V5 signer payloads now correctly include the transaction extension version and call data as an immutable base implication, matching FRAME's transaction extension pipeline semantics. (paritytech/frame-decode#104)
- Unknown
Option<T>transaction extensions are now encoded asNone(0u8) by default, allowing transaction encoding to succeed on chains with optional extensions. (from frame-decode 0.17.2)
Changed
-
0.50.207 Jul 2026Release notes
Open source →This release fixes an issue whereby setting the genesis hash in the
SubstrateConfigBuilderhad no effect, and improves the reliability of tests against public archival RPC endpoints.Changed
- Update Artifacts (auto-generated) (#2245)
- tests: Exponential backoff for archival RPC public endpoints (#2244)
Fixed
- Fix:
SubstrateConfigBuilder::set_genesis_hashis a no-op (#2236)
-
0.50.127 Apr 2026Release notes
Open source →This release bumps the light-client smoldot crate to the latest version and adds several fixes.
Changed
Fixed
-
0.50.002 Mar 2026Release notes
Open source →[0.50.0] - 2025-12-17
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 is no longer head-of-chain only, and can work with historic blocks, all the way back to genesis. Note: user-provided type information is required to do this for very old (> ~2year old, ie pre-V14 metadata) blocks.
- The MVP
subxt-historiccrate has been removed, its functionality having been merged into Subxt. - The
subxt-corecrate has been removed for now to make way for the above. Subxt itself continues to support WASM use cases.- For truly
no-stdfunctionality, theframe-decodecrate now contains much of the underlying logic used throughout Subxt to encode and decode things, and we would like to expand the functionality here. - We would like feedback from any users of the
subxt-corecrate on how they use it, and will use this feedback to drive future work in this area.
- For truly
- No more monitoring for runtime updates is needed; Subxt now works across different runtime versions automatically.
- Errors are no longer one big
Errorenum; instead different Subxt APIs return different errors, to limit the number of possible errors in any one place. These all convert intosubxt::Errorso this can continue to be used as a catch-all. - Storage APIs have been redone, fixing some issues and giving much more control over iteration, as well as key and value decoding.
There are also a couple of organizational changes which aren't visible:
- We now follow the
name.rs+name/submodule.rsconvention instead of thename/mod.rs+name/submodule.rsconvention. - CI and testing updates hopefully ensure better organization and coverage with a greater number of different feature flags being tested.
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:
Configuration
Before
Configuration (
PolkadotConfigandSubstrateConfig) 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_configmethods 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
PolkadotConfigandSubstrateConfigremain the same:- Use
PolkadotConfigfor the Polkadot Relay Chain. - Use
SubstrateConfigby default with other chains. - You may need to modify the configuration to work with some chains, as before.
See the docs for
PolkadotConfigandSubstrateConfigfor more. One example to be aware of is that if you want to work with historic blocks withSubstrateConfig, you'll need to instantiate it yourself and provide historic type information before passing it toOnlineClientorOfflineClient.Configuration:
ExtrinsicParamsAside from the new historic types support, a notable change to the configuration has been the simplification of transaction extensions, previously called
ExtrinsicParamsin ourConfig. Now, the type is calledTransactionExtensionsand the supporting traits have been simplified and named more appropriately (justTransactionExtensionsandTransactionExtension), moving to rely more onframe-decodefor 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.
Working at specific blocks
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_latesthas been renamed toat_current_blockand, like before, it fetches the current finalized block at the time of calling.at_current_blocknow accepts a block hash or block number, and returns a client that works in the context of that block.- Constants were not previously retrieved at a given block; Subxt only knew about a single
Metadataand so it was unnecessary. Now, constants are retrieved at a specific block like everything else (different blocks may have differentMetadatas). - A small thing:
runtime_api()was renamed toruntime_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.
Working with blocks
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:
- Working with finalized blocks is always the default now, and API names are shortened to make them the easiest/most obvious to use.
- Use
.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().
Transactions
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::signandSignableTransaction::sign_with_account_and_signaturenow may return an error (which previously would have led to harder to diagnose issues), and theSignableTransactiontype now has a lifetime (see #2177).- We now use
transactionsinstead oftxeverywhere to align better with other API names, but continue to providetxas a shorthand. - The word
partialis changed tosignablein 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 uglySubmittableTransaction::from_bytesmethod.
Storage Entries
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.
Runtime updates
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(); }
Dynamic values
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:
- As before when
scale_value::Valuewas used everywhere, the actual values provided are always checked at runtime against the API and invalid shapes/values will lead to an error. - Now, it's possible to provide statically typed values when you know roughly what to expect, or even to just provide your own dynamic value type that isn't
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.
Metadata
Subxt previously exposed
subxt::Metadata, which was a wrapped version ofsubxt_metadata::Metadata. The wrapping was removed, and now we have onlysubxt_metadata::Metadata, which is exposed assubxt::Metadata. This metadata can be cloned but is not cheap to clone, and so we also exposesubxt::ArcMetadata, which is used in many places and is theArc-wrapped version of it, for cheap cloning.subxt_metadata::Metadatanow exposes helper functions to construct it from variousframe_metadataversions, 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_metadataversion.A list of the main change PRs follows:
Added
- Allow passing $OUT_DIR in the runtime_metadata_path attribute (#2142)
- feat: Add
system_chainTypeto legacy rpcs (#2116) - Add --at-block option to CLI tool to download metadata at a specific block (#2079)
Changed
- Upgrade to frame-decode 0.17: remove extrinsic encode logic and use from there (#2177)
- [v0.50.0] Implement support for historic blocks in Subxt (#2131)
- subxt-historic: 0.0.8 release: expose type resolver that can be used with visitors (#2140)
- subxt-historic: 0.0.7 release: expose ClientAtBlock bits (#2138)
- subxt-historic: 0.0.6 release: expose metadata at a given block (#2135)
- [v0.50.0] Merge preliminary work to master (#2127)
- Bump smoldot / smoldot-light to latest (#2110)
- [subxt-historic]: extract call and event types from metadata at a block (#2095)
- subxt-historic: add support for returning the default values of storage entries (#2072)
Release notes
Open source →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 is no longer head-of-chain only, and can work with historic blocks, all the way back to genesis. Note: user-provided type information is required to do this for very old (> ~2year old, ie pre-V14 metadata) blocks.
- The MVP
subxt-historiccrate has been removed, its functionality having been merged into Subxt. - The
subxt-corecrate has been removed for now to make way for the above. Subxt itself continues to support WASM use cases.- For truly
no-stdfunctionality, theframe-decodecrate now contains much of the underlying logic used throughout Subxt to encode and decode things, and we would like to expand the functionality here. - We would like feedback from any users of the
subxt-corecrate on how they use it, and will use this feedback to drive future work in this area.
- For truly
- No more monitoring for runtime updates is needed; Subxt now works across different runtime versions automatically.
- Errors are no longer one big
Errorenum; instead different Subxt APIs return different errors, to limit the number of possible errors in any one place. These all convert intosubxt::Errorso this can continue to be used as a catch-all. - Storage APIs have been redone, fixing some issues and giving much more control over iteration, as well as key and value decoding.
There are also a couple of organizational changes which aren't visible:
- We now follow the
name.rs+name/submodule.rsconvention instead of thename/mod.rs+name/submodule.rsconvention. - CI and testing updates hopefully ensure better organization and coverage with a greater number of different feature flags being tested.
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:
Configuration
Before
Configuration (
PolkadotConfigandSubstrateConfig) 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_configmethods 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
PolkadotConfigandSubstrateConfigremain the same:- Use
PolkadotConfigfor the Polkadot Relay Chain. - Use
SubstrateConfigby default with other chains. - You may need to modify the configuration to work with some chains, as before.
See the docs for
PolkadotConfigandSubstrateConfigfor more. One example to be aware of is that if you want to work with historic blocks withSubstrateConfig, you'll need to instantiate it yourself and provide historic type information before passing it toOnlineClientorOfflineClient.Configuration:
ExtrinsicParamsAside from the new historic types support, a notable change to the configuration has been the simplification of transaction extensions, previously called
ExtrinsicParamsin ourConfig. Now, the type is calledTransactionExtensionsand the supporting traits have been simplified and named more appropriately (justTransactionExtensionsandTransactionExtension), moving to rely more onframe-decodefor 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.
Working at specific blocks
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_latesthas been renamed toat_current_blockand, like before, it fetches the current finalized block at the time of calling.at_current_blocknow accepts a block hash or block number, and returns a client that works in the context of that block.- Constants were not previously retrieved at a given block; Subxt only knew about a single
Metadataand so it was unnecessary. Now, constants are retrieved at a specific block like everything else (different blocks may have differentMetadatas). - A small thing:
runtime_api()was renamed toruntime_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.
Working with blocks
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:
- Working with finalized blocks is always the default now, and API names are shortened to make them the easiest/most obvious to use.
- Use
.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().
Transactions
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::signandSignableTransaction::sign_with_account_and_signaturenow may return an error (which previously would have led to harder to diagnose issues), and theSignableTransactiontype now has a lifetime (see #2177).- We now use
transactionsinstead oftxeverywhere to align better with other API names, but continue to providetxas a shorthand. - The word
partialis changed tosignablein 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 uglySubmittableTransaction::from_bytesmethod.
Storage Entries
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.
Runtime updates
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(); }Dynamic values
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:
- As before when
scale_value::Valuewas used everywhere, the actual values provided are always checked at runtime against the API and invalid shapes/values will lead to an error. - Now, it's possible to provide statically typed values when you know roughly what to expect, or even to just provide your own dynamic value type that isn't
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.
Metadata
Subxt previously exposed
subxt::Metadata, which was a wrapped version ofsubxt_metadata::Metadata. The wrapping was removed, and now we have onlysubxt_metadata::Metadata, which is exposed assubxt::Metadata. This metadata can be cloned but is not cheap to clone, and so we also exposesubxt::ArcMetadata, which is used in many places and is theArc-wrapped version of it, for cheap cloning.subxt_metadata::Metadatanow exposes helper functions to construct it from variousframe_metadataversions, 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_metadataversion.A list of the main change PRs follows:
Added
- Allow passing $OUT_DIR in the runtime_metadata_path attribute (#2142)
- feat: Add
system_chainTypeto legacy rpcs (#2116) - Add --at-block option to CLI tool to download metadata at a specific block (#2079)
Changed
- Upgrade to frame-decode 0.17: remove extrinsic encode logic and use from there (#2177)
- [v0.50.0] Implement support for historic blocks in Subxt (#2131)
- subxt-historic: 0.0.8 release: expose type resolver that can be used with visitors (#2140)
- subxt-historic: 0.0.7 release: expose ClientAtBlock bits (#2138)
- subxt-historic: 0.0.6 release: expose metadata at a given block (#2135)
- [v0.50.0] Merge preliminary work to master (#2127)
- Bump smoldot / smoldot-light to latest (#2110)
- [subxt-historic]: extract call and event types from metadata at a block (#2095)
- subxt-historic: add support for returning the default values of storage entries (#2072)
-
0.50.0-beta.423 Feb 2026 pre-release -
0.50.0-beta.314 Jan 2026 pre-release -
0.50.0-beta.213 Jan 2026 pre-releaseNothing published for this version
-
0.50.0-beta.112 Jan 2026 pre-releaseNothing published for this version
-
0.44.312 Mar 2026Nothing published for this version
-
0.44.209 Jan 2026Release notes
Open source →[0.44.2] - 2026-01-09
This manually cherry-picks #2142 onto the 0.44 branch to allow using $OUT_DIR in a couple of Subxt macro attributes.
Changed
- Allow passing $OUT_DIR in the runtime_metadata_path attribute #2142
-
0.44.108 Jan 2026Release notes
Open source →[0.44.1] - 2026-01-08
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.Fixed
- Fix tipping for ChargeAssetTxPayment tx extension(#2151)
-
0.44.029 Aug 2025Release notes
Open source →[0.44.0] - 2025-08-28
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 othersubxtcrates or otherwise merge the logic intosubxtitself.This is a minor version bump because, in theory at least, adding the
Clonebound to block headers in (#2047) is a breaking change, although I think it is unlikely that this will impact any users.Added
- Add prerelease
subxt-historiccrate for accessing historic (non head-of-chain) blocks (#2040)
Changed
Fixed
Release notes
Open source →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 othersubxtcrates or otherwise merge the logic intosubxtitself.This is a minor version bump because, in theory at least, adding the
Clonebound to block headers in (#2047) is a breaking change, although I think it is unlikely that this will impact any users.Added
- Add prerelease
subxt-historiccrate for accessing historic (non head-of-chain) blocks (#2040)
Changed
Fixed
- Add prerelease
-
0.43.018 Jul 2025Release notes
Open source →[0.43.0] - 2025-07-17
This is a reasonably small release which is mainly bug fixing, but has a couple of changes I'd like to elaborate on:
Remove
codec::Encodeandcodec::Decodederives from generated APIs by default (#2008)When generating an API using the
#[subxt::subxt(...)]macro (or programatically viasubxt-codegen), we had always previously addedparity_scale_codec::Encodeandparity_scale_codec::Decodederives to all of the generated types. Most places in Subxt have not made use of these for a long time (relying instead onscale_encode::EncodeAsTypeandscale_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::Encodeorparity_scale_codec::Decodederives on certain types, you have two options:- Use the
derive_for_typeattr 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 ) )]
- Use the
derive_for_all_typesattr 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
EncodeandDecodederives, and also as a side effect changesapi.storage().call_raw()slightly to no longer also try to decode the resulting type viaDecode, 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)?;
Address some issues around tx mortality (#2025)
Prior to this change, the intended behavior was that any transaction submitted via an
OnlineClientwould have a mortality of 32 blocks by default, and any transaction submitted via anOfflineClientwould be immortal by default. A couple of issues were present or cropped up however:- If you explicitly configure the mortality via setting params like
PolkadotExtrinsicParamsBuilder::new().mortal(32).build(), theOfflineClienttransaction 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). - The intended behaviour turned out to have been broken, and transactions were being submitted as immortal even via the
OnlineClientby default, unless mortality was explicitly configured. - There was no easy way to actually set the mortality for an
OfflineClienttransaction; 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 theOfflineClientand try to useparams.mortal(..)when configuring it, and we exposeparams.mortal_from_unchecked(..)to allow configuration for offline transactions without the ugly code above.In this PR, we also discovered an issue decoding
Erasand fixed this, so that decoding the mortality of a transaction when it is mortal should now work.Add FFI example (#2037)
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:
Added
- Add FFI example (#2037)
Changed
- Remove
codec::Encodeandcodec::Decodederives from generated APIs by default (#2008) - Address some issues around tx mortality (#2025)
Fixed
- Fix 'subxt explore storage': don't turn keys to bytes (#2038)
- Refactor: improve nonce and block injection in extrinsic params (#2032)
- Improve docs for
at_latest(#2035) - Clippy fixes for latest Rustc (#2033)
- docs: fix minor comment typos (#2027)
- chore: remove redundant backtick in comment (#2020)
- Keep codec attrs even when Encode/Decode not used (#2023)
- Run CI on v0.N.x branches or PRs to them for ease of backporting (#2017)
- De-dup types early in CLI/macro so that derives/substitutes work for de-duped types (#2015)
- If only one hasher, always treat any key as a single and not NMap key, even if it's a tuple. (#2010)
Release notes
Open source →This is a reasonably small release which is mainly bug fixing, but has a couple of changes I'd like to elaborate on:
Remove
codec::Encodeandcodec::Decodederives from generated APIs by default (#2008)When generating an API using the
#[subxt::subxt(...)]macro (or programatically viasubxt-codegen), we had always previously addedparity_scale_codec::Encodeandparity_scale_codec::Decodederives to all of the generated types. Most places in Subxt have not made use of these for a long time (relying instead onscale_encode::EncodeAsTypeandscale_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::Encodeorparity_scale_codec::Decodederives on certain types, you have two options:- Use the
derive_for_typeattr 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 ) )] - Use the
derive_for_all_typesattr 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
EncodeandDecodederives, and also as a side effect changesapi.storage().call_raw()slightly to no longer also try to decode the resulting type viaDecode, 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)?;Address some issues around tx mortality (#2025)
Prior to this change, the intended behavior was that any transaction submitted via an
OnlineClientwould have a mortality of 32 blocks by default, and any transaction submitted via anOfflineClientwould be immortal by default. A couple of issues were present or cropped up however:- If you explicitly configure the mortality via setting params like
PolkadotExtrinsicParamsBuilder::new().mortal(32).build(), theOfflineClienttransaction 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). - The intended behaviour turned out to have been broken, and transactions were being submitted as immortal even via the
OnlineClientby default, unless mortality was explicitly configured. - There was no easy way to actually set the mortality for an
OfflineClienttransaction; 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 theOfflineClientand try to useparams.mortal(..)when configuring it, and we exposeparams.mortal_from_unchecked(..)to allow configuration for offline transactions without the ugly code above.In this PR, we also discovered an issue decoding
Erasand fixed this, so that decoding the mortality of a transaction when it is mortal should now work.Add FFI example (#2037)
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:
Added
- Add FFI example (#2037)
Changed
- Remove
codec::Encodeandcodec::Decodederives from generated APIs by default (#2008) - Address some issues around tx mortality (#2025)
Fixed
- Fix 'subxt explore storage': don't turn keys to bytes (#2038)
- Refactor: improve nonce and block injection in extrinsic params (#2032)
- Improve docs for
at_latest(#2035) - Clippy fixes for latest Rustc (#2033)
- docs: fix minor comment typos (#2027)
- chore: remove redundant backtick in comment (#2020)
- Keep codec attrs even when Encode/Decode not used (#2023)
- Run CI on v0.N.x branches or PRs to them for ease of backporting (#2017)
- De-dup types early in CLI/macro so that derives/substitutes work for de-duped types (#2015)
- If only one hasher, always treat any key as a single and not NMap key, even if it's a tuple. (#2010)
- Use the
-
0.42.112 May 2025Release notes
Open source →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.
-
0.42.012 May 2025Release notes
Open source →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.
Pallet View Functions
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?;Updated
ConfigtraitAnother change to be aware of is that our
Configtrait has been tweaked. TheHashassociated type is no longer needed, as it can be obtained via theHasherassociated type already, andPolkadotConfig/SubstrateConfignow set the hasher by default to beDynamicHasher256, which will (when V16 metadata is available for a runtime) automatically select between Keccak256 and BlakeTwo256 hashers depending on what the chain requires.Other changes
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:
Added
- Support v16 metadata and use it by default if it's available (#1999)
- Metadata V16: Implement support for Pallet View Functions (#1981)
- Metadata V16: Be more dynamic over which hasher is used. (#1974)
Changed
- Update to 2024 edition (#2001)
- Update Smoldot to latest version (#1991)
- Update native test timeout to 45 mins (#2002)
- chore(deps): tokio ^1.44.2 (#1989)
- Add DefaultParams to allow more transaction extensions to be used when calling _default() methods (#1979)
- Use wat instead of wabt to avoid CI cmake error (and use supported dep) (#1980)
- Support v1 archive RPCs (#1977)
- Support V16 metadata and refactor metadata code (#1967)
- Allow submitting transactions ignoring follow events (#1962)
- Improve error message regarding failure to extract metadata from WASM runtime (#1961)
- Add docs for subxt-rpcs and fix example (#1954)
Fixed
-
0.41.011 Mar 2025Release notes
Open source →This release makes two main changes:
Add
subxt-rpcscrate.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-rpcsdirectly. This crate implements the new RPC-V2chainHead/transactionendpoints as well as the currently unstablearchiveendpoints. 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: A
jsonrpseebased RPC client for connecting to individual RPC nodes. - unstable-light-client: A Smoldot based light client which connects to multiple nodes in chains via p2p and verifies everything handed back, removing the need to trust any individual nodes.
- reconnecting-rpc-client: Another
jsonrpseebased client which handles reconnecting automatically in the event of network issues. - mock-rpc-client: A mock RPC client that can be used in tests.
Custom clients can be implemented if preferred.
Example usage via
jsonrpseefeature: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.. }Support creating V5 transactions.
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_corenow 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.subxtexposes higher level wrappers these (ieapi.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.- APIs like
sign_and_submitnow take aT::AccountIdrather than aT::Addresssince it was found to not be useful to provide the latter, and V5 transactions only expect anT::AccountId. - Signed Extensions are now referred to as Transaction Extensions, and we've tweaked the interface around how these work slightly to accomodate the fact that in V5 transactions, the signature is passed into a transaction extension where applicable (
VerifySignature). - As a side effect, it's simpler to set mortality on transactions; no more block hash needs to be provided; only the number of blocks you would like a transaction to live for.
A full list of the relevant changes is as follows:
Added
- Support constructing and submitting V5 transactions (#1931)
- Add archive RPCs to subxt-rpcs (#1940)
- Document generating interface from Runtime WASM and change feature to
runtime-wasm-path(#1936) - Split RPCs into a separate crate (#1910)
Changed
- jsonrpsee: A
-
0.40.103 Jun 2025Nothing published for this version
-
0.40.006 Mar 2025Release notes
Open source →This release reverts the usage of the
polkadot-sdkumbrella 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.
Fixed
- Remove usage of polkadot-sdk umbrella crate (#1926)
Full Changelog: https://github.com/paritytech/subxt/compare/v0.39.0...v0.40.0
-
0.39.005 Feb 2025Release notes
Open source →This release is mostly bug fixes and changes. The only change that should be a breaking change is removing the
substrate-compatfeature flag (see #1850), which we'll go into more detail about.The
substrate-compatfeature flag has been removed.The
substrate-compatfeature flag essentially provided:- An implementation of the
subxt::config::Headertrait for anything implementingsp_runtime::traits::Header(here). - Same for
subxt::config::Hasherand anything implementingsp_runtime::traits::Hasher(here). - A
subxt_core::tx::PairSignertype which could be given something implementingsp_core::Pairand then be used to sign transactions (here). - From impls for
sp_runtime::AccountId32and related forsubxt::utils::AccountId32(here). - Likewise for
sp_runtime::MultiAddressandsubxt::utils::MultiAddress(here). - Likewise for
sp_runtime::MultiSignatureandsubxt::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_coreat a time, but many versions are in use in the wild. This led to various issues regarding the mismatch betweensp_*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:
- Using the example here to see how to use a Substrate signer to sign Subxt transactions.
- Looking at
subxt_signerinstead, if it's a viable alternative in your case. - Following the "here" links above to see what impls were removed. Impls can generally be recreated as needed using wrapper types which allow converting between Substrate and Subxt types/traits, for instance:
// 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-compatfeature flag.A full list of relevant changes and fixes (nothing was added in this release) is as follows:
Changed
- remove substrate compat (#1850)
- migrate custom error trait impls to
thiserror(#1856) - re-export
jsonrpseeinsubxt::ext(#1843)
Fixed
- don't double hash: use the same hash in ExtrinsicDetails and ExtrinsicDetails (#1917)
- fix and test sr25519 signing in nostd (#1872)
- preserve custom metadata when converting between Subxt metadata and frame_metadata (#1914)
- fix: don't wrap rpc error in DisconnectedWillReconnect in reconnecting rpc client (#1904)
- fix: substrate runner, support new libp2p addr log (#1892)
- update Artifacts (auto-generated) (#1874)
- bump frame-decode and frame-metadata to latest (#1870)
- fix unstable-light-client + ChainHeadBackend tx events (#1865)
- when native feature is enabled, we need polkadot-sdk/std for eg examples to work (#1864)
- load latest metadata version from Wasm blobs. (#1859)
- minor fix - Yew example (#1852)
- update the release notes to work for current releases (#1842)
- An implementation of the
-
0.38.124 Jan 2025Nothing published for this version
-
0.38.024 Oct 2024Release notes
Open source →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:
- #1785: Support decoding V5 extrinsics in blocks (currently Subxt will still submit V4 extrinsics). This also unifies our extrinsic decoding logic into one place.
- #1802: Stabilizing the
subxt::backend::unstable::UnstableBackend(it's now calledsubxt::backend::chain_head::ChainHeadBackend). This backend can be used to interact with the modernchainHeadRPC methods exposed by Smoldot and compliant RPC nodes. See this example. - #1803: Stabilizing the
reconnecting-rpc-client. See this example. - #1720: A nice little QoL improvement if you have the raw runtime WASM and would like to generate an interface directly from that (ie with
#[subx(runtime_path = "path/to/runtime.wasm")]). - #1661: Support loading keys directly from the PolkadotJS JSON to be used in Subxt.
- #1638: Improve support for Eth style chains by defining a 20-byte account ID type directly in
subxt-core. See this example.
The notable changes in this release are as follows:
Added
- add reconnecting tests for unstable_backend (#1765)
- add support for generating metadata from runtime wasm files (#1720)
- support loading keys from Polkadot-JS accounts (#1661)
- allow tx payloads to be boxed (#1690)
- add hash method to ExtrinsicDetails (#1676)
- expose
secret_keymethod forecdsa::Keypairandeth::Keypair(#1628) - add 20-byte account id to subxt_core (#1638)
Changed
- make it clearer which extrinsic failed to decode (#1835)
- chore(deps): bump frame-metadata from 16 to 17 (#1836)
- chore(deps): bump
scale family crates,primitive-typesandimpl-serde(#1832) - chore(deps): replace
instantwithweb-time(#1830) - deps: use polkadot-sdk umbrella crate (#1786)
- stabilize reconnecting-rpc-client (#1803)
- stabilize chainhead backend (#1802)
- derive serialize on more types (#1797)
- use frame-decode for core extrinsic decode logic (including v5 support) (#1785)
- reconn-rpc-client: parse URL before connecting (#1789)
- update proc_macro_error to proc_macro_error2 (#1767)
- chore(deps): update Smoldot to the latest version (#1400)
- remove unneeded
?Sizedbound and replace never type with()(#1758) - improve test coverage for legacy
Backendimpl (#1751) - add integration tests for
unstable-reconnecting-rpc-client(#1711) - replace
reconnecting-jsonrpsee-ws-clientwithsubxt-reconnecting-rpc-client(#1705) - allow PartialExtrinsic to be held across await points (#1658)
- chore(deps): bump jsonrpsee from 0.22.5 to 0.23.1 (#1656)
Fixed
- fix stripping metadata in the case where enums like RuntimeCall are handed back (#1774)
- fix:
defalt-feature->default-featuresCargo.toml (#1828) - avoid hang by notifying subscribers when the backend is closed (#1817)
- fix: error message on rpc errors (#1804)
- docs: fix typos (#1776)
- examples: fix reconnecting logging target (#1733)
- docs: fix spelling issues (#1699)
- chore: fix some comments (#1697)
- codegen: Fix decode error by adding
#[codec(dumb_trait_bound)](#1630)
-
0.37.028 May 2024Release notes
Open source →This release mainly adds support for the sign extension
CheckMetadataHashand fixes a regression introduced in v0.36.0 where the type de-duplication was too aggressive and lots of the same type such asBoundedVecwas duplicated to plenty of different types such as BoundedVec1, BoundedVec2, .. BoundedVec<N>.Added
-
0.36.128 May 2024 withdrawn -
0.36.016 May 2024Release notes
Open source →This release adds a few new features, which I'll go over below in more detail.
subxt-coreWe now have a brand new
subxt-corecrate, 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
ExtrinsicParamstrait is now handed&ClientState<T>rather than aClient.ClientStateis just a concrete struct containing the state that one needs for things like signed extensions.Support for reconnecting
We've baked in a bunch of support for automatically reconnecting after a connection loss into Subxt. This comes in three parts:
- An RPC client that is capable of reconnecting. This is gated behind the
unstable-reconnecting-rpc-clientfeature flag at the moment, and - Handling in the subxt Backends such that when the RPC client notifies it that it is reconnecting, the backend will transparently handle this behind the scenes, or else pass on a
DisconnectedWillReconnecterror to the user where it cannot. Note that the individualLegacyRpcMethodsandUnstableRpcMethodsare not automatically retried on reconnection. Which leads us to.. - A couple of util helpers (
subxt::backend::retryandsubxt::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-clientfeature 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.
Better Ethereum support
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
AccountId20as it isAccountId32.Stabilizing the new V2 RPCs (#1540, #1539, #1538)
A bunch of the new RPCs are now stable in the spec, and have consequently been stabilized here, bringing the
unstable-backenda 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:
Added
- Add
frontier/ethereumexample (#1557) - Rpc: add full support reconnecting rpc client (#1505)
- Signer: ethereum implementation (#1501)
subxt-corecrate (#1466)
Changed
- Bump scale-decode and related deps to latest (#1583)
- Update Artifacts (auto-generated) (#1577)
- Update deps to use
scale-type-resolver0.2 (#1565) - Stabilize transactionBroadcast methods (#1540)
- Stabilize transactionWatch methods (#1539)
- Stabilize chainHead methods (#1538)
- Rename traits to remove T suffix (#1535)
- Add Debug/Clone/etc for common Configs for convenience (#1542)
- Unstable_rpc: Add transactionBroadcast and transactionStop (#1497)
Fixed
- metadata: Fix cargo clippy (#1574)
- Fixed import in
subxt-signer::eth(#1553) - chore: fix typos and link broken (#1541)
- Make subxt-core ready for publishing (#1508)
- Remove dupe storage item if we get one back, to be compatible with Smoldot + legacy RPCs (#1534)
- fix: substrate runner libp2p port (#1533)
- Swap BinaryHeap for Vec to avoid Ord constraint issue (#1523)
- storage_type: Strip key proper hash and entry bytes (32 instead of 16) (#1522)
- testing: Prepare light client testing with substrate binary and add subxt-test macro (#1507)
-
0.35.311 Apr 2024Nothing published for this version
-
0.35.209 Apr 2024Nothing published for this version
-
0.35.103 Apr 2024Nothing published for this version
-
0.35.021 Mar 2024Release notes
Open source →This release contains several fixes, adds
no_stdsupport to a couple of crates (subxt-signerandsubxt-metadata) and introduces a few quality of life improvements, which I'll quickly cover:Reworked light client (#1475)
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
RpcClientTimplementation, rather than exposing it as asubxt::client::LightClient.Typed Storage Keys (#1419)
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, egVec<scale_value::Value>.Extrinsic Params Refinement (#1439)
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_nonceinstead.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
RefineParamstrait, 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_nonceandcreate_partial_signed_with_noncefunctions have lost the_with_noncesuffix. Since we can't discover nonce/mortality settings offline, you should now provideParamsand set an explicit nonce (and mortality, if you like) when using these calls, otherwise the nonce will be set to 0 and the mortality toImmortal.For a full list of changes, please see the following:
Added
- Reworked light client (#1475)
no_stdcompatibility forsubxt-signer(#1477)- Typed Storage Keys (#1419)
- Extrinsic Params Refinement (#1439)
- Make storage_page_size for the LegacyBackend configurable (#1458)
no_stdcompatibility forsubxt-metadata(#1401)- Experimental
reconnecting-rpc-client(#1396)
Changed
scale-type-resolverintegration (#1460)- subxt: Derive
std::cmptraits for subxt payloads and addresses (#1429) - CLI: Return error on wrongly specified type paths (#1397)
- rpc v2: chainhead support multiple finalized block hashes in
FollowEvent::Initialized(#1476) - rpc v2: rename transaction to transactionWatch (#1399)
Fixed
- Avoid a panic in case we try decoding naff bytes (#1444)
- Fix error mapping to wrong transaction status (#1445)
- Update DispatchError to match latest in polkadot-sdk (#1442)
- Handle errors when fetching storage keys from Unstablebackend (#1440)
- Swap type aliases around to be semantically correct (#1441)
-
0.34.023 Jan 2024Release notes
Open source →This release introduces a bunch of features that make subxt easier to use. Let's look at a few of them.
Codegen - Integrating
scale-typegenand adding type aliases (#1249)We rewrote the code generation functionality of subxt and outsourced it to the new
scale-typegencrate, 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).
Macro - Errors for misspecified type paths (#1339)
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::JunctionsMacro - Recursive derives and attributes (#1379)
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
recursiveflag 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 {}Subxt CLI - New features and usability improvements (#1290, #1336, and #1379)
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 eventsAll CLI commands that take some metadata via
--fileor--url, can now also read the metadata directly fromstdinwith--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.rsSimilar to the macro, the
subxt codegencommand can now also userecursiveflags:subxt codegen --derive-for-type xcm::v2::multilocation::MultiLocation=Clone,recursive subxt codegen --attributes-for-type "xcm::v2::multilocation::MultiLocation=#[myerror],recursive"Minor changes and things to be aware of
- Using insecure connections is now an explicit opt-in in many places (#1309)
- When decoding extrinsics from a block into a static type, we now return it's details (e.g. signature, signed extensions, raw bytes) alongside the statically decoded extrinsic itself (#1376)
We also made a few fixes and improvements around the unstable backend and the lightclient, preparing them for more stable usage in the future.
Added
- Errors for misspecified type paths + suggestions (#1339)
- CLI: Recursive derives and attributes (#1379)
- CLI: Explore runtime APIs and events, colorized outputs, scale-typegen integration for examples (#1290)
- Add chainflip to real world usage section of README (#1351)
- CLI: Allow using
--file -to read metadata from stdin (#1336) - Codegen: Generate type aliases for better API ergonomics (#1249)
Changed
- Return Pending rather than loop around if no new finalized hash in submit_transaction (#1378)
- Return
ExtrinsicDetailsalongside decoded static extrinsics (#1376) - Improve Signed Extension and Block Decoding Examples/Book (#1357)
- Use
scale-typegenas a backend for the codegen (#1260) - Using insecure connections is now opt-in (#1309)
Fixed
- Ensure lightclient chainSpec is at least one block old (#1372)
- Typo fix in docs (#1370)
- Don't unpin blocks that may show up again (#1368)
- Runtime upgrades in unstable backend (#1348)
- Generate docs for feature gated items (#1332)
- Backend: Remove only finalized blocks from the event window (#1356)
- Runtime updates: wait until upgrade on chain (#1321)
- Cache extrinsic events (#1327)
-
0.33.007 Dec 2023Release notes
Open source →This release makes a bunch of small QoL improvements and changes. Let's look at the main ones.
Add support for configuring multiple chains (#1238)
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
OnlineClientto 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.
Support decoding signed extensions (#1209 and #1235)
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.
ChargeAssetTxPayment: Add support for generic AssetId
Still on the topic of signed extensions, the
ChargeAssetTxPaymentextension was previously not able to be used with a generic AssetId, which prohibited it from being used on the Asset Hub (which uses aMultiLocationinstead). To address this, we added anAssetIdtype to oursubxt::Config, which can now be configured.One example of doing that can be found here.
This example uses a generated
MultiLocationtype to be used as theAssetId. 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.
Change SignedExtension matching logic (#1283)
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 thesigned_extensions::AnyOftype.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.
Remove
wait_for_in_blockhelper method (#1237)One can no longer use
tx.wait_for_in_blockto wait for a transaction to enter a block. The reason for this removal is that, especially when we migrate to the newchainHeadAPIs, 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, } }Subxt-codegen: Tidy crate interface (#1225)
The
subxt-codegencrate 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:
- Using the
#[subxt]macro. - Using the
subxt codegenCLI command. - Programmatically via the
subxt-codegencrate.
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:
Added
- CLI: Add command to fetch chainSpec and optimize its size (#1278)
- Add legacy RPC usage example (#1279)
- impl RpcClientT for
Arc<T>andBox<T>(#1277) - RPC: Implement legacy RPC system_account_next_index (#1250)
- Lightclient: Add support for configuring multiple chains (#1238)
- Extrinsics: Allow static decoding of signed extensions (#1235)
- Extrinsics: Support decoding signed extensions (#1209)
- ChargeAssetTxPayment: Add support for generic AssetId (eg
u32orMultiLocation) (#1227) - Add Clone + Debug on Payloads/Addresses, and compare child storage results (#1203)
Changed
- Lightclient: Update smoldot to
0.14.0and smoldot-light to0.12.0(#1307) - Cargo: Switch to workspace lints (#1299)
- Update substrate-* and signer-related dependencies (#1297)
- Change SignedExtension matching logic and remove SkipCheckIfFeeless bits (#1283)
- Update the README with the new location of node-cli (#1282)
- Generalize
substrate-compatimpls to accept any valid hasher/header impl (#1265) - Extrinsics: Remove
wait_for_in_blockhelper method (#1237) - Subxt-codegen: Tidy crate interface (#1225)
- Lightclient: Update usage docs (#1223)
- Wee tidy to subxt-signer flags (#1200)
- Batch fetching storage values again to improve performance (#1199)
- Add
subxtfeature insubxt-signercrate to default features (#1193)
Fixed
- Using the
-
0.32.105 Oct 2023Release notes
Open source →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 viaapi.tx().account_nonce(..).The main changes are:
-
0.32.027 Sep 2023Release notes
Open source →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:
The
Backendtrait and theUnstableBackendandLegacyBackendimpls.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::Backendtrait. Higher level APIs no longer have access to RPC methods and instead have access to the currentBackendimplementation. We then added twoBackendimplementations: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)chainHeadbased 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
UnstableBackendcan 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 backBlockRefs instead of plain block hashes. As long as aBlockRefexists 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 aBlockwithblock.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 anRpcClientto 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::*tosubxt::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:
- The storage methods
fetch_keysis renamed tofetch_raw_keys(this just for consistency withfetch_raw). - The storage method
iterno longer accepts apage_sizeargument, and each item returned is now anOption<Result<(key, val)>>instead of aResult<Option<(key, val)>>(we now return a validStreamimplementation for storage entry iteration). See this example. - The events returned when you manually watch a transaction have changed in order to be consistent with the new RPC APIs (the new events can be seen here), and
next_item=>next. If you rely on higher level calls likesign_and_submit_then_watch, nothing has changed. - Previously, using
.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 aBlockRef, to keep the relevant block around while you're using the associated APIs.- To fetch the extrinsics in a block, you used to call
block.body().await?.extrinsics(). This has now been simplified toblock.extrinsics().await?.
Making
ExtrinsicParamsmore flexible withSignedExtensions.See #1107 for more information.
When configuring Subxt to work against a given chain, you needed to configure the
ExtrinsicParamsassociated 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
ExtrinsicParamstrait to give it access to metadata, so that it can be smarter about how to encode the correct values. We've also added asubxt::config::SignedExtensiontrait, 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 ofSignedExtensions, and itself implementsExtrinsicParams. 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,AnyOfmakes it easy to compose whicheverSignedExtensions you need to work with a chain.Finally, we expose
subxt::config::{ DefaultExtrinsicParams, DefaultExtrinsicParamsBuilder }; the former just usesAnyOfto 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 inSubstrateConfigandPolkadotConfig, so long story short: those configurations (and particularly theirExtrinsicParams) 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
ExtrinsicParamsif you'd prefer to ignoreSignedExtensions entirely.As a result of using the new
DefaultExtrinsicParamsinSubstrateConfigandPolkadotConfig, 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
PolkadotExtrinsicParamsBuilderand theExtrinsicParamstrait for more information.Storage: Allow iterating storage entries at different depths
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
_roothas been renamed to_iter, and if the storage entry is for instance a double map (or greater depth), we'll also now generate_iter2,iter3and 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.Custom values
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:
Added
UnstableBackend: Add a chainHead based backend implementation (#1161)UnstableBackend: Expose the chainHead RPCs (#1137)- Introduce Backend trait to allow different RPC (or other) backends to be implemented (#1126)
- Custom Values: Fixes and tests for "custom values" (#1147)
- Custom Values: Add generated APIs to statically access custom values in metadata (#1117)
- Custom Values: Support dynamically accessing custom values in metadata (#1106)
- Add
storage_version()andruntime_wasm_code()to storage (#1111) - Make ExtrinsicParams more flexible, and introduce signed extensions (#1107)
Changed
subxt-codegen: Add "web" feature for WASM compilation that works withjsonrpsee(#1175)subxt-codegen: support compiling to WASM (#1154)- CI: Use composite action to avoid dupe use-substrate code (#1177)
- Add custom
Debugimpl forDispatchErrorto avoid huge metadata output (#1153) - Remove unused start_key that new RPC API may not be able to support (#1148)
- refactor(rpc): Use the default port if one isn't provided (#1122)
- Storage: Support iterating over NMaps with partial keys (#1079)
Fixed
- metadata: Generate runtime outer enums if not present in V14 (#1174)
- Remove "std" feature from
sp-arithmeticto help substrate compat. (#1155) - integration-tests: Increase the number of events we'll wait for (#1152)
- allow 'latest' metadata to be returned from the fallback code (#1127)
- chainHead: Propagate results on the
chainHead_follow(#1116)
-
0.31.002 Aug 2023Release notes
Open source →This is a small release whose primary goal is to bump the versions of
scale-encode,scale-decodeandscale-valuebeing used, to benefit from recent changes in those crates.scale-decodechanges 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 implementsDecodeAsType). This will hopefully resolve issues around structs likePerbill. When decoding the SCALE bytes for such types intoscale_value::Value, theValuewill 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:
Added
- Add browser extension signing example (#1067)
Changed
- Bump to latest scale-encode/decode/value and fix test running (#1103)
- Set minimum supported
rust-versionto1.70(#1097)
Fixed
- Tests: support 'substrate-node' too and allow multiple binary paths (#1102)
-
0.30.125 Jul 2023Release notes
Open source →This patch release fixes a small issue whereby using
runtime_metadata_urlin 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 useruntime_metadata_pathinstead, which does not have this issue.Fixes
- codegen: Fetch and decode metadata version then fallback (#1092)
-
0.30.024 Jul 2023Release notes
Open source →This release beings with it a number of exciting additions. Let's cover a few of the most significant ones:
Light client support (unstable)
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!
V15 Metadata
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-signercrate provides the ability to sign transactions using either sr25519 or ECDSA. It's WASM compatible, and brings in fewer dependencies than usingsp_core/sp_keyringdoes, 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") ]);Breaking changes
A few small breaking changes have occurred:
- There is no longer a need for an
Indexassociated type in yourConfigimplementations; we now work it out dynamically where needed. - The "substrate-compat" feature flag is no longer enabled by default.
subxt-signeradded 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.- Note: Be aware that Substrate crates haven't been published in a while and have fallen out of date, though. This will be addressed eventually, and when it is we can bring the Substrate crates back uptodate here.
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:
Added
- Example: How to connect to parachain (#1043)
- ECDSA Support in signer (#1064)
- Add
subxt_signercrate for native & WASM compatible signing (#1016) - Add light client platform WASM compatible (#1026)
- light-client: Add experimental light-client support (#965)
- Add
diffcommand to CLI tool to visualize metadata changes (#1015) - CLI: Allow output to be written to file (#1018)
Changed
- Remove
substrate-compatdefault feature flag (#1078) - runtime API: Substitute
UncheckedExtrinsicwith custom encoding (#1076) - Remove
Indextype from Config trait (#1074) - Utilize Metadata V15 (#1041)
- chain_getBlock extrinsics encoding (#1024)
- Make tx payload details public (#1014)
- CLI tool tests (#977)
- Support NonZero numbers (#1012)
- Get account nonce via state_call (#1002)
- add
#[allow(rustdoc::broken_intra_doc_links)]to subxt-codegen (#998)
Fixed
- remove parens in hex output for CLI tool (#1017)
- Prevent bugs when reusing type ids in hashing (#1075)
- Fix invalid generation of types with >1 generic parameters (#1023)
- Fix jsonrpsee web features (#1025)
- Fix codegen validation when Runtime APIs are stripped (#1000)
- Fix hyperlink (#994)
- Remove invalid redundant clone warning (#996)
- There is no longer a need for an
-
0.0.119 Jul 2023Nothing published for this version