1use 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
33pub type FileCreateTransaction = Transaction<FileCreateTransactionData>;
35
36#[derive(Debug, Clone)]
37pub struct FileCreateTransactionData {
38 file_memo: String,
40
41 keys: Option<KeyList>,
45
46 contents: Option<Vec<u8>>,
48
49 auto_renew_period: Option<Duration>,
50
51 auto_renew_account_id: Option<AccountId>,
52
53 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 #[must_use]
73 pub fn get_file_memo(&self) -> &str {
74 &self.data().file_memo
75 }
76
77 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 #[must_use]
85 pub fn get_contents(&self) -> Option<&[u8]> {
86 self.data().contents.as_deref()
87 }
88
89 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 #[must_use]
97 pub fn get_keys(&self) -> Option<&KeyList> {
98 self.data().keys.as_ref()
99 }
100
101 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 #[must_use]
116 pub fn get_auto_renew_period(&self) -> Option<Duration> {
117 self.data().auto_renew_period
118 }
119
120 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 #[must_use]
135 pub fn get_auto_renew_account_id(&self) -> Option<AccountId> {
136 self.data().auto_renew_account_id
137 }
138
139 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 #[must_use]
151 pub fn get_expiration_time(&self) -> Option<OffsetDateTime> {
152 self.data().expiration_time
153 }
154
155 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}