mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 02:21:14 +00:00
Add subxt_signer crate for native & WASM compatible signing (#1016)
* Add and use subxt-signer crate for WASM compatible signing * cargo fmt * dev keypairs already references * WIP fix various breakages * re-jig features to be simpler and various test fixes etc * doc and web fix * fix various bits and pieces * fix a test I broke * dev-deps can't be linked to in docs, hrmph * cargo fmt * another doc link * document the subxt_signer crate more thoroughly * move feature flag for consistency * more docs, no default subxt feature flag on signer, update release instrs * Add missing license header * unwrap_inner => into_inner * extend a test a little to better check derive junctions * note more clearly that the crypto bits come from sp_core::crypto
This commit is contained in:
@@ -58,11 +58,21 @@ jobs:
|
||||
- name: Cargo check all targets.
|
||||
run: cargo check --all-targets
|
||||
|
||||
# Next, check each feature on its own and compile each crate separately. This is no good
|
||||
# for subxt/examples, which expect default features to be enabled, hence the above check
|
||||
# and why we don'#t do `--all-targets` for this one.
|
||||
# Next, check subxt features.
|
||||
# - `native` feature must always be enabled
|
||||
# - `web` feature is always ignored.
|
||||
# - This means, don't check --no-default-features and don't try enabling --all-features; both will fail
|
||||
- name: Cargo hack; check each subxt feature
|
||||
run: cargo hack -p subxt --each-feature check --exclude-no-default-features --exclude-all-features --exclude-features web --features native
|
||||
|
||||
# Subxt-signer has the "subxt" features enabled in the "check all targets" test. Run it on its own to
|
||||
# check it without. We can't enable subxt or web features here, so no cargo hack.
|
||||
- name: Cargo check subxt-signer
|
||||
run: cargo check -p subxt-signer
|
||||
|
||||
# Next, check each other package in isolation.
|
||||
- name: Cargo hack; check each feature/crate on its own
|
||||
run: cargo hack --exclude-all-features --each-feature check --workspace
|
||||
run: cargo hack --exclude subxt --exclude subxt-signer --exclude-all-features --each-feature check --workspace
|
||||
|
||||
fmt:
|
||||
name: Cargo fmt
|
||||
@@ -215,10 +225,16 @@ jobs:
|
||||
mkdir -p ~/.local/bin
|
||||
mv substrate ~/.local/bin
|
||||
|
||||
- name: Run WASM tests
|
||||
- name: Run subxt WASM tests
|
||||
run: |
|
||||
substrate --dev --tmp > /dev/null 2>&1 &
|
||||
wasm-pack test --headless --firefox
|
||||
wasm-pack test --headless --chrome
|
||||
pkill substrate
|
||||
working-directory: testing/wasm-tests
|
||||
|
||||
- name: Run subxt-signer WASM tests
|
||||
run: |
|
||||
wasm-pack test --headless --firefox
|
||||
wasm-pack test --headless --chrome
|
||||
working-directory: signer/wasm-tests
|
||||
|
||||
Generated
+111
-5
@@ -271,6 +271,23 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bip39"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
|
||||
dependencies = [
|
||||
"bitcoin_hashes",
|
||||
"serde",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitcoin_hashes"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -768,6 +785,19 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-ng"
|
||||
version = "4.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.6.4",
|
||||
"subtle-ng",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.14.4"
|
||||
@@ -1588,12 +1618,12 @@ dependencies = [
|
||||
"regex",
|
||||
"scale-info",
|
||||
"sp-core",
|
||||
"sp-keyring",
|
||||
"sp-runtime",
|
||||
"substrate-runner",
|
||||
"subxt",
|
||||
"subxt-codegen",
|
||||
"subxt-metadata",
|
||||
"subxt-signer",
|
||||
"syn 2.0.18",
|
||||
"test-runtime",
|
||||
"tokio",
|
||||
@@ -1904,6 +1934,18 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "merlin"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"keccak",
|
||||
"rand_core 0.6.4",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
@@ -2118,6 +2160,15 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0ca0b5a68607598bf3bad68f32227a8164f6254833f84eafaac409cd6746c31"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
@@ -2694,7 +2745,7 @@ dependencies = [
|
||||
"arrayvec 0.5.2",
|
||||
"curve25519-dalek 2.1.3",
|
||||
"getrandom 0.1.16",
|
||||
"merlin",
|
||||
"merlin 2.0.1",
|
||||
"rand 0.7.3",
|
||||
"rand_core 0.5.1",
|
||||
"sha2 0.8.2",
|
||||
@@ -2702,6 +2753,23 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schnorrkel"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "844b7645371e6ecdf61ff246ba1958c29e802881a749ae3fb1993675d210d28d"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec 0.7.2",
|
||||
"curve25519-dalek-ng",
|
||||
"merlin 3.0.0",
|
||||
"rand_core 0.6.4",
|
||||
"serde_bytes",
|
||||
"sha2 0.9.9",
|
||||
"subtle-ng",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@@ -2783,6 +2851,15 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.164"
|
||||
@@ -2968,7 +3045,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"libsecp256k1",
|
||||
"log",
|
||||
"merlin",
|
||||
"merlin 2.0.1",
|
||||
"parity-scale-codec",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
@@ -2976,7 +3053,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"scale-info",
|
||||
"schnorrkel",
|
||||
"schnorrkel 0.9.1",
|
||||
"secp256k1",
|
||||
"secrecy",
|
||||
"serde",
|
||||
@@ -3369,7 +3446,7 @@ checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c"
|
||||
dependencies = [
|
||||
"hmac 0.11.0",
|
||||
"pbkdf2 0.8.0",
|
||||
"schnorrkel",
|
||||
"schnorrkel 0.9.1",
|
||||
"sha2 0.9.9",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -3384,6 +3461,12 @@ version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "subtle-ng"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142"
|
||||
|
||||
[[package]]
|
||||
name = "subxt"
|
||||
version = "0.29.0"
|
||||
@@ -3416,6 +3499,7 @@ dependencies = [
|
||||
"sp-version",
|
||||
"subxt-macro",
|
||||
"subxt-metadata",
|
||||
"subxt-signer",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -3485,6 +3569,28 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-signer"
|
||||
version = "0.29.0"
|
||||
dependencies = [
|
||||
"bip39",
|
||||
"getrandom 0.2.10",
|
||||
"hex",
|
||||
"hmac 0.12.1",
|
||||
"parity-scale-codec",
|
||||
"pbkdf2 0.12.1",
|
||||
"regex",
|
||||
"schnorrkel 0.10.2",
|
||||
"secrecy",
|
||||
"sha2 0.10.6",
|
||||
"sp-core",
|
||||
"sp-core-hashing",
|
||||
"sp-keyring",
|
||||
"subxt",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
|
||||
+21
-6
@@ -8,13 +8,18 @@ members = [
|
||||
"testing/ui-tests",
|
||||
"macro",
|
||||
"metadata",
|
||||
"signer",
|
||||
"subxt"
|
||||
]
|
||||
|
||||
# This cannot be a workspace dependency, because it requires
|
||||
# mutually exclusive jsonrpsee features to work, and workspaces
|
||||
# will aggregate features used across crates:
|
||||
exclude = ["testing/wasm-tests", "examples/wasm-example"]
|
||||
# We exclude any crates that would depend on non mutually
|
||||
# exclusive feature flags and thus can't compile with the
|
||||
# workspace:
|
||||
exclude = [
|
||||
"testing/wasm-tests",
|
||||
"signer/wasm-tests",
|
||||
"examples/wasm-example"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
@@ -42,7 +47,7 @@ derivative = "2.2.0"
|
||||
either = "1.8.1"
|
||||
frame-metadata = { version = "15.1.0", features = ["v14", "v15-unstable", "std"] }
|
||||
futures = { version = "0.3.27", default-features = false, features = ["std"] }
|
||||
getrandom = "0.2"
|
||||
getrandom = { version = "0.2", default-features = false }
|
||||
hex = "0.4.3"
|
||||
heck = "0.4.1"
|
||||
impl-serde = { version = "0.4.0" }
|
||||
@@ -79,9 +84,19 @@ sp-runtime = "24.0.0"
|
||||
sp-version = "22.0.0"
|
||||
|
||||
# Subxt workspace crates:
|
||||
subxt = { version = "0.29.0", path = "subxt" }
|
||||
subxt = { version = "0.29.0", path = "subxt", default-features = false }
|
||||
subxt-macro = { version = "0.29.0", path = "macro" }
|
||||
subxt-metadata = { version = "0.29.0", path = "metadata" }
|
||||
subxt-codegen = { version = "0.29.0", path = "codegen" }
|
||||
subxt-signer = { version = "0.29.0", path = "signer" }
|
||||
test-runtime = { path = "testing/test-runtime" }
|
||||
substrate-runner = { path = "testing/substrate-runner" }
|
||||
|
||||
# subxt-signer deps that I expect aren't useful anywhere else:
|
||||
bip39 = "2.0.0"
|
||||
hmac = "0.12.1"
|
||||
pbkdf2 = { version = "0.12.1", default-features = false }
|
||||
schnorrkel = "0.10.2"
|
||||
secrecy = "0.8.0"
|
||||
sha2 = "0.10.6"
|
||||
zeroize = { version = "1", default-features = false }
|
||||
+1
-4
@@ -65,7 +65,6 @@ We also assume that ongoing work done is being merged directly to the `master` b
|
||||
2. Perform a final sanity check that everything looks ok.
|
||||
|
||||
```
|
||||
cargo hack --exclude-all-features --each-feature check --all-targets --workspace
|
||||
cargo test --all-targets
|
||||
```
|
||||
|
||||
@@ -77,12 +76,10 @@ We also assume that ongoing work done is being merged directly to the `master` b
|
||||
(cd codegen && cargo publish) && \
|
||||
(cd macro && cargo publish) && \
|
||||
(cd subxt && cargo publish) && \
|
||||
(cd signer && cargo publish) && \
|
||||
(cd cli && cargo publish);
|
||||
```
|
||||
|
||||
If you run into any issues regarding crates not being able to find suitable versions of other `subxt-*` crates,
|
||||
you may just need to wait a little longer and then run the remaining portion of that command.
|
||||
|
||||
10. If the release was successful, tag the commit that we released in the `master` branch with the
|
||||
version that we just released, for example:
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
subxt-codegen = { workspace = true }
|
||||
subxt-metadata = { workspace = true }
|
||||
subxt = { workspace = true }
|
||||
subxt = { workspace = true, features = ["native", "jsonrpsee"] }
|
||||
clap = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
color-eyre = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "subxt-signer"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
publish = true
|
||||
|
||||
license.workspace = true
|
||||
readme = "README.md"
|
||||
repository.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
description = "Sign extrinsics to be submitted by Subxt"
|
||||
keywords = ["parity", "subxt", "extrinsic", "signer"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Make the keypair algorithms here compatible with Subxt's Signer trait,
|
||||
# so that they can be used to sign transactions for compatible chains.
|
||||
subxt = ["dep:subxt"]
|
||||
|
||||
# The getrandom package is used via schnorrkel. We need to enable the JS
|
||||
# feature on it if compiling for the web.
|
||||
web = ["getrandom/js"]
|
||||
|
||||
[dependencies]
|
||||
regex = { workspace = true }
|
||||
subxt = { workspace = true, optional = true }
|
||||
hex = { workspace = true }
|
||||
codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] }
|
||||
sp-core-hashing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
pbkdf2 = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
hmac = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
bip39 = { workspace = true }
|
||||
schnorrkel = { workspace = true }
|
||||
secrecy = { workspace = true }
|
||||
|
||||
# We only pull this in to enable the JS flag for schnorrkel to use.
|
||||
getrandom = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-core = { workspace = true, features = ["std"] }
|
||||
sp-keyring = { workspace = true }
|
||||
@@ -0,0 +1,3 @@
|
||||
# Subxt-signer
|
||||
|
||||
This library exposes a small, WASM compatible signer implementation which can be used in conjunction with Subxt to sign transactions.
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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 codec::{Decode, Encode};
|
||||
|
||||
// This code is taken from sp_core::crypto::DeriveJunction. The logic should be identical,
|
||||
// though the API is tweaked a touch.
|
||||
|
||||
/// The length of the junction identifier. Note that this is also referred to as the
|
||||
/// `CHAIN_CODE_LENGTH` in the context of Schnorrkel.
|
||||
pub const JUNCTION_ID_LEN: usize = 32;
|
||||
|
||||
/// A since derivation junction description. It is the single parameter used when creating
|
||||
/// a new secret key from an existing secret key and, in the case of `SoftRaw` and `SoftIndex`
|
||||
/// a new public key from an existing public key.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Encode, Decode)]
|
||||
pub enum DeriveJunction {
|
||||
/// Soft (vanilla) derivation. Public keys have a correspondent derivation.
|
||||
Soft([u8; JUNCTION_ID_LEN]),
|
||||
/// Hard ("hardened") derivation. Public keys do not have a correspondent derivation.
|
||||
Hard([u8; JUNCTION_ID_LEN]),
|
||||
}
|
||||
|
||||
impl DeriveJunction {
|
||||
/// Consume self to return a soft derive junction with the same chain code.
|
||||
pub fn soften(self) -> Self {
|
||||
DeriveJunction::Soft(self.into_inner())
|
||||
}
|
||||
|
||||
/// Consume self to return a hard derive junction with the same chain code.
|
||||
pub fn harden(self) -> Self {
|
||||
DeriveJunction::Hard(self.into_inner())
|
||||
}
|
||||
|
||||
/// Create a new soft (vanilla) DeriveJunction from a given, encodable, value.
|
||||
///
|
||||
/// If you need a hard junction, use `hard()`.
|
||||
pub fn soft<T: Encode>(index: T) -> Self {
|
||||
let mut cc: [u8; JUNCTION_ID_LEN] = Default::default();
|
||||
index.using_encoded(|data| {
|
||||
if data.len() > JUNCTION_ID_LEN {
|
||||
cc.copy_from_slice(&sp_core_hashing::blake2_256(data));
|
||||
} else {
|
||||
cc[0..data.len()].copy_from_slice(data);
|
||||
}
|
||||
});
|
||||
DeriveJunction::Soft(cc)
|
||||
}
|
||||
|
||||
/// Create a new hard (hardened) DeriveJunction from a given, encodable, value.
|
||||
///
|
||||
/// If you need a soft junction, use `soft()`.
|
||||
pub fn hard<T: Encode>(index: T) -> Self {
|
||||
Self::soft(index).harden()
|
||||
}
|
||||
|
||||
/// Consume self to return the chain code.
|
||||
pub fn into_inner(self) -> [u8; JUNCTION_ID_LEN] {
|
||||
match self {
|
||||
DeriveJunction::Hard(c) | DeriveJunction::Soft(c) => c,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the inner junction id.
|
||||
pub fn inner(&self) -> &[u8; JUNCTION_ID_LEN] {
|
||||
match self {
|
||||
DeriveJunction::Hard(ref c) | DeriveJunction::Soft(ref c) => c,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the junction is soft.
|
||||
pub fn is_soft(&self) -> bool {
|
||||
matches!(*self, DeriveJunction::Soft(_))
|
||||
}
|
||||
|
||||
/// Return `true` if the junction is hard.
|
||||
pub fn is_hard(&self) -> bool {
|
||||
matches!(*self, DeriveJunction::Hard(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> From<T> for DeriveJunction {
|
||||
fn from(j: T) -> DeriveJunction {
|
||||
let j = j.as_ref();
|
||||
let (code, hard) = if let Some(stripped) = j.strip_prefix('/') {
|
||||
(stripped, true)
|
||||
} else {
|
||||
(j, false)
|
||||
};
|
||||
|
||||
let res = if let Ok(n) = str::parse::<u64>(code) {
|
||||
// number
|
||||
DeriveJunction::soft(n)
|
||||
} else {
|
||||
// something else
|
||||
DeriveJunction::soft(code)
|
||||
};
|
||||
|
||||
if hard {
|
||||
res.harden()
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
// The crypto module contains code adapted from sp_core::crypto.
|
||||
|
||||
mod derive_junction;
|
||||
mod secret_uri;
|
||||
mod seed_from_entropy;
|
||||
|
||||
pub use derive_junction::DeriveJunction;
|
||||
pub use secret_uri::{SecretUri, SecretUriError, DEV_PHRASE};
|
||||
pub use seed_from_entropy::seed_from_entropy;
|
||||
@@ -0,0 +1,156 @@
|
||||
// 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 super::DeriveJunction;
|
||||
use regex::Regex;
|
||||
use secrecy::SecretString;
|
||||
|
||||
// This code is taken from sp_core::crypto::DeriveJunction. The logic should be identical,
|
||||
// though the code is tweaked a touch!
|
||||
|
||||
/// A secret uri (`SURI`) that can be used to generate a key pair.
|
||||
///
|
||||
/// The `SURI` can be parsed from a string. The string takes this form:
|
||||
///
|
||||
/// ```text
|
||||
/// phrase/path0/path1///password
|
||||
/// 111111 22222 22222 33333333
|
||||
/// ```
|
||||
///
|
||||
/// Where:
|
||||
/// - 1 denotes a phrase or hex string. If this is not provided, the [`DEV_PHRASE`] is used
|
||||
/// instead.
|
||||
/// - 2's denote optional "derivation junctions" which are used to derive keys. Each of these is
|
||||
/// separated by "/". A derivation junction beginning with "/" (ie "//" in the original string)
|
||||
/// is a "hard" path.
|
||||
/// - 3 denotes an optional password which is used in conjunction with the phrase provided in 1
|
||||
/// to generate an initial key. If hex is provided for 1, it's ignored.
|
||||
///
|
||||
/// Notes:
|
||||
/// - If 1 is a `0x` prefixed 64-digit hex string, then we'll interpret it as hex, and treat the hex bytes
|
||||
/// as a seed/MiniSecretKey directly, ignoring any password.
|
||||
/// - Else if the phrase part is a valid BIP-39 phrase, we'll use the phrase (and password, if provided)
|
||||
/// to generate a seed/MiniSecretKey.
|
||||
/// - Uris like "//Alice" correspond to keys derived from a DEV_PHRASE, since no phrase part is given.
|
||||
///
|
||||
/// There is no correspondence mapping between `SURI` strings and the keys they represent.
|
||||
/// Two different non-identical strings can actually lead to the same secret being derived.
|
||||
/// Notably, integer junction indices may be legally prefixed with arbitrary number of zeros.
|
||||
/// Similarly an empty password (ending the `SURI` with `///`) is perfectly valid and will
|
||||
/// generally be equivalent to no password at all.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Parse [`DEV_PHRASE`] secret URI with junction:
|
||||
///
|
||||
/// ```
|
||||
/// # use subxt_signer::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret};
|
||||
/// # use std::str::FromStr;
|
||||
/// let suri = SecretUri::from_str("//Alice").expect("Parse SURI");
|
||||
///
|
||||
/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions);
|
||||
/// assert_eq!(DEV_PHRASE, suri.phrase.expose_secret());
|
||||
/// assert!(suri.password.is_none());
|
||||
/// ```
|
||||
///
|
||||
/// Parse [`DEV_PHRASE`] secret URI with junction and password:
|
||||
///
|
||||
/// ```
|
||||
/// # use subxt_signer::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret};
|
||||
/// # use std::str::FromStr;
|
||||
/// let suri = SecretUri::from_str("//Alice///SECRET_PASSWORD").expect("Parse SURI");
|
||||
///
|
||||
/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions);
|
||||
/// assert_eq!(DEV_PHRASE, suri.phrase.expose_secret());
|
||||
/// assert_eq!("SECRET_PASSWORD", suri.password.unwrap().expose_secret());
|
||||
/// ```
|
||||
///
|
||||
/// Parse [`DEV_PHRASE`] secret URI with hex phrase and junction:
|
||||
///
|
||||
/// ```
|
||||
/// # use subxt_signer::{SecretUri, DeriveJunction, DEV_PHRASE, ExposeSecret};
|
||||
/// # use std::str::FromStr;
|
||||
/// let suri = SecretUri::from_str("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a//Alice").expect("Parse SURI");
|
||||
///
|
||||
/// assert_eq!(vec![DeriveJunction::from("Alice").harden()], suri.junctions);
|
||||
/// assert_eq!("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a", suri.phrase.expose_secret());
|
||||
/// assert!(suri.password.is_none());
|
||||
/// ```
|
||||
pub struct SecretUri {
|
||||
/// The phrase to derive the private key.
|
||||
///
|
||||
/// This can either be a 64-bit hex string or a BIP-39 key phrase.
|
||||
pub phrase: SecretString,
|
||||
/// Optional password as given as part of the uri.
|
||||
pub password: Option<SecretString>,
|
||||
/// The junctions as part of the uri.
|
||||
pub junctions: Vec<DeriveJunction>,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for SecretUri {
|
||||
type Err = SecretUriError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let cap = secret_phrase_regex()
|
||||
.captures(s)
|
||||
.ok_or(SecretUriError::InvalidFormat)?;
|
||||
|
||||
let junctions = junction_regex()
|
||||
.captures_iter(&cap["path"])
|
||||
.map(|f| DeriveJunction::from(&f[1]))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let phrase = cap.name("phrase").map(|r| r.as_str()).unwrap_or(DEV_PHRASE);
|
||||
let password = cap.name("password");
|
||||
|
||||
Ok(Self {
|
||||
phrase: SecretString::from_str(phrase).expect("Returns infallible error; qed"),
|
||||
password: password.map(|v| {
|
||||
SecretString::from_str(v.as_str()).expect("Returns infallible error; qed")
|
||||
}),
|
||||
junctions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This is returned if `FromStr` cannot parse a string into a `SecretUri`.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, thiserror::Error)]
|
||||
pub enum SecretUriError {
|
||||
/// Parsing the secret URI from a string failed; wrong format.
|
||||
#[error("Invalid secret phrase format")]
|
||||
InvalidFormat,
|
||||
}
|
||||
|
||||
once_static! {
|
||||
/// Interpret a phrase like:
|
||||
///
|
||||
/// ```text
|
||||
/// foo bar wibble /path0/path1///password
|
||||
/// 11111111111111 222222222222 33333333
|
||||
/// ```
|
||||
/// Where 1 is the phrase, 2 the path and 3 the password.
|
||||
/// Taken from `sp_core::crypto::SECRET_PHRASE_REGEX`.
|
||||
fn secret_phrase_regex() -> regex::Regex {
|
||||
Regex::new(r"^(?P<phrase>[\d\w ]+)?(?P<path>(//?[^/]+)*)(///(?P<password>.*))?$").unwrap()
|
||||
}
|
||||
|
||||
/// Interpret a part of a path into a "junction":
|
||||
///
|
||||
/// ```text
|
||||
/// //foo/bar/wibble
|
||||
/// 1111 222 333333
|
||||
/// ```
|
||||
/// Where the numbers denote matching junctions.
|
||||
///
|
||||
/// The leading "/" deliminates each part, and then a "/" beginning
|
||||
/// a path piece denotes that it's a "hard" path. Taken from
|
||||
/// `sp_core::crypto::JUNCTION_REGEX`.
|
||||
fn junction_regex() -> regex::Regex {
|
||||
Regex::new(r"/(/?[^/]+)").unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// The root phrase for our publicly known keys.
|
||||
pub const DEV_PHRASE: &str =
|
||||
"bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
||||
@@ -0,0 +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.
|
||||
|
||||
use hmac::Hmac;
|
||||
use pbkdf2::pbkdf2;
|
||||
use sha2::Sha512;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// This is taken from `substrate-bip39` so that we can keep dependencies in line, and
|
||||
/// is the same logic that sp-core uses to go from mnemonic entropy to seed. Returns
|
||||
/// `None` if invalid length.
|
||||
pub fn seed_from_entropy(entropy: &[u8], password: &str) -> Option<[u8; 64]> {
|
||||
if entropy.len() < 16 || entropy.len() > 32 || entropy.len() % 4 != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut salt = String::with_capacity(8 + password.len());
|
||||
salt.push_str("mnemonic");
|
||||
salt.push_str(password);
|
||||
|
||||
let mut seed = [0u8; 64];
|
||||
|
||||
pbkdf2::<Hmac<Sha512>>(entropy, salt.as_bytes(), 2048, &mut seed).ok()?;
|
||||
|
||||
salt.zeroize();
|
||||
|
||||
Some(seed)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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.
|
||||
|
||||
//! # Subxt-signer
|
||||
//!
|
||||
//! The main output from this crate is the [`sr25519::Keypair`], which can
|
||||
//! be constructed from a bip39 phrase, secret URI or raw seed, and used to
|
||||
//! sign and verify arbitrary messages. This crate is aligned with how Substrate's
|
||||
//! `sp_core` crate constructs and signs keypairs, but is lighter on dependencies
|
||||
//! and can support compilation to WASM with the `web` feature.
|
||||
//!
|
||||
//! Enable the `subxt` feature to enable use of this [`sr25519::Keypair`] in signing
|
||||
//! subxt transactions for chains supporting sr25519 signatures.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
mod utils;
|
||||
mod crypto;
|
||||
|
||||
// An sr25519 key pair implementation.
|
||||
pub mod sr25519;
|
||||
|
||||
// Re-export useful bits and pieces for generating a Pair from a phrase,
|
||||
// namely the Mnemonic struct.
|
||||
pub use bip39;
|
||||
|
||||
// Used to hold strings in a more secure manner in memory for a little extra
|
||||
// protection.
|
||||
pub use secrecy::{ExposeSecret, SecretString};
|
||||
|
||||
// SecretUri's can be parsed from strings and used to generate key pairs.
|
||||
// DeriveJunctions are the "path" part of these SecretUris.
|
||||
pub use crypto::{DeriveJunction, SecretUri, SecretUriError, DEV_PHRASE};
|
||||
@@ -0,0 +1,417 @@
|
||||
// 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.
|
||||
|
||||
//! An sr25519 keypair implementation.
|
||||
|
||||
use crate::crypto::{seed_from_entropy, DeriveJunction, SecretUri};
|
||||
use hex::FromHex;
|
||||
use schnorrkel::{
|
||||
derive::{ChainCode, Derivation},
|
||||
ExpansionMode, MiniSecretKey,
|
||||
};
|
||||
use secrecy::ExposeSecret;
|
||||
|
||||
const SEED_LENGTH: usize = schnorrkel::keys::MINI_SECRET_KEY_LENGTH;
|
||||
const SIGNING_CTX: &[u8] = b"substrate";
|
||||
|
||||
/// Seed bytes used to generate a key pair.
|
||||
pub type Seed = [u8; SEED_LENGTH];
|
||||
|
||||
/// A signature generated by [`Keypair::sign()`]. These bytes are equivalent
|
||||
/// to a Substrate `MultiSignature::sr25519(bytes)`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Signature(pub [u8; 64]);
|
||||
|
||||
impl AsRef<[u8]> for Signature {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The public key for an [`Keypair`] key pair. This is equivalent to a
|
||||
/// Substrate `AccountId32`.
|
||||
pub struct PublicKey(pub [u8; 32]);
|
||||
|
||||
impl AsRef<[u8]> for PublicKey {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// An sr25519 keypair implementation. While the API is slightly different, the logic for
|
||||
/// this has been taken from `sp_core::sr25519` and we test against this to ensure conformity.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Keypair(schnorrkel::Keypair);
|
||||
|
||||
impl Keypair {
|
||||
/// Create am sr25519 keypair from a [`SecretUri`]. See the [`SecretUri`] docs for more.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use subxt_signer::{ SecretUri, sr25519::Keypair };
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// let uri = SecretUri::from_str("//Alice").unwrap();
|
||||
/// let keypair = Keypair::from_uri(&uri).unwrap();
|
||||
///
|
||||
/// keypair.sign(b"Hello world!");
|
||||
/// ```
|
||||
pub fn from_uri(uri: &SecretUri) -> Result<Self, Error> {
|
||||
let SecretUri {
|
||||
junctions,
|
||||
phrase,
|
||||
password,
|
||||
} = uri;
|
||||
|
||||
// If the phrase is hex, convert bytes directly into a seed, ignoring password.
|
||||
// Else, parse the phrase string taking the password into account. This is
|
||||
// the same approach taken in sp_core::crypto::Pair::from_string_with_seed.
|
||||
let key = if let Some(hex_str) = phrase.expose_secret().strip_prefix("0x") {
|
||||
let seed = Seed::from_hex(hex_str)?;
|
||||
Self::from_seed(seed)?
|
||||
} else {
|
||||
let phrase = bip39::Mnemonic::parse(phrase.expose_secret().as_str())?;
|
||||
let pass_str = password.as_ref().map(|p| p.expose_secret().as_str());
|
||||
Self::from_phrase(&phrase, pass_str)?
|
||||
};
|
||||
|
||||
// Now, use any "junctions" to derive a new key from this root key.
|
||||
Ok(key.derive(junctions.iter().copied()))
|
||||
}
|
||||
|
||||
/// Create am sr25519 keypair from a BIP-39 mnemonic phrase and optional password.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use subxt_signer::{ bip39::Mnemonic, sr25519::Keypair };
|
||||
///
|
||||
/// let phrase = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
||||
/// let mnemonic = Mnemonic::parse(phrase).unwrap();
|
||||
/// let keypair = Keypair::from_phrase(&mnemonic, None).unwrap();
|
||||
///
|
||||
/// keypair.sign(b"Hello world!");
|
||||
/// ```
|
||||
pub fn from_phrase(mnemonic: &bip39::Mnemonic, password: Option<&str>) -> Result<Self, Error> {
|
||||
let big_seed = seed_from_entropy(&mnemonic.to_entropy(), password.unwrap_or(""))
|
||||
.ok_or(Error::InvalidSeed)?;
|
||||
|
||||
let seed: Seed = big_seed[..SEED_LENGTH]
|
||||
.try_into()
|
||||
.expect("should be valid Seed");
|
||||
|
||||
Self::from_seed(seed)
|
||||
}
|
||||
|
||||
/// Turn a 32 byte seed into a keypair.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This will only be secure if the seed is secure!
|
||||
pub fn from_seed(seed: Seed) -> Result<Self, Error> {
|
||||
let keypair = MiniSecretKey::from_bytes(&seed)
|
||||
.map_err(|_| Error::InvalidSeed)?
|
||||
.expand_to_keypair(ExpansionMode::Ed25519);
|
||||
|
||||
Ok(Keypair(keypair))
|
||||
}
|
||||
|
||||
/// Derive a child key from this one given a series of junctions.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use subxt_signer::{ bip39::Mnemonic, sr25519::Keypair, DeriveJunction };
|
||||
///
|
||||
/// let phrase = "bottom drive obey lake curtain smoke basket hold race lonely fit walk";
|
||||
/// let mnemonic = Mnemonic::parse(phrase).unwrap();
|
||||
/// let keypair = Keypair::from_phrase(&mnemonic, None).unwrap();
|
||||
///
|
||||
/// // Equivalent to the URI path '//Alice/stash':
|
||||
/// let new_keypair = keypair.derive([
|
||||
/// DeriveJunction::hard("Alice"),
|
||||
/// DeriveJunction::soft("stash")
|
||||
/// ]);
|
||||
/// ```
|
||||
pub fn derive<Js: IntoIterator<Item = DeriveJunction>>(&self, junctions: Js) -> Self {
|
||||
let init = self.0.secret.clone();
|
||||
let result = junctions.into_iter().fold(init, |acc, j| match j {
|
||||
DeriveJunction::Soft(cc) => acc.derived_key_simple(ChainCode(cc), []).0,
|
||||
DeriveJunction::Hard(cc) => {
|
||||
let seed = acc.hard_derive_mini_secret_key(Some(ChainCode(cc)), b"").0;
|
||||
seed.expand(ExpansionMode::Ed25519)
|
||||
}
|
||||
});
|
||||
Self(result.into())
|
||||
}
|
||||
|
||||
/// Obtain the [`PublicKey`] part of this key pair, which can be used in calls to [`verify()`].
|
||||
/// or otherwise converted into an address. The public key bytes are equivalent to a Substrate
|
||||
/// `AccountId32`.
|
||||
pub fn public_key(&self) -> PublicKey {
|
||||
PublicKey(self.0.public.to_bytes())
|
||||
}
|
||||
|
||||
/// Sign some message. These bytes can be used directly in a Substrate `MultiSignature::sr25519(..)`.
|
||||
pub fn sign(&self, message: &[u8]) -> Signature {
|
||||
let context = schnorrkel::signing_context(SIGNING_CTX);
|
||||
let signature = self.0.sign(context.bytes(message));
|
||||
Signature(signature.to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that some signature for a message was created by the owner of the [`PublicKey`].
|
||||
///
|
||||
/// ```rust
|
||||
/// use subxt_signer::{ bip39::Mnemonic, sr25519 };
|
||||
///
|
||||
/// let keypair = sr25519::dev::alice();
|
||||
/// let message = b"Hello!";
|
||||
///
|
||||
/// let signature = keypair.sign(message);
|
||||
/// let public_key = keypair.public_key();
|
||||
/// assert!(sr25519::verify(&signature, message, &public_key));
|
||||
/// ```
|
||||
pub fn verify<M: AsRef<[u8]>>(sig: &Signature, message: M, pubkey: &PublicKey) -> bool {
|
||||
let Ok(signature) = schnorrkel::Signature::from_bytes(&sig.0) else {
|
||||
return false
|
||||
};
|
||||
let Ok(public) = schnorrkel::PublicKey::from_bytes(&pubkey.0) else {
|
||||
return false
|
||||
};
|
||||
public
|
||||
.verify_simple(SIGNING_CTX, message.as_ref(), &signature)
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// An error handed back if creating a keypair fails.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Invalid seed.
|
||||
#[error("Invalid seed (was it the wrong length?)")]
|
||||
InvalidSeed,
|
||||
/// Invalid phrase.
|
||||
#[error("Cannot parse phrase: {0}")]
|
||||
Phrase(#[from] bip39::Error),
|
||||
/// Invalid hex.
|
||||
#[error("Cannot parse hex string: {0}")]
|
||||
Hex(#[from] hex::FromHexError),
|
||||
}
|
||||
|
||||
/// Dev accounts, helpful for testing but not to be used in production,
|
||||
/// since the secret keys are known.
|
||||
pub mod dev {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
once_static_cloned! {
|
||||
/// Equivalent to `{DEV_PHRASE}//Alice`.
|
||||
pub fn alice() -> Keypair {
|
||||
Keypair::from_uri(&SecretUri::from_str("//Alice").unwrap()).unwrap()
|
||||
}
|
||||
/// Equivalent to `{DEV_PHRASE}//Bob`.
|
||||
pub fn bob() -> Keypair {
|
||||
Keypair::from_uri(&SecretUri::from_str("//Bob").unwrap()).unwrap()
|
||||
}
|
||||
/// Equivalent to `{DEV_PHRASE}//Charlie`.
|
||||
pub fn charlie() -> Keypair {
|
||||
Keypair::from_uri(&SecretUri::from_str("//Charlie").unwrap()).unwrap()
|
||||
}
|
||||
/// Equivalent to `{DEV_PHRASE}//Dave`.
|
||||
pub fn dave() -> Keypair {
|
||||
Keypair::from_uri(&SecretUri::from_str("//Dave").unwrap()).unwrap()
|
||||
}
|
||||
/// Equivalent to `{DEV_PHRASE}//Eve`.
|
||||
pub fn eve() -> Keypair {
|
||||
Keypair::from_uri(&SecretUri::from_str("//Eve").unwrap()).unwrap()
|
||||
}
|
||||
/// Equivalent to `{DEV_PHRASE}//Ferdie`.
|
||||
pub fn ferdie() -> Keypair {
|
||||
Keypair::from_uri(&SecretUri::from_str("//Ferdie").unwrap()).unwrap()
|
||||
}
|
||||
/// Equivalent to `{DEV_PHRASE}//One`.
|
||||
pub fn one() -> Keypair {
|
||||
Keypair::from_uri(&SecretUri::from_str("//One").unwrap()).unwrap()
|
||||
}
|
||||
/// Equivalent to `{DEV_PHRASE}//Two`.
|
||||
pub fn two() -> Keypair {
|
||||
Keypair::from_uri(&SecretUri::from_str("//Two").unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make `Keypair` usable to sign transactions in Subxt. This is optional so that
|
||||
// `subxt-signer` can be used entirely independently of Subxt.
|
||||
#[cfg(feature = "subxt")]
|
||||
mod subxt_compat {
|
||||
use super::*;
|
||||
|
||||
use subxt::config::Config;
|
||||
use subxt::tx::Signer as SignerT;
|
||||
use subxt::utils::{AccountId32, MultiAddress, MultiSignature};
|
||||
|
||||
impl From<Signature> for MultiSignature {
|
||||
fn from(value: Signature) -> Self {
|
||||
MultiSignature::Sr25519(value.0)
|
||||
}
|
||||
}
|
||||
impl From<PublicKey> for AccountId32 {
|
||||
fn from(value: PublicKey) -> Self {
|
||||
value.to_account_id()
|
||||
}
|
||||
}
|
||||
impl<T> From<PublicKey> for MultiAddress<AccountId32, T> {
|
||||
fn from(value: PublicKey) -> Self {
|
||||
value.to_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl PublicKey {
|
||||
/// A shortcut to obtain an [`AccountId32`] from a [`PublicKey`].
|
||||
/// We often want this type, and using this method avoids any
|
||||
/// ambiguous type resolution issues.
|
||||
pub fn to_account_id(self) -> AccountId32 {
|
||||
AccountId32(self.0)
|
||||
}
|
||||
/// A shortcut to obtain a [`MultiAddress`] from a [`PublicKey`].
|
||||
/// We often want this type, and using this method avoids any
|
||||
/// ambiguous type resolution issues.
|
||||
pub fn to_address<T>(self) -> MultiAddress<AccountId32, T> {
|
||||
MultiAddress::Id(self.to_account_id())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SignerT<T> for Keypair
|
||||
where
|
||||
T::AccountId: From<PublicKey>,
|
||||
T::Address: From<PublicKey>,
|
||||
T::Signature: From<Signature>,
|
||||
{
|
||||
fn account_id(&self) -> T::AccountId {
|
||||
self.public_key().into()
|
||||
}
|
||||
|
||||
fn address(&self) -> T::Address {
|
||||
self.public_key().into()
|
||||
}
|
||||
|
||||
fn sign(&self, signer_payload: &[u8]) -> T::Signature {
|
||||
self.sign(signer_payload).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
|
||||
use sp_core::crypto::Pair as _;
|
||||
use sp_core::sr25519::Pair as SpPair;
|
||||
|
||||
#[test]
|
||||
fn check_from_phrase_matches() {
|
||||
for _ in 0..20 {
|
||||
let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(None);
|
||||
let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
|
||||
let pair = Keypair::from_phrase(&phrase, None).expect("should be valid");
|
||||
|
||||
assert_eq!(sp_pair.public().0, pair.public_key().0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_from_phrase_with_password_matches() {
|
||||
for _ in 0..20 {
|
||||
let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing"));
|
||||
let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
|
||||
let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid");
|
||||
|
||||
assert_eq!(sp_pair.public().0, pair.public_key().0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_from_secret_uri_matches() {
|
||||
// Some derive junctions to check that the logic there aligns:
|
||||
let uri_paths = [
|
||||
"/foo",
|
||||
"//bar",
|
||||
"/1",
|
||||
"/0001",
|
||||
"//1",
|
||||
"//0001",
|
||||
"//foo//bar/wibble",
|
||||
"//foo//001/wibble",
|
||||
];
|
||||
|
||||
for i in 0..2 {
|
||||
for path in &uri_paths {
|
||||
// Build an sp_core::Pair that includes a phrase, path and password:
|
||||
let password = format!("Testing{i}");
|
||||
let (_sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some(&password));
|
||||
let uri = format!("{phrase}{path}///{password}");
|
||||
let sp_pair = SpPair::from_string(&uri, None).expect("should be valid");
|
||||
|
||||
// Now build a local Keypair using the equivalent API:
|
||||
let uri = SecretUri::from_str(&uri).expect("should be valid secret URI");
|
||||
let pair = Keypair::from_uri(&uri).expect("should be valid");
|
||||
|
||||
// They should match:
|
||||
assert_eq!(sp_pair.public().0, pair.public_key().0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_dev_accounts_match() {
|
||||
use sp_keyring::sr25519::Keyring::*;
|
||||
|
||||
assert_eq!(dev::alice().public_key().0, Alice.public().0);
|
||||
assert_eq!(dev::bob().public_key().0, Bob.public().0);
|
||||
assert_eq!(dev::charlie().public_key().0, Charlie.public().0);
|
||||
assert_eq!(dev::dave().public_key().0, Dave.public().0);
|
||||
assert_eq!(dev::eve().public_key().0, Eve.public().0);
|
||||
assert_eq!(dev::ferdie().public_key().0, Ferdie.public().0);
|
||||
assert_eq!(dev::one().public_key().0, One.public().0);
|
||||
assert_eq!(dev::two().public_key().0, Two.public().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_signing_and_verifying_matches() {
|
||||
use sp_core::sr25519::Signature as SpSignature;
|
||||
|
||||
for _ in 0..20 {
|
||||
let (sp_pair, phrase, _seed) = SpPair::generate_with_phrase(Some("Testing"));
|
||||
let phrase = bip39::Mnemonic::parse(phrase).expect("valid phrase expected");
|
||||
let pair = Keypair::from_phrase(&phrase, Some("Testing")).expect("should be valid");
|
||||
|
||||
let message = b"Hello world";
|
||||
let sp_sig = sp_pair.sign(message).0;
|
||||
let sig = pair.sign(message).0;
|
||||
|
||||
assert!(SpPair::verify(
|
||||
&SpSignature(sig),
|
||||
message,
|
||||
&sp_pair.public()
|
||||
));
|
||||
assert!(verify(&Signature(sp_sig), message, &pair.public_key()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_hex_uris() {
|
||||
// Hex URIs seem to ignore the password on sp_core and here. Check that this is consistent.
|
||||
let uri_str =
|
||||
"0x1122334455667788112233445566778811223344556677881122334455667788///SomePassword";
|
||||
|
||||
let uri = SecretUri::from_str(uri_str).expect("should be valid");
|
||||
let pair = Keypair::from_uri(&uri).expect("should be valid");
|
||||
let sp_pair = SpPair::from_string(uri_str, None).expect("should be valid");
|
||||
|
||||
assert_eq!(pair.public_key().0, sp_pair.public().0);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
/// Use like:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// once_static!{
|
||||
/// /// Some documentation.
|
||||
/// fn foo() -> Vec<u8> {
|
||||
/// vec![1,2,3,4]
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
macro_rules! once_static {
|
||||
($($(#[$attr:meta])* $vis:vis fn $name:ident() -> $ty:ty { $expr:expr } )+) => {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
$vis fn $name() -> &'static $ty {
|
||||
static VAR: std::sync::OnceLock<$ty> = std::sync::OnceLock::new();
|
||||
VAR.get_or_init(|| { $expr })
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
/// Like `once_static!` but clones the item out of static storage. Useful if it
|
||||
/// takes a while to create the item but cloning it is fairly cheap.
|
||||
macro_rules! once_static_cloned {
|
||||
($($(#[$attr:meta])* $vis:vis fn $name:ident() -> $ty:ty { $expr:expr } )+) => {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
$vis fn $name() -> $ty {
|
||||
static VAR: std::sync::OnceLock<$ty> = std::sync::OnceLock::new();
|
||||
VAR.get_or_init(|| { $expr }).clone()
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "wasm-tests"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.24"
|
||||
tracing-wasm = "0.2.1"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
serde_json = "1"
|
||||
|
||||
# This crate is not a part of the workspace, because we want to
|
||||
# enable the "web" feature here but don't want it enabled as part
|
||||
# of workspace builds. Also disable the "subxt" feature here because
|
||||
# we want to ensure it works in isolation of that.
|
||||
subxt-signer = { path = "..", default-features = false, features = ["web"] }
|
||||
|
||||
# this shouldn't be needed, it's in workspace.exclude, but still
|
||||
# I get the complaint unless I add it...
|
||||
[workspace]
|
||||
@@ -0,0 +1,26 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use subxt_signer::sr25519;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
// Run the tests by calling:
|
||||
//
|
||||
// ```text
|
||||
// wasm-pack test --firefox --headless`
|
||||
// ```
|
||||
//
|
||||
// These are independent of any other package to ensure that nothing
|
||||
// else enabled the same feature flag that subxt-signer needs to work ok
|
||||
// (subxt seems to, for instance).
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn wasm_signing_works() {
|
||||
let alice = sr25519::dev::alice();
|
||||
|
||||
// There's some non-determinism in the signing, so this ensures that
|
||||
// the rand stuff is configured properly to run ok in wasm.
|
||||
let signature = alice.sign(b"Hello there");
|
||||
assert!(sr25519::verify(&signature, b"Hello there", &alice.public_key()));
|
||||
}
|
||||
+37
-18
@@ -15,31 +15,49 @@ description = "Submit extrinsics (transactions) to a substrate node via RPC"
|
||||
keywords = ["parity", "substrate", "blockchain"]
|
||||
|
||||
[features]
|
||||
default = ["jsonrpsee-ws", "substrate-compat"]
|
||||
# For dev and documentation reasons we enable more features than are often desired.
|
||||
# it's recommended to use `--no-default-features` and then select what you need.
|
||||
default = ["jsonrpsee", "native", "substrate-compat"]
|
||||
|
||||
# Activate this feature to pull in extra Substrate dependencies which make it
|
||||
# possible to provide a proper extrinsic Signer implementation (PairSigner).
|
||||
# Enable this for native (ie non web/wasm builds).
|
||||
# Exactly 1 of "web" and "native" is expected.
|
||||
native = [
|
||||
"jsonrpsee?/async-client",
|
||||
"jsonrpsee?/client-ws-transport"
|
||||
]
|
||||
|
||||
# Enable this for web/wasm builds.
|
||||
# Exactly 1 of "web" and "native" is expected.
|
||||
web = [
|
||||
"jsonrpsee?/async-wasm-client",
|
||||
"jsonrpsee?/client-web-transport",
|
||||
"getrandom/js"
|
||||
]
|
||||
|
||||
# Enable this to use jsonrpsee (allowing for example `OnlineClient::from_url`).
|
||||
jsonrpsee = [
|
||||
"dep:jsonrpsee"
|
||||
]
|
||||
|
||||
# Enable this to pull in extra Substrate dependencies which make it possible to
|
||||
# use the `sp_core::crypto::Pair` Signer implementation, as well as adding some
|
||||
# `From` impls for types like `AccountId32`. Cannot be used with "web".
|
||||
substrate-compat = [
|
||||
"sp-core",
|
||||
"sp-runtime"
|
||||
]
|
||||
|
||||
# Activate this to expose functionality only used for integration testing.
|
||||
# The exposed functionality is subject to breaking changes at any point,
|
||||
# and should not be relied upon.
|
||||
integration-tests = []
|
||||
|
||||
# Jsonrpsee if the default RPC provider used in Subxt. However, it can be
|
||||
# swapped out for an alternative implementation, and so is optional.
|
||||
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.
|
||||
# Enable this to fetch and utilize the latest unstable 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 = []
|
||||
|
||||
# Enable this to expose functionality only used for integration testing.
|
||||
# The exposed functionality is subject to breaking changes at any point,
|
||||
# and should not be relied upon.
|
||||
integration-tests = []
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", workspace = true, features = ["derive"] }
|
||||
scale-info = { workspace = true }
|
||||
@@ -66,10 +84,10 @@ sp-core-hashing = { workspace = true }
|
||||
base58 = { workspace = true }
|
||||
blake2 = { workspace = true }
|
||||
|
||||
# Included if one of the jsonrpsee features is enabled.
|
||||
# Included if the "jsonrpsee" feature is enabled.
|
||||
jsonrpsee = { workspace = true, optional = true, features = ["jsonrpsee-types"] }
|
||||
|
||||
# These are only included is "substrate-compat" is enabled.
|
||||
# Included if the "substrate-compat" feature is enabled.
|
||||
sp-core = { workspace = true, optional = true }
|
||||
sp-runtime = { workspace = true, optional = true }
|
||||
|
||||
@@ -77,8 +95,8 @@ sp-runtime = { workspace = true, optional = true }
|
||||
subxt-macro = { workspace = true }
|
||||
subxt-metadata = { workspace = true }
|
||||
|
||||
[target.wasm32-unknown-unknown.dependencies]
|
||||
getrandom = { workspace = true, features = ["js"] }
|
||||
# Included if "web" feature is enabled, to enable its js feature.
|
||||
getrandom = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bitvec = { workspace = true }
|
||||
@@ -90,3 +108,4 @@ sp-runtime = { workspace = true }
|
||||
sp-keyring = { workspace = true }
|
||||
sp-version = { workspace = true }
|
||||
assert_matches = { workspace = true }
|
||||
subxt-signer = { workspace = true, features = ["subxt"] }
|
||||
@@ -1,6 +1,6 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::dynamic::Value;
|
||||
use subxt::{config::PolkadotConfig, OnlineClient};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
pub mod polkadot {}
|
||||
@@ -12,7 +12,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// Create a dynamically runtime API payload that calls the
|
||||
// `AccountNonceApi_account_nonce` function.
|
||||
let account = AccountKeyring::Alice.to_account_id();
|
||||
let account = dev::alice().public_key();
|
||||
let runtime_api_call = subxt::dynamic::runtime_api_call(
|
||||
"AccountNonceApi",
|
||||
"account_nonce",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{config::PolkadotConfig, OnlineClient};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
pub mod polkadot {}
|
||||
@@ -11,7 +11,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// Create a runtime API payload that calls into
|
||||
// `AccountNonceApi_account_nonce` function.
|
||||
let account = AccountKeyring::Alice.to_account_id().into();
|
||||
let account = dev::alice().public_key().into();
|
||||
let runtime_api_call = polkadot::apis().account_nonce_api().account_nonce(account);
|
||||
|
||||
// Submit the call and get back a result.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
// Generate an interface that we can use from the node's metadata.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
@@ -11,7 +11,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a storage query to access account information.
|
||||
let account = AccountKeyring::Alice.to_account_id().into();
|
||||
let account = dev::alice().public_key().into();
|
||||
let storage_query = polkadot::storage().system().account(&account);
|
||||
|
||||
// Use that query to `fetch` a result. This returns an `Option<_>`, which will be
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::dynamic::{At, Value};
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -8,7 +8,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a dynamic storage query to access account information.
|
||||
let account = AccountKeyring::Alice.to_account_id();
|
||||
let account = dev::alice().public_key();
|
||||
let storage_query =
|
||||
subxt::dynamic::storage("System", "Account", vec![Value::from_bytes(account)]);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
// Generate an interface that we can use from the node's metadata.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
@@ -11,12 +11,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a balance transfer extrinsic.
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
let dest = dev::bob().public_key().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 from = dev::alice();
|
||||
let events = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use futures::StreamExt;
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{
|
||||
tx::{PairSigner, TxStatus},
|
||||
OnlineClient, PolkadotConfig,
|
||||
};
|
||||
use subxt::{tx::TxStatus, OnlineClient, PolkadotConfig};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
// Generate an interface that we can use from the node's metadata.
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
@@ -15,15 +12,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a balance transfer extrinsic.
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
let dest = dev::bob().public_key().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 from = dev::alice();
|
||||
let mut balance_transfer_progress = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&balance_transfer_tx, &signer)
|
||||
.sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
|
||||
.await?;
|
||||
|
||||
while let Some(status) = balance_transfer_progress.next().await {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::config::polkadot::{Era, PlainTip, PolkadotExtrinsicParamsBuilder as Params};
|
||||
use subxt::{tx::PairSigner, OnlineClient, PolkadotConfig};
|
||||
use subxt::{OnlineClient, PolkadotConfig};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
pub mod polkadot {}
|
||||
@@ -11,7 +11,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let api = OnlineClient::<PolkadotConfig>::new().await?;
|
||||
|
||||
// Build a balance transfer extrinsic.
|
||||
let dest = AccountKeyring::Bob.to_account_id().into();
|
||||
let dest = dev::bob().public_key().into();
|
||||
let tx = polkadot::tx().balances().transfer(dest, 10_000);
|
||||
|
||||
// Configure the transaction parameters; for Polkadot the tip and era:
|
||||
@@ -20,7 +20,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.era(Era::Immortal, api.genesis_hash());
|
||||
|
||||
// submit the transaction:
|
||||
let from = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let from = dev::alice();
|
||||
let hash = api.tx().sign_and_submit(&tx, &from, tx_params).await?;
|
||||
println!("Balance transfer extrinsic submitted with hash : {hash}");
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
//! 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_full.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
@@ -27,10 +25,8 @@
|
||||
//! 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");
|
||||
//! ```
|
||||
//!
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
//! 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_small.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
@@ -33,10 +31,8 @@
|
||||
//! 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",
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
//! We can use the statically generated interface to build storage queries:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use sp_keyring::AccountKeyring;
|
||||
//! use subxt_signer::sr25519::dev;
|
||||
//!
|
||||
//! #[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_small.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
//! let account = AccountKeyring::Alice.to_account_id().into();
|
||||
//! let account = dev::alice().public_key().into();
|
||||
//! let storage_query = polkadot::storage().system().account(&account);
|
||||
//! ```
|
||||
//!
|
||||
@@ -29,10 +29,10 @@
|
||||
//! validated until it's submitted:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use sp_keyring::AccountKeyring;
|
||||
//! use subxt_signer::sr25519::dev;
|
||||
//! use subxt::dynamic::Value;
|
||||
//!
|
||||
//! let account = AccountKeyring::Alice.to_account_id();
|
||||
//! let account = dev::alice().public_key();
|
||||
//! let storage_query = subxt::dynamic::storage("System", "Account", vec![
|
||||
//! Value::from_bytes(account)
|
||||
//! ]);
|
||||
@@ -43,8 +43,6 @@
|
||||
//! 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_small.scale")]
|
||||
//! pub mod polkadot {}
|
||||
//!
|
||||
|
||||
@@ -60,28 +60,54 @@
|
||||
//! ## 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.
|
||||
//! control. To do this, you will typically first create a [`crate::tx::Signer`] instance, 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:
|
||||
//! There are two main ways to create a compatible signer instance:
|
||||
//! 1. The `subxt_signer` crate provides a WASM compatible implementation of [`crate::tx::Signer`]
|
||||
//! for chains which require sr25519 signatures (requires the `subxt` feature to be enabled).
|
||||
//! 2. Alternately, Subxt can use instances of Substrate's [`sp_core::Pair`] to sign things by wrapping
|
||||
//! them in a [`crate::tx::PairSigner`] (requires the `substrate-compat` feature to be enabled).
|
||||
//!
|
||||
//! Going for 1 leads to fewer dependencies being imported and WASM compatibility out of the box via
|
||||
//! the `web` feature flag. Going for 2 is useful if you're already using the Substrate dependencies or
|
||||
//! need additional signing algorithms that `subxt_signer` doesn't support, and don't care about WASM
|
||||
//! compatibility.
|
||||
//!
|
||||
//! Let's see how to use each of these approaches:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use subxt::config::PolkadotConfig;
|
||||
//! use std::str::FromStr;
|
||||
//!
|
||||
//! //// 1. Use a `subxt_signer` impl:
|
||||
//! use subxt_signer::{ SecretUri, sr25519 };
|
||||
//!
|
||||
//! // Get hold of a `Signer` for a test account:
|
||||
//! let alice = sr25519::dev::alice();
|
||||
//!
|
||||
//! // Or generate a keypair, here from an SURI:
|
||||
//! let uri = SecretUri::from_str("vessel ladder alter error federal sibling chat ability sun glass valve picture/0/1///Password")
|
||||
//! .expect("valid URI");
|
||||
//! let keypair = sr25519::Keypair::from_uri(&uri)
|
||||
//! .expect("valid keypair");
|
||||
//!
|
||||
//! //// 2. Use the corresponding `sp_core::Pair` impl:
|
||||
//! 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);
|
||||
//! // Get hold of a `Signer` for a test account:
|
||||
//! let alice = sp_keyring::AccountKeyring::Alice.pair();
|
||||
//! let alice = PairSigner::<PolkadotConfig,_>::new(alice);
|
||||
//!
|
||||
//! // Or generate an `sr25519` keypair to use:
|
||||
//! let (pair, _, _) = sp_core::sr25519::Pair::generate_with_phrase(Some("password"));
|
||||
//! let signer = PairSigner::<PolkadotConfig,_>::new(pair);
|
||||
//! // Or generate a keypair, here from an SURI:
|
||||
//! let keypair = sp_core::sr25519::Pair::from_string("vessel ladder alter error federal sibling chat ability sun glass valve picture/0/1///Password", None)
|
||||
//! .expect("valid URI");
|
||||
//! let keypair = PairSigner::<PolkadotConfig,_>::new(keypair);
|
||||
//! ```
|
||||
//!
|
||||
//! See the [`sp_core::Pair`] docs for more ways to generate them.
|
||||
//! See the `subxt_signer::sr25519::Keypair` or the [`sp_core::Pair`] docs for more ways to construct
|
||||
//! and work with key pairs.
|
||||
//!
|
||||
//! 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:
|
||||
@@ -118,10 +144,9 @@
|
||||
//! 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();
|
||||
//! # let signer = subxt_signer::sr25519::dev::alice();
|
||||
//! # signature = signer.sign(&signer_payload).into();
|
||||
//! # address = signer.public_key().to_address();
|
||||
//!
|
||||
//! // Now we can build an tx, which one can call `submit` or `submit_and_watch`
|
||||
//! // on to submit to a node and optionally watch the status.
|
||||
|
||||
@@ -16,8 +16,5 @@ pub use online_client::{
|
||||
ClientRuntimeUpdater, OnlineClient, OnlineClientT, RuntimeUpdaterStream, Update, UpgradeError,
|
||||
};
|
||||
|
||||
#[cfg(any(
|
||||
feature = "jsonrpsee-ws",
|
||||
all(feature = "jsonrpsee-web", target_arch = "wasm32")
|
||||
))]
|
||||
#[cfg(feature = "jsonrpsee")]
|
||||
pub use online_client::default_rpc_client;
|
||||
|
||||
@@ -55,10 +55,7 @@ impl<T: Config> std::fmt::Debug for OnlineClient<T> {
|
||||
}
|
||||
|
||||
/// The default RPC client that's used (based on [`jsonrpsee`]).
|
||||
#[cfg(any(
|
||||
feature = "jsonrpsee-ws",
|
||||
all(feature = "jsonrpsee-web", target_arch = "wasm32")
|
||||
))]
|
||||
#[cfg(feature = "jsonrpsee")]
|
||||
pub async fn default_rpc_client<U: AsRef<str>>(url: U) -> Result<impl RpcClientT, Error> {
|
||||
let client = jsonrpsee_helpers::client(url.as_ref())
|
||||
.await
|
||||
@@ -67,10 +64,7 @@ pub async fn default_rpc_client<U: AsRef<str>>(url: U) -> Result<impl RpcClientT
|
||||
}
|
||||
|
||||
// The default constructors assume Jsonrpsee.
|
||||
#[cfg(any(
|
||||
feature = "jsonrpsee-ws",
|
||||
all(feature = "jsonrpsee-web", target_arch = "wasm32")
|
||||
))]
|
||||
#[cfg(feature = "jsonrpsee")]
|
||||
impl<T: Config> OnlineClient<T> {
|
||||
/// Construct a new [`OnlineClient`] using default settings which
|
||||
/// point to a locally running node on `ws://127.0.0.1:9944`.
|
||||
@@ -428,7 +422,7 @@ impl Update {
|
||||
}
|
||||
|
||||
// helpers for a jsonrpsee specific OnlineClient.
|
||||
#[cfg(feature = "jsonrpsee-ws")]
|
||||
#[cfg(all(feature = "jsonrpsee", feature = "native"))]
|
||||
mod jsonrpsee_helpers {
|
||||
pub use jsonrpsee::{
|
||||
client_transport::ws::{InvalidUri, Receiver, Sender, Uri, WsTransportClientBuilder},
|
||||
@@ -458,7 +452,7 @@ mod jsonrpsee_helpers {
|
||||
}
|
||||
|
||||
// helpers for a jsonrpsee specific OnlineClient.
|
||||
#[cfg(all(feature = "jsonrpsee-web", target_arch = "wasm32"))]
|
||||
#[cfg(all(feature = "jsonrpsee", feature = "web"))]
|
||||
mod jsonrpsee_helpers {
|
||||
pub use jsonrpsee::{
|
||||
client_transport::web,
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::sync::Arc;
|
||||
/// A collection of events obtained from a block, bundled with the necessary
|
||||
/// information needed to decode and iterate over them.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug(bound = ""), Clone(bound = ""))]
|
||||
#[derivative(Clone(bound = ""))]
|
||||
pub struct Events<T: Config> {
|
||||
metadata: Metadata,
|
||||
block_hash: T::Hash,
|
||||
@@ -31,6 +31,18 @@ pub struct Events<T: Config> {
|
||||
num_events: u32,
|
||||
}
|
||||
|
||||
// Ignore the Metadata when debug-logging events; it's big and distracting.
|
||||
impl<T: Config> std::fmt::Debug for Events<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Events")
|
||||
.field("block_hash", &self.block_hash)
|
||||
.field("event_bytes", &self.event_bytes)
|
||||
.field("start_idx", &self.start_idx)
|
||||
.field("num_events", &self.num_events)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Events<T> {
|
||||
pub(crate) fn new(metadata: Metadata, block_hash: T::Hash, event_bytes: Vec<u8>) -> Self {
|
||||
// event_bytes is a SCALE encoded vector of events. So, pluck the
|
||||
|
||||
+15
-7
@@ -33,23 +33,31 @@
|
||||
)]
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
#[cfg(any(
|
||||
all(feature = "web", feature = "native"),
|
||||
not(any(feature = "web", feature = "native"))
|
||||
))]
|
||||
compile_error!("subxt: exactly one of the 'web' and 'native' features should be used.");
|
||||
|
||||
#[cfg(all(feature = "web", feature = "substrate-compat"))]
|
||||
compile_error!("subxt: the 'substrate-compat' feature is not compatible with the 'web' feature.");
|
||||
|
||||
// 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 _;
|
||||
mod only_used_in_docs_or_tests {
|
||||
use subxt_signer as _;
|
||||
use tokio as _;
|
||||
}
|
||||
|
||||
// Used to enable the js feature for wasm.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(feature = "web")]
|
||||
#[allow(unused_imports)]
|
||||
pub use getrandom as _;
|
||||
|
||||
#[cfg(all(feature = "jsonrpsee-ws", feature = "jsonrpsee-web"))]
|
||||
std::compile_error!(
|
||||
"Both the features `jsonrpsee-ws` and `jsonrpsee-web` are enabled which are mutually exclusive"
|
||||
);
|
||||
|
||||
pub mod blocks;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::Config;
|
||||
/// as well as actually signing a SCALE encoded payload.
|
||||
pub trait Signer<T: Config> {
|
||||
/// Return the "from" account ID.
|
||||
fn account_id(&self) -> &T::AccountId;
|
||||
fn account_id(&self) -> T::AccountId;
|
||||
|
||||
/// Return the "from" address.
|
||||
fn address(&self) -> T::Address;
|
||||
@@ -83,8 +83,8 @@ mod pair_signer {
|
||||
Pair: PairT,
|
||||
Pair::Signature: Into<T::Signature>,
|
||||
{
|
||||
fn account_id(&self) -> &T::AccountId {
|
||||
&self.account_id
|
||||
fn account_id(&self) -> T::AccountId {
|
||||
self.account_id.clone()
|
||||
}
|
||||
|
||||
fn address(&self) -> T::Address {
|
||||
|
||||
@@ -211,7 +211,7 @@ where
|
||||
Call: TxPayload,
|
||||
Signer: SignerT<T>,
|
||||
{
|
||||
let account_nonce = self.account_nonce(signer.account_id()).await?;
|
||||
let account_nonce = self.account_nonce(&signer.account_id()).await?;
|
||||
self.create_signed_with_nonce(call, signer, account_nonce, other_params)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ regex = { workspace = true }
|
||||
scale-info = { workspace = true, features = ["bit-vec"] }
|
||||
sp-core = { workspace = true }
|
||||
sp-runtime = { workspace = true }
|
||||
sp-keyring = { workspace = true }
|
||||
syn = { workspace = true }
|
||||
subxt = { workspace = true, features = ["unstable-metadata"] }
|
||||
subxt = { workspace = true, features = ["unstable-metadata", "native", "jsonrpsee", "substrate-compat"] }
|
||||
subxt-signer = { workspace = true, features = ["subxt"] }
|
||||
subxt-codegen = { workspace = true }
|
||||
subxt-metadata = { workspace = true }
|
||||
test-runtime = { workspace = true }
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{pair_signer, test_context, utils::node_runtime};
|
||||
use crate::{test_context, utils::node_runtime};
|
||||
use codec::{Compact, Encode};
|
||||
use futures::StreamExt;
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::blocks::BlocksClient;
|
||||
use subxt_metadata::Metadata;
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
// Check that we can subscribe to non-finalized blocks.
|
||||
#[tokio::test]
|
||||
@@ -122,13 +122,13 @@ async fn decode_extrinsics() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob();
|
||||
|
||||
// Generate a block that has unsigned and signed transactions.
|
||||
let tx = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer(bob.account_id().clone().into(), 10_000);
|
||||
.transfer(bob.public_key().into(), 10_000);
|
||||
|
||||
let signed_extrinsic = api
|
||||
.tx()
|
||||
|
||||
@@ -3,31 +3,29 @@
|
||||
// see LICENSE for license details.
|
||||
|
||||
use crate::{
|
||||
pair_signer, test_context, test_context_with,
|
||||
test_context, test_context_with,
|
||||
utils::{node_runtime, wait_for_blocks},
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use codec::{Compact, Decode, Encode};
|
||||
use sp_core::storage::well_known_keys;
|
||||
use sp_core::{sr25519::Pair as Sr25519Pair, Pair};
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{
|
||||
error::{DispatchError, Error, TokenError},
|
||||
rpc::types::{
|
||||
ChainHeadEvent, DryRunResult, DryRunResultBytes, FollowEvent, Initialized, RuntimeEvent,
|
||||
RuntimeVersionEvent,
|
||||
},
|
||||
tx::Signer,
|
||||
utils::AccountId32,
|
||||
};
|
||||
use subxt_metadata::Metadata;
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
#[tokio::test]
|
||||
async fn insert_key() {
|
||||
let ctx = test_context_with(AccountKeyring::Bob).await;
|
||||
let ctx = test_context_with("bob".to_string()).await;
|
||||
let api = ctx.client();
|
||||
|
||||
let public = AccountKeyring::Alice.public().as_array_ref().to_vec();
|
||||
let public = dev::alice().public_key().as_ref().to_vec();
|
||||
api.rpc()
|
||||
.insert_key(
|
||||
"aura".to_string(),
|
||||
@@ -156,14 +154,14 @@ async fn dry_run_passes() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob();
|
||||
|
||||
wait_for_blocks(&api).await;
|
||||
|
||||
let tx = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer(bob.account_id().clone().into(), 10_000);
|
||||
.transfer(bob.public_key().into(), 10_000);
|
||||
|
||||
let signed_extrinsic = api
|
||||
.tx()
|
||||
@@ -192,11 +190,11 @@ async fn dry_run_fails() {
|
||||
|
||||
wait_for_blocks(&api).await;
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let hans = pair_signer(Sr25519Pair::generate().0);
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob();
|
||||
|
||||
let tx = node_runtime::tx().balances().transfer(
|
||||
hans.account_id().clone().into(),
|
||||
bob.public_key().into(),
|
||||
// 7 more than the default amount Alice has, so this should fail; insufficient funds:
|
||||
1_000_000_000_000_000_000_007,
|
||||
);
|
||||
@@ -295,14 +293,14 @@ async fn dry_run_result_is_substrate_compatible() {
|
||||
async fn external_signing() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice = dev::alice();
|
||||
|
||||
// Create a partial extrinsic. We can get the signer payload at this point, to be
|
||||
// signed externally.
|
||||
let tx = node_runtime::tx().preimage().note_preimage(vec![0u8]);
|
||||
let partial_extrinsic = api
|
||||
.tx()
|
||||
.create_partial_signed(&tx, alice.account_id(), Default::default())
|
||||
.create_partial_signed(&tx, &alice.public_key().into(), Default::default())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -311,7 +309,8 @@ async fn external_signing() {
|
||||
// Sign it (possibly externally).
|
||||
let signature = alice.sign(&signer_payload);
|
||||
// Use this to build a signed extrinsic.
|
||||
let extrinsic = partial_extrinsic.sign_with_address_and_signature(&alice.address(), &signature);
|
||||
let extrinsic = partial_extrinsic
|
||||
.sign_with_address_and_signature(&alice.public_key().into(), &signature.into());
|
||||
|
||||
// And now submit it.
|
||||
extrinsic
|
||||
@@ -328,7 +327,7 @@ async fn submit_large_extrinsic() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice = dev::alice();
|
||||
|
||||
// 2 MiB blob of data.
|
||||
let bytes = vec![0_u8; 2 * 1024 * 1024];
|
||||
@@ -357,8 +356,8 @@ async fn decode_a_module_error() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice_addr = alice.account_id().clone().into();
|
||||
let alice = dev::alice();
|
||||
let alice_addr = alice.public_key().into();
|
||||
|
||||
// Trying to work with an asset ID 1 which doesn't exist should return an
|
||||
// "unknown" module error from the assets pallet.
|
||||
@@ -392,13 +391,9 @@ async fn unsigned_extrinsic_is_same_shape_as_polkadotjs() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let tx = node_runtime::tx().balances().transfer(
|
||||
pair_signer(AccountKeyring::Alice.pair())
|
||||
.account_id()
|
||||
.clone()
|
||||
.into(),
|
||||
12345000000000000,
|
||||
);
|
||||
let tx = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer(dev::alice().public_key().into(), 12345000000000000);
|
||||
|
||||
let actual_tx = api.tx().create_unsigned(&tx).unwrap();
|
||||
|
||||
@@ -545,7 +540,7 @@ async fn chainhead_unstable_storage() {
|
||||
};
|
||||
let sub_id = blocks.subscription_id().unwrap().clone();
|
||||
|
||||
let alice: AccountId32 = AccountKeyring::Alice.to_account_id().into();
|
||||
let alice: AccountId32 = dev::alice().public_key().into();
|
||||
let addr = node_runtime::storage().system().account(alice);
|
||||
let addr_bytes = api.storage().address_bytes(&addr).unwrap();
|
||||
|
||||
@@ -572,7 +567,7 @@ async fn chainhead_unstable_call() {
|
||||
};
|
||||
let sub_id = blocks.subscription_id().unwrap().clone();
|
||||
|
||||
let alice_id = AccountKeyring::Alice.to_account_id();
|
||||
let alice_id = dev::alice().public_key().to_account_id();
|
||||
let mut sub = api
|
||||
.rpc()
|
||||
.chainhead_unstable_call(
|
||||
@@ -646,12 +641,11 @@ async fn partial_fee_estimate_correct() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let hans = pair_signer(Sr25519Pair::generate().0);
|
||||
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob();
|
||||
let tx = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer(hans.account_id().clone().into(), 1_000_000_000_000);
|
||||
.transfer(bob.public_key().into(), 1_000_000_000_000);
|
||||
|
||||
let signed_extrinsic = api
|
||||
.tx()
|
||||
|
||||
@@ -4,26 +4,29 @@
|
||||
|
||||
use crate::{
|
||||
node_runtime::{self, balances, runtime_types, system},
|
||||
pair_signer, test_context,
|
||||
test_context,
|
||||
};
|
||||
use codec::Decode;
|
||||
use sp_core::Pair;
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{
|
||||
error::{DispatchError, Error, TokenError},
|
||||
utils::{AccountId32, MultiAddress},
|
||||
};
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
#[tokio::test]
|
||||
async fn tx_basic_transfer() -> Result<(), subxt::Error> {
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let bob_address = bob.account_id().clone().into();
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob();
|
||||
let bob_address = bob.public_key().to_address();
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice_account_addr = node_runtime::storage().system().account(alice.account_id());
|
||||
let bob_account_addr = node_runtime::storage().system().account(bob.account_id());
|
||||
let alice_account_addr = node_runtime::storage()
|
||||
.system()
|
||||
.account(alice.public_key().to_account_id());
|
||||
let bob_account_addr = node_runtime::storage()
|
||||
.system()
|
||||
.account(bob.public_key().to_account_id());
|
||||
|
||||
let alice_pre = api
|
||||
.storage()
|
||||
@@ -56,8 +59,8 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error> {
|
||||
.expect("Failed to find ExtrinisicSuccess");
|
||||
|
||||
let expected_event = balances::events::Transfer {
|
||||
from: alice.account_id().clone(),
|
||||
to: bob.account_id().clone(),
|
||||
from: alice.public_key().to_account_id(),
|
||||
to: bob.public_key().to_account_id(),
|
||||
amount: 10_000,
|
||||
};
|
||||
assert_eq!(event, expected_event);
|
||||
@@ -84,20 +87,20 @@ async fn tx_basic_transfer() -> Result<(), subxt::Error> {
|
||||
async fn tx_dynamic_transfer() -> Result<(), subxt::Error> {
|
||||
use subxt::ext::scale_value::{At, Composite, Value};
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob();
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice_account_addr = subxt::dynamic::storage(
|
||||
"System",
|
||||
"Account",
|
||||
vec![Value::from_bytes(alice.account_id())],
|
||||
vec![Value::from_bytes(alice.public_key().to_account_id())],
|
||||
);
|
||||
let bob_account_addr = subxt::dynamic::storage(
|
||||
"System",
|
||||
"Account",
|
||||
vec![Value::from_bytes(bob.account_id())],
|
||||
vec![Value::from_bytes(bob.public_key().to_account_id())],
|
||||
);
|
||||
|
||||
let alice_pre = api
|
||||
@@ -117,7 +120,10 @@ async fn tx_dynamic_transfer() -> Result<(), subxt::Error> {
|
||||
"Balances",
|
||||
"transfer",
|
||||
vec![
|
||||
Value::unnamed_variant("Id", vec![Value::from_bytes(bob.account_id())]),
|
||||
Value::unnamed_variant(
|
||||
"Id",
|
||||
vec![Value::from_bytes(bob.public_key().to_account_id())],
|
||||
),
|
||||
Value::u128(10_000u128),
|
||||
],
|
||||
);
|
||||
@@ -140,11 +146,11 @@ async fn tx_dynamic_transfer() -> Result<(), subxt::Error> {
|
||||
let expected_fields = Composite::Named(vec![
|
||||
(
|
||||
"from".into(),
|
||||
Value::unnamed_composite(vec![Value::from_bytes(alice.account_id())]),
|
||||
Value::unnamed_composite(vec![Value::from_bytes(alice.public_key().to_account_id())]),
|
||||
),
|
||||
(
|
||||
"to".into(),
|
||||
Value::unnamed_composite(vec![Value::from_bytes(bob.account_id())]),
|
||||
Value::unnamed_composite(vec![Value::from_bytes(bob.public_key().to_account_id())]),
|
||||
),
|
||||
("amount".into(), Value::u128(10_000)),
|
||||
]);
|
||||
@@ -201,13 +207,15 @@ async fn tx_dynamic_transfer() -> Result<(), subxt::Error> {
|
||||
|
||||
#[tokio::test]
|
||||
async fn multiple_transfers_work_nonce_incremented() -> Result<(), subxt::Error> {
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let bob_address: MultiAddress<AccountId32, u32> = bob.account_id().clone().into();
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob();
|
||||
let bob_address: MultiAddress<AccountId32, u32> = bob.public_key().into();
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let bob_account_addr = node_runtime::storage().system().account(bob.account_id());
|
||||
let bob_account_addr = node_runtime::storage()
|
||||
.system()
|
||||
.account(bob.public_key().to_account_id());
|
||||
|
||||
let bob_pre = api
|
||||
.storage()
|
||||
@@ -259,9 +267,9 @@ async fn storage_total_issuance() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn storage_balance_lock() -> Result<(), subxt::Error> {
|
||||
let bob_signer = pair_signer(AccountKeyring::Bob.pair());
|
||||
let bob: AccountId32 = AccountKeyring::Bob.to_account_id().into();
|
||||
let charlie: AccountId32 = AccountKeyring::Charlie.to_account_id().into();
|
||||
let bob_signer = dev::bob();
|
||||
let bob: AccountId32 = dev::bob().public_key().into();
|
||||
let charlie: AccountId32 = dev::charlie().public_key().into();
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
@@ -302,31 +310,33 @@ async fn storage_balance_lock() -> Result<(), subxt::Error> {
|
||||
|
||||
#[tokio::test]
|
||||
async fn transfer_error() {
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice_addr = alice.account_id().clone().into();
|
||||
let hans = pair_signer(Pair::generate().0);
|
||||
let hans_address = hans.account_id().clone().into();
|
||||
let alice = dev::alice();
|
||||
let alice_addr = alice.public_key().into();
|
||||
let bob = dev::one(); // some dev account with no funds.
|
||||
let bob_address = bob.public_key().into();
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let to_hans_tx = node_runtime::tx()
|
||||
let to_bob_tx = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer(hans_address, 100_000_000_000_000_000);
|
||||
.transfer(bob_address, 100_000_000_000_000_000);
|
||||
let to_alice_tx = node_runtime::tx()
|
||||
.balances()
|
||||
.transfer(alice_addr, 100_000_000_000_000_000);
|
||||
|
||||
api.tx()
|
||||
.sign_and_submit_then_watch_default(&to_hans_tx, &alice)
|
||||
.sign_and_submit_then_watch_default(&to_bob_tx, &alice)
|
||||
.await
|
||||
.unwrap()
|
||||
.wait_for_finalized_success()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// When we try giving all of the funds back, Bob doesn't have
|
||||
// anything left to pay transfer fees, so we hit an error.
|
||||
let res = api
|
||||
.tx()
|
||||
.sign_and_submit_then_watch_default(&to_alice_tx, &hans)
|
||||
.sign_and_submit_then_watch_default(&to_alice_tx, &bob)
|
||||
.await
|
||||
.unwrap()
|
||||
.wait_for_finalized_success()
|
||||
@@ -345,8 +355,8 @@ async fn transfer_error() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn transfer_implicit_subscription() {
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob: AccountId32 = AccountKeyring::Bob.to_account_id().into();
|
||||
let alice = dev::alice();
|
||||
let bob: AccountId32 = dev::bob().public_key().into();
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
@@ -369,7 +379,7 @@ async fn transfer_implicit_subscription() {
|
||||
assert_eq!(
|
||||
event,
|
||||
balances::events::Transfer {
|
||||
from: alice.account_id().clone(),
|
||||
from: alice.public_key().to_account_id(),
|
||||
to: bob,
|
||||
amount: 10_000
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use sp_keyring::AccountKeyring;
|
||||
|
||||
use crate::{
|
||||
node_runtime::{
|
||||
self,
|
||||
@@ -13,16 +11,12 @@ use crate::{
|
||||
},
|
||||
test_context, TestContext,
|
||||
};
|
||||
use sp_core::sr25519::Pair;
|
||||
use subxt::{
|
||||
tx::{PairSigner, TxProgress},
|
||||
utils::MultiAddress,
|
||||
Config, Error, OnlineClient, SubstrateConfig,
|
||||
};
|
||||
use subxt::{tx::TxProgress, utils::MultiAddress, Config, Error, OnlineClient, SubstrateConfig};
|
||||
use subxt_signer::sr25519::{self, dev};
|
||||
|
||||
struct ContractsTestContext {
|
||||
cxt: TestContext,
|
||||
signer: PairSigner<SubstrateConfig, Pair>,
|
||||
signer: sr25519::Keypair,
|
||||
}
|
||||
|
||||
type Hash = <SubstrateConfig as Config>::Hash;
|
||||
@@ -40,7 +34,7 @@ const PROOF_SIZE: u64 = u64::MAX / 2;
|
||||
impl ContractsTestContext {
|
||||
async fn init() -> Self {
|
||||
let cxt = test_context().await;
|
||||
let signer = PairSigner::new(AccountKeyring::Alice.pair());
|
||||
let signer = dev::alice();
|
||||
|
||||
Self { cxt, signer }
|
||||
}
|
||||
|
||||
@@ -11,16 +11,20 @@ use crate::{
|
||||
},
|
||||
staking,
|
||||
},
|
||||
pair_signer, test_context,
|
||||
test_context,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use sp_core::{sr25519, Pair};
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::error::{DispatchError, Error};
|
||||
use subxt_signer::{
|
||||
sr25519::{self, dev},
|
||||
SecretUri,
|
||||
};
|
||||
|
||||
/// Helper function to generate a crypto pair from seed
|
||||
fn get_from_seed(seed: &str) -> sr25519::Pair {
|
||||
sr25519::Pair::from_string(&format!("//{seed}"), None).expect("static values are valid; qed")
|
||||
fn get_from_seed(seed: &str) -> sr25519::Keypair {
|
||||
use std::str::FromStr;
|
||||
let uri = SecretUri::from_str(&format!("//{seed}")).expect("expected to be valid");
|
||||
sr25519::Keypair::from_uri(&uri).expect("expected to be valid")
|
||||
}
|
||||
|
||||
fn default_validator_prefs() -> ValidatorPrefs {
|
||||
@@ -35,7 +39,7 @@ async fn validate_with_controller_account() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice = dev::alice();
|
||||
|
||||
let tx = node_runtime::tx()
|
||||
.staking()
|
||||
@@ -55,7 +59,7 @@ async fn validate_not_possible_for_stash_account() -> Result<(), Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
|
||||
let alice_stash = get_from_seed("Alice//stash");
|
||||
|
||||
let tx = node_runtime::tx()
|
||||
.staking()
|
||||
@@ -80,12 +84,12 @@ async fn nominate_with_controller_account() {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob();
|
||||
|
||||
let tx = node_runtime::tx()
|
||||
.staking()
|
||||
.nominate(vec![bob.account_id().clone().into()]);
|
||||
.nominate(vec![bob.public_key().to_address()]);
|
||||
|
||||
api.tx()
|
||||
.sign_and_submit_then_watch_default(&tx, &alice)
|
||||
@@ -101,12 +105,12 @@ async fn nominate_not_possible_for_stash_account() -> Result<(), Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
|
||||
let bob = pair_signer(AccountKeyring::Bob.pair());
|
||||
let alice_stash = get_from_seed("Alice//stash");
|
||||
let bob = dev::bob();
|
||||
|
||||
let tx = node_runtime::tx()
|
||||
.staking()
|
||||
.nominate(vec![bob.account_id().clone().into()]);
|
||||
.nominate(vec![bob.public_key().to_address()]);
|
||||
|
||||
let nomination = api
|
||||
.tx()
|
||||
@@ -129,21 +133,23 @@ async fn chill_works_for_controller_only() -> Result<(), Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice_stash = pair_signer(get_from_seed("Alice//stash"));
|
||||
let bob_stash = pair_signer(get_from_seed("Bob//stash"));
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice_stash = get_from_seed("Alice//stash");
|
||||
let bob_stash = get_from_seed("Bob//stash");
|
||||
let alice = dev::alice();
|
||||
|
||||
// this will fail the second time, which is why this is one test, not two
|
||||
let nominate_tx = node_runtime::tx()
|
||||
.staking()
|
||||
.nominate(vec![bob_stash.account_id().clone().into()]);
|
||||
.nominate(vec![bob_stash.public_key().to_address()]);
|
||||
api.tx()
|
||||
.sign_and_submit_then_watch_default(&nominate_tx, &alice)
|
||||
.await?
|
||||
.wait_for_finalized_success()
|
||||
.await?;
|
||||
|
||||
let ledger_addr = node_runtime::storage().staking().ledger(alice.account_id());
|
||||
let ledger_addr = node_runtime::storage()
|
||||
.staking()
|
||||
.ledger(alice.public_key().to_account_id());
|
||||
let ledger = api
|
||||
.storage()
|
||||
.at_latest()
|
||||
@@ -151,7 +157,7 @@ async fn chill_works_for_controller_only() -> Result<(), Error> {
|
||||
.fetch(&ledger_addr)
|
||||
.await?
|
||||
.unwrap();
|
||||
assert_eq!(alice_stash.account_id(), &ledger.stash);
|
||||
assert_eq!(alice_stash.public_key().to_account_id(), ledger.stash);
|
||||
|
||||
let chill_tx = node_runtime::tx().staking().chill();
|
||||
|
||||
@@ -185,10 +191,10 @@ async fn tx_bond() -> Result<(), Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice = dev::alice();
|
||||
|
||||
let bond_tx = node_runtime::tx().staking().bond(
|
||||
AccountKeyring::Bob.to_account_id().into(),
|
||||
dev::bob().public_key().into(),
|
||||
100_000_000_000_000,
|
||||
RewardDestination::Stash,
|
||||
);
|
||||
|
||||
@@ -8,9 +8,9 @@ use crate::{
|
||||
runtime_types::{self, sp_weights::weight_v2::Weight},
|
||||
sudo,
|
||||
},
|
||||
pair_signer, test_context,
|
||||
test_context,
|
||||
};
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
type Call = runtime_types::kitchensink_runtime::RuntimeCall;
|
||||
type BalancesCall = runtime_types::pallet_balances::pallet::Call;
|
||||
@@ -20,8 +20,8 @@ async fn test_sudo() -> Result<(), subxt::Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = AccountKeyring::Bob.to_account_id().into();
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob().public_key().into();
|
||||
|
||||
let call = Call::Balances(BalancesCall::transfer {
|
||||
dest: bob,
|
||||
@@ -46,8 +46,8 @@ async fn test_sudo_unchecked_weight() -> Result<(), subxt::Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let bob = AccountKeyring::Bob.to_account_id().into();
|
||||
let alice = dev::alice();
|
||||
let bob = dev::bob().public_key().into();
|
||||
|
||||
let call = Call::Balances(BalancesCall::transfer {
|
||||
dest: bob,
|
||||
|
||||
@@ -4,19 +4,21 @@
|
||||
|
||||
use crate::{
|
||||
node_runtime::{self, system},
|
||||
pair_signer, test_context,
|
||||
test_context,
|
||||
};
|
||||
use assert_matches::assert_matches;
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
#[tokio::test]
|
||||
async fn storage_account() -> Result<(), subxt::Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice = dev::alice();
|
||||
|
||||
let account_info_addr = node_runtime::storage().system().account(alice.account_id());
|
||||
let account_info_addr = node_runtime::storage()
|
||||
.system()
|
||||
.account(alice.public_key().to_account_id());
|
||||
|
||||
let account_info = api
|
||||
.storage()
|
||||
@@ -34,7 +36,7 @@ async fn tx_remark_with_event() -> Result<(), subxt::Error> {
|
||||
let ctx = test_context().await;
|
||||
let api = ctx.client();
|
||||
|
||||
let alice = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice = dev::alice();
|
||||
|
||||
let tx = node_runtime::tx()
|
||||
.system()
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
// 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 crate::{node_runtime, test_context};
|
||||
use subxt::utils::AccountId32;
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
#[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();
|
||||
let alice = dev::alice();
|
||||
let alice_account_id: AccountId32 = alice.public_key().into();
|
||||
|
||||
// Check Alice nonce is starting from 0.
|
||||
let runtime_api_call = node_runtime::apis()
|
||||
.account_nonce_api()
|
||||
.account_nonce(alice.clone());
|
||||
.account_nonce(alice_account_id.clone());
|
||||
let nonce = api
|
||||
.runtime_api()
|
||||
.at_latest()
|
||||
@@ -29,14 +29,14 @@ async fn account_nonce() -> Result<(), subxt::Error> {
|
||||
// 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)
|
||||
.sign_and_submit_then_watch_default(&remark_tx, &alice)
|
||||
.await?
|
||||
.wait_for_finalized_success()
|
||||
.await?;
|
||||
|
||||
let runtime_api_call = node_runtime::apis()
|
||||
.account_nonce_api()
|
||||
.account_nonce(alice);
|
||||
.account_nonce(alice_account_id);
|
||||
let nonce = api
|
||||
.runtime_api()
|
||||
.at_latest()
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// 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, utils::wait_for_blocks};
|
||||
use sp_keyring::AccountKeyring;
|
||||
use crate::{node_runtime, test_context, utils::wait_for_blocks};
|
||||
use subxt::utils::AccountId32;
|
||||
use subxt_signer::sr25519::dev;
|
||||
|
||||
#[tokio::test]
|
||||
async fn storage_plain_lookup() -> Result<(), subxt::Error> {
|
||||
@@ -32,8 +32,8 @@ async fn storage_map_lookup() -> 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();
|
||||
let signer = dev::alice();
|
||||
let alice: AccountId32 = dev::alice().public_key().into();
|
||||
|
||||
// Do some transaction to bump the Alice nonce to 1:
|
||||
let remark_tx = node_runtime::tx().system().remark(vec![1, 2, 3, 4, 5]);
|
||||
@@ -98,9 +98,9 @@ async fn storage_n_map_storage_lookup() -> Result<(), subxt::Error> {
|
||||
// Boilerplate; we create a new asset class with ID 99, and then
|
||||
// we "approveTransfer" of some of this asset class. This gives us an
|
||||
// entry in the `Approvals` StorageNMap that we can try to look up.
|
||||
let signer = pair_signer(AccountKeyring::Alice.pair());
|
||||
let alice: AccountId32 = AccountKeyring::Alice.to_account_id().into();
|
||||
let bob: AccountId32 = AccountKeyring::Bob.to_account_id().into();
|
||||
let signer = dev::alice();
|
||||
let alice: AccountId32 = dev::alice().public_key().into();
|
||||
let bob: AccountId32 = dev::bob().public_key().into();
|
||||
|
||||
let tx1 = node_runtime::tx()
|
||||
.assets()
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
|
||||
pub(crate) use crate::{node_runtime, TestNodeProcess};
|
||||
|
||||
use sp_core::sr25519::Pair;
|
||||
use sp_keyring::AccountKeyring;
|
||||
use subxt::{tx::PairSigner, SubstrateConfig};
|
||||
use subxt::SubstrateConfig;
|
||||
|
||||
/// substrate node should be installed on the $PATH
|
||||
const SUBSTRATE_NODE_PATH: &str = "substrate";
|
||||
|
||||
pub async fn test_context_with(key: AccountKeyring) -> TestContext {
|
||||
pub async fn test_context_with(authority: String) -> TestContext {
|
||||
let path = std::env::var("SUBSTRATE_NODE_PATH").unwrap_or_else(|_| {
|
||||
if which::which(SUBSTRATE_NODE_PATH).is_err() {
|
||||
panic!(
|
||||
@@ -23,16 +21,12 @@ pub async fn test_context_with(key: AccountKeyring) -> TestContext {
|
||||
});
|
||||
|
||||
let mut proc = TestContext::build(path.as_str());
|
||||
proc.with_authority(key);
|
||||
proc.with_authority(authority);
|
||||
proc.spawn::<SubstrateConfig>().await.unwrap()
|
||||
}
|
||||
|
||||
pub type TestContext = TestNodeProcess<SubstrateConfig>;
|
||||
|
||||
pub async fn test_context() -> TestContext {
|
||||
test_context_with(AccountKeyring::Alice).await
|
||||
}
|
||||
|
||||
pub fn pair_signer(pair: Pair) -> PairSigner<SubstrateConfig, Pair> {
|
||||
PairSigner::new(pair)
|
||||
test_context_with("alice".to_string()).await
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||
// see LICENSE for license details.
|
||||
|
||||
use sp_keyring::AccountKeyring;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use substrate_runner::SubstrateNode;
|
||||
use subxt::{Config, OnlineClient};
|
||||
@@ -35,7 +34,7 @@ where
|
||||
/// Construct a test node process.
|
||||
pub struct TestNodeProcessBuilder {
|
||||
node_path: OsString,
|
||||
authority: Option<AccountKeyring>,
|
||||
authority: Option<String>,
|
||||
}
|
||||
|
||||
impl TestNodeProcessBuilder {
|
||||
@@ -50,7 +49,7 @@ impl TestNodeProcessBuilder {
|
||||
}
|
||||
|
||||
/// Set the authority dev account for a node in validator mode e.g. --alice.
|
||||
pub fn with_authority(&mut self, account: AccountKeyring) -> &mut Self {
|
||||
pub fn with_authority(&mut self, account: String) -> &mut Self {
|
||||
self.authority = Some(account);
|
||||
self
|
||||
}
|
||||
@@ -64,9 +63,8 @@ impl TestNodeProcessBuilder {
|
||||
|
||||
node_builder.binary_path(self.node_path);
|
||||
|
||||
if let Some(authority) = self.authority {
|
||||
let authority = format!("{authority:?}");
|
||||
node_builder.arg(authority.as_str().to_lowercase());
|
||||
if let Some(authority) = &self.authority {
|
||||
node_builder.arg(authority.to_lowercase());
|
||||
}
|
||||
|
||||
// Spawn the node and retrieve a URL to it:
|
||||
|
||||
@@ -5,7 +5,7 @@ edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
subxt = { workspace = true }
|
||||
subxt = { workspace = true, features = ["native"] }
|
||||
|
||||
[build-dependencies]
|
||||
substrate-runner = { workspace = true }
|
||||
|
||||
@@ -13,5 +13,5 @@ trybuild = { workspace = true }
|
||||
scale-info = { workspace = true, features = ["bit-vec"] }
|
||||
frame-metadata ={ workspace = true }
|
||||
codec = { package = "parity-scale-codec", workspace = true, features = ["derive", "bit-vec"] }
|
||||
subxt = { workspace = true }
|
||||
subxt = { workspace = true, features = ["native", "jsonrpsee"] }
|
||||
subxt-metadata = { workspace = true }
|
||||
|
||||
Generated
+39
-29
@@ -988,18 +988,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.58"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8"
|
||||
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.27"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
|
||||
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1119,23 +1119,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-decode"
|
||||
version = "0.5.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7e5527e4b3bf079d4c0b2f253418598c380722ba37ef20fac9088081407f2b6"
|
||||
checksum = "f0459d00b0dbd2e765009924a78ef36b2ff7ba116292d732f00eb0ed8e465d15"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"primitive-types",
|
||||
"scale-bits",
|
||||
"scale-decode-derive",
|
||||
"scale-info",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scale-decode-derive"
|
||||
version = "0.5.0"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b38741b2f78e4391b94eac6b102af0f6ea2b0f7fe65adb55d7f4004f507854db"
|
||||
checksum = "4391f0dfbb6690f035f6d2a15d6a12f88cc5395c36bcc056db07ffa2a90870ec"
|
||||
dependencies = [
|
||||
"darling 0.14.4",
|
||||
"proc-macro-crate",
|
||||
@@ -1146,23 +1147,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-encode"
|
||||
version = "0.1.2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15546e5efbb45f0fc2291f7e202dee8623274c5d8bbfdf9c6886cc8b44a7ced3"
|
||||
checksum = "b0401b7cdae8b8aa33725f3611a051358d5b32887ecaa0fda5953a775b2d4d76"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"primitive-types",
|
||||
"scale-bits",
|
||||
"scale-encode-derive",
|
||||
"scale-info",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scale-encode-derive"
|
||||
version = "0.1.2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd983cf0a9effd76138554ead18a6de542d1af175ac12fd5e91836c5c0268082"
|
||||
checksum = "316e0fb10ec0fee266822bd641bab5e332a4ab80ef8c5b5ff35e5401a394f5a6"
|
||||
dependencies = [
|
||||
"darling 0.14.4",
|
||||
"proc-macro-crate",
|
||||
@@ -1199,10 +1201,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "scale-value"
|
||||
version = "0.7.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11f549769261561e6764218f847e500588f9a79a289de49ce92f9e26642a3574"
|
||||
checksum = "f2096d36e94ce9bf87d8addb752423b6b19730dc88edd7cc452bb2b90573f7a7"
|
||||
dependencies = [
|
||||
"base58",
|
||||
"blake2",
|
||||
"either",
|
||||
"frame-metadata",
|
||||
"parity-scale-codec",
|
||||
@@ -1271,18 +1275,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.163"
|
||||
version = "1.0.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
|
||||
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.163"
|
||||
version = "1.0.164"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
|
||||
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1352,6 +1356,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.9"
|
||||
@@ -1379,9 +1389,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sp-core-hashing"
|
||||
version = "8.0.0"
|
||||
version = "9.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27449abdfbe41b473e625bce8113745e81d65777dd1d5a8462cf24137930dad8"
|
||||
checksum = "2ee599a8399448e65197f9a6cee338ad192e9023e35e31f22382964c3c174c68"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"byteorder",
|
||||
@@ -1394,9 +1404,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sp-std"
|
||||
version = "7.0.0"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1de8eef39962b5b97478719c493bed2926cf70cb621005bbf68ebe58252ff986"
|
||||
checksum = "53458e3c57df53698b3401ec0934bea8e8cfce034816873c0b0abbd83d7bac0d"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
@@ -1418,13 +1428,13 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.5.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "subxt"
|
||||
version = "0.28.0"
|
||||
version = "0.29.0"
|
||||
dependencies = [
|
||||
"base58",
|
||||
"blake2",
|
||||
@@ -1454,7 +1464,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-codegen"
|
||||
version = "0.28.0"
|
||||
version = "0.29.0"
|
||||
dependencies = [
|
||||
"frame-metadata",
|
||||
"heck",
|
||||
@@ -1472,7 +1482,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-macro"
|
||||
version = "0.28.0"
|
||||
version = "0.29.0"
|
||||
dependencies = [
|
||||
"darling 0.20.1",
|
||||
"proc-macro-error",
|
||||
@@ -1482,7 +1492,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "subxt-metadata"
|
||||
version = "0.28.0"
|
||||
version = "0.29.0"
|
||||
dependencies = [
|
||||
"frame-metadata",
|
||||
"parity-scale-codec",
|
||||
@@ -1841,7 +1851,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-test"
|
||||
name = "wasm-tests"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
[package]
|
||||
name = "wasm-test"
|
||||
name = "wasm-tests"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dev-dependencies]
|
||||
# This crate is not a part of the workspace, sadly, because it
|
||||
# requires a mutually exclusive jsonrpsee feature to be enabled on
|
||||
# subxt to work. So, we can't inherit versions.
|
||||
wasm-bindgen-test = "0.3.24"
|
||||
tracing-wasm = "0.2.1"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
serde_json = "1"
|
||||
subxt = { path = "../../subxt", default-features = false, features = ["jsonrpsee-web"] }
|
||||
|
||||
# This crate is not a part of the workspace, because it
|
||||
# requires the "jsonrpsee-web" feature to be enabled, which we don't
|
||||
# want enabled for workspace builds in general.
|
||||
subxt = { path = "../../subxt", default-features = false, features = ["web", "jsonrpsee"] }
|
||||
|
||||
@@ -5,7 +5,13 @@ use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
/// Run the tests by `$ wasm-pack test --firefox --headless`
|
||||
// Run the tests by calling:
|
||||
//
|
||||
// ```text
|
||||
// wasm-pack test --firefox --headless`
|
||||
// ```
|
||||
//
|
||||
// You'll need to have a substrate/polkadot node running too with eg `substrate --dev`.
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn wasm_ws_transport_works() {
|
||||
|
||||
Reference in New Issue
Block a user