Skip to main content

hiero_sdk/token/
token_unfreeze_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::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    TokenId,
24    Transaction,
25    ValidateChecksums,
26};
27
28/// Unfreezes transfers of the specified token for the account. Must be signed by the Token's freezeKey.
29///
30/// Once executed the Account is marked as Unfrozen and will be able to receive or send tokens.
31/// The operation is idempotent.
32///
33/// - If the provided account is not found, the transaction will resolve to `INVALID_ACCOUNT_ID`.
34/// - If the provided account has been deleted, the transaction will resolve to `ACCOUNT_DELETED`.
35/// - If the provided token is not found, the transaction will resolve to `INVALID_TOKEN_ID`.
36/// - If the provided token has been deleted, the transaction will resolve to `TOKEN_WAS_DELETED`.
37/// - If an Association between the provided token and account is not found, the transaction will
38/// resolve to `TOKEN_NOT_ASSOCIATED_TO_ACCOUNT`.
39/// - If no Freeze Key is defined, the transaction will resolve to `TOKEN_HAS_NO_FREEZE_KEY`.
40pub type TokenUnfreezeTransaction = Transaction<TokenUnfreezeTransactionData>;
41
42#[derive(Debug, Clone, Default)]
43pub struct TokenUnfreezeTransactionData {
44    /// The account to be unfrozen.
45    account_id: Option<AccountId>,
46
47    /// The token for which this account will be unfrozen.
48    token_id: Option<TokenId>,
49}
50
51impl TokenUnfreezeTransaction {
52    /// Returns the account to be unfrozen.
53    #[must_use]
54    pub fn get_account_id(&self) -> Option<AccountId> {
55        self.data().account_id
56    }
57
58    /// Sets the account to be unfrozen.
59    pub fn account_id(&mut self, account_id: AccountId) -> &mut Self {
60        self.data_mut().account_id = Some(account_id);
61        self
62    }
63
64    /// Returns the token for which the account will be unfrozen.
65    #[must_use]
66    pub fn get_token_id(&self) -> Option<TokenId> {
67        self.data().token_id
68    }
69
70    /// Sets the token for which this account will be unfrozen.
71    pub fn token_id(&mut self, token_id: impl Into<TokenId>) -> &mut Self {
72        self.data_mut().token_id = Some(token_id.into());
73        self
74    }
75}
76
77impl TransactionData for TokenUnfreezeTransactionData {}
78
79impl TransactionExecute for TokenUnfreezeTransactionData {
80    fn execute(
81        &self,
82        channel: Channel,
83        request: services::Transaction,
84    ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
85        Box::pin(async { TokenServiceClient::new(channel).unfreeze_token_account(request).await })
86    }
87}
88
89impl ValidateChecksums for TokenUnfreezeTransactionData {
90    fn validate_checksums(&self, ledger_id: &RefLedgerId) -> crate::Result<()> {
91        self.token_id.validate_checksums(ledger_id)?;
92        self.account_id.validate_checksums(ledger_id)
93    }
94}
95
96impl ToTransactionDataProtobuf for TokenUnfreezeTransactionData {
97    fn to_transaction_data_protobuf(
98        &self,
99        chunk_info: &ChunkInfo,
100    ) -> services::transaction_body::Data {
101        let _ = chunk_info.assert_single_transaction();
102
103        services::transaction_body::Data::TokenUnfreeze(self.to_protobuf())
104    }
105}
106
107impl ToSchedulableTransactionDataProtobuf for TokenUnfreezeTransactionData {
108    fn to_schedulable_transaction_data_protobuf(
109        &self,
110    ) -> services::schedulable_transaction_body::Data {
111        services::schedulable_transaction_body::Data::TokenUnfreeze(self.to_protobuf())
112    }
113}
114
115impl From<TokenUnfreezeTransactionData> for AnyTransactionData {
116    fn from(transaction: TokenUnfreezeTransactionData) -> Self {
117        Self::TokenUnfreeze(transaction)
118    }
119}
120
121impl FromProtobuf<services::TokenUnfreezeAccountTransactionBody> for TokenUnfreezeTransactionData {
122    fn from_protobuf(pb: services::TokenUnfreezeAccountTransactionBody) -> crate::Result<Self> {
123        Ok(Self {
124            account_id: Option::from_protobuf(pb.account)?,
125            token_id: Option::from_protobuf(pb.token)?,
126        })
127    }
128}
129
130impl ToProtobuf for TokenUnfreezeTransactionData {
131    type Protobuf = services::TokenUnfreezeAccountTransactionBody;
132
133    fn to_protobuf(&self) -> Self::Protobuf {
134        let account = self.account_id.to_protobuf();
135        let token = self.token_id.to_protobuf();
136
137        services::TokenUnfreezeAccountTransactionBody { token, account }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use expect_test::expect;
144    use hiero_sdk_proto::services;
145
146    use crate::protobuf::{
147        FromProtobuf,
148        ToProtobuf,
149    };
150    use crate::token::TokenUnfreezeTransactionData;
151    use crate::transaction::test_helpers::{
152        check_body,
153        transaction_body,
154    };
155    use crate::{
156        AccountId,
157        AnyTransaction,
158        TokenId,
159        TokenUnfreezeTransaction,
160    };
161
162    const TOKEN_ID: TokenId = TokenId::new(6, 5, 4);
163    const ACCOUNT_ID: AccountId = AccountId::new(0, 0, 222);
164
165    fn make_transaction() -> TokenUnfreezeTransaction {
166        let mut tx = TokenUnfreezeTransaction::new_for_tests();
167
168        tx.token_id(TOKEN_ID).account_id(ACCOUNT_ID).freeze().unwrap();
169
170        tx
171    }
172
173    #[test]
174    fn seriralize() {
175        let tx = make_transaction();
176
177        let tx = transaction_body(tx);
178
179        let tx = check_body(tx);
180
181        expect![[r#"
182            TokenUnfreeze(
183                TokenUnfreezeAccountTransactionBody {
184                    token: Some(
185                        TokenId {
186                            shard_num: 6,
187                            realm_num: 5,
188                            token_num: 4,
189                        },
190                    ),
191                    account: Some(
192                        AccountId {
193                            shard_num: 0,
194                            realm_num: 0,
195                            account: Some(
196                                AccountNum(
197                                    222,
198                                ),
199                            ),
200                        },
201                    ),
202                },
203            )
204        "#]]
205        .assert_debug_eq(&tx);
206    }
207
208    #[test]
209    fn to_from_bytes() {
210        let tx = make_transaction();
211
212        let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
213
214        let tx = transaction_body(tx);
215        let tx2 = transaction_body(tx2);
216
217        assert_eq!(tx, tx2);
218    }
219
220    #[test]
221    fn from_proto_body() {
222        let tx = services::TokenUnfreezeAccountTransactionBody {
223            account: Some(ACCOUNT_ID.to_protobuf()),
224            token: Some(TOKEN_ID.to_protobuf()),
225        };
226
227        let data = TokenUnfreezeTransactionData::from_protobuf(tx).unwrap();
228
229        assert_eq!(data.account_id, Some(ACCOUNT_ID));
230        assert_eq!(data.token_id, Some(TOKEN_ID));
231    }
232
233    #[test]
234    fn get_set_token_id() {
235        let mut tx = TokenUnfreezeTransaction::new();
236        tx.token_id(TOKEN_ID);
237
238        assert_eq!(tx.get_token_id(), Some(TOKEN_ID));
239    }
240
241    #[test]
242    #[should_panic]
243    fn get_set_token_id_frozen_panic() {
244        make_transaction().token_id(TOKEN_ID);
245    }
246
247    #[test]
248    fn get_set_account_id() {
249        let mut tx = TokenUnfreezeTransaction::new();
250        tx.account_id(ACCOUNT_ID);
251
252        assert_eq!(tx.get_account_id(), Some(ACCOUNT_ID));
253    }
254
255    #[test]
256    #[should_panic]
257    fn get_set_account_id_frozen_panic() {
258        make_transaction().account_id(ACCOUNT_ID);
259    }
260}