Skip to main content

hiero_sdk/token/
token_reject_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 super::NftId;
8use crate::protobuf::{
9    FromProtobuf,
10    ToProtobuf,
11};
12use crate::transaction::{
13    AnyTransactionData,
14    ChunkInfo,
15    ToSchedulableTransactionDataProtobuf,
16    ToTransactionDataProtobuf,
17    TransactionData,
18    TransactionExecute,
19};
20use crate::{
21    AccountId,
22    BoxGrpcFuture,
23    Error,
24    TokenId,
25    Transaction,
26    ValidateChecksums,
27};
28
29/// Reject undesired token(s)
30/// Transfer one or more token balances held by the requesting account to the treasury for each
31/// token type.
32/// Each transfer SHALL be one of the following
33/// - A single non-fungible/unique token.
34/// - The full balance held for a fungible/common token type.
35///
36/// A single tokenReject transaction SHALL support a maximum of 10 transfers.
37///
38/// ### Transaction Record Effects
39/// - Each successful transfer from `payer` to `treasury` SHALL be recorded in `token_transfer_list` for the transaction record.
40pub type TokenRejectTransaction = Transaction<TokenRejectTransactionData>;
41
42#[derive(Debug, Clone, Default)]
43pub struct TokenRejectTransactionData {
44    /// An account holding the tokens to be rejected.
45    owner: Option<AccountId>,
46
47    /// The list of rejected Fungible tokens.
48    token_ids: Vec<TokenId>,
49
50    /// The list of rejected Non-fungible tokens.
51    nft_ids: Vec<NftId>,
52}
53
54impl TokenRejectTransaction {
55    /// Returns the owner id of the token to be rejected.
56    #[must_use]
57    pub fn get_owner(&self) -> Option<AccountId> {
58        self.data().owner
59    }
60
61    /// Sets the owner id of the token to be rejected.
62    pub fn owner(&mut self, owner: impl Into<AccountId>) -> &mut Self {
63        self.data_mut().owner = Some(owner.into());
64        self
65    }
66
67    /// Returns the list of Fungible tokens to be rejected.
68    #[must_use]
69    pub fn get_token_ids(&self) -> Vec<TokenId> {
70        self.data().token_ids.clone()
71    }
72
73    /// Sets the list of Fungible tokens to be rejected.
74    pub fn token_ids(&mut self, token_ids: impl IntoIterator<Item = TokenId>) -> &mut Self {
75        self.data_mut().token_ids = token_ids.into_iter().collect();
76        self
77    }
78
79    /// Appends a Fungible token to the list of rejected tokens.
80    pub fn add_token_id(&mut self, token_id: TokenId) -> &mut Self {
81        self.data_mut().token_ids.push(token_id);
82        self
83    }
84
85    /// Returns the list of Non-fungible tokens to be rejected.
86    #[must_use]
87    pub fn get_nft_ids(&self) -> Vec<NftId> {
88        self.data().nft_ids.clone()
89    }
90
91    /// Sets the list of Non-fungible tokens to be rejected.
92    pub fn nft_ids(&mut self, nft_ids: impl IntoIterator<Item = NftId>) -> &mut Self {
93        self.data_mut().nft_ids = nft_ids.into_iter().collect();
94        self
95    }
96
97    /// Appends a Non-Fungible token to the list of rejected nfts.
98    pub fn add_nft_id(&mut self, nft_id: NftId) -> &mut Self {
99        self.data_mut().nft_ids.push(nft_id);
100        self
101    }
102}
103
104impl TransactionData for TokenRejectTransactionData {}
105
106impl TransactionExecute for TokenRejectTransactionData {
107    fn execute(
108        &self,
109        channel: Channel,
110        request: services::Transaction,
111    ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
112        Box::pin(async { TokenServiceClient::new(channel).reject_token(request).await })
113    }
114}
115
116impl ValidateChecksums for TokenRejectTransactionData {
117    fn validate_checksums(&self, ledger_id: &crate::ledger_id::RefLedgerId) -> Result<(), Error> {
118        self.owner.validate_checksums(ledger_id)
119    }
120}
121
122impl ToTransactionDataProtobuf for TokenRejectTransactionData {
123    fn to_transaction_data_protobuf(
124        &self,
125        chunk_info: &ChunkInfo,
126    ) -> services::transaction_body::Data {
127        let _ = chunk_info.assert_single_transaction();
128
129        services::transaction_body::Data::TokenReject(self.to_protobuf())
130    }
131}
132
133impl ToSchedulableTransactionDataProtobuf for TokenRejectTransactionData {
134    fn to_schedulable_transaction_data_protobuf(
135        &self,
136    ) -> services::schedulable_transaction_body::Data {
137        services::schedulable_transaction_body::Data::TokenReject(self.to_protobuf())
138    }
139}
140
141impl From<TokenRejectTransactionData> for AnyTransactionData {
142    fn from(transaction: TokenRejectTransactionData) -> Self {
143        Self::TokenReject(transaction)
144    }
145}
146
147impl FromProtobuf<services::TokenRejectTransactionBody> for TokenRejectTransactionData {
148    fn from_protobuf(pb: services::TokenRejectTransactionBody) -> crate::Result<Self> {
149        let mut token_ids = Vec::new();
150        let mut nft_ids = Vec::new();
151
152        for reference in pb.rejections {
153            match reference.token_identifier {
154                Some(it) => match it {
155                    services::token_reference::TokenIdentifier::FungibleToken(it) => {
156                        token_ids.push(TokenId::from_protobuf(it)?);
157                    }
158                    services::token_reference::TokenIdentifier::Nft(it) => {
159                        nft_ids.push(NftId::from_protobuf(it)?);
160                    }
161                },
162                None => {
163                    return Err(Error::from_protobuf("Invalid token identifier"));
164                }
165            }
166        }
167
168        Ok(Self { owner: Option::from_protobuf(pb.owner)?, token_ids, nft_ids })
169    }
170}
171
172impl ToProtobuf for TokenRejectTransactionData {
173    type Protobuf = services::TokenRejectTransactionBody;
174
175    fn to_protobuf(&self) -> Self::Protobuf {
176        let owner = self.owner.to_protobuf();
177
178        let rejections = self
179            .token_ids
180            .iter()
181            .map(|token_id| services::TokenReference {
182                token_identifier: Some(services::token_reference::TokenIdentifier::FungibleToken(
183                    token_id.to_protobuf(),
184                )),
185            })
186            .chain(self.nft_ids.iter().map(|nft_id| services::TokenReference {
187                token_identifier: Some(services::token_reference::TokenIdentifier::Nft(
188                    nft_id.to_protobuf(),
189                )),
190            }))
191            .collect::<Vec<_>>();
192
193        services::TokenRejectTransactionBody { owner, rejections }
194    }
195}
196
197#[cfg(test)]
198mod tests {
199
200    use expect_test::expect_file;
201    use hiero_sdk_proto::services::{
202        token_reference,
203        TokenReference,
204        TokenRejectTransactionBody,
205    };
206
207    use super::TokenRejectTransaction;
208    use crate::protobuf::{
209        FromProtobuf,
210        ToProtobuf,
211    };
212    use crate::token::TokenRejectTransactionData;
213    use crate::transaction::test_helpers::{
214        check_body,
215        transaction_body,
216        TEST_ACCOUNT_ID,
217        TEST_NFT_IDS,
218        TEST_TOKEN_IDS,
219    };
220    use crate::AnyTransaction;
221
222    fn make_transaction() -> TokenRejectTransaction {
223        let mut tx = TokenRejectTransaction::new_for_tests();
224        tx.owner(TEST_ACCOUNT_ID).token_ids(TEST_TOKEN_IDS).nft_ids(TEST_NFT_IDS).freeze().unwrap();
225
226        tx
227    }
228
229    #[test]
230    fn seriralize() {
231        let tx = make_transaction();
232
233        let tx = transaction_body(tx);
234
235        let tx = check_body(tx);
236
237        expect_file!["./snapshots/token_reject_transaction/serialize.txt"].assert_debug_eq(&tx);
238    }
239
240    #[test]
241    fn to_from_bytes() {
242        let tx = make_transaction();
243
244        let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
245
246        let tx = transaction_body(tx);
247        let tx2 = transaction_body(tx2);
248
249        assert_eq!(tx, tx2);
250    }
251
252    #[test]
253    fn from_proto_body() {
254        let mut references = Vec::new();
255
256        for token_id in TEST_TOKEN_IDS {
257            references.push(TokenReference {
258                token_identifier: Some(token_reference::TokenIdentifier::FungibleToken(
259                    token_id.to_protobuf(),
260                )),
261            });
262        }
263
264        for nft_id in TEST_NFT_IDS {
265            references.push(TokenReference {
266                token_identifier: Some(token_reference::TokenIdentifier::Nft(nft_id.to_protobuf())),
267            });
268        }
269
270        let tx = TokenRejectTransactionBody {
271            owner: Some(TEST_ACCOUNT_ID.to_protobuf()),
272            rejections: references,
273        };
274
275        let data = TokenRejectTransactionData::from_protobuf(tx).unwrap();
276
277        assert_eq!(data.owner, Some(TEST_ACCOUNT_ID));
278        assert_eq!(data.token_ids, TEST_TOKEN_IDS);
279        assert_eq!(data.nft_ids, TEST_NFT_IDS);
280    }
281
282    #[test]
283    fn get_set_owner() {
284        let mut tx = TokenRejectTransaction::new();
285
286        let tx2 = tx.owner(TEST_ACCOUNT_ID);
287
288        assert_eq!(tx2.get_owner(), Some(TEST_ACCOUNT_ID));
289    }
290
291    #[test]
292    #[should_panic]
293    fn get_set_owner_frozen_panic() {
294        let mut tx = make_transaction();
295
296        tx.owner(TEST_ACCOUNT_ID);
297    }
298
299    #[test]
300    fn get_set_token_ids() {
301        let mut tx = TokenRejectTransaction::new();
302
303        let tx2 = tx.token_ids(TEST_TOKEN_IDS);
304
305        assert_eq!(tx2.get_token_ids(), TEST_TOKEN_IDS);
306    }
307
308    #[test]
309    #[should_panic]
310    fn get_set_token_ids_frozen_panic() {
311        let mut tx = make_transaction();
312
313        tx.token_ids(TEST_TOKEN_IDS);
314    }
315
316    #[test]
317    fn get_set_nft_ids() {
318        let mut tx = TokenRejectTransaction::new();
319
320        let tx2 = tx.nft_ids(TEST_NFT_IDS);
321
322        assert_eq!(tx2.get_nft_ids(), TEST_NFT_IDS);
323    }
324
325    #[test]
326    #[should_panic]
327    fn get_set_nft_ids_frozen_panic() {
328        let mut tx = make_transaction();
329
330        tx.nft_ids(TEST_NFT_IDS);
331    }
332
333    #[test]
334    fn get_set_add_token_ids() {
335        let mut tx = TokenRejectTransaction::new();
336
337        tx.add_token_id(TEST_TOKEN_IDS[0]);
338        tx.add_token_id(TEST_TOKEN_IDS[2]);
339
340        assert_eq!(tx.get_token_ids()[0], TEST_TOKEN_IDS[0]);
341        assert_eq!(tx.get_token_ids()[1], TEST_TOKEN_IDS[2]);
342    }
343
344    #[test]
345    fn get_set_add_nft_ids() {
346        let mut tx = TokenRejectTransaction::new();
347
348        tx.add_nft_id(TEST_NFT_IDS[0]);
349        tx.add_nft_id(TEST_NFT_IDS[2]);
350
351        assert_eq!(tx.get_nft_ids()[0], TEST_NFT_IDS[0]);
352        assert_eq!(tx.get_nft_ids()[1], TEST_NFT_IDS[2]);
353    }
354}