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