use subxt::ext::sp_core::sr25519::Pair;
use subxt::{ext::scale_encode::EncodeAsFields, tx::PairSigner};
use crate::types::{MandalaClient, MandalaExtrinsics};
use super::key_manager::prelude::PrivateKey;
pub trait WrappedExtrinsic<T: EncodeAsFields> {
fn into_inner(self) -> subxt::tx::Payload<T>;
}
pub struct TxBuilder;
impl TxBuilder {
pub fn unsigned<T: EncodeAsFields>(
client: &MandalaClient,
payload: impl WrappedExtrinsic<T>,
) -> Result<MandalaExtrinsics, crate::error::Error> {
Ok(client.0.tx().create_unsigned(&payload.into_inner())?.into())
}
pub async fn signed<T: EncodeAsFields>(
client: &MandalaClient,
acc: PrivateKey,
payload: impl WrappedExtrinsic<T>,
) -> Result<MandalaExtrinsics, crate::error::Error> {
let signer = PairSigner::new(acc.0);
let tx = client
.0
.tx()
.create_signed(&payload.into_inner(), &signer, Default::default())
.await?
.into();
Ok(tx)
}
pub fn signed_with_nonce<T: EncodeAsFields>(
client: &MandalaClient,
acc: Pair,
nonce: u32,
payload: impl WrappedExtrinsic<T>,
) -> Result<MandalaExtrinsics, crate::error::Error> {
let signer = PairSigner::new(acc);
let tx = client
.0
.tx()
.create_signed_with_nonce(
&payload.into_inner(),
&signer,
nonce as u64,
Default::default(),
)?
.into();
Ok(tx)
}
}
#[cfg(test)]
mod tests {
use crate::runtime_types::api as mandala;
use std::str::FromStr;
use subxt::backend::legacy::rpc_methods::DryRunResult;
use subxt::error::DispatchError;
pub(crate) use subxt::OnlineClient;
use subxt::ext::sp_core::{crypto::Ss58Codec, Pair};
use subxt::tx::ValidationResult;
use subxt::{
utils::{AccountId32, MultiAddress},
};
use crate::types::MandalaConfig;
use super::*;
async fn mock_client() -> MandalaClient {
MandalaClient::dev().await.expect("node should be running")
}
struct MockWrappedExtrinsic<T: EncodeAsFields>(subxt::tx::Payload<T>);
impl<T: EncodeAsFields> WrappedExtrinsic<T> for MockWrappedExtrinsic<T> {
fn into_inner(self) -> subxt::tx::Payload<T> {
self.0
}
}
fn mock_payload() -> MockWrappedExtrinsic<mandala::balances::calls::types::Transfer> {
let dest = mock_acc();
MockWrappedExtrinsic(mandala::tx().balances().transfer(dest, 0))
}
fn mock_acc() -> MultiAddress<AccountId32, ()> {
let dest = sp_keyring::Sr25519Keyring::Bob.pair();
let dest = dest.public();
let dest = AccountId32::from_str(&dest.to_ss58check()).unwrap();
subxt::utils::MultiAddress::Id(dest)
}
fn mock_pair() -> subxt::ext::sp_core::sr25519::Pair {
sp_keyring::Sr25519Keyring::Alice.pair()
}
#[tokio::test]
async fn should_create_unsigned_tx() {
let node_client = mock_client().await;
let payload = mock_payload();
let extrinsic_result = TxBuilder::unsigned(&node_client, payload);
assert!(extrinsic_result.is_ok());
let extrinsic = extrinsic_result.unwrap().0;
let dry_run_result = extrinsic.validate().await.unwrap();
let actual_result = extrinsic.submit().await;
if let ValidationResult::DryRunResult::DispatchError(err) = dry_run_result {
assert_eq!(
format!("{:?}", err),
format!("{:?}", DispatchError::BadOrigin)
);
}
if let Err(actual_result) = actual_result {
println!("{}", actual_result)
}
}
#[tokio::test]
async fn should_create_signed_tx() {
let node_client = mock_client().await;
let payload = mock_payload();
let pair = mock_pair();
let extrinsic = TxBuilder::signed(&node_client, pair.into(), payload)
.await
.unwrap()
.0;
let _dry_run_result = extrinsic.dry_run(None).await.unwrap();
let actual_result = extrinsic.submit().await;
assert!(actual_result.is_ok());
}
#[tokio::test]
async fn should_create_signed_tx_with_nonce() {
let node_client = mock_client().await;
let payload = mock_payload();
let pair = mock_pair();
let query_signer = PairSigner::<MandalaConfig, subxt::ext::sp_core::sr25519::Pair>::new(pair.clone());
let query_pair = query_signer.account_id();
let nonce = node_client
.0
.rpc()
.system_account_next_index(query_pair)
.await
.unwrap();
let extrinsic = TxBuilder::signed_with_nonce(&node_client, pair, nonce, payload)
.unwrap()
.0;
let _dry_run_result = extrinsic.dry_run(None).await.unwrap();
let actual_result = extrinsic.submit().await;
assert!(actual_result.is_ok());
}
}