Skip to main content

hiero_sdk/token/
token_claim_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::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
28/// Token claim airdrop
29/// Complete one or more pending transfers on behalf of the
30/// recipient(s) for an airdrop.
31///
32/// The sender MUST have sufficient balance to fulfill the airdrop at the
33/// time of claim. If the sender does not have sufficient balance, the
34/// claim SHALL fail.
35/// Each pending airdrop successfully claimed SHALL be removed from state and
36/// SHALL NOT be available to claim again.
37/// Each claim SHALL be represented in the transaction body and
38/// SHALL NOT be restated in the record file.
39/// All claims MUST succeed for this transaction to succeed.
40///
41/// ### Record Stream Effects
42/// The completed transfers SHALL be present in the transfer list.
43///
44pub type TokenClaimAirdropTransaction = Transaction<TokenClaimAirdropTransactionData>;
45
46#[derive(Debug, Clone, Default)]
47pub struct TokenClaimAirdropTransactionData {
48    /// A list of one or more pending airdrop identifiers.
49    ///
50    /// This transaction MUST be signed by the account identified by
51    /// the `receiver_id` for each entry in this list.
52    /// This list MUST contain between 1 and 10 entries, inclusive.
53    /// This list MUST NOT have any duplicate entries.
54    pending_airdrop_ids: Vec<PendingAirdropId>,
55}
56
57impl TokenClaimAirdropTransaction {
58    /// Adds the list of pending airdrop identifiers to claim.
59    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    /// Returns the list of pending airdrop identifiers to claim.
68    #[must_use]
69    pub fn get_pending_airdrop_ids(&self) -> Vec<PendingAirdropId> {
70        self.data().pending_airdrop_ids.clone()
71    }
72
73    /// Adds a pending airdrop identifier to the list of pending airdrop identifiers.
74    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}