use async_trait::async_trait;
use hedera_proto::services;
use hedera_proto::services::crypto_service_client::CryptoServiceClient;
use tonic::transport::Channel;
use crate::entity_id::AutoValidateChecksum;
use crate::transaction::{
AnyTransactionData,
ToTransactionDataProtobuf,
TransactionExecute,
};
use crate::{
AccountId,
Error,
Hbar,
LedgerId,
NftId,
ToProtobuf,
TokenId,
Transaction,
};
pub type AccountAllowanceApproveTransaction = Transaction<AccountAllowanceApproveTransactionData>;
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ffi", serde(rename_all = "camelCase"))]
pub struct AccountAllowanceApproveTransactionData {
hbar_allowances: Vec<HbarAllowance>,
token_allowances: Vec<TokenAllowance>,
nft_allowances: Vec<NftAllowance>,
}
impl AccountAllowanceApproveTransaction {
pub fn approve_hbar_allowance(
&mut self,
owner_account_id: AccountId,
spender_account_id: AccountId,
amount: Hbar,
) -> &mut Self {
self.body.data.hbar_allowances.push(HbarAllowance {
owner_account_id,
spender_account_id,
amount,
});
self
}
pub fn approve_token_allowance(
&mut self,
token_id: TokenId,
owner_account_id: AccountId,
spender_account_id: AccountId,
amount: u64,
) -> &mut Self {
self.body.data.token_allowances.push(TokenAllowance {
token_id,
owner_account_id,
spender_account_id,
amount,
});
self
}
pub fn approve_token_nft_allowance(
&mut self,
nft_id: impl Into<NftId>,
owner_account_id: AccountId,
spender_account_id: AccountId,
) -> &mut Self {
let nft_id = nft_id.into();
let owner_account_id = owner_account_id;
let spender_account_id = spender_account_id;
if let Some(allowance) = self.body.data.nft_allowances.iter_mut().find(|allowance| {
allowance.token_id == nft_id.token_id
&& allowance.owner_account_id == owner_account_id
&& allowance.spender_account_id == spender_account_id
&& allowance.approved_for_all.is_none()
}) {
allowance.serials.push(nft_id.serial as i64);
} else {
self.body.data.nft_allowances.push(NftAllowance {
serials: vec![nft_id.serial as i64],
token_id: nft_id.token_id,
spender_account_id,
owner_account_id,
delegating_spender_account_id: None,
approved_for_all: None,
});
}
self
}
pub fn approve_token_nft_allowance_all_serials(
&mut self,
token_id: TokenId,
owner_account_id: AccountId,
spender_account_id: AccountId,
) -> &mut Self {
let owner_account_id = owner_account_id;
let spender_account_id = spender_account_id;
self.body.data.nft_allowances.push(NftAllowance {
approved_for_all: Some(true),
delegating_spender_account_id: None,
spender_account_id,
owner_account_id,
token_id,
serials: Vec::new(),
});
self
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ffi", serde(rename_all = "camelCase"))]
struct HbarAllowance {
owner_account_id: AccountId,
spender_account_id: AccountId,
amount: Hbar,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ffi", serde(rename_all = "camelCase"))]
struct TokenAllowance {
token_id: TokenId,
owner_account_id: AccountId,
spender_account_id: AccountId,
amount: u64,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ffi", serde(rename_all = "camelCase"))]
struct NftAllowance {
token_id: TokenId,
owner_account_id: AccountId,
spender_account_id: AccountId,
serials: Vec<i64>,
approved_for_all: Option<bool>,
delegating_spender_account_id: Option<AccountId>,
}
#[async_trait]
impl TransactionExecute for AccountAllowanceApproveTransactionData {
fn validate_checksums_for_ledger_id(&self, ledger_id: &LedgerId) -> Result<(), Error> {
for hbar_allowance in &self.hbar_allowances {
hbar_allowance.owner_account_id.validate_checksum_for_ledger_id(ledger_id)?;
hbar_allowance.spender_account_id.validate_checksum_for_ledger_id(ledger_id)?;
}
for token_allowance in &self.token_allowances {
token_allowance.token_id.validate_checksum_for_ledger_id(ledger_id)?;
token_allowance.owner_account_id.validate_checksum_for_ledger_id(ledger_id)?;
token_allowance.spender_account_id.validate_checksum_for_ledger_id(ledger_id)?;
}
for nft_allowance in &self.nft_allowances {
nft_allowance.token_id.validate_checksum_for_ledger_id(ledger_id)?;
nft_allowance.spender_account_id.validate_checksum_for_ledger_id(ledger_id)?;
nft_allowance.owner_account_id.validate_checksum_for_ledger_id(ledger_id)?;
if let Some(delegating_spender) = nft_allowance.delegating_spender_account_id {
delegating_spender.validate_checksum_for_ledger_id(ledger_id)?;
}
}
Ok(())
}
async fn execute(
&self,
channel: Channel,
request: services::Transaction,
) -> Result<tonic::Response<services::TransactionResponse>, tonic::Status> {
CryptoServiceClient::new(channel).approve_allowances(request).await
}
}
impl ToTransactionDataProtobuf for AccountAllowanceApproveTransactionData {
fn to_transaction_data_protobuf(
&self,
_node_account_id: AccountId,
_transaction_id: &crate::TransactionId,
) -> services::transaction_body::Data {
let crypto_allowances = self.hbar_allowances.to_protobuf();
let token_allowances = self.token_allowances.to_protobuf();
let nft_allowances = self.nft_allowances.to_protobuf();
services::transaction_body::Data::CryptoApproveAllowance(
services::CryptoApproveAllowanceTransactionBody {
crypto_allowances,
nft_allowances,
token_allowances,
},
)
}
}
impl From<AccountAllowanceApproveTransactionData> for AnyTransactionData {
fn from(transaction: AccountAllowanceApproveTransactionData) -> Self {
Self::AccountAllowanceApprove(transaction)
}
}
impl ToProtobuf for HbarAllowance {
type Protobuf = services::CryptoAllowance;
fn to_protobuf(&self) -> Self::Protobuf {
Self::Protobuf {
owner: Some(self.owner_account_id.to_protobuf()),
spender: Some(self.spender_account_id.to_protobuf()),
amount: self.amount.to_tinybars(),
}
}
}
impl ToProtobuf for TokenAllowance {
type Protobuf = services::TokenAllowance;
fn to_protobuf(&self) -> Self::Protobuf {
Self::Protobuf {
token_id: Some(self.token_id.to_protobuf()),
owner: Some(self.owner_account_id.to_protobuf()),
spender: Some(self.spender_account_id.to_protobuf()),
amount: self.amount as i64,
}
}
}
impl ToProtobuf for NftAllowance {
type Protobuf = services::NftAllowance;
fn to_protobuf(&self) -> Self::Protobuf {
Self::Protobuf {
token_id: Some(self.token_id.to_protobuf()),
owner: Some(self.owner_account_id.to_protobuf()),
spender: Some(self.spender_account_id.to_protobuf()),
serial_numbers: self.serials.clone(),
approved_for_all: self.approved_for_all,
delegating_spender: self.delegating_spender_account_id.to_protobuf(),
}
}
}