hiero_sdk/file/
file_create_transaction.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use hiero_sdk_proto::services;
4use hiero_sdk_proto::services::file_service_client::FileServiceClient;
5use time::{
6    Duration,
7    OffsetDateTime,
8};
9use tonic::transport::Channel;
10
11use crate::entity_id::ValidateChecksums;
12use crate::ledger_id::RefLedgerId;
13use crate::protobuf::{
14    FromProtobuf,
15    ToProtobuf,
16};
17use crate::transaction::{
18    AnyTransactionData,
19    ChunkInfo,
20    ToSchedulableTransactionDataProtobuf,
21    ToTransactionDataProtobuf,
22    TransactionData,
23    TransactionExecute,
24};
25use crate::{
26    AccountId,
27    BoxGrpcFuture,
28    Key,
29    KeyList,
30    Transaction,
31};
32
33/// Create a new file, containing the given contents.
34pub type FileCreateTransaction = Transaction<FileCreateTransactionData>;
35
36#[derive(Debug, Clone)]
37pub struct FileCreateTransactionData {
38    /// The memo associated with the file.
39    file_memo: String,
40
41    /// All keys at the top level of a key list must sign to create or
42    /// modify the file. Any one of the keys at the top level key list
43    /// can sign to delete the file.
44    keys: Option<KeyList>,
45
46    /// The bytes that are to be the contents of the file.
47    contents: Option<Vec<u8>>,
48
49    auto_renew_period: Option<Duration>,
50
51    auto_renew_account_id: Option<AccountId>,
52
53    /// The time at which this file should expire.
54    expiration_time: Option<OffsetDateTime>,
55}
56
57impl Default for FileCreateTransactionData {
58    fn default() -> Self {
59        Self {
60            file_memo: String::new(),
61            keys: None,
62            contents: None,
63            auto_renew_period: None,
64            auto_renew_account_id: None,
65            expiration_time: Some(OffsetDateTime::now_utc() + Duration::days(90)),
66        }
67    }
68}
69
70impl FileCreateTransaction {
71    /// Returns the memo to be associated with the file.
72    #[must_use]
73    pub fn get_file_memo(&self) -> &str {
74        &self.data().file_memo
75    }
76
77    /// Sets the memo associated with the file.
78    pub fn file_memo(&mut self, memo: impl Into<String>) -> &mut Self {
79        self.data_mut().file_memo = memo.into();
80        self
81    }
82
83    /// Returns the bytes that are to be the contents of the file.
84    #[must_use]
85    pub fn get_contents(&self) -> Option<&[u8]> {
86        self.data().contents.as_deref()
87    }
88
89    /// Sets the bytes that are to be the contents of the file.
90    pub fn contents(&mut self, contents: impl Into<Vec<u8>>) -> &mut Self {
91        self.data_mut().contents = Some(contents.into());
92        self
93    }
94
95    /// Returns the keys for this file.
96    #[must_use]
97    pub fn get_keys(&self) -> Option<&KeyList> {
98        self.data().keys.as_ref()
99    }
100
101    /// Sets the keys for this file.
102    ///
103    /// All keys at the top level of a key list must sign to create or
104    /// modify the file. Any one of the keys at the top level key list
105    /// can sign to delete the file.
106    pub fn keys<K: Into<Key>>(&mut self, keys: impl IntoIterator<Item = K>) -> &mut Self {
107        self.data_mut().keys = Some(keys.into_iter().map(Into::into).collect());
108        self
109    }
110
111    /// Returns the auto renew period for this file.
112    ///
113    /// # Network Support
114    /// Please note that this not supported on any hedera network at this time.
115    #[must_use]
116    pub fn get_auto_renew_period(&self) -> Option<Duration> {
117        self.data().auto_renew_period
118    }
119
120    /// Sets the auto renew period for this file.
121    ///
122    /// # Network Support
123    /// Please note that this not supported on any hedera network at this time.
124    pub fn auto_renew_period(&mut self, duration: Duration) -> &mut Self {
125        self.data_mut().auto_renew_period = Some(duration);
126        self
127    }
128
129    /// Returns the account to be used at the file's expiration time to extend the
130    /// life of the file.
131    ///
132    /// # Network Support
133    /// Please note that this not supported on any hedera network at this time.
134    #[must_use]
135    pub fn get_auto_renew_account_id(&self) -> Option<AccountId> {
136        self.data().auto_renew_account_id
137    }
138
139    /// Sets the account to be used at the files's expiration time to extend the
140    /// life of the file.
141    ///
142    /// # Network Support
143    /// Please note that this not supported on any hedera network at this time.
144    pub fn auto_renew_account_id(&mut self, id: AccountId) -> &mut Self {
145        self.data_mut().auto_renew_account_id = Some(id);
146        self
147    }
148
149    /// Returns the time at which this file should expire.
150    #[must_use]
151    pub fn get_expiration_time(&self) -> Option<OffsetDateTime> {
152        self.data().expiration_time
153    }
154
155    /// Sets the time at which this file should expire.
156    pub fn expiration_time(&mut self, at: OffsetDateTime) -> &mut Self {
157        self.require_not_frozen();
158        self.data_mut().expiration_time = Some(at);
159        self
160    }
161}
162
163impl TransactionData for FileCreateTransactionData {
164    fn default_max_transaction_fee(&self) -> crate::Hbar {
165        crate::Hbar::new(5)
166    }
167}
168
169impl TransactionExecute for FileCreateTransactionData {
170    fn execute(
171        &self,
172        channel: Channel,
173        request: services::Transaction,
174    ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
175        Box::pin(async { FileServiceClient::new(channel).create_file(request).await })
176    }
177}
178
179impl ValidateChecksums for FileCreateTransactionData {
180    fn validate_checksums(&self, ledger_id: &RefLedgerId) -> crate::Result<()> {
181        self.auto_renew_account_id.validate_checksums(ledger_id)?;
182
183        Ok(())
184    }
185}
186
187impl ToTransactionDataProtobuf for FileCreateTransactionData {
188    fn to_transaction_data_protobuf(
189        &self,
190        chunk_info: &ChunkInfo,
191    ) -> services::transaction_body::Data {
192        let _ = chunk_info.assert_single_transaction();
193
194        services::transaction_body::Data::FileCreate(self.to_protobuf())
195    }
196}
197
198impl ToSchedulableTransactionDataProtobuf for FileCreateTransactionData {
199    fn to_schedulable_transaction_data_protobuf(
200        &self,
201    ) -> services::schedulable_transaction_body::Data {
202        services::schedulable_transaction_body::Data::FileCreate(self.to_protobuf())
203    }
204}
205
206impl From<FileCreateTransactionData> for AnyTransactionData {
207    fn from(transaction: FileCreateTransactionData) -> Self {
208        Self::FileCreate(transaction)
209    }
210}
211
212impl FromProtobuf<services::FileCreateTransactionBody> for FileCreateTransactionData {
213    fn from_protobuf(pb: services::FileCreateTransactionBody) -> crate::Result<Self> {
214        Ok(Self {
215            file_memo: pb.memo,
216            keys: Option::from_protobuf(pb.keys)?,
217            contents: Some(pb.contents),
218            auto_renew_period: None,
219            auto_renew_account_id: None,
220            expiration_time: pb.expiration_time.map(Into::into),
221        })
222    }
223}
224
225impl ToProtobuf for FileCreateTransactionData {
226    type Protobuf = services::FileCreateTransactionBody;
227
228    #[allow(deprecated)]
229    fn to_protobuf(&self) -> Self::Protobuf {
230        services::FileCreateTransactionBody {
231            expiration_time: self.expiration_time.to_protobuf(),
232            keys: self.keys.to_protobuf(),
233            contents: self.contents.clone().unwrap_or_default(),
234            shard_id: None,
235            realm_id: None,
236            new_realm_admin_key: None,
237            memo: self.file_memo.clone(),
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use expect_test::expect;
245    use hiero_sdk_proto::services;
246    use hex_literal::hex;
247    use time::OffsetDateTime;
248
249    use crate::file::FileCreateTransactionData;
250    use crate::protobuf::{
251        FromProtobuf,
252        ToProtobuf,
253    };
254    use crate::transaction::test_helpers::{
255        check_body,
256        transaction_body,
257        unused_private_key,
258    };
259    use crate::{
260        AnyTransaction,
261        FileCreateTransaction,
262        Key,
263        KeyList,
264    };
265
266    const CONTENTS: [u8; 4] = hex!("deadbeef");
267
268    const EXPIRATION_TIME: OffsetDateTime = match OffsetDateTime::from_unix_timestamp(1554158728) {
269        Ok(it) => it,
270        Err(_) => panic!("Panic in `const` unwrap"),
271    };
272
273    fn keys() -> impl IntoIterator<Item = Key> {
274        [unused_private_key().public_key().into()]
275    }
276
277    const FILE_MEMO: &str = "Hello memo";
278
279    fn make_transaction() -> FileCreateTransaction {
280        let mut tx = FileCreateTransaction::new_for_tests();
281
282        tx.contents(CONTENTS)
283            .expiration_time(EXPIRATION_TIME)
284            .keys(keys())
285            .file_memo(FILE_MEMO)
286            .freeze()
287            .unwrap();
288
289        tx
290    }
291
292    #[test]
293    fn serialize() {
294        let tx = make_transaction();
295
296        let tx = transaction_body(tx);
297
298        let tx = check_body(tx);
299
300        expect![[r#"
301            FileCreate(
302                FileCreateTransactionBody {
303                    expiration_time: Some(
304                        Timestamp {
305                            seconds: 1554158728,
306                            nanos: 0,
307                        },
308                    ),
309                    keys: Some(
310                        KeyList {
311                            keys: [
312                                Key {
313                                    key: Some(
314                                        Ed25519(
315                                            [
316                                                224,
317                                                200,
318                                                236,
319                                                39,
320                                                88,
321                                                165,
322                                                135,
323                                                159,
324                                                250,
325                                                194,
326                                                38,
327                                                161,
328                                                60,
329                                                12,
330                                                81,
331                                                107,
332                                                121,
333                                                158,
334                                                114,
335                                                227,
336                                                81,
337                                                65,
338                                                160,
339                                                221,
340                                                130,
341                                                143,
342                                                148,
343                                                211,
344                                                121,
345                                                136,
346                                                164,
347                                                183,
348                                            ],
349                                        ),
350                                    ),
351                                },
352                            ],
353                        },
354                    ),
355                    contents: [
356                        222,
357                        173,
358                        190,
359                        239,
360                    ],
361                    shard_id: None,
362                    realm_id: None,
363                    new_realm_admin_key: None,
364                    memo: "Hello memo",
365                },
366            )
367        "#]]
368        .assert_debug_eq(&tx)
369    }
370
371    #[test]
372    fn to_from_bytes() {
373        let tx = make_transaction();
374
375        let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
376
377        let tx = transaction_body(tx);
378
379        let tx2 = transaction_body(tx2);
380
381        assert_eq!(tx, tx2);
382    }
383
384    #[test]
385    fn from_proto_body() {
386        #[allow(deprecated)]
387        let tx = services::FileCreateTransactionBody {
388            expiration_time: Some(EXPIRATION_TIME.to_protobuf()),
389            keys: Some(KeyList::from_iter(keys()).to_protobuf()),
390            contents: CONTENTS.into(),
391            memo: FILE_MEMO.to_owned(),
392            shard_id: None,
393            realm_id: None,
394            new_realm_admin_key: None,
395        };
396
397        let tx = FileCreateTransactionData::from_protobuf(tx).unwrap();
398
399        assert_eq!(tx.contents.as_deref(), Some(CONTENTS.as_slice()));
400        assert_eq!(tx.expiration_time, Some(EXPIRATION_TIME));
401        assert_eq!(tx.keys, Some(KeyList::from_iter(keys())));
402        assert_eq!(tx.file_memo, FILE_MEMO);
403    }
404
405    mod get_set {
406        use super::*;
407
408        #[test]
409        fn contents() {
410            let mut tx = FileCreateTransaction::new();
411            tx.contents(CONTENTS);
412
413            assert_eq!(tx.get_contents(), Some(CONTENTS.as_slice()));
414        }
415
416        #[test]
417        #[should_panic]
418        fn contents_frozen_panics() {
419            make_transaction().contents(CONTENTS);
420        }
421
422        #[test]
423        fn expiration_time() {
424            let mut tx = FileCreateTransaction::new();
425            tx.expiration_time(EXPIRATION_TIME);
426
427            assert_eq!(tx.get_expiration_time(), Some(EXPIRATION_TIME));
428        }
429
430        #[test]
431        #[should_panic]
432        fn expiration_time_frozen_panics() {
433            make_transaction().expiration_time(EXPIRATION_TIME);
434        }
435
436        #[test]
437        fn keys() {
438            let mut tx = FileCreateTransaction::new();
439            tx.keys(super::keys());
440
441            assert_eq!(tx.get_keys(), Some(&KeyList::from_iter(super::keys())));
442        }
443
444        #[test]
445        #[should_panic]
446        fn keys_frozen_panics() {
447            make_transaction().keys(super::keys());
448        }
449
450        #[test]
451        fn file_memo() {
452            let mut tx = FileCreateTransaction::new();
453            tx.file_memo(FILE_MEMO);
454
455            assert_eq!(tx.get_file_memo(), FILE_MEMO);
456        }
457
458        #[test]
459        #[should_panic]
460        fn file_memo_frozen_panics() {
461            make_transaction().file_memo(FILE_MEMO);
462        }
463    }
464}