1use hiero_sdk_proto::services;
4use hiero_sdk_proto::services::token_service_client::TokenServiceClient;
5use tonic::transport::Channel;
6
7use crate::ledger_id::RefLedgerId;
8use crate::pending_airdrop_id::PendingAirdropId;
9use crate::transaction::{
10 AnyTransactionData,
11 ChunkInfo,
12 ToSchedulableTransactionDataProtobuf,
13 ToTransactionDataProtobuf,
14 TransactionData,
15 TransactionExecute,
16};
17use crate::{
18 BoxGrpcFuture,
19 Error,
20 FromProtobuf,
21 ToProtobuf,
22 Transaction,
23 ValidateChecksums,
24};
25
26pub type TokenCancelAirdropTransaction = Transaction<TokenCancelAirdropTransactionData>;
35
36#[derive(Debug, Clone, Default)]
37pub struct TokenCancelAirdropTransactionData {
38 pending_airdrop_ids: Vec<PendingAirdropId>,
40}
41
42impl TokenCancelAirdropTransaction {
43 pub fn pending_airdrop_ids(
45 &mut self,
46 pending_airdrop_ids: impl IntoIterator<Item = PendingAirdropId>,
47 ) -> &mut Self {
48 self.data_mut().pending_airdrop_ids = pending_airdrop_ids.into_iter().collect();
49 self
50 }
51
52 pub fn get_pending_airdrop_ids(&self) -> Vec<PendingAirdropId> {
54 self.data().pending_airdrop_ids.clone()
55 }
56
57 pub fn add_pending_airdrop_id(&mut self, pending_airdrop_id: PendingAirdropId) -> &mut Self {
59 self.data_mut().pending_airdrop_ids.push(pending_airdrop_id);
60 self
61 }
62}
63
64impl TransactionData for TokenCancelAirdropTransactionData {}
65
66impl TransactionExecute for TokenCancelAirdropTransactionData {
67 fn execute(
68 &self,
69 channel: Channel,
70 request: services::Transaction,
71 ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
72 Box::pin(async { TokenServiceClient::new(channel).cancel_airdrop(request).await })
73 }
74}
75
76impl ValidateChecksums for TokenCancelAirdropTransactionData {
77 fn validate_checksums(&self, ledger_id: &RefLedgerId) -> Result<(), Error> {
78 self.pending_airdrop_ids
79 .iter()
80 .try_for_each(|pending_airdrop_id| pending_airdrop_id.validate_checksums(ledger_id))?;
81 Ok(())
82 }
83}
84
85impl ToTransactionDataProtobuf for TokenCancelAirdropTransactionData {
86 fn to_transaction_data_protobuf(
87 &self,
88 chunk_info: &ChunkInfo,
89 ) -> services::transaction_body::Data {
90 let _ = chunk_info.assert_single_transaction();
91
92 services::transaction_body::Data::TokenCancelAirdrop(self.to_protobuf())
93 }
94}
95
96impl ToSchedulableTransactionDataProtobuf for TokenCancelAirdropTransactionData {
97 fn to_schedulable_transaction_data_protobuf(
98 &self,
99 ) -> services::schedulable_transaction_body::Data {
100 services::schedulable_transaction_body::Data::TokenCancelAirdrop(self.to_protobuf())
101 }
102}
103
104impl From<TokenCancelAirdropTransactionData> for AnyTransactionData {
105 fn from(transaction: TokenCancelAirdropTransactionData) -> Self {
106 Self::TokenCancelAirdrop(transaction)
107 }
108}
109
110impl ToProtobuf for TokenCancelAirdropTransactionData {
111 type Protobuf = services::TokenCancelAirdropTransactionBody;
112
113 fn to_protobuf(&self) -> Self::Protobuf {
114 services::TokenCancelAirdropTransactionBody {
115 pending_airdrops: self.pending_airdrop_ids.iter().map(|id| id.to_protobuf()).collect(),
116 }
117 }
118}
119
120impl FromProtobuf<services::TokenCancelAirdropTransactionBody>
121 for TokenCancelAirdropTransactionData
122{
123 fn from_protobuf(pb: services::TokenCancelAirdropTransactionBody) -> crate::Result<Self>
124 where
125 Self: Sized,
126 {
127 let pending_airdrop_ids = pb
128 .pending_airdrops
129 .into_iter()
130 .map(|id: services::PendingAirdropId| PendingAirdropId::from_protobuf(id))
131 .collect::<Result<Vec<_>, _>>()?;
132 Ok(Self { pending_airdrop_ids })
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use expect_test::expect_file;
139 use hiero_sdk_proto::services;
140
141 use crate::pending_airdrop_id::PendingAirdropId;
142 use crate::protobuf::{
143 FromProtobuf,
144 ToProtobuf,
145 };
146 use crate::token::TokenCancelAirdropTransactionData;
147 use crate::transaction::test_helpers::{
148 check_body,
149 transaction_body,
150 unused_private_key,
151 };
152 use crate::{
153 AccountId,
154 AnyTransaction,
155 TokenCancelAirdropTransaction,
156 TokenId,
157 };
158
159 fn make_transaction() -> TokenCancelAirdropTransaction {
160 let pending_airdrop_ids: Vec<PendingAirdropId> = vec![
161 PendingAirdropId::new_token_id(
162 AccountId::new(0, 2, 134),
163 AccountId::new(0, 2, 6),
164 TokenId::new(0, 0, 312),
165 ),
166 PendingAirdropId::new_nft_id(
167 AccountId::new(0, 2, 134),
168 AccountId::new(0, 2, 6),
169 TokenId::new(1, 3, 5).nft(2),
170 ),
171 ]
172 .into_iter()
173 .collect();
174 let mut tx = TokenCancelAirdropTransaction::new_for_tests();
175
176 tx.pending_airdrop_ids(pending_airdrop_ids).freeze().unwrap().sign(unused_private_key());
177 tx
178 }
179
180 #[test]
181 fn serialize() {
182 let tx = make_transaction();
183
184 let tx = transaction_body(tx);
185
186 let tx = check_body(tx);
187
188 expect_file!["./snapshots/token_cancel_airdrop_transaction/serialize.txt"]
189 .assert_debug_eq(&tx);
190 }
191
192 #[test]
193 fn to_from_bytes() {
194 let tx = make_transaction();
195
196 let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
197
198 let tx = transaction_body(tx);
199 let tx2 = transaction_body(tx2);
200
201 assert_eq!(tx, tx2)
202 }
203
204 #[test]
205 fn from_proto_body() {
206 let tx = services::TokenCancelAirdropTransactionBody {
207 pending_airdrops: vec![
208 PendingAirdropId::new_token_id(
209 AccountId::new(0, 0, 415),
210 AccountId::new(0, 0, 6),
211 TokenId::new(0, 0, 312),
212 )
213 .to_protobuf(),
214 PendingAirdropId::new_nft_id(
215 AccountId::new(0, 2, 134),
216 AccountId::new(0, 2, 6),
217 TokenId::new(0, 0, 123).nft(1),
218 )
219 .to_protobuf(),
220 ],
221 };
222
223 let data = TokenCancelAirdropTransactionData::from_protobuf(tx).unwrap();
224
225 let nft_ids: Vec<_> =
226 data.pending_airdrop_ids.clone().into_iter().filter_map(|id| id.nft_id).collect();
227 let token_ids: Vec<_> =
228 data.pending_airdrop_ids.into_iter().filter_map(|id| id.token_id).collect();
229
230 assert_eq!(nft_ids.len(), 1);
231 assert_eq!(token_ids.len(), 1);
232 assert!(token_ids.contains(&TokenId::new(0, 0, 312)));
233 assert!(nft_ids.contains(&TokenId::new(0, 0, 123).nft(1)));
234 }
235
236 #[test]
237 fn get_set_pending_airdrop_ids() {
238 let pending_airdrop_ids = [
239 PendingAirdropId::new_token_id(
240 AccountId::new(0, 0, 134),
241 AccountId::new(0, 0, 6),
242 TokenId::new(0, 0, 420),
243 ),
244 PendingAirdropId::new_nft_id(
245 AccountId::new(0, 2, 134),
246 AccountId::new(0, 2, 6),
247 TokenId::new(0, 0, 112).nft(1),
248 ),
249 ];
250 let mut tx = TokenCancelAirdropTransaction::new();
251 tx.pending_airdrop_ids(pending_airdrop_ids);
252
253 let pending_airdrop_ids = tx.get_pending_airdrop_ids();
254
255 let nft_ids: Vec<_> =
256 pending_airdrop_ids.clone().into_iter().filter_map(|id| id.nft_id).collect();
257 let token_ids: Vec<_> =
258 pending_airdrop_ids.into_iter().filter_map(|id| id.token_id).collect();
259
260 assert_eq!(nft_ids.len(), 1);
261 assert_eq!(token_ids.len(), 1);
262
263 assert!(token_ids.contains(&TokenId::new(0, 0, 420)));
264 assert!(nft_ids.contains(&TokenId::new(0, 0, 112).nft(1)));
265 }
266
267 #[test]
268 #[should_panic]
269 fn set_pending_airdrop_ids_frozen_panic() {
270 make_transaction().pending_airdrop_ids([PendingAirdropId::new_token_id(
271 AccountId::new(0, 0, 134),
272 AccountId::new(0, 0, 6),
273 TokenId::new(0, 0, 420),
274 )]);
275 }
276
277 #[test]
278 fn get_set_add_pending_airdrop_id() {
279 let mut tx = TokenCancelAirdropTransaction::new();
280 tx.add_pending_airdrop_id(PendingAirdropId::new_token_id(
281 AccountId::new(0, 0, 134),
282 AccountId::new(0, 0, 6),
283 TokenId::new(0, 0, 420),
284 ));
285
286 let pending_airdrop_ids = tx.get_pending_airdrop_ids();
287
288 let token_ids: Vec<_> =
289 pending_airdrop_ids.into_iter().filter_map(|id| id.token_id).collect();
290
291 assert!(token_ids.contains(&TokenId::new(0, 0, 420)));
292 }
293}