subxt-rpcs
Make RPC calls to Substrate based nodes
0.50.3
4.3M downloads/mo
#4832 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 2 months
Nearly every release is documented
notes for 11 of 12 stable releases
Nothing withdrawn
no release was ever pulled
1 years old
16 releases · first in 2025
12 releases in the last 12 months
see the full history below
Release timeline
16 releases · Mar 2025 to Aug 2026Releases
latest 16-
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