use jsonrpsee::core::client::Subscription;
use codec::{Decode, Encode};
use sp_runtime::generic::Era;
use async_trait::async_trait;
use serde::{de::DeserializeOwned, ser::Serialize};
use crate::*;
pub trait CodecSerde:
Clone + Encode + Decode + Serialize + DeserializeOwned + std::fmt::Debug
{
}
impl<T> CodecSerde for T where
T: Clone + Encode + Decode + Serialize + DeserializeOwned + std::fmt::Debug
{
}
#[async_trait]
pub trait ChainApi {
type RuntimeCall: CodecSerde;
type RuntimeEvent: CodecSerde;
async fn get_nonce(&self, account: AccountId) -> Result<u32>;
async fn block_events(
&self,
block: Option<BlockHash>,
) -> Result<Vec<EventRecord<Self::RuntimeEvent>>>;
fn client(&self) -> &Client;
}
pub struct TransactionResults<'api, Api: ChainApi> {
api: &'api Api,
sub: Option<Subscription<TransactionStatus>>,
tx_hash: TxHash,
status: Option<TransactionStatus>,
block: Option<BlockHash>,
events: Option<EventRecords<Api::RuntimeEvent>>,
finalized: bool,
}
impl<'api, Api: ChainApi> TransactionResults<'api, Api> {
pub fn new(api: &'api Api, sub: Subscription<TransactionStatus>, tx_hash: TxHash) -> Self {
Self {
api,
sub: Some(sub),
tx_hash,
status: None,
block: None,
events: None,
finalized: false,
}
}
async fn next_status(&mut self) -> Result<bool> {
if let Some(sub) = &mut self.sub {
match sub.next().await {
None => {
self.sub = None;
Ok(false)
}
Some(Ok(status)) => {
use TransactionStatus::*;
match status {
InBlock(block) => {
self.block = Some(block);
}
Finalized(block) => {
self.finalized = true;
self.block = Some(block);
}
Future | Ready | Broadcast(_) => (),
Retracted(_) => {
self.block = None;
}
_ => {
self.block = None;
self.sub = None;
}
}
self.status = Some(status);
Ok(true)
}
Some(Err(err)) => {
self.sub = None;
Err(err)?
}
}
} else {
Ok(false)
}
}
pub async fn events(&mut self) -> Result<Option<&EventRecords<Api::RuntimeEvent>>> {
self.load_events().await?;
Ok(self.events.as_ref())
}
async fn load_events(&mut self) -> Result<bool> {
if self.events.is_some() {
return Ok(true);
}
let block_hash = if let Some(block) = self.block {
block
} else {
match self.wait_in_block().await? {
None => {
return Ok(false);
}
Some(block) => block,
}
};
let client = self.api.client();
let idx = client
.find_extrinsic_block_index(block_hash, self.tx_hash)
.await?;
if let Some(idx) = idx {
let events = self.api.block_events(Some(block_hash)).await?;
self.events = Some(EventRecords::from_vec(
events,
Some(Phase::ApplyExtrinsic(idx as u32)),
));
Ok(true)
} else {
Ok(false)
}
}
pub fn status(&self) -> Option<&TransactionStatus> {
self.status.as_ref()
}
pub async fn wait_in_block(&mut self) -> Result<Option<BlockHash>> {
while self.block.is_none() {
if !self.next_status().await? {
return Ok(None);
}
}
return Ok(self.block);
}
}
pub struct Call<'api, Api: ChainApi> {
pub api: &'api Api,
call: Api::RuntimeCall,
}
impl<'api, Api: ChainApi> Call<'api, Api> {
pub fn new(api: &'api Api, call: Api::RuntimeCall) -> Self {
Self { api, call }
}
pub fn runtime_call(&self) -> &Api::RuntimeCall {
&self.call
}
pub fn into_runtime_call(self) -> Api::RuntimeCall {
self.call
}
pub fn encoded(&self) -> Encoded {
let call = &self.call;
call.into()
}
pub async fn submit_unsigned_and_watch(&self) -> Result<TransactionResults<'api, Api>> {
Ok(
self
.submit_and_watch(ExtrinsicV4::unsigned(self.encoded()))
.await?,
)
}
pub async fn sign_submit_and_watch(
&self,
signer: &mut impl Signer,
) -> Result<TransactionResults<'api, Api>> {
let client = self.api.client();
let account = signer.account();
let nonce = match signer.nonce() {
Some(0) | None => self.api.get_nonce(account.clone()).await?,
Some(nonce) => nonce,
};
let encoded_call = self.encoded();
let extra = Extra::new(Era::Immortal, nonce);
let payload = SignedPayload::new(&encoded_call, &extra, client.get_signed_extra());
let payload = payload.encode();
let sig = signer.sign(&payload[..]).await?;
let xt = ExtrinsicV4::signed(account, sig, extra, encoded_call);
let res = self.submit_and_watch(xt).await?;
signer.set_nonce(nonce + 1);
Ok(res)
}
pub async fn submit_and_watch(&self, xt: ExtrinsicV4) -> Result<TransactionResults<'api, Api>> {
let (tx_hex, tx_hash) = xt.as_hex_and_hash();
let status = self.api.client().submit_and_watch(tx_hex).await?;
Ok(TransactionResults::new(self.api, status, tx_hash))
}
}
impl<'api, Api: ChainApi> Encode for Call<'api, Api> {
fn size_hint(&self) -> usize {
self.call.size_hint()
}
fn encode_to<T: ::codec::Output + ?Sized>(&self, dest: &mut T) {
self.call.encode_to(dest)
}
}
impl<'api, Api: ChainApi> std::fmt::Debug for Call<'api, Api> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.call.fmt(f)
}
}