1use std::path::PathBuf;
6
7use iota_genesis_builder::validator_info::GenesisValidatorMetadata;
8use iota_move_build::{BuildConfig, CompiledPackage};
9use iota_sdk::{
10 rpc_types::{
11 IotaObjectDataOptions, IotaTransactionBlockEffectsAPI, IotaTransactionBlockResponse,
12 get_new_package_obj_from_response,
13 },
14 wallet_context::WalletContext,
15};
16use iota_types::{
17 IOTA_RANDOMNESS_STATE_OBJECT_ID, IOTA_SYSTEM_PACKAGE_ID, TypeTag,
18 base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber},
19 crypto::{AccountKeyPair, Signature, Signer, get_key_pair},
20 digests::TransactionDigest,
21 iota_system_state::IOTA_SYSTEM_MODULE_NAME,
22 multisig::{BitmapUnit, MultiSig, MultiSigPublicKey},
23 object::Owner,
24 signature::GenericSignature,
25 transaction::{
26 CallArg, DEFAULT_VALIDATOR_GAS_PRICE, ObjectArg, ProgrammableTransaction,
27 TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE, TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
28 Transaction, TransactionData,
29 },
30};
31use move_core_types::ident_str;
32use shared_crypto::intent::{Intent, IntentMessage};
33
34pub struct TestTransactionBuilder {
35 test_data: TestTransactionData,
36 sender: IotaAddress,
37 gas_object: ObjectRef,
38 gas_price: u64,
39 gas_budget: Option<u64>,
40}
41
42impl TestTransactionBuilder {
43 pub fn new(sender: IotaAddress, gas_object: ObjectRef, gas_price: u64) -> Self {
44 Self {
45 test_data: TestTransactionData::Empty,
46 sender,
47 gas_object,
48 gas_price,
49 gas_budget: None,
50 }
51 }
52
53 pub fn sender(&self) -> IotaAddress {
54 self.sender
55 }
56
57 pub fn gas_object(&self) -> ObjectRef {
58 self.gas_object
59 }
60
61 pub fn move_call(
63 mut self,
64 package_id: ObjectID,
65 module: &'static str,
66 function: &'static str,
67 args: Vec<CallArg>,
68 ) -> Self {
69 assert!(matches!(self.test_data, TestTransactionData::Empty));
70 self.test_data = TestTransactionData::Move(MoveData {
71 package_id,
72 module,
73 function,
74 args,
75 type_args: vec![],
76 });
77 self
78 }
79
80 pub fn with_type_args(mut self, type_args: Vec<TypeTag>) -> Self {
81 if let TestTransactionData::Move(data) = &mut self.test_data {
82 assert!(data.type_args.is_empty());
83 data.type_args = type_args;
84 } else {
85 panic!("Cannot set type args for non-move call");
86 }
87 self
88 }
89
90 pub fn with_gas_budget(mut self, gas_budget: u64) -> Self {
91 self.gas_budget = Some(gas_budget);
92 self
93 }
94
95 pub fn call_counter_create(self, package_id: ObjectID) -> Self {
96 self.move_call(package_id, "counter", "create", vec![])
97 }
98
99 pub fn call_counter_increment(
100 self,
101 package_id: ObjectID,
102 counter_id: ObjectID,
103 counter_initial_shared_version: SequenceNumber,
104 ) -> Self {
105 self.move_call(
106 package_id,
107 "counter",
108 "increment",
109 vec![CallArg::Object(ObjectArg::SharedObject {
110 id: counter_id,
111 initial_shared_version: counter_initial_shared_version,
112 mutable: true,
113 })],
114 )
115 }
116
117 pub fn call_counter_read(
118 self,
119 package_id: ObjectID,
120 counter_id: ObjectID,
121 counter_initial_shared_version: SequenceNumber,
122 ) -> Self {
123 self.move_call(
124 package_id,
125 "counter",
126 "value",
127 vec![CallArg::Object(ObjectArg::SharedObject {
128 id: counter_id,
129 initial_shared_version: counter_initial_shared_version,
130 mutable: false,
131 })],
132 )
133 }
134
135 pub fn call_counter_delete(
136 self,
137 package_id: ObjectID,
138 counter_id: ObjectID,
139 counter_initial_shared_version: SequenceNumber,
140 ) -> Self {
141 self.move_call(
142 package_id,
143 "counter",
144 "delete",
145 vec![CallArg::Object(ObjectArg::SharedObject {
146 id: counter_id,
147 initial_shared_version: counter_initial_shared_version,
148 mutable: true,
149 })],
150 )
151 }
152
153 pub fn call_nft_create(self, package_id: ObjectID) -> Self {
154 self.move_call(
155 package_id,
156 "testnet_nft",
157 "mint_to_sender",
158 vec![
159 CallArg::Pure(bcs::to_bytes("example_nft_name").unwrap()),
160 CallArg::Pure(bcs::to_bytes("example_nft_description").unwrap()),
161 CallArg::Pure(
162 bcs::to_bytes("https://iota.org/_nuxt/img/iota-logo.8d3c44e.svg").unwrap(),
163 ),
164 ],
165 )
166 }
167
168 pub fn call_nft_delete(self, package_id: ObjectID, nft_to_delete: ObjectRef) -> Self {
169 self.move_call(
170 package_id,
171 "testnet_nft",
172 "burn",
173 vec![CallArg::Object(ObjectArg::ImmOrOwnedObject(nft_to_delete))],
174 )
175 }
176
177 pub fn call_staking(self, stake_coin: ObjectRef, validator: IotaAddress) -> Self {
178 self.move_call(
179 IOTA_SYSTEM_PACKAGE_ID,
180 IOTA_SYSTEM_MODULE_NAME.as_str(),
181 "request_add_stake",
182 vec![
183 CallArg::IOTA_SYSTEM_MUT,
184 CallArg::Object(ObjectArg::ImmOrOwnedObject(stake_coin)),
185 CallArg::Pure(bcs::to_bytes(&validator).unwrap()),
186 ],
187 )
188 }
189
190 pub fn call_emit_random(
191 self,
192 package_id: ObjectID,
193 randomness_initial_shared_version: SequenceNumber,
194 ) -> Self {
195 self.move_call(
196 package_id,
197 "random",
198 "new",
199 vec![CallArg::Object(ObjectArg::SharedObject {
200 id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
201 initial_shared_version: randomness_initial_shared_version,
202 mutable: false,
203 })],
204 )
205 }
206
207 pub fn call_request_add_validator(self) -> Self {
208 self.move_call(
209 IOTA_SYSTEM_PACKAGE_ID,
210 IOTA_SYSTEM_MODULE_NAME.as_str(),
211 "request_add_validator",
212 vec![CallArg::IOTA_SYSTEM_MUT],
213 )
214 }
215
216 pub fn call_request_add_validator_candidate(
217 self,
218 validator: &GenesisValidatorMetadata,
219 ) -> Self {
220 self.move_call(
221 IOTA_SYSTEM_PACKAGE_ID,
222 IOTA_SYSTEM_MODULE_NAME.as_str(),
223 "request_add_validator_candidate",
224 vec![
225 CallArg::IOTA_SYSTEM_MUT,
226 CallArg::Pure(bcs::to_bytes(&validator.authority_public_key).unwrap()),
227 CallArg::Pure(bcs::to_bytes(&validator.network_public_key).unwrap()),
228 CallArg::Pure(bcs::to_bytes(&validator.protocol_public_key).unwrap()),
229 CallArg::Pure(bcs::to_bytes(&validator.proof_of_possession).unwrap()),
230 CallArg::Pure(bcs::to_bytes(validator.name.as_bytes()).unwrap()),
231 CallArg::Pure(bcs::to_bytes(validator.description.as_bytes()).unwrap()),
232 CallArg::Pure(bcs::to_bytes(validator.image_url.as_bytes()).unwrap()),
233 CallArg::Pure(bcs::to_bytes(validator.project_url.as_bytes()).unwrap()),
234 CallArg::Pure(bcs::to_bytes(&validator.network_address).unwrap()),
235 CallArg::Pure(bcs::to_bytes(&validator.p2p_address).unwrap()),
236 CallArg::Pure(bcs::to_bytes(&validator.primary_address).unwrap()),
237 CallArg::Pure(bcs::to_bytes(&DEFAULT_VALIDATOR_GAS_PRICE).unwrap()), CallArg::Pure(bcs::to_bytes(&0u64).unwrap()), ],
240 )
241 }
242
243 pub fn call_request_remove_validator(self) -> Self {
244 self.move_call(
245 IOTA_SYSTEM_PACKAGE_ID,
246 IOTA_SYSTEM_MODULE_NAME.as_str(),
247 "request_remove_validator",
248 vec![CallArg::IOTA_SYSTEM_MUT],
249 )
250 }
251
252 pub fn transfer(mut self, object: ObjectRef, recipient: IotaAddress) -> Self {
253 self.test_data = TestTransactionData::Transfer(TransferData { object, recipient });
254 self
255 }
256
257 pub fn transfer_iota(mut self, amount: Option<u64>, recipient: IotaAddress) -> Self {
258 self.test_data = TestTransactionData::TransferIota(TransferIotaData { amount, recipient });
259 self
260 }
261
262 pub fn publish(mut self, path: PathBuf) -> Self {
263 assert!(matches!(self.test_data, TestTransactionData::Empty));
264 self.test_data = TestTransactionData::Publish(PublishData::Source(path, false));
265 self
266 }
267
268 pub fn publish_with_deps(mut self, path: PathBuf) -> Self {
269 assert!(matches!(self.test_data, TestTransactionData::Empty));
270 self.test_data = TestTransactionData::Publish(PublishData::Source(path, true));
271 self
272 }
273
274 pub fn publish_with_data(mut self, data: PublishData) -> Self {
275 assert!(matches!(self.test_data, TestTransactionData::Empty));
276 self.test_data = TestTransactionData::Publish(data);
277 self
278 }
279
280 pub fn publish_examples(self, subpath: &'static str) -> Self {
281 let path = if let Ok(p) = std::env::var("MOVE_EXAMPLES_DIR") {
282 let mut path = PathBuf::from(p);
283 path.extend([subpath]);
284 path
285 } else {
286 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
287 path.extend(["..", "..", "examples", "move", subpath]);
288 path
289 };
290 self.publish(path)
291 }
292
293 pub fn programmable(mut self, programmable: ProgrammableTransaction) -> Self {
294 self.test_data = TestTransactionData::Programmable(programmable);
295 self
296 }
297
298 pub fn build(self) -> TransactionData {
299 match self.test_data {
300 TestTransactionData::Move(data) => TransactionData::new_move_call(
301 self.sender,
302 data.package_id,
303 ident_str!(data.module).to_owned(),
304 ident_str!(data.function).to_owned(),
305 data.type_args,
306 self.gas_object,
307 data.args,
308 self.gas_budget
309 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE),
310 self.gas_price,
311 )
312 .unwrap(),
313 TestTransactionData::Transfer(data) => TransactionData::new_transfer(
314 data.recipient,
315 data.object,
316 self.sender,
317 self.gas_object,
318 self.gas_budget
319 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER),
320 self.gas_price,
321 ),
322 TestTransactionData::TransferIota(data) => TransactionData::new_transfer_iota(
323 data.recipient,
324 self.sender,
325 data.amount,
326 self.gas_object,
327 self.gas_budget
328 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER),
329 self.gas_price,
330 ),
331 TestTransactionData::Publish(data) => {
332 let (all_module_bytes, dependencies) = match data {
333 PublishData::Source(path, with_unpublished_deps) => {
334 let compiled_package = BuildConfig::new_for_testing().build(&path).unwrap();
335 let all_module_bytes =
336 compiled_package.get_package_bytes(with_unpublished_deps);
337 let dependencies = compiled_package.get_dependency_storage_package_ids();
338 (all_module_bytes, dependencies)
339 }
340 PublishData::ModuleBytes(bytecode) => (bytecode, vec![]),
341 PublishData::CompiledPackage(compiled_package) => {
342 let all_module_bytes = compiled_package.get_package_bytes(false);
343 let dependencies = compiled_package.get_dependency_storage_package_ids();
344 (all_module_bytes, dependencies)
345 }
346 };
347
348 TransactionData::new_module(
349 self.sender,
350 self.gas_object,
351 all_module_bytes,
352 dependencies,
353 self.gas_budget.unwrap_or(
354 self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE,
355 ),
356 self.gas_price,
357 )
358 }
359 TestTransactionData::Programmable(pt) => TransactionData::new_programmable(
360 self.sender,
361 vec![self.gas_object],
362 pt,
363 self.gas_budget
364 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE),
365 self.gas_price,
366 ),
367 TestTransactionData::Empty => {
368 panic!("Cannot build empty transaction");
369 }
370 }
371 }
372
373 pub fn build_and_sign(self, signer: &dyn Signer<Signature>) -> Transaction {
374 Transaction::from_data_and_signer(self.build(), vec![signer])
375 }
376
377 pub fn build_and_sign_multisig(
378 self,
379 multisig_pk: MultiSigPublicKey,
380 signers: &[&dyn Signer<Signature>],
381 bitmap: BitmapUnit,
382 ) -> Transaction {
383 let data = self.build();
384 let intent_msg = IntentMessage::new(Intent::iota_transaction(), data.clone());
385
386 let mut signatures = Vec::with_capacity(signers.len());
387 for signer in signers {
388 signatures.push(
389 GenericSignature::from(Signature::new_secure(&intent_msg, *signer))
390 .to_compressed()
391 .unwrap(),
392 );
393 }
394
395 let multisig =
396 GenericSignature::MultiSig(MultiSig::insecure_new(signatures, bitmap, multisig_pk));
397
398 Transaction::from_generic_sig_data(data, vec![multisig])
399 }
400}
401
402#[expect(clippy::large_enum_variant)]
403enum TestTransactionData {
404 Move(MoveData),
405 Transfer(TransferData),
406 TransferIota(TransferIotaData),
407 Publish(PublishData),
408 Programmable(ProgrammableTransaction),
409 Empty,
410}
411
412struct MoveData {
413 package_id: ObjectID,
414 module: &'static str,
415 function: &'static str,
416 args: Vec<CallArg>,
417 type_args: Vec<TypeTag>,
418}
419
420#[expect(clippy::large_enum_variant)]
421pub enum PublishData {
422 Source(PathBuf, bool),
426 ModuleBytes(Vec<Vec<u8>>),
427 CompiledPackage(CompiledPackage),
428}
429
430struct TransferData {
431 object: ObjectRef,
432 recipient: IotaAddress,
433}
434
435struct TransferIotaData {
436 amount: Option<u64>,
437 recipient: IotaAddress,
438}
439
440pub async fn batch_make_transfer_transactions(
451 context: &WalletContext,
452 max_txn_num: usize,
453) -> Vec<Transaction> {
454 let recipient = get_key_pair::<AccountKeyPair>().0;
455 let result = context.get_all_accounts_and_gas_objects().await;
456 let accounts_and_objs = result.unwrap();
457 let mut res = Vec::with_capacity(max_txn_num);
458
459 let gas_price = context.get_reference_gas_price().await.unwrap();
460 for (address, objs) in accounts_and_objs {
461 for obj in objs {
462 if res.len() >= max_txn_num {
463 return res;
464 }
465 let data = TransactionData::new_transfer_iota(
466 recipient,
467 address,
468 Some(2),
469 obj,
470 gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
471 gas_price,
472 );
473 let tx = context.sign_transaction(&data);
474 res.push(tx);
475 }
476 }
477 res
478}
479
480pub async fn make_transfer_iota_transaction(
481 context: &WalletContext,
482 recipient: Option<IotaAddress>,
483 amount: Option<u64>,
484) -> Transaction {
485 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
486 let gas_price = context.get_reference_gas_price().await.unwrap();
487 context.sign_transaction(
488 &TestTransactionBuilder::new(sender, gas_object, gas_price)
489 .transfer_iota(amount, recipient.unwrap_or(sender))
490 .build(),
491 )
492}
493
494pub async fn make_staking_transaction(
495 context: &WalletContext,
496 validator_address: IotaAddress,
497) -> Transaction {
498 let accounts_and_objs = context.get_all_accounts_and_gas_objects().await.unwrap();
499 let sender = accounts_and_objs[0].0;
500 let gas_object = accounts_and_objs[0].1[0];
501 let stake_object = accounts_and_objs[0].1[1];
502 let gas_price = context.get_reference_gas_price().await.unwrap();
503 context.sign_transaction(
504 &TestTransactionBuilder::new(sender, gas_object, gas_price)
505 .call_staking(stake_object, validator_address)
506 .build(),
507 )
508}
509
510pub async fn make_publish_transaction(context: &WalletContext, path: PathBuf) -> Transaction {
511 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
512 let gas_price = context.get_reference_gas_price().await.unwrap();
513 context.sign_transaction(
514 &TestTransactionBuilder::new(sender, gas_object, gas_price)
515 .publish(path)
516 .build(),
517 )
518}
519
520pub async fn make_publish_transaction_with_deps(
521 context: &WalletContext,
522 path: PathBuf,
523) -> Transaction {
524 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
525 let gas_price = context.get_reference_gas_price().await.unwrap();
526 context.sign_transaction(
527 &TestTransactionBuilder::new(sender, gas_object, gas_price)
528 .publish_with_deps(path)
529 .build(),
530 )
531}
532
533pub async fn publish_package(context: &WalletContext, path: PathBuf) -> ObjectRef {
534 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
535 let gas_price = context.get_reference_gas_price().await.unwrap();
536 let txn = context.sign_transaction(
537 &TestTransactionBuilder::new(sender, gas_object, gas_price)
538 .publish(path)
539 .build(),
540 );
541 let resp = context.execute_transaction_must_succeed(txn).await;
542 get_new_package_obj_from_response(&resp).unwrap()
543}
544
545pub async fn publish_basics_package(context: &WalletContext) -> ObjectRef {
548 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
549 let gas_price = context.get_reference_gas_price().await.unwrap();
550 let txn = context.sign_transaction(
551 &TestTransactionBuilder::new(sender, gas_object, gas_price)
552 .publish_examples("basics")
553 .build(),
554 );
555 let resp = context.execute_transaction_must_succeed(txn).await;
556 get_new_package_obj_from_response(&resp).unwrap()
557}
558
559pub async fn publish_basics_package_and_make_counter(
562 context: &WalletContext,
563) -> (ObjectRef, ObjectRef) {
564 let package_ref = publish_basics_package(context).await;
565 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
566 let gas_price = context.get_reference_gas_price().await.unwrap();
567 let counter_creation_txn = context.sign_transaction(
568 &TestTransactionBuilder::new(sender, gas_object, gas_price)
569 .call_counter_create(package_ref.0)
570 .build(),
571 );
572 let resp = context
573 .execute_transaction_must_succeed(counter_creation_txn)
574 .await;
575 let counter_ref = resp
576 .effects
577 .unwrap()
578 .created()
579 .iter()
580 .find(|obj_ref| matches!(obj_ref.owner, Owner::Shared { .. }))
581 .unwrap()
582 .reference
583 .to_object_ref();
584 (package_ref, counter_ref)
585}
586
587pub async fn increment_counter(
590 context: &WalletContext,
591 sender: IotaAddress,
592 gas_object_id: Option<ObjectID>,
593 package_id: ObjectID,
594 counter_id: ObjectID,
595 initial_shared_version: SequenceNumber,
596) -> IotaTransactionBlockResponse {
597 let gas_object = if let Some(gas_object_id) = gas_object_id {
598 context.get_object_ref(gas_object_id).await.unwrap()
599 } else {
600 context
601 .get_one_gas_object_owned_by_address(sender)
602 .await
603 .unwrap()
604 .unwrap()
605 };
606 let rgp = context.get_reference_gas_price().await.unwrap();
607 let txn = context.sign_transaction(
608 &TestTransactionBuilder::new(sender, gas_object, rgp)
609 .call_counter_increment(package_id, counter_id, initial_shared_version)
610 .build(),
611 );
612 context.execute_transaction_must_succeed(txn).await
613}
614
615pub async fn emit_new_random_u128(
618 context: &WalletContext,
619 package_id: ObjectID,
620) -> IotaTransactionBlockResponse {
621 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
622 let rgp = context.get_reference_gas_price().await.unwrap();
623
624 let client = context.get_client().await.unwrap();
625 let random_obj = client
626 .read_api()
627 .get_object_with_options(
628 IOTA_RANDOMNESS_STATE_OBJECT_ID,
629 IotaObjectDataOptions::new().with_owner(),
630 )
631 .await
632 .unwrap()
633 .into_object()
634 .unwrap();
635 let random_obj_owner = random_obj
636 .owner
637 .expect("Expect Randomness object to have an owner");
638
639 let Owner::Shared {
640 initial_shared_version,
641 } = random_obj_owner
642 else {
643 panic!("Expect Randomness to be shared object")
644 };
645 let random_call_arg = CallArg::Object(ObjectArg::SharedObject {
646 id: IOTA_RANDOMNESS_STATE_OBJECT_ID,
647 initial_shared_version,
648 mutable: false,
649 });
650
651 let txn = context.sign_transaction(
652 &TestTransactionBuilder::new(sender, gas_object, rgp)
653 .move_call(package_id, "random", "new", vec![random_call_arg])
654 .build(),
655 );
656 context.execute_transaction_must_succeed(txn).await
657}
658
659pub async fn publish_nfts_package(
662 context: &WalletContext,
663) -> (ObjectID, ObjectID, TransactionDigest) {
664 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
665 let gas_id = gas_object.0;
666 let gas_price = context.get_reference_gas_price().await.unwrap();
667 let txn = context.sign_transaction(
668 &TestTransactionBuilder::new(sender, gas_object, gas_price)
669 .publish_examples("nft")
670 .build(),
671 );
672 let resp = context.execute_transaction_must_succeed(txn).await;
673 let package_id = get_new_package_obj_from_response(&resp).unwrap().0;
674 (package_id, gas_id, resp.digest)
675}
676
677pub async fn create_nft(
681 context: &WalletContext,
682 package_id: ObjectID,
683) -> (IotaAddress, ObjectID, TransactionDigest) {
684 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
685 let rgp = context.get_reference_gas_price().await.unwrap();
686
687 let txn = context.sign_transaction(
688 &TestTransactionBuilder::new(sender, gas_object, rgp)
689 .call_nft_create(package_id)
690 .build(),
691 );
692 let resp = context.execute_transaction_must_succeed(txn).await;
693
694 let object_id = resp
695 .effects
696 .as_ref()
697 .unwrap()
698 .created()
699 .first()
700 .unwrap()
701 .reference
702 .object_id;
703
704 (sender, object_id, resp.digest)
705}
706
707pub async fn delete_nft(
709 context: &WalletContext,
710 sender: IotaAddress,
711 package_id: ObjectID,
712 nft_to_delete: ObjectRef,
713) -> IotaTransactionBlockResponse {
714 let gas = context
715 .get_one_gas_object_owned_by_address(sender)
716 .await
717 .unwrap()
718 .unwrap_or_else(|| panic!("Expect {sender} to have at least one gas object"));
719 let rgp = context.get_reference_gas_price().await.unwrap();
720 let txn = context.sign_transaction(
721 &TestTransactionBuilder::new(sender, gas, rgp)
722 .call_nft_delete(package_id, nft_to_delete)
723 .build(),
724 );
725 context.execute_transaction_must_succeed(txn).await
726}