mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 13:31:10 +00:00
Merge branch 'master' into tadeo-hepperle-master-clone-for-ci-comparison
This commit is contained in:
Generated
+78
-46
@@ -94,9 +94,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450"
|
||||
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -143,9 +143,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.70"
|
||||
version = "1.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
|
||||
[[package]]
|
||||
name = "array-bytes"
|
||||
@@ -353,9 +353,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bounded-collections"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a071c348a5ef6da1d3a87166b408170b46002382b1dda83992b5c2208cefb370"
|
||||
checksum = "e3888522b497857eb606bf51695988dba7096941822c1bcf676e3a929a9ae7a0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"parity-scale-codec",
|
||||
@@ -458,9 +458,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.23"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
|
||||
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_lex 0.2.4",
|
||||
@@ -470,9 +470,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.2.5"
|
||||
version = "4.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a1f23fa97e1d1641371b51f35535cb26959b8e27ab50d167a8b996b5bada819"
|
||||
checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -481,9 +481,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.2.5"
|
||||
version = "4.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fdc5d93c358224b4d6867ef1356d740de2303e9892edc06c5340daeccd96bab"
|
||||
checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -640,7 +640,7 @@ dependencies = [
|
||||
"atty",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap 3.2.23",
|
||||
"clap 3.2.25",
|
||||
"criterion-plot",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
@@ -831,8 +831,18 @@ version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.14.4",
|
||||
"darling_macro 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944"
|
||||
dependencies = [
|
||||
"darling_core 0.20.1",
|
||||
"darling_macro 0.20.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -849,17 +859,42 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.14.4",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
|
||||
dependencies = [
|
||||
"darling_core 0.20.1",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
@@ -1628,7 +1663,7 @@ dependencies = [
|
||||
"subxt",
|
||||
"subxt-codegen",
|
||||
"subxt-metadata",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.15",
|
||||
"test-runtime",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -1656,7 +1691,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"io-lifetimes",
|
||||
"rustix 0.37.14",
|
||||
"rustix 0.37.19",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -1870,9 +1905,9 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.4"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
|
||||
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@@ -1923,7 +1958,7 @@ version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e"
|
||||
dependencies = [
|
||||
"rustix 0.37.14",
|
||||
"rustix 0.37.19",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2595,15 +2630,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.14"
|
||||
version = "0.37.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f"
|
||||
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys 0.3.4",
|
||||
"linux-raw-sys 0.3.7",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -2692,7 +2727,7 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b38741b2f78e4391b94eac6b102af0f6ea2b0f7fe65adb55d7f4004f507854db"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.14.4",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2719,7 +2754,7 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd983cf0a9effd76138554ead18a6de542d1af175ac12fd5e91836c5c0268082"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.14.4",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2888,18 +2923,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
version = "1.0.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.160"
|
||||
version = "1.0.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
|
||||
checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3422,9 +3457,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "ss58-registry"
|
||||
version = "1.39.0"
|
||||
version = "1.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecf0bd63593ef78eca595a7fc25e9a443ca46fe69fd472f8f09f5245cdcd769d"
|
||||
checksum = "eb47a8ad42e5fc72d5b1eb104a5546937eaf39843499948bb666d6e93c62423b"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"num-format",
|
||||
@@ -3539,7 +3574,7 @@ dependencies = [
|
||||
name = "subxt-cli"
|
||||
version = "0.28.0"
|
||||
dependencies = [
|
||||
"clap 4.2.5",
|
||||
"clap 4.2.7",
|
||||
"color-eyre",
|
||||
"frame-metadata",
|
||||
"hex",
|
||||
@@ -3549,7 +3584,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"subxt-codegen",
|
||||
"subxt-metadata",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.15",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -3558,7 +3593,6 @@ name = "subxt-codegen"
|
||||
version = "0.28.0"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"darling",
|
||||
"frame-metadata",
|
||||
"heck",
|
||||
"hex",
|
||||
@@ -3569,7 +3603,7 @@ dependencies = [
|
||||
"quote",
|
||||
"scale-info",
|
||||
"subxt-metadata",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.15",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
@@ -3580,23 +3614,19 @@ version = "0.28.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"hex",
|
||||
"parity-scale-codec",
|
||||
"sp-core",
|
||||
"sp-keyring",
|
||||
"sp-runtime",
|
||||
"subxt",
|
||||
"tokio",
|
||||
"tracing-subscriber 0.3.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-macro"
|
||||
version = "0.28.0"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.1",
|
||||
"proc-macro-error",
|
||||
"subxt-codegen",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3658,8 +3688,10 @@ dependencies = [
|
||||
name = "test-runtime"
|
||||
version = "0.28.0"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"impl-serde",
|
||||
"jsonrpsee",
|
||||
"parity-scale-codec",
|
||||
"serde",
|
||||
"substrate-runner",
|
||||
"subxt",
|
||||
@@ -3788,9 +3820,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
|
||||
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -4602,9 +4634,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.1"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
|
||||
checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
+4
-4
@@ -38,7 +38,7 @@ criterion = "0.4"
|
||||
codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false }
|
||||
color-eyre = "0.6.1"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
darling = "0.14.4"
|
||||
darling = "0.20.0"
|
||||
derivative = "2.2.0"
|
||||
either = "1.8.1"
|
||||
frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] }
|
||||
@@ -59,9 +59,9 @@ scale-value = "0.7.0"
|
||||
scale-bits = "0.3"
|
||||
scale-decode = "0.5.0"
|
||||
scale-encode = "0.1.0"
|
||||
serde = { version = "1.0.159" }
|
||||
serde = { version = "1.0.162" }
|
||||
serde_json = { version = "1.0.96" }
|
||||
syn = "1.0.109"
|
||||
syn = { version = "2.0.15", features = ["full", "extra-traits"] }
|
||||
thiserror = "1.0.40"
|
||||
tokio = { version = "1.28", features = ["macros", "time", "rt-multi-thread"] }
|
||||
tracing = "0.1.34"
|
||||
@@ -86,4 +86,4 @@ subxt-macro = { version = "0.28.0", path = "macro" }
|
||||
subxt-metadata = { version = "0.28.0", path = "metadata" }
|
||||
subxt-codegen = { version = "0.28.0", path = "codegen" }
|
||||
test-runtime = { path = "testing/test-runtime" }
|
||||
substrate-runner = { path = "testing/substrate-runner" }
|
||||
substrate-runner = { path = "testing/substrate-runner" }
|
||||
|
||||
Binary file not shown.
@@ -11,6 +11,7 @@ use frame_metadata::{
|
||||
use jsonrpsee::client_transport::ws::Uri;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use subxt_codegen::utils::MetadataVersion;
|
||||
use subxt_metadata::{get_metadata_hash, get_pallet_hash, metadata_v14_to_latest};
|
||||
|
||||
/// Verify metadata compatibility between substrate nodes.
|
||||
@@ -25,16 +26,35 @@ pub struct Opts {
|
||||
/// The validation will omit the full metadata check and focus instead on the pallet.
|
||||
#[clap(long, value_parser)]
|
||||
pallet: Option<String>,
|
||||
/// Specify the metadata version.
|
||||
///
|
||||
/// - unstable:
|
||||
///
|
||||
/// Use the latest unstable metadata of the node.
|
||||
///
|
||||
/// - number
|
||||
///
|
||||
/// Use this specific metadata version.
|
||||
///
|
||||
/// Defaults to latest.
|
||||
#[clap(long = "version", default_value = "latest")]
|
||||
version: MetadataVersion,
|
||||
}
|
||||
|
||||
pub async fn run(opts: Opts) -> color_eyre::Result<()> {
|
||||
match opts.pallet {
|
||||
Some(pallet) => handle_pallet_metadata(opts.nodes.as_slice(), pallet.as_str()).await,
|
||||
None => handle_full_metadata(opts.nodes.as_slice()).await,
|
||||
Some(pallet) => {
|
||||
handle_pallet_metadata(opts.nodes.as_slice(), pallet.as_str(), opts.version).await
|
||||
}
|
||||
None => handle_full_metadata(opts.nodes.as_slice(), opts.version).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_pallet_metadata(nodes: &[Uri], name: &str) -> color_eyre::Result<()> {
|
||||
async fn handle_pallet_metadata(
|
||||
nodes: &[Uri],
|
||||
name: &str,
|
||||
version: MetadataVersion,
|
||||
) -> color_eyre::Result<()> {
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CompatibilityPallet {
|
||||
@@ -44,7 +64,7 @@ async fn handle_pallet_metadata(nodes: &[Uri], name: &str) -> color_eyre::Result
|
||||
|
||||
let mut compatibility: CompatibilityPallet = Default::default();
|
||||
for node in nodes.iter() {
|
||||
let metadata = fetch_runtime_metadata(node).await?;
|
||||
let metadata = fetch_runtime_metadata(node, version).await?;
|
||||
|
||||
match metadata.pallets.iter().find(|pallet| pallet.name == name) {
|
||||
Some(pallet_metadata) => {
|
||||
@@ -73,10 +93,10 @@ async fn handle_pallet_metadata(nodes: &[Uri], name: &str) -> color_eyre::Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_full_metadata(nodes: &[Uri]) -> color_eyre::Result<()> {
|
||||
async fn handle_full_metadata(nodes: &[Uri], version: MetadataVersion) -> color_eyre::Result<()> {
|
||||
let mut compatibility_map: HashMap<String, Vec<String>> = HashMap::new();
|
||||
for node in nodes.iter() {
|
||||
let metadata = fetch_runtime_metadata(node).await?;
|
||||
let metadata = fetch_runtime_metadata(node, version).await?;
|
||||
let hash = get_metadata_hash(&metadata);
|
||||
let hex_hash = hex::encode(hash);
|
||||
println!("Node {node:?} has metadata hash {hex_hash:?}",);
|
||||
@@ -96,8 +116,11 @@ async fn handle_full_metadata(nodes: &[Uri]) -> color_eyre::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_runtime_metadata(url: &Uri) -> color_eyre::Result<RuntimeMetadataV15> {
|
||||
let bytes = subxt_codegen::utils::fetch_metadata_bytes(url).await?;
|
||||
async fn fetch_runtime_metadata(
|
||||
url: &Uri,
|
||||
version: MetadataVersion,
|
||||
) -> color_eyre::Result<RuntimeMetadataV15> {
|
||||
let bytes = subxt_codegen::utils::fetch_metadata_bytes(url, version).await?;
|
||||
|
||||
let metadata = <RuntimeMetadataPrefixed as Decode>::decode(&mut &bytes[..])?;
|
||||
if metadata.0 != META_RESERVED {
|
||||
|
||||
+35
-7
@@ -5,7 +5,7 @@
|
||||
use clap::Args;
|
||||
use color_eyre::eyre;
|
||||
use std::{fs, io::Read, path::PathBuf};
|
||||
use subxt_codegen::utils::Uri;
|
||||
use subxt_codegen::utils::{MetadataVersion, Uri};
|
||||
|
||||
/// The source of the metadata.
|
||||
#[derive(Debug, Args)]
|
||||
@@ -16,29 +16,57 @@ pub struct FileOrUrl {
|
||||
/// The path to the encoded metadata file.
|
||||
#[clap(long, value_parser)]
|
||||
file: Option<PathBuf>,
|
||||
/// Specify the metadata version.
|
||||
///
|
||||
/// - unstable:
|
||||
///
|
||||
/// Use the latest unstable metadata of the node.
|
||||
///
|
||||
/// - number
|
||||
///
|
||||
/// Use this specific metadata version.
|
||||
///
|
||||
/// Defaults to 14.
|
||||
#[clap(long)]
|
||||
version: Option<MetadataVersion>,
|
||||
}
|
||||
|
||||
impl FileOrUrl {
|
||||
/// Fetch the metadata bytes.
|
||||
pub async fn fetch(&self) -> color_eyre::Result<Vec<u8>> {
|
||||
match (&self.file, &self.url) {
|
||||
match (&self.file, &self.url, self.version) {
|
||||
// Can't provide both --file and --url
|
||||
(Some(_), Some(_)) => {
|
||||
(Some(_), Some(_), _) => {
|
||||
eyre::bail!("specify one of `--url` or `--file` but not both")
|
||||
}
|
||||
// Load from --file path
|
||||
(Some(path), None) => {
|
||||
(Some(path), None, None) => {
|
||||
let mut file = fs::File::open(path)?;
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
// Cannot load the metadata from the file and specify a version to fetch.
|
||||
(Some(_), None, Some(_)) => {
|
||||
// Note: we could provide the ability to convert between metadata versions
|
||||
// but that would be involved because we'd need to convert
|
||||
// from each metadata to the latest one and from the
|
||||
// latest one to each metadata version. For now, disable the conversion.
|
||||
eyre::bail!("`--file` is incompatible with `--version`")
|
||||
}
|
||||
// Fetch from --url
|
||||
(None, Some(uri)) => Ok(subxt_codegen::utils::fetch_metadata_bytes(uri).await?),
|
||||
(None, Some(uri), version) => Ok(subxt_codegen::utils::fetch_metadata_bytes(
|
||||
uri,
|
||||
version.unwrap_or_default(),
|
||||
)
|
||||
.await?),
|
||||
// Default if neither is provided; fetch from local url
|
||||
(None, None) => {
|
||||
(None, None, version) => {
|
||||
let uri = Uri::from_static("http://localhost:9933");
|
||||
Ok(subxt_codegen::utils::fetch_metadata_bytes(&uri).await?)
|
||||
Ok(
|
||||
subxt_codegen::utils::fetch_metadata_bytes(&uri, version.unwrap_or_default())
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ description = "Generate an API for interacting with a substrate node from FRAME
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] }
|
||||
darling = { workspace = true }
|
||||
frame-metadata = { workspace = true }
|
||||
heck = { workspace = true }
|
||||
proc-macro2 = { workspace = true }
|
||||
|
||||
@@ -90,11 +90,11 @@ pub fn generate_calls(
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#( #call_fn_args, )*
|
||||
) -> #crate_path::tx::Payload<#struct_name> {
|
||||
) -> #crate_path::tx::Payload<types::#struct_name> {
|
||||
#crate_path::tx::Payload::new_static(
|
||||
#pallet_name,
|
||||
#call_name,
|
||||
#struct_name { #( #call_args, )* },
|
||||
types::#struct_name { #( #call_args, )* },
|
||||
[#(#call_hash,)*]
|
||||
)
|
||||
}
|
||||
@@ -120,7 +120,11 @@ pub fn generate_calls(
|
||||
|
||||
type DispatchError = #types_mod_ident::sp_runtime::DispatchError;
|
||||
|
||||
#( #call_structs )*
|
||||
pub mod types {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#( #call_structs )*
|
||||
}
|
||||
|
||||
pub struct TransactionApi;
|
||||
|
||||
|
||||
+28
-4
@@ -8,6 +8,7 @@ mod calls;
|
||||
mod constants;
|
||||
mod errors;
|
||||
mod events;
|
||||
mod runtime_apis;
|
||||
mod storage;
|
||||
|
||||
use frame_metadata::v15::RuntimeMetadataV15;
|
||||
@@ -18,7 +19,7 @@ use crate::error::CodegenError;
|
||||
use crate::{
|
||||
ir,
|
||||
types::{CompositeDef, CompositeDefFields, TypeGenerator, TypeSubstitutes},
|
||||
utils::{fetch_metadata_bytes_blocking, Uri},
|
||||
utils::{fetch_metadata_bytes_blocking, MetadataVersion, Uri},
|
||||
CratePath,
|
||||
};
|
||||
use codec::Decode;
|
||||
@@ -95,7 +96,11 @@ pub fn generate_runtime_api_from_url(
|
||||
should_gen_docs: bool,
|
||||
runtime_types_only: bool,
|
||||
) -> Result<TokenStream2, CodegenError> {
|
||||
let bytes = fetch_metadata_bytes_blocking(url)?;
|
||||
// Fetch latest unstable version, if that fails fall back to the latest stable.
|
||||
let bytes = match fetch_metadata_bytes_blocking(url, MetadataVersion::Unstable) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(_) => fetch_metadata_bytes_blocking(url, MetadataVersion::Latest)?,
|
||||
};
|
||||
|
||||
generate_runtime_api_from_bytes(
|
||||
item_mod,
|
||||
@@ -220,8 +225,13 @@ impl RuntimeGenerator {
|
||||
// Preserve any Rust items that were previously defined in the adorned module
|
||||
#( #rust_items ) *
|
||||
|
||||
// Make it easy to access the root via `root_mod` at different levels:
|
||||
use super::#mod_ident as root_mod;
|
||||
// Make it easy to access the root items via `root_mod` at different levels
|
||||
// without reaching out of this module.
|
||||
#[allow(unused_imports)]
|
||||
mod root_mod {
|
||||
pub use super::*;
|
||||
}
|
||||
|
||||
#types_mod
|
||||
}
|
||||
})
|
||||
@@ -434,6 +444,14 @@ impl RuntimeGenerator {
|
||||
|
||||
let rust_items = item_mod_ir.rust_items();
|
||||
|
||||
let apis_mod = runtime_apis::generate_runtime_apis(
|
||||
&self.metadata,
|
||||
&type_gen,
|
||||
types_mod_ident,
|
||||
&crate_path,
|
||||
should_gen_docs,
|
||||
)?;
|
||||
|
||||
Ok(quote! {
|
||||
#( #item_mod_attrs )*
|
||||
#[allow(dead_code, unused_imports, non_camel_case_types)]
|
||||
@@ -487,6 +505,12 @@ impl RuntimeGenerator {
|
||||
TransactionApi
|
||||
}
|
||||
|
||||
pub fn apis() -> runtime_apis::RuntimeApi {
|
||||
runtime_apis::RuntimeApi
|
||||
}
|
||||
|
||||
#apis_mod
|
||||
|
||||
pub struct ConstantsApi;
|
||||
impl ConstantsApi {
|
||||
#(
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{types::TypeGenerator, CodegenError, CratePath};
|
||||
use frame_metadata::v15::{RuntimeApiMetadata, RuntimeMetadataV15};
|
||||
use heck::ToSnakeCase as _;
|
||||
use heck::ToUpperCamelCase as _;
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use scale_info::form::PortableForm;
|
||||
|
||||
/// Generates runtime functions for the given API metadata.
|
||||
fn generate_runtime_api(
|
||||
metadata: &RuntimeMetadataV15,
|
||||
api: &RuntimeApiMetadata<PortableForm>,
|
||||
type_gen: &TypeGenerator,
|
||||
types_mod_ident: &syn::Ident,
|
||||
crate_path: &CratePath,
|
||||
should_gen_docs: bool,
|
||||
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
|
||||
// Trait name must remain as is (upper case) to identity the runtime call.
|
||||
let trait_name = &api.name;
|
||||
// The snake case for the trait name.
|
||||
let trait_name_snake = format_ident!("{}", api.name.to_snake_case());
|
||||
let docs = &api.docs;
|
||||
let docs: TokenStream2 = should_gen_docs
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
.unwrap_or_default();
|
||||
|
||||
let structs_and_methods: Vec<_> = api.methods.iter().map(|method| {
|
||||
let method_name = format_ident!("{}", method.name);
|
||||
|
||||
// Runtime function name is `TraitName_MethodName`.
|
||||
let runtime_fn_name = format!("{}_{}", trait_name, method_name);
|
||||
let docs = &method.docs;
|
||||
let docs: TokenStream2 = should_gen_docs
|
||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||
.unwrap_or_default();
|
||||
|
||||
let inputs: Vec<_> = method.inputs.iter().map(|input| {
|
||||
let name = format_ident!("{}", &input.name);
|
||||
let ty = type_gen.resolve_type_path(input.ty.id);
|
||||
|
||||
let param = quote!(#name: #ty);
|
||||
(param, name)
|
||||
}).collect();
|
||||
|
||||
let params = inputs.iter().map(|(param, _)| param);
|
||||
let param_names = inputs.iter().map(|(_, name)| name);
|
||||
|
||||
// From the method metadata generate a structure that holds
|
||||
// all parameter types. This structure is used with metadata
|
||||
// to encode parameters to the call via `encode_as_fields_to`.
|
||||
let derives = type_gen.default_derives();
|
||||
let struct_name = format_ident!("{}", method.name.to_upper_camel_case());
|
||||
let struct_params = params.clone();
|
||||
let struct_input = quote!(
|
||||
#derives
|
||||
pub struct #struct_name {
|
||||
#( pub #struct_params, )*
|
||||
}
|
||||
);
|
||||
|
||||
let output = type_gen.resolve_type_path(method.output.id);
|
||||
|
||||
let Ok(call_hash) =
|
||||
subxt_metadata::get_runtime_api_hash(metadata, trait_name, &method.name) else {
|
||||
return Err(CodegenError::MissingRuntimeApiMetadata(
|
||||
trait_name.into(),
|
||||
method.name.clone(),
|
||||
))
|
||||
};
|
||||
|
||||
let method = quote!(
|
||||
#docs
|
||||
pub fn #method_name(&self, #( #params, )* ) -> #crate_path::runtime_api::Payload<types::#struct_name, #output> {
|
||||
#crate_path::runtime_api::Payload::new_static(
|
||||
#runtime_fn_name,
|
||||
types::#struct_name { #( #param_names, )* },
|
||||
[#(#call_hash,)*],
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
Ok((struct_input, method))
|
||||
}).collect::<Result<_, _>>()?;
|
||||
|
||||
let trait_name = format_ident!("{}", trait_name);
|
||||
|
||||
let structs = structs_and_methods.iter().map(|(struct_, _)| struct_);
|
||||
let methods = structs_and_methods.iter().map(|(_, method)| method);
|
||||
|
||||
let runtime_api = quote!(
|
||||
pub mod #trait_name_snake {
|
||||
use super::root_mod;
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#docs
|
||||
pub struct #trait_name;
|
||||
|
||||
impl #trait_name {
|
||||
#( #methods )*
|
||||
}
|
||||
|
||||
pub mod types {
|
||||
use super::#types_mod_ident;
|
||||
|
||||
#( #structs )*
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// A getter for the `RuntimeApi` to get the trait structure.
|
||||
let trait_getter = quote!(
|
||||
pub fn #trait_name_snake(&self) -> #trait_name_snake::#trait_name {
|
||||
#trait_name_snake::#trait_name
|
||||
}
|
||||
);
|
||||
|
||||
Ok((runtime_api, trait_getter))
|
||||
}
|
||||
|
||||
/// Generate the runtime APIs.
|
||||
pub fn generate_runtime_apis(
|
||||
metadata: &RuntimeMetadataV15,
|
||||
type_gen: &TypeGenerator,
|
||||
types_mod_ident: &syn::Ident,
|
||||
crate_path: &CratePath,
|
||||
should_gen_docs: bool,
|
||||
) -> Result<TokenStream2, CodegenError> {
|
||||
let apis = &metadata.apis;
|
||||
|
||||
let runtime_fns: Vec<_> = apis
|
||||
.iter()
|
||||
.map(|api| {
|
||||
generate_runtime_api(
|
||||
metadata,
|
||||
api,
|
||||
type_gen,
|
||||
types_mod_ident,
|
||||
crate_path,
|
||||
should_gen_docs,
|
||||
)
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let runtime_apis_def = runtime_fns.iter().map(|(apis, _)| apis);
|
||||
let runtime_apis_getters = runtime_fns.iter().map(|(_, getters)| getters);
|
||||
|
||||
Ok(quote! {
|
||||
pub mod runtime_apis {
|
||||
use super::root_mod;
|
||||
use super::#types_mod_ident;
|
||||
|
||||
use #crate_path::ext::codec::Encode;
|
||||
|
||||
pub struct RuntimeApi;
|
||||
|
||||
impl RuntimeApi {
|
||||
#( #runtime_apis_getters )*
|
||||
}
|
||||
|
||||
#( #runtime_apis_def )*
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -42,6 +42,9 @@ pub enum CodegenError {
|
||||
/// Metadata for call could not be found.
|
||||
#[error("Metadata for call entry {0}_{1} could not be found. Make sure you are providing a valid substrate-based metadata")]
|
||||
MissingCallMetadata(String, String),
|
||||
/// Metadata for call could not be found.
|
||||
#[error("Metadata for runtime API entry {0}_{1} could not be found. Make sure you are providing a valid substrate-based metadata")]
|
||||
MissingRuntimeApiMetadata(String, String),
|
||||
/// Call variant must have all named fields.
|
||||
#[error("Call variant for type {0} must have all named fields. Make sure you are providing a valid substrate-based metadata")]
|
||||
InvalidCallVariant(u32),
|
||||
@@ -77,10 +80,14 @@ impl CodegenError {
|
||||
pub enum FetchMetadataError {
|
||||
#[error("Cannot decode hex value: {0}")]
|
||||
DecodeError(#[from] hex::FromHexError),
|
||||
#[error("Cannot scale encode/decode value: {0}")]
|
||||
CodecError(#[from] codec::Error),
|
||||
#[error("Request error: {0}")]
|
||||
RequestError(#[from] jsonrpsee::core::Error),
|
||||
#[error("'{0}' not supported, supported URI schemes are http, https, ws or wss.")]
|
||||
InvalidScheme(String),
|
||||
#[error("Other error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
/// Error attempting to do type substitution.
|
||||
|
||||
@@ -11,7 +11,6 @@ mod type_def;
|
||||
mod type_def_params;
|
||||
mod type_path;
|
||||
|
||||
use darling::FromMeta;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, ToTokens};
|
||||
use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef};
|
||||
@@ -353,7 +352,7 @@ impl ToTokens for CratePath {
|
||||
|
||||
impl From<&str> for CratePath {
|
||||
fn from(crate_path: &str) -> Self {
|
||||
Self(syn::Path::from_string(crate_path).unwrap_or_else(|err| {
|
||||
Self(syn::parse_str(crate_path).unwrap_or_else(|err| {
|
||||
panic!("failed converting {crate_path:?} to `syn::Path`: {err:?}");
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::error::FetchMetadataError;
|
||||
use codec::{Decode, Encode};
|
||||
use jsonrpsee::{
|
||||
async_client::ClientBuilder,
|
||||
client_transport::ws::{Uri, WsTransportClientBuilder},
|
||||
@@ -12,14 +13,51 @@ use jsonrpsee::{
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
/// The metadata version that is fetched from the node.
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub enum MetadataVersion {
|
||||
/// Latest stable version of the metadata.
|
||||
#[default]
|
||||
Latest,
|
||||
/// Fetch a specified version of the metadata.
|
||||
Version(u32),
|
||||
/// Latest unstable version of the metadata.
|
||||
Unstable,
|
||||
}
|
||||
|
||||
// Note: Implementation needed for the CLI tool.
|
||||
impl std::str::FromStr for MetadataVersion {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
match input {
|
||||
"unstable" => Ok(MetadataVersion::Unstable),
|
||||
"latest" => Ok(MetadataVersion::Latest),
|
||||
version => {
|
||||
let num: u32 = version
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid metadata version specified {:?}", version))?;
|
||||
|
||||
Ok(MetadataVersion::Version(num))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the metadata bytes from the provided URL, blocking the current thread.
|
||||
pub fn fetch_metadata_bytes_blocking(url: &Uri) -> Result<Vec<u8>, FetchMetadataError> {
|
||||
tokio_block_on(fetch_metadata_bytes(url))
|
||||
pub fn fetch_metadata_bytes_blocking(
|
||||
url: &Uri,
|
||||
version: MetadataVersion,
|
||||
) -> Result<Vec<u8>, FetchMetadataError> {
|
||||
tokio_block_on(fetch_metadata_bytes(url, version))
|
||||
}
|
||||
|
||||
/// Returns the raw, 0x prefixed metadata hex from the provided URL, blocking the current thread.
|
||||
pub fn fetch_metadata_hex_blocking(url: &Uri) -> Result<String, FetchMetadataError> {
|
||||
tokio_block_on(fetch_metadata_hex(url))
|
||||
pub fn fetch_metadata_hex_blocking(
|
||||
url: &Uri,
|
||||
version: MetadataVersion,
|
||||
) -> Result<String, FetchMetadataError> {
|
||||
tokio_block_on(fetch_metadata_hex(url, version))
|
||||
}
|
||||
|
||||
// Block on some tokio runtime for sync contexts
|
||||
@@ -32,26 +70,36 @@ fn tokio_block_on<T, Fut: std::future::Future<Output = T>>(fut: Fut) -> T {
|
||||
}
|
||||
|
||||
/// Returns the metadata bytes from the provided URL.
|
||||
pub async fn fetch_metadata_bytes(url: &Uri) -> Result<Vec<u8>, FetchMetadataError> {
|
||||
let hex = fetch_metadata_hex(url).await?;
|
||||
let bytes = hex::decode(hex.trim_start_matches("0x"))?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Returns the raw, 0x prefixed metadata hex from the provided URL.
|
||||
pub async fn fetch_metadata_hex(url: &Uri) -> Result<String, FetchMetadataError> {
|
||||
let hex_data = match url.scheme_str() {
|
||||
Some("http") | Some("https") => fetch_metadata_http(url).await,
|
||||
Some("ws") | Some("wss") => fetch_metadata_ws(url).await,
|
||||
pub async fn fetch_metadata_bytes(
|
||||
url: &Uri,
|
||||
version: MetadataVersion,
|
||||
) -> Result<Vec<u8>, FetchMetadataError> {
|
||||
let bytes = match url.scheme_str() {
|
||||
Some("http") | Some("https") => fetch_metadata_http(url, version).await,
|
||||
Some("ws") | Some("wss") => fetch_metadata_ws(url, version).await,
|
||||
invalid_scheme => {
|
||||
let scheme = invalid_scheme.unwrap_or("no scheme");
|
||||
Err(FetchMetadataError::InvalidScheme(scheme.to_owned()))
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Returns the raw, 0x prefixed metadata hex from the provided URL.
|
||||
pub async fn fetch_metadata_hex(
|
||||
url: &Uri,
|
||||
version: MetadataVersion,
|
||||
) -> Result<String, FetchMetadataError> {
|
||||
let bytes = fetch_metadata_bytes(url, version).await?;
|
||||
let hex_data = format!("0x{}", hex::encode(bytes));
|
||||
Ok(hex_data)
|
||||
}
|
||||
|
||||
async fn fetch_metadata_ws(url: &Uri) -> Result<String, FetchMetadataError> {
|
||||
async fn fetch_metadata_ws(
|
||||
url: &Uri,
|
||||
version: MetadataVersion,
|
||||
) -> Result<Vec<u8>, FetchMetadataError> {
|
||||
let (sender, receiver) = WsTransportClientBuilder::default()
|
||||
.build(url.to_string().parse::<Uri>().unwrap())
|
||||
.await
|
||||
@@ -62,13 +110,124 @@ async fn fetch_metadata_ws(url: &Uri) -> Result<String, FetchMetadataError> {
|
||||
.max_notifs_per_subscription(4096)
|
||||
.build_with_tokio(sender, receiver);
|
||||
|
||||
Ok(client.request("state_getMetadata", rpc_params![]).await?)
|
||||
fetch_metadata(client, version).await
|
||||
}
|
||||
|
||||
async fn fetch_metadata_http(url: &Uri) -> Result<String, FetchMetadataError> {
|
||||
async fn fetch_metadata_http(
|
||||
url: &Uri,
|
||||
version: MetadataVersion,
|
||||
) -> Result<Vec<u8>, FetchMetadataError> {
|
||||
let client = HttpClientBuilder::default()
|
||||
.request_timeout(Duration::from_secs(180))
|
||||
.build(url.to_string())?;
|
||||
|
||||
Ok(client.request("state_getMetadata", rpc_params![]).await?)
|
||||
fetch_metadata(client, version).await
|
||||
}
|
||||
|
||||
/// The innermost call to fetch metadata:
|
||||
async fn fetch_metadata(
|
||||
client: impl ClientT,
|
||||
version: MetadataVersion,
|
||||
) -> Result<Vec<u8>, FetchMetadataError> {
|
||||
const UNSTABLE_METADATA_VERSION: u32 = u32::MAX;
|
||||
|
||||
// Fetch metadata using the "new" state_call interface
|
||||
async fn fetch_inner(
|
||||
client: &impl ClientT,
|
||||
version: MetadataVersion,
|
||||
) -> Result<Vec<u8>, FetchMetadataError> {
|
||||
// Look up supported versions:
|
||||
let supported_versions: Vec<u32> = {
|
||||
let res: String = client
|
||||
.request(
|
||||
"state_call",
|
||||
rpc_params!["Metadata_metadata_versions", "0x"],
|
||||
)
|
||||
.await?;
|
||||
let raw_bytes = hex::decode(res.trim_start_matches("0x"))?;
|
||||
Decode::decode(&mut &raw_bytes[..])?
|
||||
};
|
||||
|
||||
// Return the version the user wants if it's supported:
|
||||
let version = match version {
|
||||
MetadataVersion::Latest => *supported_versions
|
||||
.iter()
|
||||
.filter(|&&v| v != UNSTABLE_METADATA_VERSION)
|
||||
.max()
|
||||
.ok_or_else(|| {
|
||||
FetchMetadataError::Other("No valid metadata versions returned".to_string())
|
||||
})?,
|
||||
MetadataVersion::Unstable => {
|
||||
if supported_versions.contains(&UNSTABLE_METADATA_VERSION) {
|
||||
UNSTABLE_METADATA_VERSION
|
||||
} else {
|
||||
return Err(FetchMetadataError::Other(
|
||||
"The node does not have an unstable metadata version available".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
MetadataVersion::Version(version) => {
|
||||
if supported_versions.contains(&version) {
|
||||
version
|
||||
} else {
|
||||
return Err(FetchMetadataError::Other(format!(
|
||||
"The node does not have version {version} available"
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let bytes = version.encode();
|
||||
let version: String = format!("0x{}", hex::encode(&bytes));
|
||||
|
||||
// Fetch the metadata at that version:
|
||||
let metadata_string: String = client
|
||||
.request(
|
||||
"state_call",
|
||||
rpc_params!["Metadata_metadata_at_version", &version],
|
||||
)
|
||||
.await?;
|
||||
// Decode the metadata.
|
||||
let metadata_bytes = hex::decode(metadata_string.trim_start_matches("0x"))?;
|
||||
let metadata: Option<frame_metadata::OpaqueMetadata> =
|
||||
Decode::decode(&mut &metadata_bytes[..])?;
|
||||
let Some(metadata) = metadata else {
|
||||
return Err(FetchMetadataError::Other(format!(
|
||||
"The node does not have version {version} available"
|
||||
)));
|
||||
};
|
||||
Ok(metadata.0)
|
||||
}
|
||||
|
||||
// Fetch metadata using the "old" state_call interface
|
||||
async fn fetch_inner_legacy(
|
||||
client: &impl ClientT,
|
||||
version: MetadataVersion,
|
||||
) -> Result<Vec<u8>, FetchMetadataError> {
|
||||
if !matches!(
|
||||
version,
|
||||
MetadataVersion::Latest | MetadataVersion::Version(14)
|
||||
) {
|
||||
return Err(FetchMetadataError::Other(
|
||||
"The node can only return version 14 metadata but you've asked for something else"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Fetch the metadata at that version:
|
||||
let metadata_string: String = client
|
||||
.request("state_call", rpc_params!["Metadata_metadata", "0x"])
|
||||
.await?;
|
||||
|
||||
// Decode the metadata.
|
||||
let metadata_bytes = hex::decode(metadata_string.trim_start_matches("0x"))?;
|
||||
let metadata: frame_metadata::OpaqueMetadata = Decode::decode(&mut &metadata_bytes[..])?;
|
||||
Ok(metadata.0)
|
||||
}
|
||||
|
||||
// Fetch using the new interface, falling back to trying old one if there's an error.
|
||||
match fetch_inner(&client, version).await {
|
||||
Ok(s) => Ok(s),
|
||||
Err(_) => fetch_inner_legacy(&client, version).await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ pub use jsonrpsee::client_transport::ws::Uri;
|
||||
|
||||
pub use fetch_metadata::{
|
||||
fetch_metadata_bytes, fetch_metadata_bytes_blocking, fetch_metadata_hex,
|
||||
fetch_metadata_hex_blocking,
|
||||
fetch_metadata_hex_blocking, MetadataVersion,
|
||||
};
|
||||
|
||||
@@ -16,9 +16,5 @@ description = "Subxt example usage"
|
||||
subxt = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
codec = { package = "parity-scale-codec", workspace = true, features = ["derive", "bit-vec"] }
|
||||
hex = { workspace = true }
|
||||
sp-keyring = { workspace = true }
|
||||
sp-core = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
# Subxt Examples
|
||||
|
||||
Take a look in the [examples](./examples) subfolder for various `subxt` usage examples.
|
||||
Take a look in the [examples](./examples) subfolder for various `subxt` usage examples.
|
||||
|
||||
All examples form part of the `subxt` documentation; there should be no examples without corresponding links from the docs.
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Create a transaction to submit:
|
||||
let tx = polkadot::tx()
|
||||
.balances()
|
||||
.transfer(dest, 123_456_789_012_345);
|
||||
|
||||
// Submit the transaction with default params:
|
||||
let hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
|
||||
|
||||
println!("Balance transfer extrinsic submitted: {hash}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
|
||||
// Generate an interface that we can use from the node's metadata.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a balance transfer extrinsic.
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
|
||||
|
||||
// Submit the balance transfer extrinsic from Alice, and wait for it to be successful
|
||||
// and in a finalized block. We get back the extrinsic events if all is well.
|
||||
let from = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let events = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
|
||||
.await?
|
||||
.wait_for_finalized_success()
|
||||
.await?;
|
||||
|
||||
// Find a Transfer event and print it.
|
||||
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
|
||||
if let Some(event) = transfer_event {
|
||||
println!("Balance transfer success: {event:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use futures::StreamExt;
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{
|
||||
tx::{PairSigner, TxStatus},
|
||||
OnlineClient, PolkadotConfig,
|
||||
};
|
||||
|
||||
// Generate an interface that we can use from the node's metadata.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a balance transfer extrinsic.
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
|
||||
|
||||
// Submit the balance transfer extrinsic from Alice, and then monitor the
|
||||
// progress of it.
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let mut balance_transfer_progress = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
|
||||
.await?;
|
||||
|
||||
while let Some(status) = balance_transfer_progress.next().await {
|
||||
match status? {
|
||||
// It's finalized in a block!
|
||||
TxStatus::Finalized(in_block) => {
|
||||
println!(
|
||||
"Transaction {:?} is finalized in block {:?}",
|
||||
in_block.extrinsic_hash(),
|
||||
in_block.block_hash()
|
||||
);
|
||||
|
||||
// grab the events and fail if no ExtrinsicSuccess event seen:
|
||||
let events = in_block.wait_for_success().await?;
|
||||
// We can look for events (this uses the static interface; we can also iterate
|
||||
// over them and dynamically decode them):
|
||||
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
|
||||
|
||||
if let Some(event) = transfer_event {
|
||||
println!("Balance transfer success: {event:?}");
|
||||
} else {
|
||||
println!("Failed to find Balances::Transfer Event");
|
||||
}
|
||||
}
|
||||
// Just log any other status we encounter:
|
||||
other => {
|
||||
println!("Status: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,52 +1,28 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{
|
||||
config::{
|
||||
polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params},
|
||||
PolkadotConfig,
|
||||
},
|
||||
tx::PairSigner,
|
||||
OnlineClient,
|
||||
};
|
||||
use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params};
|
||||
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
// Create a client to use:
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Create a transaction to submit:
|
||||
let tx = polkadot::tx()
|
||||
.balances()
|
||||
.transfer(dest, 123_456_789_012_345);
|
||||
// Build a balance transfer extrinsic.
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
let tx = polkadot::tx().balances().transfer(dest, 10_000);
|
||||
|
||||
// Configure the transaction tip and era:
|
||||
// Configure the transaction parameters; for Polkadot the tip and era:
|
||||
let tx_params = Params::new()
|
||||
.tip(PlainTip::new(20_000_000_000))
|
||||
.tip(PlainTip::new(1_000))
|
||||
.era(Era::Immortal, api.genesis_hash());
|
||||
|
||||
// submit the transaction:
|
||||
let hash = api.tx().sign_and_submit(&tx, &signer, tx_params).await?;
|
||||
|
||||
println!("Balance transfer extrinsic submitted: {hash}");
|
||||
let from = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let hash = api.tx().sign_and_submit(&tx, &from, tx_params).await?;
|
||||
println!("Balance transfer extrinsic submitted with hash : {hash}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.29-41a9d84b152.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.29/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use futures::StreamExt;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
@@ -18,14 +6,13 @@ pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Subscribe to all finalized blocks:
|
||||
let mut blocks_sub = api.blocks().subscribe_finalized().await?;
|
||||
|
||||
// For each block, print a bunch of information about it:
|
||||
while let Some(block) = blocks_sub.next().await {
|
||||
let block = block?;
|
||||
|
||||
@@ -36,6 +23,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!(" Hash: {block_hash}");
|
||||
println!(" Extrinsics:");
|
||||
|
||||
// Log each of the extrinsic with it's associated events:
|
||||
let body = block.body().await?;
|
||||
for ext in body.extrinsics() {
|
||||
let idx = ext.index();
|
||||
@@ -51,8 +39,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let pallet_name = evt.pallet_name();
|
||||
let event_name = evt.variant_name();
|
||||
let event_values = evt.field_values()?;
|
||||
|
||||
println!(" {pallet_name}_{event_name}");
|
||||
println!(" {}", event_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use futures::join;
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
let addr = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
// Construct storage addresses to access:
|
||||
let staking_bonded = polkadot::storage().staking().bonded(&addr);
|
||||
let staking_ledger = polkadot::storage().staking().ledger(&addr);
|
||||
|
||||
// For storage requests, we can join futures together to
|
||||
// await multiple futures concurrently:
|
||||
let a_fut = api.storage().at_latest().await?.fetch(&staking_bonded);
|
||||
let b_fut = api.storage().at_latest().await?.fetch(&staking_ledger);
|
||||
let (a, b) = join!(a_fut, b_fut);
|
||||
|
||||
println!("{a:?}, {b:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// A dynamic query to obtain some contant:
|
||||
let constant_query = subxt::dynamic::constant("System", "BlockLength");
|
||||
|
||||
// Obtain the value:
|
||||
let value = api.constants().at(&constant_query)?;
|
||||
|
||||
println!("Constant bytes: {:?}", value.encoded());
|
||||
println!("Constant value: {}", value.to_value()?);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// A query to obtain some contant:
|
||||
let constant_query = polkadot::constants().system().block_length();
|
||||
|
||||
// Obtain the value:
|
||||
let value = api.constants().at(&constant_query)?;
|
||||
|
||||
println!("Block length: {value:?}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! This example should compile but should fail to work, since we've modified the
|
||||
//! config to not align with a Polkadot node.
|
||||
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{
|
||||
config::{substrate::SubstrateExtrinsicParams, Config, SubstrateConfig},
|
||||
tx::PairSigner,
|
||||
OnlineClient,
|
||||
};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
/// Custom [`Config`] impl where the default types for the target chain differ from the
|
||||
/// [`DefaultConfig`]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct MyConfig;
|
||||
impl Config for MyConfig {
|
||||
// This is different from the default `u32`.
|
||||
//
|
||||
// *Note* that in this example it does differ from the actual `Index` type in the
|
||||
// polkadot runtime used, so some operations will fail. Normally when using a custom `Config`
|
||||
// impl types MUST match exactly those used in the actual runtime.
|
||||
type Index = u64;
|
||||
type Hash = <SubstrateConfig as Config>::Hash;
|
||||
type Hasher = <SubstrateConfig as Config>::Hasher;
|
||||
type Header = <SubstrateConfig as Config>::Header;
|
||||
type AccountId = <SubstrateConfig as Config>::AccountId;
|
||||
type Address = <SubstrateConfig as Config>::Address;
|
||||
type Signature = <SubstrateConfig as Config>::Signature;
|
||||
// ExtrinsicParams makes use of the index type, so we need to adjust it
|
||||
// too to align with our modified index type, above:
|
||||
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<MyConfig>::new().await?;
|
||||
|
||||
// Create a transaction to submit:
|
||||
let tx = polkadot::tx()
|
||||
.balances()
|
||||
.transfer(dest, 123_456_789_012_345);
|
||||
|
||||
// submit the transaction with default params:
|
||||
let hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
|
||||
|
||||
println!("Balance transfer extrinsic submitted: {hash}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
// If you'd like to use metadata directly from a running node, you
|
||||
// can provide a URL to that node here. HTTP or WebSocket URLs can be
|
||||
// provided. Note that if the metadata cannot be retrieved from this
|
||||
// node URL at compile time, compilation will fail.
|
||||
#[subxt::subxt(runtime_metadata_url = "wss://rpc.polkadot.io:443")]
|
||||
pub mod polkadot {}
|
||||
|
||||
fn main() {}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Example verified against polkadot polkadot 0.9.25-5174e9ae75b.
|
||||
#![allow(clippy::redundant_clone)]
|
||||
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
// We can add (certain) custom derives to the generated types by providing
|
||||
// a comma separated list to the below attribute. Most useful for adding `Clone`.
|
||||
// The derives that we can add ultimately is limited to the traits that the base
|
||||
// types relied upon by the codegen implement.
|
||||
derive_for_all_types = "Clone, PartialEq, Eq",
|
||||
|
||||
// To apply derives to specific generated types, add a `derive_for_type` per type,
|
||||
// mapping the type path to the derives which should be added for that type only.
|
||||
// Note that these derives will be in addition to those specified above in
|
||||
// `derive_for_all_types`
|
||||
derive_for_type(type = "frame_support::PalletId", derive = "Eq, Ord, PartialOrd"),
|
||||
derive_for_type(type = "sp_runtime::ModuleError", derive = "Eq, Hash"),
|
||||
)]
|
||||
pub mod polkadot {}
|
||||
|
||||
use polkadot::runtime_types::frame_support::PalletId;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let pallet_id = PalletId([1u8; 8]);
|
||||
let _ = pallet_id.clone();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.31-3711c6f9b2a.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.31/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{dynamic::Value, tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// My account.
|
||||
let signer_account = AccountKeyring::Alice;
|
||||
let signer_account_id = signer_account.to_account_id();
|
||||
let signer = PairSigner::new(signer_account.pair());
|
||||
|
||||
// Transfer balance to this destination:
|
||||
let dest = AccountKeyring::Bob.to_account_id();
|
||||
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Create the inner balance transfer call.
|
||||
let inner_tx = subxt::dynamic::tx(
|
||||
"Balances",
|
||||
"transfer",
|
||||
vec![
|
||||
Value::unnamed_variant("Id", [Value::from_bytes(&dest)]),
|
||||
Value::u128(123_456_789_012_345),
|
||||
],
|
||||
);
|
||||
|
||||
// Now, build an outer call which this inner call will be a part of.
|
||||
// This sets up the multisig arrangement.
|
||||
//
|
||||
// Note: Since this is a dynamic call, we can either use named or unnamed
|
||||
// arguments (if unnamed, the order matters).
|
||||
let tx = subxt::dynamic::tx(
|
||||
"Multisig",
|
||||
"as_multi",
|
||||
vec![
|
||||
("threshold", Value::u128(1)),
|
||||
(
|
||||
"other_signatories",
|
||||
Value::unnamed_composite([Value::from_bytes(&signer_account_id)]),
|
||||
),
|
||||
("maybe_timepoint", Value::unnamed_variant("None", [])),
|
||||
("call", inner_tx.into_value()),
|
||||
(
|
||||
"max_weight",
|
||||
Value::named_composite([
|
||||
("ref_time", Value::u128(10000000000)),
|
||||
("proof_size", Value::u128(1)),
|
||||
]),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Submit it:
|
||||
let encoded = hex::encode(api.tx().call_data(&tx)?);
|
||||
println!("Call data: {encoded}");
|
||||
let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
|
||||
println!("Submitted tx with hash {tx_hash}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
// This example showcases working with dynamic values rather than those that are generated via the subxt proc macro.
|
||||
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{dynamic::Value, tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// 1. Dynamic Balance Transfer (the dynamic equivalent to the balance_transfer example).
|
||||
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id();
|
||||
|
||||
// Create a transaction to submit:
|
||||
let tx = subxt::dynamic::tx(
|
||||
"Balances",
|
||||
"transfer",
|
||||
vec![
|
||||
// A value representing a MultiAddress<AccountId32, _>. We want the "Id" variant, and that
|
||||
// will ultimately contain the bytes for our destination address (there is a new type wrapping
|
||||
// the address, but our encoding will happily ignore such things and do it's best to line up what
|
||||
// we provide with what it needs).
|
||||
Value::unnamed_variant("Id", [Value::from_bytes(&dest)]),
|
||||
// A value representing the amount we'd like to transfer.
|
||||
Value::u128(123_456_789_012_345),
|
||||
],
|
||||
);
|
||||
|
||||
// submit the transaction with default params:
|
||||
let hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
|
||||
println!("Balance transfer extrinsic submitted: {hash}");
|
||||
|
||||
// 2. Dynamic constant access (the dynamic equivalent to the fetch_constants example).
|
||||
|
||||
let constant_address = subxt::dynamic::constant("Balances", "ExistentialDeposit");
|
||||
let existential_deposit = api.constants().at(&constant_address)?.to_value()?;
|
||||
println!("Existential Deposit: {existential_deposit}");
|
||||
|
||||
// 3. Dynamic storage access
|
||||
|
||||
let storage_address = subxt::dynamic::storage(
|
||||
"System",
|
||||
"Account",
|
||||
vec![
|
||||
// Something that encodes to an AccountId32 is what we need for the map key here:
|
||||
Value::from_bytes(&dest),
|
||||
],
|
||||
);
|
||||
let account = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch_or_default(&storage_address)
|
||||
.await?
|
||||
.to_value()?;
|
||||
println!("Bob's account details: {account}");
|
||||
|
||||
// 4. Dynamic storage iteration (the dynamic equivalent to the fetch_all_accounts example).
|
||||
|
||||
let storage_address = subxt::dynamic::storage_root("System", "Account");
|
||||
let mut iter = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.iter(storage_address, 10)
|
||||
.await?;
|
||||
while let Some((key, account)) = iter.next().await? {
|
||||
println!("{}: {}", hex::encode(key), account.to_value()?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Get events for the latest block:
|
||||
let events = api.events().at_latest().await?;
|
||||
|
||||
// We can dynamically decode events:
|
||||
println!("Dynamic event details:");
|
||||
for event in events.iter() {
|
||||
let event = event?;
|
||||
|
||||
let pallet = event.pallet_name();
|
||||
let variant = event.variant_name();
|
||||
let field_values = event.field_values()?;
|
||||
|
||||
println!("{pallet}::{variant}: {field_values}");
|
||||
}
|
||||
|
||||
// Or we can attempt to statically decode them into the root Event type:
|
||||
println!("Static event details:");
|
||||
for event in events.iter() {
|
||||
let event = event?;
|
||||
|
||||
if let Ok(ev) = event.as_root_event::<polkadot::Event>() {
|
||||
println!("{ev:?}");
|
||||
} else {
|
||||
println!("<Cannot decode event>");
|
||||
}
|
||||
}
|
||||
|
||||
// Or we can attempt to decode them into a specific arbitraty pallet enum
|
||||
// (We could also set the output type to Value to dynamically decode, here):
|
||||
println!("Event details for Balances pallet:");
|
||||
for event in events.iter() {
|
||||
let event = event?;
|
||||
|
||||
if let Ok(ev) = event.as_pallet_event::<polkadot::balances::Event>() {
|
||||
println!("{ev:?}");
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Or we can look for specific events which match our statically defined ones:
|
||||
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
|
||||
if let Some(ev) = transfer_event {
|
||||
println!(" - Balance transfer success: value: {:?}", ev.amount);
|
||||
} else {
|
||||
println!(" - No balance transfer event found in this block");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
let address = polkadot::storage().system().account_root();
|
||||
|
||||
let mut iter = api.storage().at_latest().await?.iter(address, 10).await?;
|
||||
|
||||
while let Some((key, account)) = iter.next().await? {
|
||||
println!("{}: {}", hex::encode(key), account.data.free);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
// Generate the API from a static metadata path.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a constant address to query:
|
||||
let address = polkadot::constants().balances().existential_deposit();
|
||||
|
||||
// Look it up:
|
||||
let existential_deposit = api.constants().at(&address)?;
|
||||
|
||||
println!("Existential Deposit: {existential_deposit}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use sp_core::{sr25519, Pair};
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{utils::AccountId32, OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
let active_era_addr = polkadot::storage().staking().active_era();
|
||||
let era = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&active_era_addr)
|
||||
.await?
|
||||
.unwrap();
|
||||
println!(
|
||||
"Staking active era: index: {:?}, start: {:?}",
|
||||
era.index, era.start
|
||||
);
|
||||
|
||||
let alice_id = AccountKeyring::Alice.to_account_id();
|
||||
println!(" Alice account id: {alice_id:?}");
|
||||
|
||||
// Get Alice' Stash account ID
|
||||
let alice_stash_id: AccountId32 = sr25519::Pair::from_string("//Alice//stash", None)
|
||||
.expect("Could not obtain stash signer pair")
|
||||
.public()
|
||||
.into();
|
||||
println!(" Alice//stash account id: {alice_stash_id:?}");
|
||||
|
||||
// Map from all locked "stash" accounts to the controller account.
|
||||
let controller_acc_addr = polkadot::storage().staking().bonded(&alice_stash_id);
|
||||
let controller_acc = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&controller_acc_addr)
|
||||
.await?
|
||||
.unwrap();
|
||||
println!(" account controlled by: {controller_acc:?}");
|
||||
|
||||
let era_reward_addr = polkadot::storage().staking().eras_reward_points(era.index);
|
||||
let era_result = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&era_reward_addr)
|
||||
.await?;
|
||||
println!("Era reward points: {era_result:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Each individual request will be validated against the static code that was
|
||||
// used to construct it, if possible. We can also validate the entirety of the
|
||||
// statically generated code against some client at a given point in time using
|
||||
// this check. If it fails, then there is some breaking change between the metadata
|
||||
// used to generate this static code, and the runtime metadata from a node that the
|
||||
// client is using.
|
||||
polkadot::validate_codegen(&api)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.31-3711c6f9b2a.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.31/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// My account.
|
||||
let signer_account = AccountKeyring::Alice;
|
||||
let signer_account_id = signer_account.to_account_id();
|
||||
let signer = PairSigner::new(signer_account.pair());
|
||||
|
||||
// Transfer balance to this destination:
|
||||
let dest = AccountKeyring::Bob.to_account_id();
|
||||
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Create the inner balance transfer call.
|
||||
//
|
||||
// Note: This call, being manually constructed, will have a specific pallet and call index
|
||||
// which is determined by the generated code. If you're trying to submit this to a node which
|
||||
// has the pallets/calls at different indexes, it will fail. See `dynamic_multisig.rs` for a
|
||||
// workaround in this case which will work regardless of pallet and call indexes.
|
||||
let inner_tx = polkadot::runtime_types::polkadot_runtime::RuntimeCall::Balances(
|
||||
polkadot::runtime_types::pallet_balances::pallet::Call::transfer {
|
||||
dest: dest.into(),
|
||||
value: 123_456_789_012_345,
|
||||
},
|
||||
);
|
||||
|
||||
// Now, build an outer call which this inner call will be a part of.
|
||||
// This sets up the multisig arrangement.
|
||||
let tx = polkadot::tx().multisig().as_multi(
|
||||
// threshold
|
||||
1,
|
||||
// other signatories
|
||||
vec![signer_account_id.into()],
|
||||
// maybe timepoint
|
||||
None,
|
||||
// call
|
||||
inner_tx,
|
||||
// max weight
|
||||
polkadot::runtime_types::sp_weights::weight_v2::Weight {
|
||||
ref_time: 10000000000,
|
||||
proof_size: 1,
|
||||
},
|
||||
);
|
||||
|
||||
// Submit the extrinsic with default params:
|
||||
let encoded = hex::encode(api.tx().call_data(&tx)?);
|
||||
println!("Call data: {encoded}");
|
||||
let tx_hash = api.tx().sign_and_submit_default(&tx, &signer).await?;
|
||||
println!("Submitted tx with hash {tx_hash}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
let block_number = 1u32;
|
||||
|
||||
let block_hash = api.rpc().block_hash(Some(block_number.into())).await?;
|
||||
|
||||
if let Some(hash) = block_hash {
|
||||
println!("Block hash for block number {block_number}: {hash}");
|
||||
} else {
|
||||
println!("Block number {block_number} not found.");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use subxt::{config::Header, OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let api = OnlineClient::<PolkadotConfig>::from_url("wss://rpc.polkadot.io:443").await?;
|
||||
|
||||
// For non-finalised blocks use `.subscribe_blocks()`
|
||||
let mut blocks = api.rpc().subscribe_finalized_block_headers().await?;
|
||||
|
||||
while let Some(Ok(block)) = blocks.next().await {
|
||||
println!(
|
||||
"block number: {} hash:{} parent:{} state root:{} extrinsics root:{}",
|
||||
block.number,
|
||||
block.hash(),
|
||||
block.parent_hash,
|
||||
block.state_root,
|
||||
block.extrinsics_root
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::dynamic::Value;
|
||||
use subxt::{config::PolkadotConfig, OnlineClient};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Create a dynamically runtime API payload that calls the
|
||||
// `AccountNonceApi_account_nonce` function.
|
||||
let account = AccountKeyring::Alice.to_account_id();
|
||||
let runtime_api_call = subxt::dynamic::runtime_api_call(
|
||||
"AccountNonceApi_account_nonce",
|
||||
vec![Value::from_bytes(account)],
|
||||
);
|
||||
|
||||
// Submit the call to get back a result.
|
||||
let nonce = api
|
||||
.runtime_api()
|
||||
.at_latest()
|
||||
.await?
|
||||
.call(runtime_api_call)
|
||||
.await?;
|
||||
|
||||
println!("Account nonce: {:#?}", nonce.to_value());
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
use subxt::ext::codec::Compact;
|
||||
use subxt::ext::frame_metadata::RuntimeMetadataPrefixed;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Use runtime APIs at the latest block:
|
||||
let runtime_apis = api.runtime_api().at_latest().await?;
|
||||
|
||||
// Ask for metadata and decode it:
|
||||
let (_, meta): (Compact<u32>, RuntimeMetadataPrefixed) =
|
||||
runtime_apis.call_raw("Metadata_metadata", None).await?;
|
||||
|
||||
println!("{meta:?}");
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{config::PolkadotConfig, OnlineClient};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Create a runtime API payload that calls into
|
||||
// `AccountNonceApi_account_nonce` function.
|
||||
let account = AccountKeyring::Alice.to_account_id().into();
|
||||
let runtime_api_call = polkadot::apis().account_nonce_api().account_nonce(account);
|
||||
|
||||
// Submit the call and get back a result.
|
||||
let nonce = api
|
||||
.runtime_api()
|
||||
.at_latest()
|
||||
.await?
|
||||
.call(runtime_api_call)
|
||||
.await;
|
||||
|
||||
println!("AccountNonceApi_account_nonce for Alice: {:?}", nonce);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! In some cases we are interested only in the `RuntimeCall` enum (or more generally, only in some
|
||||
//! runtime types). We can ask `subxt` to generate only runtime types by passing a corresponding
|
||||
//! flag.
|
||||
//!
|
||||
//! Here we present how to correctly create `Block` type for the Polkadot chain.
|
||||
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
generic,
|
||||
traits::{BlakeTwo256, Block as _, Header as _},
|
||||
Digest,
|
||||
};
|
||||
use subxt::PolkadotConfig;
|
||||
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
derive_for_all_types = "Clone, PartialEq, Eq",
|
||||
runtime_types_only
|
||||
)]
|
||||
pub mod polkadot {}
|
||||
|
||||
type RuntimeCall = polkadot::runtime_types::polkadot_runtime::RuntimeCall;
|
||||
|
||||
type UncheckedExtrinsic = generic::UncheckedExtrinsic<
|
||||
<PolkadotConfig as subxt::Config>::Address,
|
||||
RuntimeCall,
|
||||
<PolkadotConfig as subxt::Config>::Signature,
|
||||
// Usually we are not interested in `SignedExtra`.
|
||||
(),
|
||||
>;
|
||||
|
||||
type Header = generic::Header<u32, BlakeTwo256>;
|
||||
type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Although we could build an online client, we do not have access to the full runtime API. For
|
||||
// that, we would have to specify `runtime_types_only = false` (or just skipping it).
|
||||
//
|
||||
// let api = subxt::OnlineClient::<PolkadotConfig>::new().await?;
|
||||
// let address = polkadot::constants().balances().existential_deposit(); <- this won't compile!
|
||||
|
||||
let polkadot_header = Header::new(
|
||||
41,
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
H256::default(),
|
||||
Digest::default(),
|
||||
);
|
||||
|
||||
let polkadot_block = Block::new(polkadot_header, vec![]);
|
||||
|
||||
println!("{polkadot_block:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use subxt::{
|
||||
config::{substrate::SubstrateExtrinsicParams, Config, SubstrateConfig},
|
||||
OnlineClient,
|
||||
};
|
||||
|
||||
/// Define a custom config type (see the `subxt::config::Config` docs for
|
||||
/// more information about each type):
|
||||
enum MyConfig {}
|
||||
impl Config for MyConfig {
|
||||
// This is different from the default `u32`:
|
||||
type Index = u64;
|
||||
// We can point to the default types if we don't need to change things:
|
||||
type Hash = <SubstrateConfig as Config>::Hash;
|
||||
type Hasher = <SubstrateConfig as Config>::Hasher;
|
||||
type Header = <SubstrateConfig as Config>::Header;
|
||||
type AccountId = <SubstrateConfig as Config>::AccountId;
|
||||
type Address = <SubstrateConfig as Config>::Address;
|
||||
type Signature = <SubstrateConfig as Config>::Signature;
|
||||
// ExtrinsicParams makes use of the index type, so we need to tweak it
|
||||
// too to align with our modified index type, above:
|
||||
type ExtrinsicParams = SubstrateExtrinsicParams<Self>;
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a client which uses the custom config:
|
||||
let _api = OnlineClient::<MyConfig>::new().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use std::{
|
||||
fmt::Write,
|
||||
pin::Pin,
|
||||
@@ -64,13 +60,8 @@ impl RpcClientT for MyLoggingClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Instantiate our replacement RPC client.
|
||||
let log = Arc::default();
|
||||
let rpc_client = MyLoggingClient {
|
||||
@@ -0,0 +1,37 @@
|
||||
use subxt::ext::codec::Decode;
|
||||
use subxt::ext::frame_metadata::RuntimeMetadataPrefixed;
|
||||
use subxt::metadata::Metadata;
|
||||
use subxt::utils::H256;
|
||||
use subxt::{config::PolkadotConfig, OfflineClient};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// We need to obtain the following details for an OfflineClient to be instantiated:
|
||||
|
||||
// 1. Genesis hash (RPC call: chain_getBlockHash(0)):
|
||||
let genesis_hash = {
|
||||
let h = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3";
|
||||
let bytes = hex::decode(h).unwrap();
|
||||
H256::from_slice(&bytes)
|
||||
};
|
||||
|
||||
// 2. A runtime version (system_version constant on a Substrate node has these):
|
||||
let runtime_version = subxt::rpc::types::RuntimeVersion {
|
||||
spec_version: 9370,
|
||||
transaction_version: 20,
|
||||
other: Default::default(),
|
||||
};
|
||||
|
||||
// 3. Metadata (I'll load it from the downloaded metadata, but you can use
|
||||
// `subxt metadata > file.scale` to download it):
|
||||
let metadata = {
|
||||
let bytes = std::fs::read("./artifacts/polkadot_metadata.scale").unwrap();
|
||||
let metadata = RuntimeMetadataPrefixed::decode(&mut &*bytes).unwrap();
|
||||
Metadata::try_from(metadata).unwrap()
|
||||
};
|
||||
|
||||
// Create an offline client using the details obtained above:
|
||||
let _api = OfflineClient::<PolkadotConfig>::new(genesis_hash, runtime_version, metadata);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
// Generate an interface that we can use from the node's metadata.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a storage query to access account information.
|
||||
let account = AccountKeyring::Alice.to_account_id().into();
|
||||
let storage_query = polkadot::storage().system().account(&account);
|
||||
|
||||
// Use that query to `fetch` a result. This returns an `Option<_>`, which will be
|
||||
// `None` if no value exists at the given address. You can also use `fetch_default`
|
||||
// where applicable, which will return the default value if none exists.
|
||||
let result = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_query)
|
||||
.await?;
|
||||
|
||||
println!("Alice has free balance: {}", result.unwrap().data.free);
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::dynamic::{At, Value};
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a dynamic storage query to access account information.
|
||||
let account = AccountKeyring::Alice.to_account_id();
|
||||
let storage_query =
|
||||
subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]);
|
||||
|
||||
// Use that query to `fetch` a result. Because the query is dynamic, we don't know what the result
|
||||
// type will be either, and so we get a type back that can be decoded into a dynamic Value type.
|
||||
let result = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_query)
|
||||
.await?;
|
||||
let value = result.unwrap().to_value()?;
|
||||
|
||||
println!("Alice has free balance: {:?}", value.at("data").at("free"));
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,108 +1,29 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
// Generate an interface that we can use from the node's metadata.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Create a client to use:
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Example 1. Iterate over (keys, value) using the storage client.
|
||||
// This is the standard and most ergonomic approach.
|
||||
{
|
||||
let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root();
|
||||
// Build a storage query to iterate over account information.
|
||||
let storage_query = polkadot::storage().system().account_root();
|
||||
|
||||
let mut iter = api.storage().at_latest().await?.iter(key_addr, 10).await?;
|
||||
// Get back an iterator of results (here, we are fetching 10 items at
|
||||
// a time from the node, but we always iterate over oen at a time).
|
||||
let mut results = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.iter(storage_query, 10)
|
||||
.await?;
|
||||
|
||||
println!("\nExample 1. Obtained keys:");
|
||||
while let Some((key, value)) = iter.next().await? {
|
||||
println!("Key: 0x{}", hex::encode(key));
|
||||
println!(" Value: {value}");
|
||||
}
|
||||
}
|
||||
|
||||
// Example 2. Iterate over fetched keys manually. Here, you forgo any static type
|
||||
// safety and work directly with the bytes on either side.
|
||||
{
|
||||
let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root();
|
||||
|
||||
// Fetch at most 10 keys from below the prefix XcmPallet' VersionNotifiers.
|
||||
let keys = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch_keys(&key_addr.to_root_bytes(), 10, None)
|
||||
.await?;
|
||||
|
||||
println!("Example 2. Obtained keys:");
|
||||
for key in keys.iter() {
|
||||
println!("Key: 0x{}", hex::encode(key));
|
||||
|
||||
if let Some(storage_data) = api.storage().at_latest().await?.fetch_raw(&key.0).await? {
|
||||
// We know the return value to be `QueryId` (`u64`) from inspecting either:
|
||||
// - polkadot code
|
||||
// - polkadot.rs generated file under `version_notifiers()` fn
|
||||
// - metadata in json format
|
||||
let value = u64::decode(&mut &*storage_data)?;
|
||||
println!(" Value: {value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example 3. Custom iteration over double maps. Here, we manually append one lookup
|
||||
// key to the root and just iterate over the values underneath that.
|
||||
{
|
||||
let key_addr = polkadot::storage().xcm_pallet().version_notifiers_root();
|
||||
|
||||
// Obtain the root bytes (`twox_128("XcmPallet") ++ twox_128("VersionNotifiers")`).
|
||||
let mut query_key = key_addr.to_root_bytes();
|
||||
|
||||
// We know that the first key is a u32 (the `XcmVersion`) and is hashed by twox64_concat.
|
||||
// twox64_concat is just the result of running the twox_64 hasher on some value and concatenating
|
||||
// the value itself after it:
|
||||
query_key.extend(subxt::ext::sp_core::twox_64(&2u32.encode()));
|
||||
query_key.extend(&2u32.encode());
|
||||
|
||||
// The final query key is essentially the result of:
|
||||
// `twox_128("XcmPallet") ++ twox_128("VersionNotifiers") ++ twox_64(scale_encode(2u32)) ++ scale_encode(2u32)`
|
||||
println!("\nExample 3\nQuery key: 0x{}", hex::encode(&query_key));
|
||||
|
||||
let keys = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch_keys(&query_key, 10, None)
|
||||
.await?;
|
||||
|
||||
println!("Obtained keys:");
|
||||
for key in keys.iter() {
|
||||
println!("Key: 0x{}", hex::encode(key));
|
||||
|
||||
if let Some(storage_data) = api.storage().at_latest().await?.fetch_raw(&key.0).await? {
|
||||
// We know the return value to be `QueryId` (`u64`) from inspecting either:
|
||||
// - polkadot code
|
||||
// - polkadot.rs generated file under `version_notifiers()` fn
|
||||
// - metadata in json format
|
||||
let value = u64::decode(&mut &storage_data[..])?;
|
||||
println!(" Value: {value}");
|
||||
}
|
||||
}
|
||||
while let Some((key, value)) = results.next().await? {
|
||||
println!("Key: 0x{}", hex::encode(&key));
|
||||
println!("Value: {:?}", value);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create a new API client, configured to talk to Polkadot nodes.
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a dynamic storage query to iterate account information.
|
||||
let storage_query = subxt::dynamic::storage_root("System", "Account");
|
||||
|
||||
// Use that query to return an iterator over the results.
|
||||
let mut results = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.iter(storage_query, 10)
|
||||
.await?;
|
||||
|
||||
while let Some((key, value)) = results.next().await? {
|
||||
println!("Key: 0x{}", hex::encode(&key));
|
||||
println!("Value: {:?}", value.to_value()?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use futures::StreamExt;
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
simple_transfer().await?;
|
||||
simple_transfer_separate_events().await?;
|
||||
handle_transfer_events().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This is the highest level approach to using this API. We use `wait_for_finalized_success`
|
||||
/// to wait for the transaction to make it into a finalized block, and also ensure that the
|
||||
/// transaction was successful according to the associated events.
|
||||
async fn simple_transfer() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
|
||||
|
||||
let balance_transfer = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
|
||||
.await?
|
||||
.wait_for_finalized_success()
|
||||
.await?;
|
||||
|
||||
let transfer_event = balance_transfer.find_first::<polkadot::balances::events::Transfer>()?;
|
||||
|
||||
if let Some(event) = transfer_event {
|
||||
println!("Balance transfer success: {event:?}");
|
||||
} else {
|
||||
println!("Failed to find Balances::Transfer Event");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This is very similar to `simple_transfer`, except to show that we can handle
|
||||
/// waiting for the transaction to be finalized separately from obtaining and checking
|
||||
/// for success on the events.
|
||||
async fn simple_transfer_separate_events() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
|
||||
|
||||
let balance_transfer = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
|
||||
.await?
|
||||
.wait_for_finalized()
|
||||
.await?;
|
||||
|
||||
// Now we know it's been finalized, we can get hold of a couple of
|
||||
// details, including events. Calling `wait_for_finalized_success` is
|
||||
// equivalent to calling `wait_for_finalized` and then `wait_for_success`:
|
||||
let _events = balance_transfer.wait_for_success().await?;
|
||||
|
||||
// Alternately, we could just `fetch_events`, which grabs all of the events like
|
||||
// the above, but does not check for success, and leaves it up to you:
|
||||
let events = balance_transfer.fetch_events().await?;
|
||||
|
||||
let failed_event = events.find_first::<polkadot::system::events::ExtrinsicFailed>()?;
|
||||
|
||||
if let Some(_ev) = failed_event {
|
||||
// We found a failed event; the transfer didn't succeed.
|
||||
println!("Balance transfer failed");
|
||||
} else {
|
||||
// We didn't find a failed event; the transfer succeeded. Find
|
||||
// more details about it to report..
|
||||
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
|
||||
if let Some(event) = transfer_event {
|
||||
println!("Balance transfer success: {event:?}");
|
||||
} else {
|
||||
println!("Failed to find Balances::Transfer Event");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If we need more visibility into the state of the transaction, we can also ditch
|
||||
/// `wait_for_finalized` entirely and stream the transaction progress events, handling
|
||||
/// them more manually.
|
||||
async fn handle_transfer_events() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
let balance_transfer_tx = polkadot::tx().balances().transfer(dest, 10_000);
|
||||
|
||||
let mut balance_transfer_progress = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
|
||||
.await?;
|
||||
|
||||
while let Some(ev) = balance_transfer_progress.next().await {
|
||||
let ev = ev?;
|
||||
use subxt::tx::TxStatus::*;
|
||||
|
||||
// Made it into a block, but not finalized.
|
||||
if let InBlock(details) = ev {
|
||||
println!(
|
||||
"Transaction {:?} made it into block {:?}",
|
||||
details.extrinsic_hash(),
|
||||
details.block_hash()
|
||||
);
|
||||
|
||||
let events = details.wait_for_success().await?;
|
||||
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
|
||||
|
||||
if let Some(event) = transfer_event {
|
||||
println!("Balance transfer is now in block (but not finalized): {event:?}");
|
||||
} else {
|
||||
println!("Failed to find Balances::Transfer Event");
|
||||
}
|
||||
}
|
||||
// Finalized!
|
||||
else if let Finalized(details) = ev {
|
||||
println!(
|
||||
"Transaction {:?} is finalized in block {:?}",
|
||||
details.extrinsic_hash(),
|
||||
details.block_hash()
|
||||
);
|
||||
|
||||
let events = details.wait_for_success().await?;
|
||||
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
|
||||
|
||||
if let Some(event) = transfer_event {
|
||||
println!("Balance transfer success: {event:?}");
|
||||
} else {
|
||||
println!("Failed to find Balances::Transfer Event");
|
||||
}
|
||||
}
|
||||
// Report other statuses we see.
|
||||
else {
|
||||
println!("Current transaction status: {ev:?}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use futures::StreamExt;
|
||||
use sp_keyring::AccountKeyring;
|
||||
use std::time::Duration;
|
||||
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
pub mod polkadot {}
|
||||
|
||||
/// Subscribe to all events, and then manually look through them and
|
||||
/// pluck out the events that we care about.
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Subscribe to (in this case, finalized) blocks.
|
||||
let mut block_sub = api.blocks().subscribe_finalized().await?;
|
||||
|
||||
// While this subscription is active, balance transfers are made somewhere:
|
||||
tokio::task::spawn({
|
||||
let api = api.clone();
|
||||
async move {
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let mut transfer_amount = 1_000_000_000;
|
||||
|
||||
// Make small balance transfers from Alice to Bob in a loop:
|
||||
loop {
|
||||
let transfer_tx = polkadot::tx()
|
||||
.balances()
|
||||
.transfer(AccountKeyring::Bob.to_account_id().into(), transfer_amount);
|
||||
api.tx()
|
||||
.sign_and_submit_default(&transfer_tx, &signer)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
transfer_amount += 100_000_000;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Get each finalized block as it arrives.
|
||||
while let Some(block) = block_sub.next().await {
|
||||
let block = block?;
|
||||
|
||||
// Ask for the events for this block.
|
||||
let events = block.events().await?;
|
||||
|
||||
let block_hash = block.hash();
|
||||
|
||||
// We can dynamically decode events:
|
||||
println!(" Dynamic event details: {block_hash:?}:");
|
||||
for event in events.iter() {
|
||||
let event = event?;
|
||||
let is_balance_transfer = event
|
||||
.as_event::<polkadot::balances::events::Transfer>()?
|
||||
.is_some();
|
||||
let pallet = event.pallet_name();
|
||||
let variant = event.variant_name();
|
||||
println!(" {pallet}::{variant} (is balance transfer? {is_balance_transfer})");
|
||||
}
|
||||
|
||||
// Or we can find the first transfer event, ignoring any others:
|
||||
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
|
||||
|
||||
if let Some(ev) = transfer_event {
|
||||
println!(" - Balance transfer success: value: {:?}", ev.amount);
|
||||
} else {
|
||||
println!(" - No balance transfer event found in this block");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! To run this example, a local polkadot node should be running. Example verified against polkadot v0.9.28-9ffe6e9e3da.
|
||||
//!
|
||||
//! E.g.
|
||||
//! ```bash
|
||||
//! curl "https://github.com/paritytech/polkadot/releases/download/v0.9.28/polkadot" --output /usr/local/bin/polkadot --location
|
||||
//! polkadot --dev --tmp
|
||||
//! ```
|
||||
|
||||
use std::time::Duration;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Create a client to use:
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Start a new tokio task to perform the runtime updates while
|
||||
// utilizing the API for other use cases.
|
||||
let update_client = api.updater();
|
||||
tokio::spawn(async move {
|
||||
let result = update_client.perform_runtime_updates().await;
|
||||
println!("Runtime update failed with result={result:?}");
|
||||
});
|
||||
|
||||
// If this client is kept in use a while, it'll update its metadata and such
|
||||
// as needed when the node it's pointed at updates.
|
||||
tokio::time::sleep(Duration::from_secs(10_000)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+18
-118
@@ -2,118 +2,11 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Generate a strongly typed API for interacting with a Substrate runtime from its metadata.
|
||||
//!
|
||||
//! Usage:
|
||||
//!
|
||||
//! Download metadata from a running Substrate node using `subxt-cli`:
|
||||
//!
|
||||
//! ```bash
|
||||
//! subxt metadata > polkadot_metadata.scale
|
||||
//! ```
|
||||
//!
|
||||
//! Annotate a Rust module with the `subxt` attribute referencing the aforementioned metadata file.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[subxt::subxt(
|
||||
//! runtime_metadata_path = "polkadot_metadata.scale",
|
||||
//! )]
|
||||
//! pub mod polkadot {}
|
||||
//! ```
|
||||
//!
|
||||
//! The `subxt` macro will populate the annotated module with all of the methods and types required
|
||||
//! for submitting extrinsics and reading from storage for the given runtime.
|
||||
//!
|
||||
//! ## Substituting types
|
||||
//!
|
||||
//! In order to replace a generated type by a user-defined type, use `substitute_type`:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[subxt::subxt(
|
||||
//! runtime_metadata_path = "polkadot_metadata.scale",
|
||||
//! substitute_type(type = "sp_arithmetic::per_things::Perbill", with = "sp_runtime::Perbill")
|
||||
//! )]
|
||||
//! pub mod polkadot {}
|
||||
//! ```
|
||||
//!
|
||||
//! This will replace the generated type and any usages with the specified type at the `use` import.
|
||||
//! It is useful for using custom decoding for specific types, or to provide a type with foreign
|
||||
//! trait implementations, or other specialized functionality.
|
||||
|
||||
//! ## Custom Derives
|
||||
//!
|
||||
//! By default all generated types are annotated with `scale::Encode` and `scale::Decode` derives.
|
||||
//! However when using the generated types in the client, they may require additional derives to be
|
||||
//! useful.
|
||||
//!
|
||||
//! ### Adding derives for all types
|
||||
//!
|
||||
//! Add `derive_for_all_types` with a comma separated list of the derives to apply to *all* types
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[subxt::subxt(
|
||||
//! runtime_metadata_path = "polkadot_metadata.scale",
|
||||
//! derive_for_all_types = "Eq, PartialEq"
|
||||
//! )]
|
||||
//! pub mod polkadot {}
|
||||
//! ```
|
||||
//!
|
||||
//! ### Adding derives for specific types
|
||||
//!
|
||||
//! Add `derive_for_type` for each specific type with a comma separated list of the derives to
|
||||
//! apply for that type only.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[subxt::subxt(
|
||||
//! runtime_metadata_path = "polkadot_metadata.scale",
|
||||
//! derive_for_all_types = "Eq, PartialEq",
|
||||
//! derive_for_type(type = "frame_support::PalletId", derive = "Ord, PartialOrd"),
|
||||
//! derive_for_type(type = "sp_runtime::ModuleError", derive = "Hash"),
|
||||
//! )]
|
||||
//! pub mod polkadot {}
|
||||
//! ```
|
||||
//!
|
||||
//! ### Custom crate path
|
||||
//!
|
||||
//! In order to specify a custom crate path to be used for the code generation:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[subxt::subxt(crate = "crate::path::to::subxt")]
|
||||
//! pub mod polkadot {}
|
||||
//! ```
|
||||
//!
|
||||
//! By default the path `::subxt` is used.
|
||||
//!
|
||||
//! ### Expose documentation
|
||||
//!
|
||||
//! In order to expose the documentation from the runtime metadata on the generated
|
||||
//! code, users must specify the `generate_docs` flag:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[subxt::subxt(generate_docs)]
|
||||
//! pub mod polkadot {}
|
||||
//! ```
|
||||
//!
|
||||
//! By default the documentation is not generated.
|
||||
//!
|
||||
//! ### Runtime types generation
|
||||
//!
|
||||
//! In some cases, you may be interested only in the runtime types, like `RuntimeCall` enum. You can
|
||||
//! limit code generation to just `runtime_types` module with `runtime_types_only` flag:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[subxt::subxt(runtime_types_only)]
|
||||
//! // or equivalently
|
||||
//! #[subxt::subxt(runtime_types_only = true)]
|
||||
//! ```
|
||||
|
||||
#![deny(unused_crate_dependencies)]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use darling::FromMeta;
|
||||
use darling::{ast::NestedMeta, FromMeta};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error::{abort_call_site, proc_macro_error};
|
||||
use subxt_codegen::{utils::Uri, CodegenError, DerivesRegistry, TypeSubstitutes};
|
||||
@@ -158,29 +51,32 @@ struct RuntimeMetadataArgs {
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct DeriveForType {
|
||||
#[darling(rename = "type")]
|
||||
ty: syn::TypePath,
|
||||
path: syn::TypePath,
|
||||
derive: Punctuated<syn::Path, syn::Token![,]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct AttributesForType {
|
||||
#[darling(rename = "type")]
|
||||
ty: syn::TypePath,
|
||||
path: syn::TypePath,
|
||||
attributes: Punctuated<OuterAttribute, syn::Token![,]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
struct SubstituteType {
|
||||
#[darling(rename = "type")]
|
||||
ty: syn::Path,
|
||||
path: syn::Path,
|
||||
with: syn::Path,
|
||||
}
|
||||
|
||||
// Note: docs for this are in the subxt library; don't add any here as they will be appended.
|
||||
#[proc_macro_attribute]
|
||||
#[proc_macro_error]
|
||||
pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let attr_args = parse_macro_input!(args as syn::AttributeArgs);
|
||||
let attr_args = match NestedMeta::parse_meta_list(args.into()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return TokenStream::from(darling::Error::from(e).write_errors());
|
||||
}
|
||||
};
|
||||
let item_mod = parse_macro_input!(input as syn::ItemMod);
|
||||
let args = match RuntimeMetadataArgs::from_list(&attr_args) {
|
||||
Ok(v) => v,
|
||||
@@ -205,11 +101,15 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
);
|
||||
|
||||
for derives in &args.derive_for_type {
|
||||
derives_registry.extend_for_type(derives.ty.clone(), derives.derive.iter().cloned(), vec![])
|
||||
derives_registry.extend_for_type(
|
||||
derives.path.clone(),
|
||||
derives.derive.iter().cloned(),
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
for attributes in &args.attributes_for_type {
|
||||
derives_registry.extend_for_type(
|
||||
attributes.ty.clone(),
|
||||
attributes.path.clone(),
|
||||
vec![],
|
||||
attributes.attributes.iter().map(|a| a.0.clone()),
|
||||
)
|
||||
@@ -223,7 +123,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let substitute_args_res: Result<(), _> = args.substitute_type.into_iter().try_for_each(|sub| {
|
||||
sub.with
|
||||
.try_into()
|
||||
.and_then(|with| type_substitutes.insert(sub.ty, with))
|
||||
.and_then(|with| type_substitutes.insert(sub.path, with))
|
||||
});
|
||||
|
||||
if let Err(err) = substitute_args_res {
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ use frame_metadata::{v14::RuntimeMetadataV14, v15::RuntimeMetadataV15};
|
||||
pub use retain::retain_metadata_pallets;
|
||||
pub use validation::{
|
||||
get_call_hash, get_constant_hash, get_metadata_hash, get_metadata_per_pallet_hash,
|
||||
get_pallet_hash, get_storage_hash, NotFound,
|
||||
get_pallet_hash, get_runtime_api_hash, get_runtime_trait_hash, get_storage_hash, NotFound,
|
||||
};
|
||||
|
||||
/// Convert the metadata V14 to the latest metadata version.
|
||||
|
||||
+35
-1
@@ -5,7 +5,7 @@
|
||||
//! Utility functions to generate a subset of the metadata.
|
||||
|
||||
use frame_metadata::v15::{
|
||||
ExtrinsicMetadata, PalletMetadata, RuntimeMetadataV15, StorageEntryType,
|
||||
ExtrinsicMetadata, PalletMetadata, RuntimeApiMetadata, RuntimeMetadataV15, StorageEntryType,
|
||||
};
|
||||
use scale_info::{form::PortableForm, interner::UntrackedSymbol, TypeDef};
|
||||
use std::{
|
||||
@@ -105,6 +105,36 @@ fn update_extrinsic_types(
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all type IDs needed to represent the runtime APIs.
|
||||
fn collect_runtime_api_types(
|
||||
apis: &[RuntimeApiMetadata<PortableForm>],
|
||||
type_ids: &mut HashSet<u32>,
|
||||
) {
|
||||
for api in apis {
|
||||
for method in &api.methods {
|
||||
for input in &method.inputs {
|
||||
type_ids.insert(input.ty.id);
|
||||
}
|
||||
type_ids.insert(method.output.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update all type IDs of the provided runtime APIs metadata using the new type IDs from the portable registry.
|
||||
fn update_runtime_api_types(
|
||||
apis: &mut [RuntimeApiMetadata<PortableForm>],
|
||||
map_ids: &BTreeMap<u32, u32>,
|
||||
) {
|
||||
for api in apis {
|
||||
for method in &mut api.methods {
|
||||
for input in &mut method.inputs {
|
||||
update_type(&mut input.ty, map_ids);
|
||||
}
|
||||
update_type(&mut method.output, map_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the given type using the new type ID from the portable registry.
|
||||
///
|
||||
/// # Panics
|
||||
@@ -191,6 +221,9 @@ where
|
||||
// Keep the "runtime" type ID, since it's referenced in our metadata.
|
||||
type_ids.insert(metadata.ty.id);
|
||||
|
||||
// Keep the runtime APIs types.
|
||||
collect_runtime_api_types(&metadata.apis, &mut type_ids);
|
||||
|
||||
// Additionally, subxt depends on the `DispatchError` type existing; we use the same
|
||||
// logic here that is used when building our `Metadata`.
|
||||
let dispatch_error_ty = metadata
|
||||
@@ -211,6 +244,7 @@ where
|
||||
}
|
||||
update_extrinsic_types(&mut metadata.extrinsic, &map_ids);
|
||||
update_type(&mut metadata.ty, &map_ids);
|
||||
update_runtime_api_types(&mut metadata.apis, &map_ids);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+124
-7
@@ -5,7 +5,8 @@
|
||||
//! Utility functions for metadata validation.
|
||||
|
||||
use frame_metadata::v15::{
|
||||
ExtrinsicMetadata, PalletMetadata, RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType,
|
||||
ExtrinsicMetadata, PalletMetadata, RuntimeApiMetadata, RuntimeApiMethodMetadata,
|
||||
RuntimeMetadataV15, StorageEntryMetadata, StorageEntryType,
|
||||
};
|
||||
use scale_info::{form::PortableForm, Field, PortableRegistry, TypeDef, Variant};
|
||||
use std::collections::HashSet;
|
||||
@@ -253,7 +254,7 @@ pub fn get_storage_hash(
|
||||
.pallets
|
||||
.iter()
|
||||
.find(|p| p.name == pallet_name)
|
||||
.ok_or(NotFound::Pallet)?;
|
||||
.ok_or(NotFound::Root)?;
|
||||
|
||||
let storage = pallet.storage.as_ref().ok_or(NotFound::Item)?;
|
||||
|
||||
@@ -277,7 +278,7 @@ pub fn get_constant_hash(
|
||||
.pallets
|
||||
.iter()
|
||||
.find(|p| p.name == pallet_name)
|
||||
.ok_or(NotFound::Pallet)?;
|
||||
.ok_or(NotFound::Root)?;
|
||||
|
||||
let constant = pallet
|
||||
.constants
|
||||
@@ -300,7 +301,7 @@ pub fn get_call_hash(
|
||||
.pallets
|
||||
.iter()
|
||||
.find(|p| p.name == pallet_name)
|
||||
.ok_or(NotFound::Pallet)?;
|
||||
.ok_or(NotFound::Root)?;
|
||||
|
||||
let call_id = pallet.calls.as_ref().ok_or(NotFound::Item)?.ty.id;
|
||||
|
||||
@@ -321,6 +322,96 @@ pub fn get_call_hash(
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
fn get_runtime_method_hash(
|
||||
metadata: &RuntimeMetadataV15,
|
||||
trait_metadata: &RuntimeApiMetadata<PortableForm>,
|
||||
method_metadata: &RuntimeApiMethodMetadata<PortableForm>,
|
||||
visited_ids: &mut HashSet<u32>,
|
||||
) -> [u8; 32] {
|
||||
// The trait name is part of the runtime API call that is being
|
||||
// generated for this method. Therefore the trait name is strongly
|
||||
// connected to the method in the same way as a parameter is
|
||||
// to the method.
|
||||
let mut bytes = hash(trait_metadata.name.as_bytes());
|
||||
bytes = xor(bytes, hash(method_metadata.name.as_bytes()));
|
||||
|
||||
for input in &method_metadata.inputs {
|
||||
bytes = xor(bytes, hash(input.name.as_bytes()));
|
||||
bytes = xor(
|
||||
bytes,
|
||||
get_type_hash(&metadata.types, input.ty.id, visited_ids),
|
||||
);
|
||||
}
|
||||
|
||||
bytes = xor(
|
||||
bytes,
|
||||
get_type_hash(&metadata.types, method_metadata.output.id, visited_ids),
|
||||
);
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Obtain the hash of a specific runtime trait.
|
||||
pub fn get_runtime_trait_hash(
|
||||
metadata: &RuntimeMetadataV15,
|
||||
trait_metadata: &RuntimeApiMetadata<PortableForm>,
|
||||
) -> [u8; 32] {
|
||||
// Start out with any hash, the trait name is already part of the
|
||||
// runtime method hash.
|
||||
let mut bytes = hash(trait_metadata.name.as_bytes());
|
||||
let mut visited_ids = HashSet::new();
|
||||
|
||||
let mut methods: Vec<_> = trait_metadata
|
||||
.methods
|
||||
.iter()
|
||||
.map(|method_metadata| {
|
||||
let bytes = get_runtime_method_hash(
|
||||
metadata,
|
||||
trait_metadata,
|
||||
method_metadata,
|
||||
&mut visited_ids,
|
||||
);
|
||||
(&*method_metadata.name, bytes)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Sort by method name to create a deterministic representation of the underlying metadata.
|
||||
methods.sort_by_key(|&(name, _hash)| name);
|
||||
|
||||
// Note: Hash already takes into account the method name.
|
||||
for (_, hash) in methods {
|
||||
bytes = xor(bytes, hash);
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
|
||||
/// Obtain the hash of a specific runtime API function, or an error if it's not found.
|
||||
pub fn get_runtime_api_hash(
|
||||
metadata: &RuntimeMetadataV15,
|
||||
trait_name: &str,
|
||||
method_name: &str,
|
||||
) -> Result<[u8; 32], NotFound> {
|
||||
let trait_metadata = metadata
|
||||
.apis
|
||||
.iter()
|
||||
.find(|m| m.name == trait_name)
|
||||
.ok_or(NotFound::Root)?;
|
||||
|
||||
let method_metadata = trait_metadata
|
||||
.methods
|
||||
.iter()
|
||||
.find(|m| m.name == method_name)
|
||||
.ok_or(NotFound::Item)?;
|
||||
|
||||
Ok(get_runtime_method_hash(
|
||||
metadata,
|
||||
trait_metadata,
|
||||
method_metadata,
|
||||
&mut HashSet::new(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Obtain the hash representation of a `frame_metadata::v15::PalletMetadata`.
|
||||
pub fn get_pallet_hash(
|
||||
registry: &PortableRegistry,
|
||||
@@ -404,6 +495,19 @@ pub fn get_metadata_hash(metadata: &RuntimeMetadataV15) -> [u8; 32] {
|
||||
&mut visited_ids,
|
||||
));
|
||||
|
||||
let mut apis: Vec<_> = metadata
|
||||
.apis
|
||||
.iter()
|
||||
.map(|api| (&*api.name, get_runtime_trait_hash(metadata, api)))
|
||||
.collect();
|
||||
|
||||
// Sort the runtime APIs by trait name to provide a deterministic output.
|
||||
apis.sort_by_key(|&(name, _hash)| name);
|
||||
|
||||
for (_, hash) in apis.iter() {
|
||||
bytes.extend(hash)
|
||||
}
|
||||
|
||||
hash(&bytes)
|
||||
}
|
||||
|
||||
@@ -449,11 +553,24 @@ pub fn get_metadata_per_pallet_hash<T: AsRef<str>>(
|
||||
hash(&bytes)
|
||||
}
|
||||
|
||||
/// An error returned if we attempt to get the hash for a specific call, constant
|
||||
/// or storage item that doesn't exist.
|
||||
/// An error returned if we attempt to get the hash for a specific call, constant,
|
||||
/// storage or runtime API function does not exist.
|
||||
///
|
||||
/// The location of the specific item (call, constant, storage or runtime API function)
|
||||
/// is stored with two indirections:
|
||||
/// - Root
|
||||
/// The root location of the item. For calls, constants, storage this represents the
|
||||
/// pallet name. While for runtime API function this represents the trait name.
|
||||
/// - Item
|
||||
/// The actual item. For calls, constants, storage this represents the actual name.
|
||||
/// While for runtime API functions this represents the method name.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum NotFound {
|
||||
Pallet,
|
||||
/// The root location of the item cannot be found.
|
||||
/// - pallet name: for calls, constants, storage
|
||||
/// - trait name: for runtime API functions
|
||||
Root,
|
||||
/// The actual item name cannot be found.
|
||||
Item,
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,12 @@ integration-tests = []
|
||||
jsonrpsee-ws = ["jsonrpsee/async-client", "jsonrpsee/client-ws-transport"]
|
||||
jsonrpsee-web = ["jsonrpsee/async-wasm-client", "jsonrpsee/client-web-transport"]
|
||||
|
||||
# Activate this to fetch and utilize the latest unstabl metadata from a node.
|
||||
# The unstable metadata is subject to breaking changes and the subxt might
|
||||
# fail to decode the metadata properly. Use this to experiment with the
|
||||
# latest features exposed by the metadata.
|
||||
unstable-metadata = []
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] }
|
||||
scale-info = { workspace = true }
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
mod block_types;
|
||||
mod blocks_client;
|
||||
|
||||
pub use block_types::{Block, Extrinsic, ExtrinsicEvents};
|
||||
pub use block_types::{Block, BlockBody, Extrinsic, ExtrinsicEvents};
|
||||
pub use blocks_client::{subscribe_to_block_headers_filling_in_gaps, BlocksClient};
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
// Dev note; I used the following command to normalize and wrap comments:
|
||||
// rustfmt +nightly --config wrap_comments=true,comment_width=100,normalize_comments=true subxt/src/book/mod.rs
|
||||
// It messed up comments in code blocks though, so be prepared to go and fix those.
|
||||
|
||||
//! # The Subxt Guide
|
||||
//!
|
||||
//! Subxt is a library for interacting with Substrate based nodes. It has a focus on **sub**mitting
|
||||
//! e**xt**rinsics, hence the name, however it's also capable of reading blocks, storage, events and
|
||||
//! constants from a node. The aim of this guide is to explain key concepts and get you started with
|
||||
//! using Subxt.
|
||||
//!
|
||||
//! 1. [Features](#features-at-a-glance)
|
||||
//! 2. [Limitations](#limitations)
|
||||
//! 3. [Quick start](#quick-start)
|
||||
//! 4. [Usage](#usage)
|
||||
//!
|
||||
//! ## Features at a glance
|
||||
//!
|
||||
//! Here's a quick overview of the features that Subxt has to offer:
|
||||
//!
|
||||
//! - Subxt allows you to generate a static, type safe interface to a node given some metadata; this
|
||||
//! allows you to catch many errors at compile time rather than runtime.
|
||||
//! - Subxt also makes heavy use of node metadata to encode/decode the data sent to/from it. This
|
||||
//! allows it to target almost any node which can output the correct metadata, and allows it some
|
||||
//! flexibility in encoding and decoding things to account for cross-node differences.
|
||||
//! - Subxt has a pallet-oriented interface, meaning that code you write to talk to some pallet on
|
||||
//! one node will often "Just Work" when pointed at different nodes that use the same pallet.
|
||||
//! - Subxt can work offline; you can generate and sign transactions, access constants from node
|
||||
//! metadata and more, without a network connection. This is all checked at compile time, so you
|
||||
//! can be certain it won't try to establish a network connection if you don't want it to.
|
||||
//! - Subxt can forego the statically generated interface and build transactions, storage queries
|
||||
//! and constant queries using data provided at runtime, rather than queries constructed
|
||||
//! statically.
|
||||
//! - Subxt can be compiled to WASM to run in the browser, allowing it to back Rust based browser
|
||||
//! apps, or even bind to JS apps.
|
||||
//!
|
||||
//! ## Limitations
|
||||
//!
|
||||
//! In various places, you can provide a block hash to access data at a particular block, for
|
||||
//! instance:
|
||||
//!
|
||||
//! - [`crate::storage::StorageClient::at`]
|
||||
//! - [`crate::events::EventsClient::at`]
|
||||
//! - [`crate::blocks::BlocksClient::at`]
|
||||
//! - [`crate::runtime_api::RuntimeApiClient::at`]
|
||||
//!
|
||||
//! However, Subxt is (by default) only capable of properly working with blocks that were produced
|
||||
//! after the most recent runtime update. This is because it uses the most recent metadata given
|
||||
//! back by a node to encode and decode things. It's possible to decode older blocks produced by a
|
||||
//! runtime that emits compatible (currently, V14) metadata by manually setting the metadata used by
|
||||
//! the client using [`crate::client::OnlineClient::set_metadata()`].
|
||||
//!
|
||||
//! Subxt does not support working with blocks produced prior to the runtime update that introduces
|
||||
//! V14 metadata. It may have some success decoding older blocks using newer metadata, but may also
|
||||
//! completely fail to do so.
|
||||
//!
|
||||
//! ## Quick start
|
||||
//!
|
||||
//! Here is a simple but complete example of using Subxt to transfer some tokens from the example
|
||||
//! accounts, Alice to Bob:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../examples/examples/balance_transfer_basic.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! This example assumes that a Polkadot node is running locally (Subxt endeavors to support all
|
||||
//! recent releases). Typically, to use Subxt to talk to some custom Substrate node (for example a
|
||||
//! parachain node), you'll want to:
|
||||
//!
|
||||
//! 1. [Generate an interface](setup::codegen).
|
||||
//! 2. [Configure and instantiate the client](setup::client).
|
||||
//!
|
||||
//! Follow the above links to learn more about each step.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! Once Subxt is configured, the next step is interacting with a node. Follow the links
|
||||
//! below to learn more about how to use Subxt for each of the following things:
|
||||
//!
|
||||
//! - [Extrinsics](usage::extrinsics): Subxt can build and submit extrinsics, wait until they are in
|
||||
//! blocks, and retrieve the associated events.
|
||||
//! - [Storage](usage::storage): Subxt can query the node storage.
|
||||
//! - [Events](usage::events): Subxt can read the events emitted for recent blocks.
|
||||
//! - [Constants](usage::constants): Subxt can access the constant values stored in a node, which
|
||||
//! remain the same for a given runtime version.
|
||||
//! - [Blocks](usage::blocks): Subxt can load recent blocks or subscribe to new/finalized blocks,
|
||||
//! reading the extrinsics, events and storage at these blocks.
|
||||
//! - [Runtime APIs](usage::runtime_apis): Subxt can make calls into pallet runtime APIs to retrieve
|
||||
//! data.
|
||||
//!
|
||||
pub mod setup;
|
||||
pub mod usage;
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Configuring the Subxt client
|
||||
//!
|
||||
//! Subxt ships with two clients, an [offline client](crate::client::OfflineClient) and an [online
|
||||
//! client](crate::client::OnlineClient). These are backed by the traits
|
||||
//! [`crate::client::OfflineClientT`] and [`crate::client::OnlineClientT`], so in theory it's
|
||||
//! possible for users to implement their own clients, although this isn't generally expected.
|
||||
//!
|
||||
//! Both clients are generic over a [`crate::config::Config`] trait, which is the way that we give
|
||||
//! the client certain information about how to interact with a node that isn't otherwise available
|
||||
//! or possible to include in the node metadata. Subxt ships out of the box with two default
|
||||
//! implementations:
|
||||
//!
|
||||
//! - [`crate::config::PolkadotConfig`] for talking to Polkadot nodes, and
|
||||
//! - [`crate::config::SubstrateConfig`] for talking to generic nodes built with Substrate.
|
||||
//!
|
||||
//! The latter will generally work in many cases, but will need modifying if the chain you'd like to
|
||||
//! connect to has altered any of the details mentioned in [the trait](`crate::config::Config`).
|
||||
//!
|
||||
//! In the case of the [`crate::OnlineClient`], we have a few options to instantiate it:
|
||||
//!
|
||||
//! - [`crate::OnlineClient::new()`] to connect to a node running locally.
|
||||
//! - [`crate::OnlineClient::from_url()`] to connect to a node at a specific URL.
|
||||
//! - [`crate::OnlineClient::from_rpc_client()`] to instantiate the client with a custom RPC
|
||||
//! implementation.
|
||||
//!
|
||||
//! The latter accepts anything that implements the low level [`crate::rpc::RpcClientT`] trait; this
|
||||
//! allows you to decide how Subxt will attempt to talk to a node if you'd prefer something other
|
||||
//! than the provided interfaces.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! Defining some custom config based off the default Substrate config:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/setup_client_custom_config.rs")]
|
||||
//! ```
|
||||
//! Writing a custom [`crate::rpc::RpcClientT`] implementation:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/setup_client_custom_rpc.rs")]
|
||||
//! ```
|
||||
//! Creating an [`crate::OfflineClient`]:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/setup_client_offline.rs")]
|
||||
//! ```
|
||||
//!
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Generating an interface
|
||||
//!
|
||||
//! The simplest way to use Subxt is to generate an interface to a chain that you'd like to interact
|
||||
//! with. This generated interface allows you to build transactions and construct queries to access
|
||||
//! data while leveraging the full type safety of the Rust compiler.
|
||||
//!
|
||||
//! ## The `#[subxt]` macro
|
||||
//!
|
||||
//! The most common way to generate the interface is to use the [`#[subxt]`](crate::subxt) macro.
|
||||
//! Using this macro looks something like:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//! ```
|
||||
//!
|
||||
//! The macro takes a path to some node metadata, and uses that to generate the interface you'll use
|
||||
//! to talk to it. [Go here](crate::subxt) to learn more about the options available to the macro.
|
||||
//!
|
||||
//! To obtain this metadata you'll need for the above, you can use the `subxt` CLI tool to download it
|
||||
//! from a node. The tool can be installed via `cargo`:
|
||||
//!
|
||||
//! ```shell
|
||||
//! cargo install subxt-cli
|
||||
//! ```
|
||||
//!
|
||||
//! And then it can be used to fetch metadata and save it to a file:
|
||||
//!
|
||||
//! ```shell
|
||||
//! # Download and save all of the metadata:
|
||||
//! subxt metadata > metadata.scale
|
||||
//! # Download and save only the pallets you want to generate an interface for:
|
||||
//! subxt metadata --pallets Balances,System > metadata.scale
|
||||
//! ```
|
||||
//!
|
||||
//! Explicitly specifying pallets will cause the tool to strip out all unnecessary metadata and type
|
||||
//! information, making the bundle much smaller in the event that you only need to generate an
|
||||
//! interface for a subset of the available pallets on the node.
|
||||
//!
|
||||
//! ## The CLI tool
|
||||
//!
|
||||
//! Using the [`#[subxt]`](crate::subxt) macro carries some downsides:
|
||||
//!
|
||||
//! - Using it to generate an interface will have a small impact on compile times (though much less of
|
||||
//! one if you only need a few pallets).
|
||||
//! - IDE support for autocompletion and documentation when using the macro interface can be poor.
|
||||
//! - It's impossible to manually look at the generated code to understand and debug things.
|
||||
//!
|
||||
//! If these are an issue, you can manually generate the same code that the macro generates under the hood
|
||||
//! by using the `subxt codegen` command:
|
||||
//!
|
||||
//! ```shell
|
||||
//! # Install the CLI tool if you haven't already:
|
||||
//! cargo install subxt-cli
|
||||
//! # Generate and format rust code, saving it to `interface.rs`:
|
||||
//! subxt codegen | rustfmt > interface.rs
|
||||
//! ```
|
||||
//!
|
||||
//! Use `subxt codegen --help` for more options; many of the options available via the macro are
|
||||
//! also available via the CLI tool, such as the ability to substitute generated types for others,
|
||||
//! or strip out docs from the generated code.
|
||||
//!
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! This modules contains details on setting up Subxt:
|
||||
//!
|
||||
//! - [Codegen](codegen)
|
||||
//! - [Client](client)
|
||||
//!
|
||||
//! Alternately, [go back](super).
|
||||
|
||||
pub mod client;
|
||||
pub mod codegen;
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Blocks
|
||||
//!
|
||||
//! The [blocks API](crate::blocks::BlocksClient) in Subxt unifies many of the other interfaces, and
|
||||
//! allows you to:
|
||||
//!
|
||||
//! - Access information about specific blocks (see [`crate::blocks::BlocksClient::at()`] and
|
||||
//! [`crate::blocks::BlocksClient::at_latest()`]).
|
||||
//! - Subscribe to [all](crate::blocks::BlocksClient::subscribe_all()),
|
||||
//! [best](crate::blocks::BlocksClient::subscribe_best()) or
|
||||
//! [finalized](crate::blocks::BlocksClient::subscribe_finalized()) blocks as they are produced.
|
||||
//! Prefer to subscribe to finalized blocks unless you know what you're doing.
|
||||
//!
|
||||
//! In either case, you'll end up with [`crate::blocks::Block`]'s, from which you can access various
|
||||
//! information about the block, such a the [header](crate::blocks::Block::header()), [block
|
||||
//! number](crate::blocks::Block::number()) and [body](crate::blocks::Block::body()).
|
||||
//! [`crate::blocks::Block`]'s also provide shortcuts to other Subxt APIs that will operate at the
|
||||
//! given block:
|
||||
//!
|
||||
//! - [storage](crate::blocks::Block::storage()),
|
||||
//! - [events](crate::blocks::Block::events())
|
||||
//! - [runtime APIs](crate::blocks::Block::runtime_api())
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! Given a block, you can [download the block body](crate::blocks::Block::body()) and iterate over
|
||||
//! the extrinsics stored within it using [`crate::blocks::BlockBody::extrinsics()`].
|
||||
//!
|
||||
//! Here's an example in which we subscribe to blocks and print a bunch of information about each
|
||||
//! one:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/blocks_subscribing.rs")]
|
||||
//! ```
|
||||
//!
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Constants
|
||||
//!
|
||||
//! There are various constants stored in a node; the types and values of these are defined in a
|
||||
//! runtime, and can only change when the runtime is updated. Much like [`super::storage`], we can
|
||||
//! query these using Subxt by taking the following steps:
|
||||
//!
|
||||
//! 1. [Constructing a constant query](#constructing-a-query).
|
||||
//! 2. [Submitting the query to get back the associated value](#submitting-it).
|
||||
//!
|
||||
//! ## Constructing a constant query
|
||||
//!
|
||||
//! We can use the statically generated interface to build constant queries:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use sp_keyring::AccountKeyring;
|
||||
//!
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! let constant_query = polkadot::constants().system().block_length();
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, we can dynamically construct a constant query:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use sp_keyring::AccountKeyring;
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let account = AccountKeyring::Alice.to_account_id();
|
||||
//! let storage_query = subxt::dynamic::constant("System", "BlockLength");
|
||||
//! ```
|
||||
//!
|
||||
//! Static queries also have a static return type, so the constant is decoded appropriately. In
|
||||
//! addition, they are validated at runtime to ensure that they align with the current node state.
|
||||
//! Dynamic queries must be decoded into some static type manually, or into the dynamic
|
||||
//! [`crate::dynamic::Value`] type.
|
||||
//!
|
||||
//! ## Submitting it
|
||||
//!
|
||||
//! Constant queries are handed to Subxt via [`crate::constants::ConstantsClient::at()`]. It's worth
|
||||
//! noting that constant values are pulled directly out of the node metadata which Subxt has
|
||||
//! already acquired, and so this function requires no network access and is available from a
|
||||
//! [`crate::OfflineClient`].
|
||||
//!
|
||||
//! Here's an example using a static query:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/constants_static.rs")]
|
||||
//! ```
|
||||
//! And here's one using a dynamic query:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/constants_dynamic.rs")]
|
||||
//! ```
|
||||
//!
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Events
|
||||
//!
|
||||
//! In the process of adding extrinsics to a block, they are executed. When extrinsics are executed,
|
||||
//! they normally produce events describing what's happening (at the very least, an event dictating whether
|
||||
//! the extrinsic has succeeded or failed). The node may also emit some events of its own as the block is
|
||||
//! processed.
|
||||
//!
|
||||
//! Events live in a single location in node storage which is overwritten at each block. Normal nodes tend to
|
||||
//! keep a snapshot of the state at a small number of previous blocks, so you can sometimes access
|
||||
//! older events by using [`crate::events::EventsClient::at()`] and providing an older block hash.
|
||||
//!
|
||||
//! When we submit extrinsics using Subxt, methods like [`crate::tx::TxProgress::wait_for_finalized_success()`]
|
||||
//! return [`crate::blocks::ExtrinsicEvents`], which can be used to iterate and inspect the events produced
|
||||
//! for a specific extrinsic. We can also access _all_ of the events produced in a single block using one of these
|
||||
//! two interfaces:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! use subxt::client::OnlineClient;
|
||||
//! use subxt::config::PolkadotConfig;
|
||||
//!
|
||||
//! // Create client:
|
||||
//! let client = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
//!
|
||||
//! // Get events from the latest block (use .at() to specify a block hash):
|
||||
//! let events = client.blocks().at_latest().await?.events().await?;
|
||||
//! // We can use this shorthand too:
|
||||
//! let events = client.events().at_latest().await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! Once we've loaded our events, we can iterate all events or search for specific events via
|
||||
//! methods like [`crate::events::Events::iter()`] and [`crate::events::Events::find()`]. See
|
||||
//! [`crate::events::Events`] and [`crate::events::EventDetails`] for more information.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! Here's an example which puts this all together:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/events.rs")]
|
||||
//! ```
|
||||
//!
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Extrinsics
|
||||
//!
|
||||
//! Extrinsics define function calls and their parameters, and are the only way that you can change
|
||||
//! the state of the blockchain. Submitting extrinsics to a node is one of the core features of
|
||||
//! Subxt, and generally consists of the following steps:
|
||||
//!
|
||||
//! 1. [Constructing an extrinsic payload to submit](#constructing-an-extrinsic-payload).
|
||||
//! 2. [Signing it](#signing-it).
|
||||
//! 3. [Submitting it (optionally with some additional parameters)](#submitting-it).
|
||||
//!
|
||||
//! We'll look at each of these steps in turn.
|
||||
//!
|
||||
//! > As a side note, an _extrinsic_ is anything that can be added to a block, and a _transaction_
|
||||
//! > is an extrinsic that is submitted from a particular user (and is therefore also signed). Subxt
|
||||
//! > can construct unsigned extrinsics, but overwhelmingly you'll need to sign them, and so the
|
||||
//! > documentation tends to use the terms _extrinsic_ and _transaction_ interchangeably.
|
||||
//!
|
||||
//! ## Constructing an extrinsic payload
|
||||
//!
|
||||
//! We can use the statically generated interface to build extrinsic payloads:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! let remark = "Hello there".as_bytes().to_vec();
|
||||
//! let extrinsic_payload = polkadot::tx().system().remark(remark);
|
||||
//! ```
|
||||
//!
|
||||
//! > If you're not sure what types to import and use to build a given payload, you can use the
|
||||
//! > `subxt` CLI tool to generate the interface by using something like `subxt codegen | rustfmt >
|
||||
//! > interface.rs`, to see what types and things are available (or even just to use directly
|
||||
//! > instead of the [`#[subxt]`](crate::subxt) macro).
|
||||
//!
|
||||
//! Alternately, we can dynamically construct an extrinsic payload. This will not be type checked or
|
||||
//! validated until it's submitted:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let extrinsic_payload = subxt::dynamic::tx("System", "remark", vec![
|
||||
//! Value::from_bytes("Hello there")
|
||||
//! ]);
|
||||
//! ```
|
||||
//!
|
||||
//! The [`crate::dynamic::Value`] type is a dynamic type much like a `serde_json::Value` but instead
|
||||
//! represents any type of data that can be SCALE encoded or decoded. It can be serialized,
|
||||
//! deserialized and parsed from/to strings.
|
||||
//!
|
||||
//! A valid extrinsic payload is just something that implements the [`crate::tx::TxPayload`] trait;
|
||||
//! you can implement this trait on your own custom types if the built-in ones are not suitable for
|
||||
//! your needs.
|
||||
//!
|
||||
//! ## Signing it
|
||||
//!
|
||||
//! You'll normally need to sign an extrinsic to prove that it originated from an account that you
|
||||
//! control. To do this, you will typically first create an [`crate::tx::Signer`], which tells Subxt
|
||||
//! who the extrinsic is from, and takes care of signing the relevant details to prove this.
|
||||
//!
|
||||
//! Subxt provides a [`crate::tx::PairSigner`] which implements this trait (if the
|
||||
//! `substrate-compat` feature is enabled) which accepts any valid [`sp_core::Pair`] and uses that
|
||||
//! to sign transactions:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use subxt::tx::PairSigner;
|
||||
//! use sp_core::Pair;
|
||||
//! use subxt::config::PolkadotConfig;
|
||||
//!
|
||||
//! // Get hold of a `Signer` given a test account:
|
||||
//! let pair = sp_keyring::AccountKeyring::Alice.pair();
|
||||
//! let signer = PairSigner::<PolkadotConfig,_>::new(pair);
|
||||
//!
|
||||
//! // Or generate an `sr25519` keypair to use:
|
||||
//! let (pair, _, _) = sp_core::sr25519::Pair::generate_with_phrase(Some("password"));
|
||||
//! let signer = PairSigner::<PolkadotConfig,_>::new(pair);
|
||||
//! ```
|
||||
//!
|
||||
//! See the [`sp_core::Pair`] docs for more ways to generate them.
|
||||
//!
|
||||
//! If this isn't suitable/available, you can either implement [`crate::tx::Signer`] yourself to use
|
||||
//! custom signing logic, or you can use some external signing logic, like so:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! use subxt::client::OnlineClient;
|
||||
//! use subxt::config::PolkadotConfig;
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! // Create client:
|
||||
//! let client = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
//!
|
||||
//! // Create a dummy extrinsic payload to sign:
|
||||
//! let payload = subxt::dynamic::tx("System", "remark", vec![
|
||||
//! Value::from_bytes("Hello there")
|
||||
//! ]);
|
||||
//!
|
||||
//! // Construct the extrinsic but don't sign it. You need to provide the nonce
|
||||
//! // here, or can use `create_partial_signed` to fetch the correct nonce.
|
||||
//! let partial_extrinsic = client.tx().create_partial_signed_with_nonce(
|
||||
//! &payload,
|
||||
//! 0,
|
||||
//! Default::default()
|
||||
//! )?;
|
||||
//!
|
||||
//! // Fetch the payload that needs to be signed:
|
||||
//! let signer_payload = partial_extrinsic.signer_payload();
|
||||
//!
|
||||
//! // ... At this point, we can hand off the `signer_payload` to be signed externally.
|
||||
//! // Ultimately we need to be given back a `signature` (or really, anything
|
||||
//! // that can be SCALE encoded) and an `address`:
|
||||
//! let signature;
|
||||
//! let address;
|
||||
//! # use subxt::tx::Signer;
|
||||
//! # let pair = sp_keyring::AccountKeyring::Alice.pair();
|
||||
//! # let signer = subxt::tx::PairSigner::<PolkadotConfig,_>::new(pair);
|
||||
//! # signature = signer.sign(&signer_payload);
|
||||
//! # address = signer.address();
|
||||
//!
|
||||
//! // Now we can build an extrinsic, which one can call `submit` or `submit_and_watch`
|
||||
//! // on to submit to a node and optionally watch the status.
|
||||
//! let extrinsic = partial_extrinsic.sign_with_address_and_signature(
|
||||
//! &address,
|
||||
//! &signature
|
||||
//! );
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Submitting it
|
||||
//!
|
||||
//! Once we are able to sign the extrinsic, we need to submit it.
|
||||
//!
|
||||
//! ### The high level API
|
||||
//!
|
||||
//! The highest level approach to doing this is to call
|
||||
//! [`crate::tx::TxClient::sign_and_submit_then_watch_default`]. This hands back a
|
||||
//! [`crate::tx::TxProgress`] struct which will monitor the transaction status. We can then call
|
||||
//! [`crate::tx::TxProgress::wait_for_finalized_success()`] to wait for this transaction to make it
|
||||
//! into a finalized block, check for an `ExtrinsicSuccess` event, and then hand back the events for
|
||||
//! inspection. This looks like:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/balance_transfer_basic.rs")]
|
||||
//! ```
|
||||
//! ### Providing extrinsic parameters
|
||||
//!
|
||||
//! If you'd like to provide extrinsic parameters (such as mortality), you can use
|
||||
//! [`crate::tx::TxClient::sign_and_submit_then_watch`] instead, and provide parameters for your
|
||||
//! chain:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/balance_transfer_with_params.rs")]
|
||||
//! ```
|
||||
//! This example doesn't wait for the extrinsic to be included in a block; it just submits it and
|
||||
//! hopes for the best!
|
||||
//!
|
||||
//! ### Custom handling of transaction status updates
|
||||
//!
|
||||
//! If you'd like more control or visibility over exactly which status updates are being emitted for
|
||||
//! the transaction, you can monitor them as they are emitted and react however you choose:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/balance_transfer_status_stream.rs")]
|
||||
//! ```
|
||||
//! Take a look at the API docs for [`crate::tx::TxProgress`], [`crate::tx::TxStatus`] and
|
||||
//! [`crate::tx::TxInBlock`] for more options.
|
||||
//!
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! This modules contains examples of using Subxt; follow the links for more:
|
||||
//!
|
||||
//! - [Extrinsics](extrinsics)
|
||||
//! - [Storage](storage)
|
||||
//! - [Events](events)
|
||||
//! - [Constants](constants)
|
||||
//! - [Blocks](blocks)
|
||||
//! - [Runtime APIs](runtime_apis)
|
||||
//!
|
||||
//! Alternately, [go back](super).
|
||||
|
||||
pub mod blocks;
|
||||
pub mod constants;
|
||||
pub mod events;
|
||||
pub mod extrinsics;
|
||||
pub mod runtime_apis;
|
||||
pub mod storage;
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Runtime API interface
|
||||
//!
|
||||
//! The Runtime API interface allows Subxt to call runtime APIs exposed by certain pallets in order
|
||||
//! to obtain information. Much like [`super::storage`] and [`super::extrinsics`], Making a runtime
|
||||
//! call to a node and getting the response back takes the following steps:
|
||||
//!
|
||||
//! 1. [Constructing a runtime call](#constructing-a-runtime-call)
|
||||
//! 2. [Submitting it to get back the response](#submitting-it)
|
||||
//!
|
||||
//! **Note:** Runtime APIs are only available when using V15 metadata, which is currently unstable.
|
||||
//! You'll need to use `subxt metadata --version unstable` command to download the unstable V15 metadata,
|
||||
//! and activate the `unstable-metadata` feature in Subxt for it to also use this metadata from a node. The
|
||||
//! metadata format is unstable because it may change and break compatibility with Subxt at any moment, so
|
||||
//! use at your own risk.
|
||||
//!
|
||||
//! ## Constructing a runtime call
|
||||
//!
|
||||
//! We can use the statically generated interface to build runtime calls:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use sp_keyring::AccountKeyring;
|
||||
//!
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! let runtime_call = polkadot::apis().metadata().metadata_versions();
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, we can dynamically construct a runtime call:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use sp_keyring::AccountKeyring;
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let account = AccountKeyring::Alice.to_account_id();
|
||||
//! let runtime_call = subxt::dynamic::runtime_api_call(
|
||||
//! "Metadata_metadata_versions",
|
||||
//! Vec::<Value<()>>::new()
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! All valid runtime calls implement [`crate::runtime_api::RuntimeApiPayload`], a trait which
|
||||
//! describes how to encode the runtime call arguments and what return type to decode from the
|
||||
//! response.
|
||||
//!
|
||||
//! ## Submitting it
|
||||
//!
|
||||
//! Runtime calls can be handed to [`crate::runtime_api::RuntimeApi::call()`], which will submit
|
||||
//! them and hand back the associated response.
|
||||
//!
|
||||
//! ### Making a static Runtime API call
|
||||
//!
|
||||
//! The easiest way to make a runtime API call is to use the statically generated interface.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/runtime_apis_static.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ### Making a dynamic Runtime API call
|
||||
//!
|
||||
//! If you'd prefer to construct the call at runtime, you can do this using the
|
||||
//! [`crate::dynamic::runtime_api_call`] method.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/runtime_apis_dynamic.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! ### Making a raw call
|
||||
//!
|
||||
//! This is generally discouraged in favour of one of the above, but may be necessary (especially if
|
||||
//! the node you're talking to does not yet serve V15 metadata). Here, you must manually encode
|
||||
//! the argument bytes and manually provide a type for the response bytes to be decoded into.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/runtime_apis_raw.rs")]
|
||||
//! ```
|
||||
//!
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! # Storage
|
||||
//!
|
||||
//! A Substrate based chain has storage, whose values are determined by the extrinsics added to past
|
||||
//! blocks. Subxt allows you to query the storage of a node, which consists of the following steps:
|
||||
//!
|
||||
//! 1. [Constructing a storage query](#constructing-a-storage-query).
|
||||
//! 2. [Submitting the query to get back the associated values](#submitting-it).
|
||||
//!
|
||||
//! ## Constructing a storage query
|
||||
//!
|
||||
//! We can use the statically generated interface to build storage queries:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use sp_keyring::AccountKeyring;
|
||||
//!
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! let account = AccountKeyring::Alice.to_account_id().into();
|
||||
//! let storage_query = polkadot::storage().system().account(&account);
|
||||
//! ```
|
||||
//!
|
||||
//! Alternately, we can dynamically construct a storage query. This will not be type checked or
|
||||
//! validated until it's submitted:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use sp_keyring::AccountKeyring;
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let account = AccountKeyring::Alice.to_account_id();
|
||||
//! let storage_query = subxt::dynamic::storage("System", "Account", vec![
|
||||
//! Value::from_bytes(account)
|
||||
//! ]);
|
||||
//! ```
|
||||
//!
|
||||
//! As well as accessing specific entries, some storage locations can also be iterated over (such as
|
||||
//! the map of account information). To do this, suffix `_root` onto the query constructor (this
|
||||
//! will only be available on static constructors when iteration is actually possible):
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use sp_keyring::AccountKeyring;
|
||||
//!
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! // A static query capable of iterating over accounts:
|
||||
//! let storage_query = polkadot::storage().system().account_root();
|
||||
//! // A dynamic query to do the same:
|
||||
//! let storage_query = subxt::dynamic::storage_root("System", "Account");
|
||||
//! ```
|
||||
//!
|
||||
//! All valid storage queries implement [`crate::storage::StorageAddress`]. As well as describing
|
||||
//! how to build a valid storage query, this trait also has some associated types that determine the
|
||||
//! shape of the result you'll get back, and determine what you can do with it (ie, can you iterate
|
||||
//! over storage entries using it).
|
||||
//!
|
||||
//! Static queries set appropriate values for these associated types, and can therefore only be used
|
||||
//! where it makes sense. Dynamic queries don't know any better and can be used in more places, but
|
||||
//! may fail at runtime instead if they are invalid in those places.
|
||||
//!
|
||||
//! ## Submitting it
|
||||
//!
|
||||
//! Storage queries can be handed to various functions in [`crate::storage::Storage`] in order to
|
||||
//! obtain the associated values (also referred to as storage entries) back.
|
||||
//!
|
||||
//! ### Fetching storage entries
|
||||
//!
|
||||
//! The simplest way to access storage entries is to construct a query and then call either
|
||||
//! [`crate::storage::Storage::fetch()`] or [`crate::storage::Storage::fetch_or_default()`] (the
|
||||
//! latter will only work for storage queries that have a default value when empty):
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/storage_fetch.rs")]
|
||||
//! ```
|
||||
//! For completeness, below is an example using a dynamic query instead. The return type from a
|
||||
//! dynamic query is a [`crate::dynamic::DecodedValueThunk`], which can be decoded into a
|
||||
//! [`crate::dynamic::Value`], or else the raw bytes can be accessed instead.
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/storage_fetch_dynamic.rs")]
|
||||
//! ```
|
||||
//! ### Iterating storage entries
|
||||
//!
|
||||
//! Many storage entries are maps of values; as well as fetching individual values, it's possible to
|
||||
//! iterate over all of the values stored at that location:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/storage_iterating.rs")]
|
||||
//! ```
|
||||
//! Here's the same logic but using dynamically constructed values instead:
|
||||
//!
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../../../examples/examples/storage_iterating_dynamic.rs")]
|
||||
//! ```
|
||||
//! ### Advanced
|
||||
//!
|
||||
//! For more advanced use cases, have a look at [`crate::storage::Storage::fetch_raw`] and
|
||||
//! [`crate::storage::Storage::fetch_keys`]. Both of these take raw bytes as arguments, which can be
|
||||
//! obtained from a [`crate::storage::StorageAddress`] by using
|
||||
//! [`crate::storage::StorageClient::address_bytes()`] or
|
||||
//! [`crate::storage::StorageClient::address_root_bytes()`].
|
||||
//!
|
||||
@@ -17,9 +17,7 @@ use crate::{
|
||||
tx::TxClient,
|
||||
Config, Metadata,
|
||||
};
|
||||
use codec::Compact;
|
||||
use derivative::Derivative;
|
||||
use frame_metadata::RuntimeMetadataPrefixed;
|
||||
use futures::future;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
@@ -136,10 +134,19 @@ impl<T: Config> OnlineClient<T> {
|
||||
|
||||
/// Fetch the metadata from substrate using the runtime API.
|
||||
async fn fetch_metadata(rpc: &Rpc<T>) -> Result<Metadata, Error> {
|
||||
let (_, meta) = rpc
|
||||
.state_call::<(Compact<u32>, RuntimeMetadataPrefixed)>("Metadata_metadata", None, None)
|
||||
.await?;
|
||||
Ok(meta.try_into()?)
|
||||
#[cfg(feature = "unstable-metadata")]
|
||||
{
|
||||
// Try to fetch the latest unstable metadata, if that fails fall back to
|
||||
// fetching the latest stable metadata.
|
||||
const V15_METADATA_VERSION: u32 = u32::MAX;
|
||||
match rpc.metadata_at_version(V15_METADATA_VERSION).await {
|
||||
Ok(bytes) => Ok(bytes),
|
||||
Err(_) => rpc.metadata().await,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unstable-metadata"))]
|
||||
rpc.metadata().await
|
||||
}
|
||||
|
||||
/// Create an object which can be used to keep the runtime up to date
|
||||
@@ -383,7 +390,7 @@ impl<T: Config> RuntimeUpdaterStream<T> {
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
|
||||
let metadata = match self.client.rpc().metadata(None).await {
|
||||
let metadata = match self.client.rpc().metadata().await {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) => return Some(Err(err)),
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ pub trait Config: 'static {
|
||||
/// transactions associated with a sender account.
|
||||
type Index: Debug + Copy + DeserializeOwned + Into<u64>;
|
||||
|
||||
/// The output of the `Hashing` function.
|
||||
/// The output of the `Hasher` function.
|
||||
type Hash: Debug
|
||||
+ Copy
|
||||
+ Send
|
||||
@@ -95,7 +95,6 @@ pub trait Header: Sized + Encode {
|
||||
/// Take a type implementing [`Config`] (eg [`SubstrateConfig`]), and some type which describes the
|
||||
/// additional and extra parameters to pass to an extrinsic (see [`ExtrinsicParams`]),
|
||||
/// and returns a type implementing [`Config`] with those new [`ExtrinsicParams`].
|
||||
/// ```
|
||||
pub struct WithExtrinsicParams<T: Config, E: extrinsic_params::ExtrinsicParams<T::Index, T::Hash>> {
|
||||
_marker: std::marker::PhantomData<(T, E)>,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
};
|
||||
use scale_decode::DecodeAsType;
|
||||
|
||||
pub use scale_value::Value;
|
||||
pub use scale_value::{At, Value};
|
||||
|
||||
/// A [`scale_value::Value`] type endowed with contextual information
|
||||
/// regarding what type was used to decode each part of it. This implements
|
||||
@@ -28,6 +28,9 @@ pub use crate::constants::dynamic as constant;
|
||||
// Lookup storage values dynamically.
|
||||
pub use crate::storage::{dynamic as storage, dynamic_root as storage_root};
|
||||
|
||||
// Execute runtime API function call dynamically.
|
||||
pub use crate::runtime_api::dynamic as runtime_api_call;
|
||||
|
||||
/// This is the result of making a dynamic request to a node. From this,
|
||||
/// we can return the raw SCALE bytes that we were handed back, or we can
|
||||
/// complete the decoding of the bytes into a [`DecodedValue`] type.
|
||||
|
||||
@@ -76,7 +76,7 @@ impl<T: Config> Events<T> {
|
||||
/// .await?
|
||||
/// .expect("didn't pass a block number; qed");
|
||||
/// // Fetch the metadata of the given block.
|
||||
/// let metadata = client.rpc().metadata(Some(block_hash)).await?;
|
||||
/// let metadata = client.rpc().metadata_legacy(Some(block_hash)).await?;
|
||||
/// // Fetch the events from the client.
|
||||
/// let events = Events::new_from_client(metadata, block_hash, client);
|
||||
/// # Ok(())
|
||||
|
||||
+207
-105
@@ -2,112 +2,13 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
//! Subxt is a library to **sub**mit e**xt**rinsics to a [substrate](https://github.com/paritytech/substrate) node via RPC.
|
||||
//! Subxt is a library for interacting with Substrate based nodes. Using it looks something like this:
|
||||
//!
|
||||
//! The generated Subxt API exposes the ability to:
|
||||
//! - [Submit extrinsics](https://docs.substrate.io/v3/concepts/extrinsics/) (Calls)
|
||||
//! - [Query storage](https://docs.substrate.io/v3/runtime/storage/) (Storage)
|
||||
//! - [Query constants](https://docs.substrate.io/how-to-guides/v3/basics/configurable-constants/) (Constants)
|
||||
//! - [Subscribe to events](https://docs.substrate.io/v3/runtime/events-and-errors/) (Events)
|
||||
//!
|
||||
//! # Initializing the API client
|
||||
//!
|
||||
//! To interact with a node, you'll need to construct a client.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use subxt::{OnlineClient, PolkadotConfig};
|
||||
//!
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() {
|
||||
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
|
||||
//! # }
|
||||
//! ```rust,ignore
|
||||
#![doc = include_str!("../../examples/examples/balance_transfer_basic.rs")]
|
||||
//! ```
|
||||
//!
|
||||
//! This default client connects to a locally running node, but can be configured to point anywhere.
|
||||
//! Additionally, an [`crate::OfflineClient`] is available to perform operations that don't require a
|
||||
//! network connection to a node.
|
||||
//!
|
||||
//! The client takes a type parameter, here [`crate::PolkadotConfig`], which bakes in assumptions about
|
||||
//! the structure of extrinsics and the underlying types used by the node for things like block numbers.
|
||||
//! If the node you'd like to interact with deviates from Polkadot or the default Substrate node in these
|
||||
//! areas, you'll need to configure them by implementing the [`crate::config::Config`] type yourself.
|
||||
//!
|
||||
//! # Generating runtime types
|
||||
//!
|
||||
//! Subxt can optionally generate types at compile time to help you interact with a node. These types are
|
||||
//! generated using metadata which can be downloaded from a node using the [subxt-cli](https://crates.io/crates/subxt-cli)
|
||||
//! tool. These generated types provide a degree of type safety when interacting with a node that is compatible with
|
||||
//! the metadata that they were generated using. We also do runtime checks in case the node you're talking to has
|
||||
//! deviated from the types you're using to communicate with it (see below).
|
||||
//!
|
||||
//! To generate the types, use the `subxt` macro and point it at the metadata you've downloaded, like so:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! #[subxt::subxt(runtime_metadata_path = "metadata.scale")]
|
||||
//! pub mod node_runtime { }
|
||||
//! ```
|
||||
//!
|
||||
//! For more information, please visit the [subxt-codegen](https://docs.rs/subxt-codegen/latest/subxt_codegen/)
|
||||
//! documentation.
|
||||
//!
|
||||
//! You can opt to skip this step and use dynamic queries to talk to nodes instead, which can be useful in some cases,
|
||||
//! but doesn't provide any type safety.
|
||||
//!
|
||||
//! # Interacting with the API
|
||||
//!
|
||||
//! Once instantiated, a client exposes four core functions:
|
||||
//! - `.tx()` for submitting extrinsics/transactions. See [`crate::tx::TxClient`] for more details, or see
|
||||
//! the [balance_transfer](../examples/examples/balance_transfer.rs) example.
|
||||
//! - `.storage()` for fetching and iterating over storage entries. See [`crate::storage::StorageClient`] for more details, or see
|
||||
//! the [fetch_staking_details](../examples/examples/fetch_staking_details.rs) example.
|
||||
//! - `.constants()` for getting hold of constants. See [`crate::constants::ConstantsClient`] for more details, or see
|
||||
//! the [fetch_constants](../examples/examples/fetch_constants.rs) example.
|
||||
//! - `.events()` for subscribing/obtaining events. See [`crate::events::EventsClient`] for more details, or see:
|
||||
//! - [subscribe_all_events](../examples/examples/subscribe_all_events.rs): Subscribe to events emitted from blocks.
|
||||
//! - [subscribe_one_event](../examples/examples/subscribe_one_event.rs): Subscribe and filter by one event.
|
||||
//! - [subscribe_some_events](../examples/examples/subscribe_some_events.rs): Subscribe and filter event.
|
||||
//!
|
||||
//! # Static Metadata Validation
|
||||
//!
|
||||
//! If you use types generated by the [`crate::subxt`] macro, there is a chance that they will fall out of sync
|
||||
//! with the actual state of the node you're trying to interact with.
|
||||
//!
|
||||
//! When you attempt to use any of these static types to interact with a node, Subxt will validate that they are
|
||||
//! still compatible and issue an error if they have deviated.
|
||||
//!
|
||||
//! Additionally, you can validate that the entirety of the statically generated code aligns with a node like so:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use subxt::{OnlineClient, PolkadotConfig};
|
||||
//!
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! # #[tokio::main]
|
||||
//! # async fn main() {
|
||||
//! let api = OnlineClient::<PolkadotConfig>::new().await.unwrap();
|
||||
//!
|
||||
//! if let Err(_e) = polkadot::validate_codegen(&api) {
|
||||
//! println!("Generated code is not up to date with node we're connected to");
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//! ## Opting out of static validation
|
||||
//!
|
||||
//! The static types that are used to query/access information are validated by default, to make sure that they are
|
||||
//! compatible with the node being queried. You can generally call `.unvalidated()` on these static types to
|
||||
//! disable this validation.
|
||||
//!
|
||||
//! # Runtime Updates
|
||||
//!
|
||||
//! The node you're connected to may occasionally perform runtime updates while you're connected, which would ordinarily
|
||||
//! leave the runtime state of the node out of sync with the information Subxt requires to do things like submit
|
||||
//! transactions.
|
||||
//!
|
||||
//! If this is a concern, you can use the `UpdateClient` API to keep the `RuntimeVersion` and `Metadata` of the client
|
||||
//! synced with the target node.
|
||||
//!
|
||||
//! Please visit the [subscribe_runtime_updates](../examples/examples/subscribe_runtime_updates.rs) example for more details.
|
||||
//! Take a look at [the Subxt guide](book) to learn more about how to use Subxt.
|
||||
|
||||
#![deny(
|
||||
bad_style,
|
||||
@@ -132,13 +33,14 @@
|
||||
)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
// The guide is here.
|
||||
pub mod book;
|
||||
|
||||
// Suppress an unused dependency warning because tokio is
|
||||
// only used in example code snippets at the time of writing.
|
||||
#[cfg(test)]
|
||||
use tokio as _;
|
||||
|
||||
pub use subxt_macro::subxt;
|
||||
|
||||
// Used to enable the js feature for wasm.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use getrandom as _;
|
||||
@@ -184,3 +86,203 @@ pub mod ext {
|
||||
#[cfg(feature = "substrate-compat")]
|
||||
pub use sp_runtime;
|
||||
}
|
||||
|
||||
/// Generate a strongly typed API for interacting with a Substrate runtime from its metadata.
|
||||
///
|
||||
/// # Metadata
|
||||
///
|
||||
/// First, you'll need to get hold of some metadata for the node you'd like to interact with. One
|
||||
/// way to do this is by using the `subxt` CLI tool:
|
||||
///
|
||||
/// ```bash
|
||||
/// # Install the CLI tool:
|
||||
/// cargo install subxt-cli
|
||||
/// # Use it to download metadata (in this case, from a node running locally)
|
||||
/// subxt metadata > polkadot_metadata.scale
|
||||
/// ```
|
||||
///
|
||||
/// Run `subxt metadata --help` for more options.
|
||||
///
|
||||
/// # Basic usage
|
||||
///
|
||||
/// Annotate a Rust module with the `subxt` attribute referencing the aforementioned metadata file.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
///
|
||||
/// The `subxt` macro will populate the annotated module with all of the methods and types required
|
||||
/// for interacting with the runtime that the metadata came from via Subxt.
|
||||
///
|
||||
/// # Configuration
|
||||
///
|
||||
/// This macro supports a number of attributes to configure what is generated:
|
||||
///
|
||||
/// ## `crate = "..."`
|
||||
///
|
||||
/// Use this attribute to specify a custom path to the `subxt` crate:
|
||||
///
|
||||
/// ```rust
|
||||
/// # pub extern crate subxt;
|
||||
/// # pub mod path { pub mod to { pub use subxt; } }
|
||||
/// # fn main() {}
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
/// crate = "crate::path::to::subxt"
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
///
|
||||
/// This is useful if you write a library which uses this macro, but don't want to force users to depend on `subxt`
|
||||
/// at the top level too. By default the path `::subxt` is used.
|
||||
///
|
||||
/// ## `substitute_type(path = "...", with = "...")`
|
||||
///
|
||||
/// This attribute replaces any reference to the generated type at the path given by `path` with a
|
||||
/// reference to the path given by `with`.
|
||||
///
|
||||
/// ```rust
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
/// substitute_type(path = "sp_arithmetic::per_things::Perbill", with = "crate::Foo")
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
///
|
||||
/// # #[derive(
|
||||
/// # scale_encode::EncodeAsType,
|
||||
/// # scale_decode::DecodeAsType,
|
||||
/// # codec::Encode,
|
||||
/// # codec::Decode,
|
||||
/// # Clone,
|
||||
/// # Debug,
|
||||
/// # )]
|
||||
/// // In reality this needs some traits implementing on
|
||||
/// // it to allow it to be used in place of Perbill:
|
||||
/// pub struct Foo(u32);
|
||||
/// # impl codec::CompactAs for Foo {
|
||||
/// # type As = u32;
|
||||
/// # fn encode_as(&self) -> &Self::As {
|
||||
/// # &self.0
|
||||
/// # }
|
||||
/// # fn decode_from(x: Self::As) -> Result<Self, codec::Error> {
|
||||
/// # Ok(Foo(x))
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl From<codec::Compact<Foo>> for Foo {
|
||||
/// # fn from(v: codec::Compact<Foo>) -> Foo {
|
||||
/// # v.0
|
||||
/// # }
|
||||
/// # }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// If the type you're substituting contains generic parameters, you can "pattern match" on those, and
|
||||
/// make use of them in the substituted type, like so:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
/// substitute_type(
|
||||
/// path = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
/// with = "::subxt::utils::Static<::sp_runtime::MultiAddress<A, B>>"
|
||||
/// )
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
///
|
||||
/// The above is also an example of using the [`crate::utils::Static`] type to wrap some type which doesn't
|
||||
/// on it's own implement [`scale_encode::EncodeAsType`] or [`scale_decode::DecodeAsType`], which are required traits
|
||||
/// for any substitute type to implement by default.
|
||||
///
|
||||
/// ## `derive_for_all_types = "..."`
|
||||
///
|
||||
/// By default, all generated types derive a small set of traits. This attribute allows you to derive additional
|
||||
/// traits on all generated types:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
/// derive_for_all_types = "Eq, PartialEq"
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
///
|
||||
/// Any substituted types (including the default substitutes) must also implement these traits in order to avoid errors
|
||||
/// here.
|
||||
///
|
||||
/// ## `derive_for_type(path = "...", derive = "...")`
|
||||
///
|
||||
/// Unlike the above, which derives some trait on every generated type, this attribute allows you to derive traits only
|
||||
/// for specific types. Note that any types which are used inside the specified type may also need to derive the same traits.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
/// derive_for_all_types = "Eq, PartialEq",
|
||||
/// derive_for_type(path = "frame_support::PalletId", derive = "Ord, PartialOrd"),
|
||||
/// derive_for_type(path = "sp_runtime::ModuleError", derive = "Hash"),
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
///
|
||||
/// ## `runtime_metadata_url = "..."`
|
||||
///
|
||||
/// This attribute can be used instead of `runtime_metadata_path` and will tell the macro to download metadata from a node running
|
||||
/// at the provided URL, rather than a node running locally. This can be useful in CI, but is **not recommended** in production code,
|
||||
/// since it runs at compile time and will cause compilation to fail if the node at the given address is unavailable or unresponsive.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_url = "wss://rpc.polkadot.io:443"
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
///
|
||||
/// ## `generate_docs`
|
||||
///
|
||||
/// By default, documentation is not generated via the macro, since IDEs do not typically make use of it. This attribute
|
||||
/// forces documentation to be generated, too.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
/// generate_docs
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
///
|
||||
/// ## `runtime_types_only`
|
||||
///
|
||||
/// By default, the macro will generate various interfaces to make using Subxt simpler in addition with any types that need
|
||||
/// generating to make this possible. This attribute makes the codegen only generate the types and not the Subxt interface.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
/// runtime_types_only
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
/// ## `no_default_derives`
|
||||
///
|
||||
/// By default, the macro will add all derives necessary for the generated code to play nicely with Subxt. Adding this attribute
|
||||
/// removes all default derives.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// #[subxt::subxt(
|
||||
/// runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
|
||||
/// runtime_types_only,
|
||||
/// no_default_derives,
|
||||
/// derive_for_all_types="codec::Encode, codec::Decode"
|
||||
/// )]
|
||||
/// mod polkadot {}
|
||||
/// ```
|
||||
///
|
||||
/// **Note**: At the moment, you must derive at least one of `codec::Encode` or `codec::Decode` or `scale_encode::EncodeAsType` or
|
||||
/// `scale_decode::DecodeAsType` (because we add `#[codec(..)]` attributes on some fields/types during codegen), and you must use this
|
||||
/// feature in conjunction with `runtime_types_only` (or manually specify a bunch of defaults to make codegen work properly when
|
||||
/// generating the subxt interfaces).
|
||||
pub use subxt_macro::subxt;
|
||||
|
||||
@@ -5,23 +5,23 @@
|
||||
use parking_lot::RwLock;
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
||||
/// A cache with the simple goal of storing 32 byte hashes against pallet+item keys
|
||||
/// A cache with the simple goal of storing 32 byte hashes against root+item keys
|
||||
#[derive(Default, Debug)]
|
||||
pub struct HashCache {
|
||||
inner: RwLock<HashMap<PalletItemKey<'static>, [u8; 32]>>,
|
||||
inner: RwLock<HashMap<RootItemKey<'static>, [u8; 32]>>,
|
||||
}
|
||||
|
||||
impl HashCache {
|
||||
/// get a hash out of the cache by its pallet and item key. If the item doesn't exist,
|
||||
/// get a hash out of the cache by its root and item key. If the item doesn't exist,
|
||||
/// run the function provided to obtain a hash to insert (or bail with some error on failure).
|
||||
pub fn get_or_insert<F, E>(&self, pallet: &str, item: &str, f: F) -> Result<[u8; 32], E>
|
||||
pub fn get_or_insert<F, E>(&self, root: &str, item: &str, f: F) -> Result<[u8; 32], E>
|
||||
where
|
||||
F: FnOnce() -> Result<[u8; 32], E>,
|
||||
{
|
||||
let maybe_hash = self
|
||||
.inner
|
||||
.read()
|
||||
.get(&PalletItemKey::new(pallet, item))
|
||||
.get(&RootItemKey::new(root, item))
|
||||
.copied();
|
||||
|
||||
if let Some(hash) = maybe_hash {
|
||||
@@ -29,10 +29,9 @@ impl HashCache {
|
||||
}
|
||||
|
||||
let hash = f()?;
|
||||
self.inner.write().insert(
|
||||
PalletItemKey::new(pallet.to_string(), item.to_string()),
|
||||
hash,
|
||||
);
|
||||
self.inner
|
||||
.write()
|
||||
.insert(RootItemKey::new(root.to_string(), item.to_string()), hash);
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
@@ -41,14 +40,14 @@ impl HashCache {
|
||||
/// This exists so that we can look items up in the cache using &strs, without having to allocate
|
||||
/// Strings first (as you'd have to do to construct something like an `&(String,String)` key).
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
struct PalletItemKey<'a> {
|
||||
struct RootItemKey<'a> {
|
||||
pallet: Cow<'a, str>,
|
||||
item: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> PalletItemKey<'a> {
|
||||
impl<'a> RootItemKey<'a> {
|
||||
fn new(pallet: impl Into<Cow<'a, str>>, item: impl Into<Cow<'a, str>>) -> Self {
|
||||
PalletItemKey {
|
||||
RootItemKey {
|
||||
pallet: pallet.into(),
|
||||
item: item.into(),
|
||||
}
|
||||
@@ -75,7 +74,7 @@ mod tests {
|
||||
cache
|
||||
.inner
|
||||
.read()
|
||||
.get(&PalletItemKey::new(pallet, item))
|
||||
.get(&RootItemKey::new(pallet, item))
|
||||
.unwrap(),
|
||||
&value.unwrap()
|
||||
);
|
||||
|
||||
@@ -30,6 +30,9 @@ pub enum MetadataError {
|
||||
/// Event is not in metadata.
|
||||
#[error("Pallet {0}, Error {0} not found")]
|
||||
ErrorNotFound(u8, u8),
|
||||
/// Runtime function is not in metadata.
|
||||
#[error("Runtime function not found")]
|
||||
RuntimeFnNotFound,
|
||||
/// Storage is not in metadata.
|
||||
#[error("Storage not found")]
|
||||
StorageNotFound,
|
||||
@@ -57,6 +60,9 @@ pub enum MetadataError {
|
||||
/// Runtime storage metadata is incompatible with the static one.
|
||||
#[error("Pallet {0} Storage {0} has incompatible metadata")]
|
||||
IncompatibleStorageMetadata(String, String),
|
||||
/// Runtime API metadata is incompatible with the static one.
|
||||
#[error("Runtime API Trait {0} Method {0} has incompatible metadata")]
|
||||
IncompatibleRuntimeApiMetadata(String, String),
|
||||
/// Runtime metadata is not fully compatible with the static one.
|
||||
#[error("Node metadata is not fully compatible")]
|
||||
IncompatibleMetadata,
|
||||
@@ -79,6 +85,9 @@ struct MetadataInner {
|
||||
// an extrinsic fails.
|
||||
dispatch_error_ty: Option<u32>,
|
||||
|
||||
// Runtime API metadata
|
||||
runtime_apis: HashMap<String, RuntimeFnMetadata>,
|
||||
|
||||
// The hashes uniquely identify parts of the metadata; different
|
||||
// hashes mean some type difference exists between static and runtime
|
||||
// versions. We cache them here to avoid recalculating:
|
||||
@@ -86,6 +95,7 @@ struct MetadataInner {
|
||||
cached_call_hashes: HashCache,
|
||||
cached_constant_hashes: HashCache,
|
||||
cached_storage_hashes: HashCache,
|
||||
cached_runtime_hashes: HashCache,
|
||||
}
|
||||
|
||||
/// A representation of the runtime metadata received from a node.
|
||||
@@ -95,6 +105,14 @@ pub struct Metadata {
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// Returns a reference to [`RuntimeFnMetadata`].
|
||||
pub fn runtime_fn(&self, name: &str) -> Result<&RuntimeFnMetadata, MetadataError> {
|
||||
self.inner
|
||||
.runtime_apis
|
||||
.get(name)
|
||||
.ok_or(MetadataError::RuntimeFnNotFound)
|
||||
}
|
||||
|
||||
/// Returns a reference to [`PalletMetadata`].
|
||||
pub fn pallet(&self, name: &str) -> Result<&PalletMetadata, MetadataError> {
|
||||
self.inner
|
||||
@@ -158,7 +176,7 @@ impl Metadata {
|
||||
.get_or_insert(pallet, storage, || {
|
||||
subxt_metadata::get_storage_hash(&self.inner.metadata, pallet, storage).map_err(
|
||||
|e| match e {
|
||||
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
|
||||
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
|
||||
subxt_metadata::NotFound::Item => MetadataError::StorageNotFound,
|
||||
},
|
||||
)
|
||||
@@ -172,7 +190,7 @@ impl Metadata {
|
||||
.get_or_insert(pallet, constant, || {
|
||||
subxt_metadata::get_constant_hash(&self.inner.metadata, pallet, constant).map_err(
|
||||
|e| match e {
|
||||
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
|
||||
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
|
||||
subxt_metadata::NotFound::Item => MetadataError::ConstantNotFound,
|
||||
},
|
||||
)
|
||||
@@ -186,13 +204,27 @@ impl Metadata {
|
||||
.get_or_insert(pallet, function, || {
|
||||
subxt_metadata::get_call_hash(&self.inner.metadata, pallet, function).map_err(|e| {
|
||||
match e {
|
||||
subxt_metadata::NotFound::Pallet => MetadataError::PalletNotFound,
|
||||
subxt_metadata::NotFound::Root => MetadataError::PalletNotFound,
|
||||
subxt_metadata::NotFound::Item => MetadataError::CallNotFound,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Obtain the unique hash for a runtime API function.
|
||||
pub fn runtime_api_hash(
|
||||
&self,
|
||||
trait_name: &str,
|
||||
method_name: &str,
|
||||
) -> Result<[u8; 32], MetadataError> {
|
||||
self.inner
|
||||
.cached_runtime_hashes
|
||||
.get_or_insert(trait_name, method_name, || {
|
||||
subxt_metadata::get_runtime_api_hash(&self.inner.metadata, trait_name, method_name)
|
||||
.map_err(|_| MetadataError::RuntimeFnNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
/// Obtain the unique hash for this metadata.
|
||||
pub fn metadata_hash<T: AsRef<str>>(&self, pallets: &[T]) -> [u8; 32] {
|
||||
if let Some(hash) = *self.inner.cached_metadata_hash.read() {
|
||||
@@ -206,6 +238,42 @@ impl Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for a specific runtime API function.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RuntimeFnMetadata {
|
||||
/// The trait name of the runtime function.
|
||||
trait_name: String,
|
||||
/// The method name of the runtime function.
|
||||
method_name: String,
|
||||
/// The parameter name and type IDs interpreted as `scale_info::Field`
|
||||
/// for ease of decoding.
|
||||
fields: Vec<scale_info::Field<scale_info::form::PortableForm>>,
|
||||
/// The type ID of the return type.
|
||||
return_id: u32,
|
||||
}
|
||||
|
||||
impl RuntimeFnMetadata {
|
||||
/// Get the parameters as fields.
|
||||
pub fn fields(&self) -> &[scale_info::Field<scale_info::form::PortableForm>] {
|
||||
&self.fields
|
||||
}
|
||||
|
||||
/// Return the trait name of the runtime function.
|
||||
pub fn trait_name(&self) -> &str {
|
||||
&self.trait_name
|
||||
}
|
||||
|
||||
/// Return the method name of the runtime function.
|
||||
pub fn method_name(&self) -> &str {
|
||||
&self.method_name
|
||||
}
|
||||
|
||||
/// Get the type ID of the return type.
|
||||
pub fn return_id(&self) -> u32 {
|
||||
self.return_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for a specific pallet.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PalletMetadata {
|
||||
@@ -376,6 +444,49 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
|
||||
_ => return Err(InvalidMetadataError::InvalidVersion),
|
||||
};
|
||||
|
||||
let runtime_apis: HashMap<String, RuntimeFnMetadata> = metadata
|
||||
.apis
|
||||
.iter()
|
||||
.flat_map(|trait_metadata| {
|
||||
let trait_name = &trait_metadata.name;
|
||||
|
||||
trait_metadata
|
||||
.methods
|
||||
.iter()
|
||||
.map(|method_metadata| {
|
||||
// Function named used by substrate to identify the runtime call.
|
||||
let fn_name = format!("{}_{}", trait_name, method_metadata.name);
|
||||
|
||||
// Parameters mapped as `scale_info::Field` to allow dynamic decoding.
|
||||
let fields: Vec<_> = method_metadata
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|input| {
|
||||
let name = input.name.clone();
|
||||
let ty = input.ty.id;
|
||||
scale_info::Field {
|
||||
name: Some(name),
|
||||
ty: ty.into(),
|
||||
type_name: None,
|
||||
docs: Default::default(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let return_id = method_metadata.output.id;
|
||||
let metadata = RuntimeFnMetadata {
|
||||
fields,
|
||||
return_id,
|
||||
trait_name: trait_name.clone(),
|
||||
method_name: method_metadata.name.clone(),
|
||||
};
|
||||
|
||||
(fn_name, metadata)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let get_type_def_variant = |type_id: u32| {
|
||||
let ty = metadata
|
||||
.types
|
||||
@@ -492,10 +603,12 @@ impl TryFrom<RuntimeMetadataPrefixed> for Metadata {
|
||||
events,
|
||||
errors,
|
||||
dispatch_error_ty,
|
||||
runtime_apis,
|
||||
cached_metadata_hash: Default::default(),
|
||||
cached_call_hashes: Default::default(),
|
||||
cached_constant_hashes: Default::default(),
|
||||
cached_storage_hashes: Default::default(),
|
||||
cached_runtime_hashes: Default::default(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ pub use metadata_location::MetadataLocation;
|
||||
|
||||
pub use metadata_type::{
|
||||
ErrorMetadata, EventMetadata, InvalidMetadataError, Metadata, MetadataError, PalletMetadata,
|
||||
RuntimeFnMetadata,
|
||||
};
|
||||
|
||||
pub use decode_encode_traits::{DecodeWithMetadata, EncodeWithMetadata};
|
||||
|
||||
+48
-5
@@ -143,8 +143,8 @@ impl<T: Config> Rpc<T> {
|
||||
genesis_hash.ok_or_else(|| "Genesis hash not found".into())
|
||||
}
|
||||
|
||||
/// Fetch the metadata
|
||||
pub async fn metadata(&self, at: Option<T::Hash>) -> Result<Metadata, Error> {
|
||||
/// Fetch the metadata via the legacy `state_getMetadata` RPC method.
|
||||
pub async fn metadata_legacy(&self, at: Option<T::Hash>) -> Result<Metadata, Error> {
|
||||
let bytes: types::Bytes = self
|
||||
.client
|
||||
.request("state_getMetadata", rpc_params![at])
|
||||
@@ -347,13 +347,13 @@ impl<T: Config> Rpc<T> {
|
||||
Ok(xt_hash)
|
||||
}
|
||||
|
||||
/// Execute a runtime API call.
|
||||
pub async fn state_call<Res: Decode>(
|
||||
/// Execute a runtime API call via `state_call` RPC method.
|
||||
pub async fn state_call_raw(
|
||||
&self,
|
||||
function: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: Option<T::Hash>,
|
||||
) -> Result<Res, Error> {
|
||||
) -> Result<types::Bytes, Error> {
|
||||
let call_parameters = call_parameters.unwrap_or_default();
|
||||
let bytes: types::Bytes = self
|
||||
.client
|
||||
@@ -362,11 +362,54 @@ impl<T: Config> Rpc<T> {
|
||||
rpc_params![function, to_hex(call_parameters), at],
|
||||
)
|
||||
.await?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Execute a runtime API call and decode the result.
|
||||
pub async fn state_call<Res: Decode>(
|
||||
&self,
|
||||
function: &str,
|
||||
call_parameters: Option<&[u8]>,
|
||||
at: Option<T::Hash>,
|
||||
) -> Result<Res, Error> {
|
||||
let bytes = self.state_call_raw(function, call_parameters, at).await?;
|
||||
let cursor = &mut &bytes[..];
|
||||
let res: Res = Decode::decode(cursor)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Execute runtime API call and return the specified runtime metadata version.
|
||||
pub async fn metadata_at_version(&self, version: u32) -> Result<Metadata, Error> {
|
||||
let param = version.encode();
|
||||
let opaque: Option<frame_metadata::OpaqueMetadata> = self
|
||||
.state_call("Metadata_metadata_at_version", Some(¶m), None)
|
||||
.await?;
|
||||
|
||||
let bytes = opaque.ok_or(Error::Other("Metadata version not found".into()))?;
|
||||
|
||||
let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes.0[..])?;
|
||||
|
||||
let metadata: Metadata = meta.try_into()?;
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// Execute a runtime API call into `Metadata_metadata` method
|
||||
/// to fetch the latest available metadata.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This returns the same output as [`Self::metadata`], but calls directly
|
||||
/// into the runtime.
|
||||
pub async fn metadata(&self) -> Result<Metadata, Error> {
|
||||
let bytes: frame_metadata::OpaqueMetadata =
|
||||
self.state_call("Metadata_metadata", None, None).await?;
|
||||
|
||||
let meta: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes.0[..])?;
|
||||
|
||||
let metadata: Metadata = meta.try_into()?;
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// Create and submit an extrinsic and return a subscription to the events triggered.
|
||||
pub async fn watch_extrinsic<X: Encode>(
|
||||
&self,
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
//! Types associated with executing runtime API calls.
|
||||
|
||||
mod runtime_client;
|
||||
mod runtime_payload;
|
||||
mod runtime_types;
|
||||
|
||||
pub use runtime_client::RuntimeApiClient;
|
||||
pub use runtime_payload::{dynamic, DynamicRuntimeApiPayload, Payload, RuntimeApiPayload};
|
||||
pub use runtime_types::RuntimeApi;
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use scale_encode::EncodeAsFields;
|
||||
use scale_value::Composite;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::dynamic::DecodedValueThunk;
|
||||
use crate::{metadata::DecodeWithMetadata, Error, Metadata};
|
||||
|
||||
/// This represents a runtime API payload that can call into the runtime of node.
|
||||
///
|
||||
/// # Components
|
||||
///
|
||||
/// - associated return type
|
||||
///
|
||||
/// Resulting bytes of the call are interpreted into this type.
|
||||
///
|
||||
/// - runtime function name
|
||||
///
|
||||
/// The function name of the runtime API call. This is obtained by concatenating
|
||||
/// the runtime trait name with the trait's method.
|
||||
///
|
||||
/// For example, the substrate runtime trait [Metadata](https://github.com/paritytech/substrate/blob/cb954820a8d8d765ce75021e244223a3b4d5722d/primitives/api/src/lib.rs#L745)
|
||||
/// contains the `metadata_at_version` function. The corresponding runtime function
|
||||
/// is `Metadata_metadata_at_version`.
|
||||
///
|
||||
/// - encoded arguments
|
||||
///
|
||||
/// Each argument of the runtime function must be scale-encoded.
|
||||
pub trait RuntimeApiPayload {
|
||||
/// The return type of the function call.
|
||||
// Note: `DecodeWithMetadata` is needed to decode the function call result
|
||||
// with the `subxt::Metadata.
|
||||
type ReturnType: DecodeWithMetadata;
|
||||
|
||||
/// The runtime API function name.
|
||||
fn fn_name(&self) -> &str;
|
||||
|
||||
/// Scale encode the arguments data.
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error>;
|
||||
|
||||
/// Encode arguments data and return the output. This is a convenience
|
||||
/// wrapper around [`RuntimeApiPayload::encode_args_to`].
|
||||
fn encode_args(&self, metadata: &Metadata) -> Result<Vec<u8>, Error> {
|
||||
let mut v = Vec::new();
|
||||
self.encode_args_to(metadata, &mut v)?;
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
/// Returns the statically generated validation hash.
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A runtime API payload containing the generic argument data
|
||||
/// and interpreting the result of the call as `ReturnTy`.
|
||||
///
|
||||
/// This can be created from static values (ie those generated
|
||||
/// via the `subxt` macro) or dynamic values via [`dynamic`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Payload<ArgsData, ReturnTy> {
|
||||
fn_name: Cow<'static, str>,
|
||||
args_data: ArgsData,
|
||||
validation_hash: Option<[u8; 32]>,
|
||||
_marker: PhantomData<ReturnTy>,
|
||||
}
|
||||
|
||||
impl<ArgsData: EncodeAsFields, ReturnTy: DecodeWithMetadata> RuntimeApiPayload
|
||||
for Payload<ArgsData, ReturnTy>
|
||||
{
|
||||
type ReturnType = ReturnTy;
|
||||
|
||||
fn fn_name(&self) -> &str {
|
||||
&self.fn_name
|
||||
}
|
||||
|
||||
fn encode_args_to(&self, metadata: &Metadata, out: &mut Vec<u8>) -> Result<(), Error> {
|
||||
let fn_metadata = metadata.runtime_fn(&self.fn_name)?;
|
||||
|
||||
self.args_data
|
||||
.encode_as_fields_to(fn_metadata.fields(), metadata.types(), out)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validation_hash(&self) -> Option<[u8; 32]> {
|
||||
self.validation_hash
|
||||
}
|
||||
}
|
||||
|
||||
/// A dynamic runtime API payload.
|
||||
pub type DynamicRuntimeApiPayload = Payload<Composite<()>, DecodedValueThunk>;
|
||||
|
||||
impl<ReturnTy, ArgsData> Payload<ArgsData, ReturnTy> {
|
||||
/// Create a new [`Payload`].
|
||||
pub fn new(fn_name: impl Into<String>, args_data: ArgsData) -> Self {
|
||||
Payload {
|
||||
fn_name: Cow::Owned(fn_name.into()),
|
||||
args_data,
|
||||
validation_hash: None,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new static [`Payload`] using static function name
|
||||
/// and scale-encoded argument data.
|
||||
///
|
||||
/// This is only expected to be used from codegen.
|
||||
#[doc(hidden)]
|
||||
pub fn new_static(
|
||||
fn_name: &'static str,
|
||||
args_data: ArgsData,
|
||||
hash: [u8; 32],
|
||||
) -> Payload<ArgsData, ReturnTy> {
|
||||
Payload {
|
||||
fn_name: Cow::Borrowed(fn_name),
|
||||
args_data,
|
||||
validation_hash: Some(hash),
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Do not validate this call prior to submitting it.
|
||||
pub fn unvalidated(self) -> Self {
|
||||
Self {
|
||||
validation_hash: None,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the function name.
|
||||
pub fn fn_name(&self) -> &str {
|
||||
&self.fn_name
|
||||
}
|
||||
|
||||
/// Returns the arguments data.
|
||||
pub fn args_data(&self) -> &ArgsData {
|
||||
&self.args_data
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicRuntimeApiPayload`].
|
||||
pub fn dynamic(
|
||||
fn_name: impl Into<String>,
|
||||
args_data: impl Into<Composite<()>>,
|
||||
) -> DynamicRuntimeApiPayload {
|
||||
Payload::new(fn_name, args_data.into())
|
||||
}
|
||||
@@ -2,11 +2,13 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{client::OnlineClientT, error::Error, Config};
|
||||
use crate::{client::OnlineClientT, error::Error, metadata::DecodeWithMetadata, Config};
|
||||
use codec::Decode;
|
||||
use derivative::Derivative;
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
|
||||
use super::RuntimeApiPayload;
|
||||
|
||||
/// Execute runtime API calls.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Clone(bound = "Client: Clone"))]
|
||||
@@ -50,4 +52,57 @@ where
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a runtime API call.
|
||||
pub fn call<Call: RuntimeApiPayload>(
|
||||
&self,
|
||||
payload: Call,
|
||||
) -> impl Future<Output = Result<Call::ReturnType, Error>> {
|
||||
let client = self.client.clone();
|
||||
let block_hash = self.block_hash;
|
||||
// Ensure that the returned future doesn't have a lifetime tied to api.runtime_api(),
|
||||
// which is a temporary thing we'll be throwing away quickly:
|
||||
async move {
|
||||
let metadata = client.metadata();
|
||||
let function = payload.fn_name();
|
||||
|
||||
// Check if the function is present in the runtime metadata.
|
||||
let fn_metadata = metadata.runtime_fn(function)?;
|
||||
// Return type ID used for dynamic decoding.
|
||||
let return_id = fn_metadata.return_id();
|
||||
|
||||
// Validate the runtime API payload hash against the compile hash from codegen.
|
||||
if let Some(static_hash) = payload.validation_hash() {
|
||||
let runtime_hash = metadata
|
||||
.runtime_api_hash(fn_metadata.trait_name(), fn_metadata.method_name())?;
|
||||
|
||||
if static_hash != runtime_hash {
|
||||
return Err(
|
||||
crate::metadata::MetadataError::IncompatibleRuntimeApiMetadata(
|
||||
fn_metadata.trait_name().into(),
|
||||
fn_metadata.method_name().into(),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the arguments of the runtime call.
|
||||
// For static payloads (codegen) this is pass-through, bytes are not altered.
|
||||
// For dynamic payloads this relies on `scale_value::encode_as_fields_to`.
|
||||
let params = payload.encode_args(&metadata)?;
|
||||
|
||||
let bytes = client
|
||||
.rpc()
|
||||
.state_call_raw(function, Some(params.as_slice()), Some(block_hash))
|
||||
.await?;
|
||||
|
||||
let value = <Call::ReturnType as DecodeWithMetadata>::decode_with_metadata(
|
||||
&mut &bytes[..],
|
||||
return_id,
|
||||
&metadata,
|
||||
)?;
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ sp-core = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-keyring = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
subxt = { workspace = true }
|
||||
subxt = { workspace = true, features = ["unstable-metadata"] }
|
||||
subxt-codegen = { workspace = true }
|
||||
subxt-metadata = { workspace = true }
|
||||
test-runtime = { workspace = true }
|
||||
|
||||
@@ -113,7 +113,7 @@ async fn runtime_api_call() -> Result<(), subxt::Error> {
|
||||
};
|
||||
|
||||
// Compare the runtime API call against the `state_getMetadata`.
|
||||
let metadata = api.rpc().metadata(None).await?;
|
||||
let metadata = api.rpc().metadata_legacy(None).await?;
|
||||
let metadata = metadata.runtime_metadata();
|
||||
assert_eq!(&metadata_call, metadata);
|
||||
Ok(())
|
||||
|
||||
@@ -438,7 +438,7 @@ async fn rpc_state_call() {
|
||||
_ => panic!("Metadata V14 or V15 unavailable"),
|
||||
};
|
||||
// Compare the runtime API call against the `state_getMetadata`.
|
||||
let metadata = api.rpc().metadata(None).await.unwrap();
|
||||
let metadata = api.rpc().metadata_legacy(None).await.unwrap();
|
||||
let metadata = metadata.runtime_metadata();
|
||||
assert_eq!(&metadata_call, metadata);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,14 @@ fn metadata_docs() -> Vec<String> {
|
||||
// Note: Extrinsics do not have associated documentation, but is implied by
|
||||
// associated Type.
|
||||
|
||||
// Inspect the runtime API types and collect the documentation.
|
||||
for api in metadata.apis {
|
||||
docs.extend(api.docs);
|
||||
for method in api.methods {
|
||||
docs.extend(method.docs);
|
||||
}
|
||||
}
|
||||
|
||||
docs
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -18,6 +18,8 @@ mod frame;
|
||||
#[cfg(test)]
|
||||
mod metadata;
|
||||
#[cfg(test)]
|
||||
mod runtime_api;
|
||||
#[cfg(test)]
|
||||
mod storage;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{node_runtime, pair_signer, test_context};
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::utils::AccountId32;
|
||||
|
||||
#[tokio::test]
|
||||
async fn account_nonce() -> Result<(), subxt::Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let signer = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice: AccountId32 = AccountKeyring::Alice.to_account_id().into();
|
||||
|
||||
// Check Alice nonce is starting from 0.
|
||||
let runtime_api_call = node_runtime::apis()
|
||||
.account_nonce_api()
|
||||
.account_nonce(alice.clone());
|
||||
let nonce = api
|
||||
.runtime_api()
|
||||
.at_latest()
|
||||
.await?
|
||||
.call(runtime_api_call)
|
||||
.await?;
|
||||
assert_eq!(nonce, 0);
|
||||
|
||||
// Do some transaction to bump the Alice nonce to 1:
|
||||
let remark_tx = node_runtime::tx().system().remark(vec![1, 2, 3, 4, 5]);
|
||||
api.tx()
|
||||
.sign_and_submit_then_watch_default(&remark_tx, &signer)
|
||||
.await?
|
||||
.wait_for_finalized_success()
|
||||
.await?;
|
||||
|
||||
let runtime_api_call = node_runtime::apis()
|
||||
.account_nonce_api()
|
||||
.account_nonce(alice);
|
||||
let nonce = api
|
||||
.runtime_api()
|
||||
.at_latest()
|
||||
.await?
|
||||
.call(runtime_api_call)
|
||||
.await?;
|
||||
assert_eq!(nonce, 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -14,3 +14,5 @@ serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
which = { workspace = true }
|
||||
jsonrpsee = { workspace = true, features = ["async-client", "client-ws-transport"] }
|
||||
hex = { workspace = true }
|
||||
codec = { workspace = true }
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use std::{env, fs, path::Path};
|
||||
use substrate_runner::{Error as SubstrateNodeError, SubstrateNode};
|
||||
|
||||
@@ -36,22 +37,31 @@ async fn run() {
|
||||
|
||||
// Download metadata from binary. Avoid Subxt dep on `subxt::rpc::types::Bytes`and just impl here.
|
||||
// This may at least prevent this script from running so often (ie whenever we change Subxt).
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct Bytes(#[serde(with = "impl_serde::serialize")] pub Vec<u8>);
|
||||
let metadata_bytes: Bytes = {
|
||||
const V15_METADATA_VERSION: u32 = u32::MAX;
|
||||
let bytes = V15_METADATA_VERSION.encode();
|
||||
let version: String = format!("0x{}", hex::encode(&bytes));
|
||||
let raw: String = {
|
||||
use client::ClientT;
|
||||
client::build(&format!("ws://localhost:{port}"))
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("Failed to connect to node: {e}"))
|
||||
.request("state_getMetadata", client::rpc_params![])
|
||||
.request(
|
||||
"state_call",
|
||||
client::rpc_params!["Metadata_metadata_at_version", &version],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("Failed to obtain metadata from node: {e}"))
|
||||
};
|
||||
let raw_bytes = hex::decode(raw.trim_start_matches("0x"))
|
||||
.unwrap_or_else(|e| panic!("Failed to hex-decode metadata: {e}"));
|
||||
let bytes: Option<Vec<u8>> = Decode::decode(&mut &raw_bytes[..])
|
||||
.unwrap_or_else(|e| panic!("Failed to decode metadata bytes: {e}"));
|
||||
let metadata_bytes = bytes.expect("Metadata version not found");
|
||||
|
||||
// Save metadata to a file:
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let metadata_path = Path::new(&out_dir).join("metadata.scale");
|
||||
fs::write(&metadata_path, metadata_bytes.0).expect("Couldn't write metadata output");
|
||||
fs::write(&metadata_path, metadata_bytes).expect("Couldn't write metadata output");
|
||||
|
||||
// Write out our expression to generate the runtime API to a file. Ideally, we'd just write this code
|
||||
// in lib.rs, but we must pass a string literal (and not `concat!(..)`) as an arg to `runtime_metadata_path`,
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct DoesntImplEncodeDecodeAsType(u16);
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale",
|
||||
substitute_type(
|
||||
type = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
// Discarding both params:
|
||||
with = "crate::CustomAddress"
|
||||
)
|
||||
@@ -32,7 +32,7 @@ pub mod node_runtime {}
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale",
|
||||
substitute_type(
|
||||
type = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
// Discarding second param:
|
||||
with = "crate::Generic<A>"
|
||||
)
|
||||
@@ -42,7 +42,7 @@ pub mod node_runtime2 {}
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale",
|
||||
substitute_type(
|
||||
type = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
// Discarding first param:
|
||||
with = "crate::Generic<B>"
|
||||
)
|
||||
@@ -52,7 +52,7 @@ pub mod node_runtime3 {}
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale",
|
||||
substitute_type(
|
||||
type = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
// Swapping params:
|
||||
with = "crate::Second<B, A>"
|
||||
)
|
||||
@@ -62,7 +62,7 @@ pub mod node_runtime4 {}
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale",
|
||||
substitute_type(
|
||||
type = "sp_runtime::multiaddress::MultiAddress",
|
||||
path = "sp_runtime::multiaddress::MultiAddress",
|
||||
// Ignore input params and just use concrete types on output:
|
||||
with = "crate::Second<bool, ::std::vec::Vec<u8>>"
|
||||
)
|
||||
@@ -72,7 +72,7 @@ pub mod node_runtime5 {}
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale",
|
||||
substitute_type(
|
||||
type = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
// We can put a static type in, too:
|
||||
with = "crate::Second<B, u16>"
|
||||
)
|
||||
@@ -82,7 +82,7 @@ pub mod node_runtime6 {}
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale",
|
||||
substitute_type(
|
||||
type = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
// Check that things can be wrapped in our Static type:
|
||||
with = "::subxt::utils::Static<crate::DoesntImplEncodeDecodeAsType>"
|
||||
)
|
||||
@@ -92,7 +92,7 @@ pub mod node_runtime7 {}
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale",
|
||||
substitute_type(
|
||||
type = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
path = "sp_runtime::multiaddress::MultiAddress<A, B>",
|
||||
// Recursive type param substitution should work too (swapping out nested A and B):
|
||||
with = "::subxt::utils::Static<crate::Second<A, B>>"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[subxt::subxt(
|
||||
runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale",
|
||||
substitute_type(
|
||||
type = "sp_arithmetic::per_things::Perbill",
|
||||
path = "sp_arithmetic::per_things::Perbill",
|
||||
with = "sp_runtime::Perbill"
|
||||
)
|
||||
)]
|
||||
|
||||
Reference in New Issue
Block a user