>::Extra>,
Error
>
{
let account_nonce = self.account(account_id).await?.nonce;
let version = self.runtime_version.spec_version;
let genesis_hash = self.genesis_hash;
let call = self
.metadata()
.module_with_calls(&call.module)
.and_then(|module| module.call(&call.function, call.args))?;
let extra: E = E::new(version, account_nonce, genesis_hash);
let raw_payload = SignedPayload::new(call, extra.extra())?;
Ok(raw_payload)
}
/// Create a transaction builder for a private key.
pub async fn xt(
&self,
signer: P,
nonce: Option,
) -> Result, Error>
where
P: Pair,
P::Signature: Codec,
S: Verify,
S::Signer: From + IdentifyAccount,
{
let account_id = S::Signer::from(signer.public()).into_account();
let nonce = match nonce {
Some(nonce) => nonce,
None => self.account(account_id).await?.nonce,
};
let genesis_hash = self.genesis_hash;
let runtime_version = self.runtime_version.clone();
Ok(XtBuilder {
client: self.clone(),
nonce,
runtime_version,
genesis_hash,
signer,
})
}
}
/// Transaction builder.
#[derive(Clone)]
pub struct XtBuilder {
client: Client,
nonce: T::Index,
runtime_version: RuntimeVersion,
genesis_hash: T::Hash,
signer: P,
}
impl XtBuilder
where
P: Pair,
E: SignedExtra + SignedExtension + 'static,
{
/// Returns the chain metadata.
pub fn metadata(&self) -> &Metadata {
self.client.metadata()
}
/// Returns the nonce.
pub fn nonce(&self) -> T::Index {
self.nonce
}
/// Sets the nonce to a new value.
pub fn set_nonce(&mut self, nonce: T::Index) -> &mut XtBuilder {
self.nonce = nonce;
self
}
/// Increment the nonce
pub fn increment_nonce(&mut self) -> &mut XtBuilder {
self.set_nonce(self.nonce() + 1.into());
self
}
}
impl XtBuilder
where
P: Pair,
S: Verify + Codec + From,
S::Signer: From + IdentifyAccount,
T::Address: From,
E: SignedExtra + SignedExtension + 'static,
{
/// Creates and signs an Extrinsic for the supplied `Call`
pub fn create_and_sign(
&self,
call: Call,
) -> Result<
UncheckedExtrinsic>::Extra>,
Error,
>
where
C: codec::Encode,
{
let signer = self.signer.clone();
let account_nonce = self.nonce;
let version = self.runtime_version.spec_version;
let genesis_hash = self.genesis_hash;
let call = self
.metadata()
.module_with_calls(&call.module)
.and_then(|module| module.call(&call.function, call.args))?;
log::info!(
"Creating Extrinsic with genesis hash {:?} and account nonce {:?}",
genesis_hash,
account_nonce
);
let extra = E::new(version, account_nonce, genesis_hash);
let xt = extrinsic::create_and_sign::<_, _, _, S, _>(signer, call, extra)?;
Ok(xt)
}
/// Submits a transaction to the chain.
pub async fn submit(&self, call: Call) -> Result {
let extrinsic = self.create_and_sign(call)?;
let xt_hash = self.client.submit_extrinsic(extrinsic).await?;
Ok(xt_hash)
}
/// Submits transaction to the chain and watch for events.
pub fn watch(self) -> EventsSubscriber {
let metadata = self.client.metadata().clone();
let decoder = EventsDecoder::try_from(metadata).map_err(Into::into);
EventsSubscriber {
client: self.client.clone(),
builder: self,
decoder,
}
}
}
/// Submits an extrinsic and subscribes to the triggered events
pub struct EventsSubscriber {
client: Client,
builder: XtBuilder,
decoder: Result, EventsError>,
}
impl
EventsSubscriber
where
P: Pair,
S: Verify + Codec + From,
S::Signer: From + IdentifyAccount,
T::Address: From,
E: SignedExtra + SignedExtension + 'static,
{
/// Access the events decoder for registering custom type sizes
pub fn events_decoder<
F: FnOnce(&mut EventsDecoder) -> Result,
>(
self,
f: F,
) -> Self {
let mut this = self;
if let Ok(ref mut decoder) = this.decoder {
if let Err(err) = f(decoder) {
this.decoder = Err(err)
}
}
this
}
/// Submits transaction to the chain and watch for events.
pub async fn submit(
self,
call: Call,
) -> Result, Error> {
let decoder = self.decoder?;
let extrinsic = self.builder.create_and_sign(call)?;
let xt_success = self
.client
.submit_and_watch_extrinsic(extrinsic, decoder)
.await?;
Ok(xt_success)
}
}
/// Wraps an already encoded byte vector, prevents being encoded as a raw byte vector as part of
/// the transaction payload
#[derive(Clone)]
pub struct Encoded(pub Vec);
impl codec::Encode for Encoded {
fn encode(&self) -> Vec {
self.0.to_owned()
}
}
#[cfg(test)]
mod tests {
use sp_keyring::{
AccountKeyring,
Ed25519Keyring,
};
use super::*;
use crate::{
DefaultNodeRuntime as Runtime,
Error,
};
pub(crate) async fn test_client() -> Client {
ClientBuilder::::new()
.build()
.await
.expect("Error creating client")
}
#[test]
#[ignore] // requires locally running substrate node
fn test_tx_transfer_balance() {
env_logger::try_init().ok();
let transfer = async_std::task::block_on(async move {
let signer = AccountKeyring::Alice.pair();
let dest = AccountKeyring::Bob.to_account_id();
let client = test_client().await;
let mut xt = client.xt(signer, None).await?;
let _ = xt
.submit(balances::transfer::(dest.clone().into(), 10_000))
.await?;
// check that nonce is handled correctly
xt.increment_nonce()
.submit(balances::transfer::(dest.clone().into(), 10_000))
.await
});
assert!(transfer.is_ok())
}
#[test]
#[ignore] // requires locally running substrate node
fn test_getting_hash() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let block_hash = client.block_hash(None).await?;
Ok(block_hash)
});
assert!(result.is_ok())
}
#[test]
#[ignore] // requires locally running substrate node
fn test_getting_block() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let block_hash = client.block_hash(None).await?;
let block = client.block(block_hash).await?;
Ok(block)
});
assert!(result.is_ok())
}
#[test]
#[ignore] // requires locally running substrate node
fn test_state_read_free_balance() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let account = AccountKeyring::Alice.to_account_id();
let client = test_client().await;
let balance = client.account(account.into()).await?.data.free;
Ok(balance)
});
assert!(result.is_ok())
}
#[test]
#[ignore] // requires locally running substrate node
fn test_chain_subscribe_blocks() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let mut blocks = client.subscribe_blocks().await?;
let block = blocks.next().await;
Ok(block)
});
assert!(result.is_ok())
}
#[test]
#[ignore] // requires locally running substrate node
fn test_chain_subscribe_finalized_blocks() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let client = test_client().await;
let mut blocks = client.subscribe_finalized_blocks().await?;
let block = blocks.next().await;
Ok(block)
});
assert!(result.is_ok())
}
#[test]
#[ignore] // requires locally running substrate node
fn test_create_raw_payload() {
let result: Result<_, Error> = async_std::task::block_on(async move {
let signer_pair = Ed25519Keyring::Alice.pair();
let signer_account_id = Ed25519Keyring::Alice.to_account_id();
let dest = AccountKeyring::Bob.to_account_id();
let client = test_client().await;
// create raw payload with AccoundId and sign it
let raw_payload = client
.create_raw_payload(
signer_account_id,
balances::transfer::(dest.clone().into(), 10_000),
)
.await?;
let raw_signature =
signer_pair.sign(raw_payload.encode().as_slice());
let raw_multisig = MultiSignature::from(raw_signature);
// create signature with Xtbuilder
let xt = client.xt(signer_pair.clone(), None).await?;
let xt_multi_sig = xt
.create_and_sign(balances::transfer::(
dest.clone().into(),
10_000,
))?
.signature
.unwrap()
.1;
// compare signatures
assert_eq!(raw_multisig, xt_multi_sig);
Ok(())
});
assert!(result.is_ok())
}
}