use async_trait::async_trait;
use hedera_proto::services::schedule_service_client::ScheduleServiceClient;
use hedera_proto::services::transaction_body::Data;
use hedera_proto::services::{
self,
schedulable_transaction_body,
transaction_body,
};
use time::OffsetDateTime;
use tonic::transport::Channel;
use crate::entity_id::AutoValidateChecksum;
use crate::protobuf::ToProtobuf;
use crate::transaction::{
AnyTransactionData,
ToTransactionDataProtobuf,
TransactionExecute,
};
use crate::{
AccountId,
Error,
Hbar,
Key,
LedgerId,
Transaction,
TransactionId,
};
pub type ScheduleCreateTransaction = Transaction<ScheduleCreateTransactionData>;
#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ffi", serde(rename_all = "camelCase", default))]
pub struct ScheduleCreateTransactionData {
scheduled_transaction: Option<SchedulableTransactionBody>,
schedule_memo: Option<String>,
admin_key: Option<Key>,
payer_account_id: Option<AccountId>,
#[cfg_attr(
feature = "ffi",
serde(with = "serde_with::As::<Option<serde_with::TimestampNanoSeconds>>")
)]
expiration_time: Option<OffsetDateTime>,
wait_for_expiry: bool,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ffi", serde(rename_all = "camelCase"))]
struct SchedulableTransactionBody {
#[cfg_attr(feature = "ffi", serde(flatten))]
data: Box<AnyTransactionData>,
#[cfg_attr(feature = "ffi", serde(default))]
max_transaction_fee: Option<Hbar>,
#[cfg_attr(feature = "ffi", serde(default, skip_serializing_if = "String::is_empty"))]
transaction_memo: String,
}
impl ScheduleCreateTransaction {
pub fn scheduled_transaction<D>(&mut self, transaction: Transaction<D>) -> &mut Self
where
D: TransactionExecute,
{
let body = transaction.into_body();
self.data_mut().scheduled_transaction = Some(SchedulableTransactionBody {
max_transaction_fee: body.max_transaction_fee,
transaction_memo: body.transaction_memo,
data: Box::new(body.data.into()),
});
self
}
#[must_use]
pub fn get_expiration_time(&self) -> Option<OffsetDateTime> {
self.data().expiration_time
}
pub fn expiration_time(&mut self, time: OffsetDateTime) -> &mut Self {
self.data_mut().expiration_time = Some(time);
self
}
#[must_use]
pub fn get_wait_for_expiry(&self) -> bool {
self.data().wait_for_expiry
}
pub fn wait_for_expiry(&mut self, wait: bool) -> &mut Self {
self.data_mut().wait_for_expiry = wait;
self
}
#[must_use]
pub fn get_payer_account_id(&self) -> Option<AccountId> {
self.data().payer_account_id
}
pub fn payer_account_id(&mut self, id: AccountId) -> &mut Self {
self.data_mut().payer_account_id = Some(id);
self
}
#[must_use]
pub fn get_schedule_memo(&self) -> Option<&str> {
self.data().schedule_memo.as_deref()
}
pub fn schedule_memo(&mut self, memo: impl Into<String>) -> &mut Self {
self.data_mut().schedule_memo = Some(memo.into());
self
}
#[must_use]
pub fn get_admin_key(&self) -> Option<&Key> {
self.data().admin_key.as_ref()
}
pub fn admin_key(&mut self, key: impl Into<Key>) -> &mut Self {
self.data_mut().admin_key = Some(key.into());
self
}
}
#[async_trait]
impl TransactionExecute for ScheduleCreateTransactionData {
fn validate_checksums_for_ledger_id(&self, ledger_id: &LedgerId) -> Result<(), Error> {
self.payer_account_id.validate_checksum_for_ledger_id(ledger_id)
}
async fn execute(
&self,
channel: Channel,
request: services::Transaction,
) -> Result<tonic::Response<services::TransactionResponse>, tonic::Status> {
ScheduleServiceClient::new(channel).create_schedule(request).await
}
}
impl ToTransactionDataProtobuf for ScheduleCreateTransactionData {
#[allow(clippy::too_many_lines)]
fn to_transaction_data_protobuf(
&self,
node_account_id: AccountId,
transaction_id: &TransactionId,
) -> transaction_body::Data {
let body = self.scheduled_transaction.as_ref().map(|scheduled| {
let data = scheduled.data.to_transaction_data_protobuf(node_account_id, transaction_id);
#[allow(clippy::match_same_arms)]
let data = match data {
transaction_body::Data::ConsensusCreateTopic(data) => {
Some(schedulable_transaction_body::Data::ConsensusCreateTopic(data))
}
transaction_body::Data::ContractCreateInstance(data) => {
Some(schedulable_transaction_body::Data::ContractCreateInstance(data))
}
transaction_body::Data::ContractUpdateInstance(data) => {
Some(schedulable_transaction_body::Data::ContractUpdateInstance(data))
}
transaction_body::Data::ContractDeleteInstance(data) => {
Some(schedulable_transaction_body::Data::ContractDeleteInstance(data))
}
transaction_body::Data::EthereumTransaction(_) => {
None
}
transaction_body::Data::CryptoAddLiveHash(_) => {
None
}
transaction_body::Data::CryptoApproveAllowance(data) => {
Some(schedulable_transaction_body::Data::CryptoApproveAllowance(data))
}
transaction_body::Data::CryptoDeleteAllowance(data) => {
Some(schedulable_transaction_body::Data::CryptoDeleteAllowance(data))
}
transaction_body::Data::CryptoCreateAccount(data) => {
Some(schedulable_transaction_body::Data::CryptoCreateAccount(data))
}
transaction_body::Data::CryptoDelete(data) => {
Some(schedulable_transaction_body::Data::CryptoDelete(data))
}
transaction_body::Data::CryptoDeleteLiveHash(_) => {
None
}
transaction_body::Data::CryptoTransfer(data) => {
Some(schedulable_transaction_body::Data::CryptoTransfer(data))
}
transaction_body::Data::CryptoUpdateAccount(data) => {
Some(schedulable_transaction_body::Data::CryptoUpdateAccount(data))
}
transaction_body::Data::FileAppend(data) => {
Some(schedulable_transaction_body::Data::FileAppend(data))
}
transaction_body::Data::FileCreate(data) => {
Some(schedulable_transaction_body::Data::FileCreate(data))
}
transaction_body::Data::FileDelete(data) => {
Some(schedulable_transaction_body::Data::FileDelete(data))
}
transaction_body::Data::FileUpdate(data) => {
Some(schedulable_transaction_body::Data::FileUpdate(data))
}
transaction_body::Data::SystemDelete(data) => {
Some(schedulable_transaction_body::Data::SystemDelete(data))
}
transaction_body::Data::SystemUndelete(data) => {
Some(schedulable_transaction_body::Data::SystemUndelete(data))
}
transaction_body::Data::Freeze(data) => {
Some(schedulable_transaction_body::Data::Freeze(data))
}
transaction_body::Data::ConsensusUpdateTopic(data) => {
Some(schedulable_transaction_body::Data::ConsensusUpdateTopic(data))
}
transaction_body::Data::ConsensusDeleteTopic(data) => {
Some(schedulable_transaction_body::Data::ConsensusDeleteTopic(data))
}
transaction_body::Data::ConsensusSubmitMessage(data) => {
Some(schedulable_transaction_body::Data::ConsensusSubmitMessage(data))
}
transaction_body::Data::UncheckedSubmit(_) => {
None
}
transaction_body::Data::TokenCreation(data) => {
Some(schedulable_transaction_body::Data::TokenCreation(data))
}
transaction_body::Data::TokenFreeze(data) => {
Some(schedulable_transaction_body::Data::TokenFreeze(data))
}
transaction_body::Data::TokenUnfreeze(data) => {
Some(schedulable_transaction_body::Data::TokenUnfreeze(data))
}
transaction_body::Data::TokenGrantKyc(data) => {
Some(schedulable_transaction_body::Data::TokenGrantKyc(data))
}
transaction_body::Data::TokenRevokeKyc(data) => {
Some(schedulable_transaction_body::Data::TokenRevokeKyc(data))
}
transaction_body::Data::TokenDeletion(data) => {
Some(schedulable_transaction_body::Data::TokenDeletion(data))
}
transaction_body::Data::TokenUpdate(data) => {
Some(schedulable_transaction_body::Data::TokenUpdate(data))
}
transaction_body::Data::TokenMint(data) => {
Some(schedulable_transaction_body::Data::TokenMint(data))
}
transaction_body::Data::TokenBurn(data) => {
Some(schedulable_transaction_body::Data::TokenBurn(data))
}
transaction_body::Data::TokenWipe(data) => {
Some(schedulable_transaction_body::Data::TokenWipe(data))
}
transaction_body::Data::TokenAssociate(data) => {
Some(schedulable_transaction_body::Data::TokenAssociate(data))
}
transaction_body::Data::TokenDissociate(data) => {
Some(schedulable_transaction_body::Data::TokenDissociate(data))
}
transaction_body::Data::TokenFeeScheduleUpdate(data) => {
Some(schedulable_transaction_body::Data::TokenFeeScheduleUpdate(data))
}
transaction_body::Data::TokenPause(data) => {
Some(schedulable_transaction_body::Data::TokenPause(data))
}
transaction_body::Data::TokenUnpause(data) => {
Some(schedulable_transaction_body::Data::TokenUnpause(data))
}
transaction_body::Data::ScheduleCreate(_) => {
None
}
transaction_body::Data::ScheduleDelete(data) => {
Some(schedulable_transaction_body::Data::ScheduleDelete(data))
}
transaction_body::Data::ScheduleSign(_) => {
None
}
transaction_body::Data::ContractCall(data) => {
Some(schedulable_transaction_body::Data::ContractCall(data))
}
Data::NodeStakeUpdate(_) => {
unimplemented!("NodeStakeUpdate has not been implemented")
}
Data::UtilPrng(_) => {
unimplemented!("UtilPrng has not been implemented")
}
};
services::SchedulableTransactionBody {
data,
memo: scheduled.transaction_memo.clone(),
transaction_fee: scheduled
.max_transaction_fee
.unwrap_or_else(|| scheduled.data.default_max_transaction_fee())
.to_tinybars() as u64,
}
});
let payer_account_id = self.payer_account_id.to_protobuf();
let admin_key = self.admin_key.to_protobuf();
let expiration_time = self.expiration_time.map(Into::into);
transaction_body::Data::ScheduleCreate(services::ScheduleCreateTransactionBody {
scheduled_transaction_body: body,
memo: self.schedule_memo.clone().unwrap_or_default(),
admin_key,
payer_account_id,
expiration_time,
wait_for_expiry: self.wait_for_expiry,
})
}
}
impl From<ScheduleCreateTransactionData> for AnyTransactionData {
fn from(transaction: ScheduleCreateTransactionData) -> Self {
Self::ScheduleCreate(transaction)
}
}