1use hiero_sdk_proto::services;
4use hiero_sdk_proto::services::schedule_service_client::ScheduleServiceClient;
5use time::OffsetDateTime;
6use tonic::transport::Channel;
7
8use super::schedulable_transaction_body::SchedulableTransactionBody;
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 AccountId,
23 BoxGrpcFuture,
24 Error,
25 Key,
26 Transaction,
27 ValidateChecksums,
28};
29
30pub type ScheduleCreateTransaction = Transaction<ScheduleCreateTransactionData>;
39
40#[derive(Default, Debug, Clone)]
41pub struct ScheduleCreateTransactionData {
42 scheduled_transaction: Option<SchedulableTransactionBody>,
43
44 schedule_memo: Option<String>,
45
46 admin_key: Option<Key>,
47
48 payer_account_id: Option<AccountId>,
49
50 expiration_time: Option<OffsetDateTime>,
51
52 wait_for_expiry: bool,
53}
54
55impl ScheduleCreateTransaction {
56 pub fn scheduled_transaction<D>(&mut self, transaction: Transaction<D>) -> &mut Self
68 where
69 D: TransactionExecute,
70 {
71 let body = transaction.into_body();
72
73 let data: AnyTransactionData = body.data.into();
75
76 self.data_mut().scheduled_transaction = Some(SchedulableTransactionBody {
77 max_transaction_fee: body.max_transaction_fee,
78 transaction_memo: body.transaction_memo,
79 data: Box::new(data.try_into().unwrap()),
80 });
81
82 self
83 }
84
85 #[must_use]
87 pub fn get_expiration_time(&self) -> Option<OffsetDateTime> {
88 self.data().expiration_time
89 }
90
91 pub fn expiration_time(&mut self, time: OffsetDateTime) -> &mut Self {
93 self.data_mut().expiration_time = Some(time);
94 self
95 }
96
97 #[must_use]
100 pub fn get_wait_for_expiry(&self) -> bool {
101 self.data().wait_for_expiry
102 }
103
104 pub fn wait_for_expiry(&mut self, wait: bool) -> &mut Self {
107 self.data_mut().wait_for_expiry = wait;
108 self
109 }
110
111 #[must_use]
114 pub fn get_payer_account_id(&self) -> Option<AccountId> {
115 self.data().payer_account_id
116 }
117
118 pub fn payer_account_id(&mut self, id: AccountId) -> &mut Self {
121 self.data_mut().payer_account_id = Some(id);
122 self
123 }
124
125 #[must_use]
127 pub fn get_schedule_memo(&self) -> Option<&str> {
128 self.data().schedule_memo.as_deref()
129 }
130
131 pub fn schedule_memo(&mut self, memo: impl Into<String>) -> &mut Self {
133 self.data_mut().schedule_memo = Some(memo.into());
134 self
135 }
136
137 #[must_use]
139 pub fn get_admin_key(&self) -> Option<&Key> {
140 self.data().admin_key.as_ref()
141 }
142
143 pub fn admin_key(&mut self, key: impl Into<Key>) -> &mut Self {
145 self.data_mut().admin_key = Some(key.into());
146 self
147 }
148}
149
150impl TransactionData for ScheduleCreateTransactionData {}
151
152impl TransactionExecute for ScheduleCreateTransactionData {
153 fn execute(
154 &self,
155 channel: Channel,
156 request: services::Transaction,
157 ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
158 Box::pin(async { ScheduleServiceClient::new(channel).create_schedule(request).await })
159 }
160}
161
162impl ValidateChecksums for ScheduleCreateTransactionData {
163 fn validate_checksums(&self, ledger_id: &crate::ledger_id::RefLedgerId) -> Result<(), Error> {
164 self.payer_account_id.validate_checksums(ledger_id)
165 }
166}
167
168impl ToTransactionDataProtobuf for ScheduleCreateTransactionData {
169 #[allow(clippy::too_many_lines)]
171 fn to_transaction_data_protobuf(
172 &self,
173 chunk_info: &ChunkInfo,
174 ) -> services::transaction_body::Data {
175 let _ = chunk_info.assert_single_transaction();
176
177 let body = self.scheduled_transaction.as_ref().map(|scheduled| {
178 let data = scheduled.data.to_schedulable_transaction_data_protobuf();
179
180 services::SchedulableTransactionBody {
181 data: Some(data),
182 memo: scheduled.transaction_memo.clone(),
183 transaction_fee: scheduled
185 .max_transaction_fee
186 .unwrap_or_else(|| scheduled.data.default_max_transaction_fee())
187 .to_tinybars() as u64,
188 max_custom_fees: vec![],
189 }
190 });
191
192 let payer_account_id = self.payer_account_id.to_protobuf();
193 let admin_key = self.admin_key.to_protobuf();
194 let expiration_time = self.expiration_time.map(Into::into);
195
196 services::transaction_body::Data::ScheduleCreate(services::ScheduleCreateTransactionBody {
197 scheduled_transaction_body: body,
198 memo: self.schedule_memo.clone().unwrap_or_default(),
199 admin_key,
200 payer_account_id,
201 expiration_time,
202 wait_for_expiry: self.wait_for_expiry,
203 })
204 }
205}
206
207impl From<ScheduleCreateTransactionData> for AnyTransactionData {
208 fn from(transaction: ScheduleCreateTransactionData) -> Self {
209 Self::ScheduleCreate(transaction)
210 }
211}
212
213impl FromProtobuf<services::ScheduleCreateTransactionBody> for ScheduleCreateTransactionData {
214 fn from_protobuf(pb: services::ScheduleCreateTransactionBody) -> crate::Result<Self> {
215 Ok(Self {
216 scheduled_transaction: Option::from_protobuf(pb.scheduled_transaction_body)?,
217 schedule_memo: Some(pb.memo),
218 admin_key: Option::from_protobuf(pb.admin_key)?,
219 payer_account_id: Option::from_protobuf(pb.payer_account_id)?,
220 expiration_time: pb.expiration_time.map(Into::into),
221 wait_for_expiry: pb.wait_for_expiry,
222 })
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use expect_test::expect;
229 use hiero_sdk_proto::services;
230 use time::OffsetDateTime;
231
232 use super::ScheduleCreateTransactionData;
233 use crate::protobuf::{
234 FromProtobuf,
235 ToProtobuf,
236 };
237 use crate::transaction::test_helpers::{
238 check_body,
239 transaction_body,
240 unused_private_key,
241 VALID_START,
242 };
243 use crate::transaction::ToSchedulableTransactionDataProtobuf;
244 use crate::{
245 AccountId,
246 AnyTransaction,
247 Hbar,
248 PublicKey,
249 ScheduleCreateTransaction,
250 TransferTransaction,
251 };
252
253 fn scheduled_transaction() -> TransferTransaction {
254 let mut tx = TransferTransaction::new();
255 tx.hbar_transfer("0.0.555".parse().unwrap(), -Hbar::new(10))
256 .hbar_transfer("0.0.666".parse().unwrap(), Hbar::new(10));
257 tx
258 }
259
260 fn admin_key() -> PublicKey {
261 unused_private_key().public_key()
262 }
263
264 const PAYER_ACCOUNT_ID: AccountId = AccountId::new(0, 0, 222);
265 const SCHEDULE_MEMO: &str = "hi";
266 const EXPIRATION_TIME: OffsetDateTime = VALID_START;
267
268 fn make_transaction() -> ScheduleCreateTransaction {
269 let mut tx = ScheduleCreateTransaction::new_for_tests();
270
271 tx.scheduled_transaction(scheduled_transaction())
272 .admin_key(admin_key())
273 .payer_account_id(PAYER_ACCOUNT_ID)
274 .schedule_memo(SCHEDULE_MEMO)
275 .expiration_time(EXPIRATION_TIME)
276 .freeze()
277 .unwrap();
278
279 tx
280 }
281
282 #[test]
283 fn serialize() {
284 let tx = make_transaction();
285
286 let tx = transaction_body(tx);
287
288 let tx = check_body(tx);
289
290 expect![[r#"
291 ScheduleCreate(
292 ScheduleCreateTransactionBody {
293 scheduled_transaction_body: Some(
294 SchedulableTransactionBody {
295 transaction_fee: 200000000,
296 memo: "",
297 max_custom_fees: [],
298 data: Some(
299 CryptoTransfer(
300 CryptoTransferTransactionBody {
301 transfers: Some(
302 TransferList {
303 account_amounts: [
304 AccountAmount {
305 account_id: Some(
306 AccountId {
307 shard_num: 0,
308 realm_num: 0,
309 account: Some(
310 AccountNum(
311 555,
312 ),
313 ),
314 },
315 ),
316 amount: -1000000000,
317 is_approval: false,
318 hook_call: None,
319 },
320 AccountAmount {
321 account_id: Some(
322 AccountId {
323 shard_num: 0,
324 realm_num: 0,
325 account: Some(
326 AccountNum(
327 666,
328 ),
329 ),
330 },
331 ),
332 amount: 1000000000,
333 is_approval: false,
334 hook_call: None,
335 },
336 ],
337 },
338 ),
339 token_transfers: [],
340 },
341 ),
342 ),
343 },
344 ),
345 memo: "hi",
346 admin_key: Some(
347 Key {
348 key: Some(
349 Ed25519(
350 [
351 224,
352 200,
353 236,
354 39,
355 88,
356 165,
357 135,
358 159,
359 250,
360 194,
361 38,
362 161,
363 60,
364 12,
365 81,
366 107,
367 121,
368 158,
369 114,
370 227,
371 81,
372 65,
373 160,
374 221,
375 130,
376 143,
377 148,
378 211,
379 121,
380 136,
381 164,
382 183,
383 ],
384 ),
385 ),
386 },
387 ),
388 payer_account_id: Some(
389 AccountId {
390 shard_num: 0,
391 realm_num: 0,
392 account: Some(
393 AccountNum(
394 222,
395 ),
396 ),
397 },
398 ),
399 expiration_time: Some(
400 Timestamp {
401 seconds: 1554158542,
402 nanos: 0,
403 },
404 ),
405 wait_for_expiry: false,
406 },
407 )
408 "#]]
409 .assert_debug_eq(&tx)
410 }
411
412 #[test]
413 fn to_from_bytes() {
414 let tx = make_transaction();
415
416 let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
417
418 let tx = transaction_body(tx);
419
420 let tx2 = transaction_body(tx2);
421
422 assert_eq!(tx, tx2);
423 }
424
425 #[test]
426 fn from_proto_body() {
427 let tx = services::ScheduleCreateTransactionBody {
428 scheduled_transaction_body: Some(services::SchedulableTransactionBody {
429 transaction_fee: Hbar::new(2).to_tinybars() as _,
430 memo: String::new(),
431 data: Some(
432 scheduled_transaction().data().to_schedulable_transaction_data_protobuf(),
433 ),
434 max_custom_fees: vec![],
435 }),
436 memo: SCHEDULE_MEMO.to_owned(),
437 admin_key: Some(admin_key().to_protobuf()),
438 payer_account_id: Some(PAYER_ACCOUNT_ID.to_protobuf()),
439 expiration_time: Some(EXPIRATION_TIME.to_protobuf()),
440 wait_for_expiry: false,
441 };
442
443 let tx = ScheduleCreateTransactionData::from_protobuf(tx).unwrap();
444
445 expect![[r#"
446 SchedulableTransactionBody {
447 data: Transfer(
448 TransferTransactionData {
449 transfers: [
450 Transfer {
451 account_id: "0.0.555",
452 amount: -1000000000,
453 is_approval: false,
454 hook_call: None,
455 },
456 Transfer {
457 account_id: "0.0.666",
458 amount: 1000000000,
459 is_approval: false,
460 hook_call: None,
461 },
462 ],
463 token_transfers: [],
464 },
465 ),
466 max_transaction_fee: Some(
467 "2 ℏ",
468 ),
469 transaction_memo: "",
470 }
471 "#]]
472 .assert_debug_eq(&tx.scheduled_transaction.unwrap());
473
474 assert_eq!(tx.schedule_memo.as_deref(), Some(SCHEDULE_MEMO));
475 assert_eq!(tx.admin_key, Some(admin_key().into()));
476 assert_eq!(tx.payer_account_id, Some(PAYER_ACCOUNT_ID));
477 assert_eq!(tx.expiration_time, Some(EXPIRATION_TIME));
478 assert_eq!(tx.wait_for_expiry, false);
479 }
480
481 mod get_set {
482 use super::*;
483 #[test]
484 fn admin_key() {
485 let mut tx = ScheduleCreateTransaction::new();
486 tx.admin_key(super::admin_key());
487
488 assert_eq!(tx.get_admin_key(), Some(&super::admin_key().into()));
489 }
490
491 #[test]
492 #[should_panic]
493 fn admin_key_frozen_panics() {
494 make_transaction().admin_key(super::admin_key());
495 }
496
497 #[test]
498 fn payer_account_id() {
499 let mut tx = ScheduleCreateTransaction::new();
500 tx.payer_account_id(PAYER_ACCOUNT_ID);
501
502 assert_eq!(tx.get_payer_account_id(), Some(PAYER_ACCOUNT_ID));
503 }
504
505 #[test]
506 #[should_panic]
507 fn payer_account_id_frozen_panics() {
508 make_transaction().payer_account_id(PAYER_ACCOUNT_ID);
509 }
510
511 #[test]
512 fn expiration_time() {
513 let mut tx = ScheduleCreateTransaction::new();
514 tx.expiration_time(EXPIRATION_TIME);
515
516 assert_eq!(tx.get_expiration_time(), Some(EXPIRATION_TIME));
517 }
518
519 #[test]
520 #[should_panic]
521 fn expiration_time_frozen_panics() {
522 make_transaction().expiration_time(EXPIRATION_TIME);
523 }
524
525 #[test]
526 fn wait_for_expiry() {
527 let mut tx = ScheduleCreateTransaction::new();
528 tx.wait_for_expiry(true);
529
530 assert_eq!(tx.get_wait_for_expiry(), true);
531 }
532
533 #[test]
534 #[should_panic]
535 fn wait_for_expiry_frozen_panics() {
536 make_transaction().wait_for_expiry(true);
537 }
538 }
539}