Skip to main content

hiero_sdk/token/
token_cancel_airdrop_transaction.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use 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
26/// Token cancel airdrop
27/// Remove one or more pending airdrops from state on behalf of the sender(s)
28/// for each airdrop.
29///
30/// Each pending airdrop canceled SHALL be removed from state and SHALL NOT be available to claim.
31/// Each cancellation SHALL be represented in the transaction body and SHALL NOT be restated
32/// in the record file.
33/// All cancellations MUST succeed for this transaction to succeed.
34pub type TokenCancelAirdropTransaction = Transaction<TokenCancelAirdropTransactionData>;
35
36#[derive(Debug, Clone, Default)]
37pub struct TokenCancelAirdropTransactionData {
38    /// The ID of the pending airdrop to cancel
39    pending_airdrop_ids: Vec<PendingAirdropId>,
40}
41
42impl TokenCancelAirdropTransaction {
43    /// Adds the list of pending airdrop identifiers to cancel.
44    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    /// Returns the list of pending airdrop identifiers to cancel.
53    pub fn get_pending_airdrop_ids(&self) -> Vec<PendingAirdropId> {
54        self.data().pending_airdrop_ids.clone()
55    }
56
57    /// Adds a pending airdrop identifier to the list of pending airdrop identifiers to cancel.
58    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}