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_sdk_crypto::Signer as SdkSigner;
17use iota_sdk_types::{
18 Address, Identifier, Input, ObjectId, Owner, ProgrammableTransaction, TransactionKind, TypeTag,
19 crypto::{Intent, IntentMessage, SimpleSignature},
20};
21use iota_types::{
22 base_types::{ObjectRef, SequenceNumber},
23 crypto::{AccountKeyPair, Signature, Signer, get_key_pair},
24 digests::TransactionDigest,
25 multisig::{BitmapUnit, MultiSig, MultiSigPublicKey},
26 signature::GenericSignature,
27 transaction::{
28 CallArg, DEFAULT_VALIDATOR_GAS_PRICE, SharedObjectRef,
29 TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE, TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
30 Transaction, TransactionData, TransactionDataAPI,
31 },
32 utils::to_sender_signed_transaction,
33};
34use rand::Rng;
35
36pub struct TestTransactionBuilder {
37 test_data: TestTransactionData,
38 sender: Address,
39 gas_object: ObjectRef,
40 gas_price: u64,
41 gas_budget: Option<u64>,
42 nonce: Option<u64>,
43}
44
45impl TestTransactionBuilder {
46 pub fn new(sender: Address, gas_object: ObjectRef, gas_price: u64) -> Self {
47 Self {
48 test_data: TestTransactionData::Empty,
49 sender,
50 gas_object,
51 gas_price,
52 gas_budget: None,
53 nonce: None,
54 }
55 }
56
57 pub fn ensure_unique(mut self) -> Self {
64 self.nonce = Some(rand::thread_rng().gen());
65 self
66 }
67
68 pub fn sender(&self) -> Address {
69 self.sender
70 }
71
72 pub fn gas_object(&self) -> ObjectRef {
73 self.gas_object
74 }
75
76 pub fn move_call(
78 mut self,
79 package_id: ObjectId,
80 module: &str,
81 function: &str,
82 args: Vec<CallArg>,
83 ) -> Self {
84 assert!(matches!(self.test_data, TestTransactionData::Empty));
85 self.test_data = TestTransactionData::Move(MoveData {
86 package_id,
87 module: Identifier::new(module).unwrap(),
88 function: Identifier::new(function).unwrap(),
89 args,
90 type_args: vec![],
91 });
92 self
93 }
94
95 pub fn with_type_args(mut self, type_args: Vec<TypeTag>) -> Self {
96 if let TestTransactionData::Move(data) = &mut self.test_data {
97 assert!(data.type_args.is_empty());
98 data.type_args = type_args;
99 } else {
100 panic!("Cannot set type args for non-move call");
101 }
102 self
103 }
104
105 pub fn with_gas_budget(mut self, gas_budget: u64) -> Self {
106 self.gas_budget = Some(gas_budget);
107 self
108 }
109
110 pub fn call_counter_create(self, package_id: ObjectId) -> Self {
111 self.move_call(package_id, "counter", "create", vec![])
112 }
113
114 pub fn call_counter_increment(
115 self,
116 package_id: ObjectId,
117 counter_id: ObjectId,
118 counter_initial_shared_version: SequenceNumber,
119 ) -> Self {
120 self.move_call(
121 package_id,
122 "counter",
123 "increment",
124 vec![CallArg::Shared(SharedObjectRef::new(
125 counter_id,
126 counter_initial_shared_version,
127 true,
128 ))],
129 )
130 }
131
132 pub fn call_counter_read(
133 self,
134 package_id: ObjectId,
135 counter_id: ObjectId,
136 counter_initial_shared_version: SequenceNumber,
137 ) -> Self {
138 self.move_call(
139 package_id,
140 "counter",
141 "value",
142 vec![CallArg::Shared(SharedObjectRef::new(
143 counter_id,
144 counter_initial_shared_version,
145 false,
146 ))],
147 )
148 }
149
150 pub fn call_counter_delete(
151 self,
152 package_id: ObjectId,
153 counter_id: ObjectId,
154 counter_initial_shared_version: SequenceNumber,
155 ) -> Self {
156 self.move_call(
157 package_id,
158 "counter",
159 "delete",
160 vec![CallArg::Shared(SharedObjectRef::new(
161 counter_id,
162 counter_initial_shared_version,
163 true,
164 ))],
165 )
166 }
167
168 pub fn call_nft_create(self, package_id: ObjectId) -> Self {
169 self.move_call(
170 package_id,
171 "testnet_nft",
172 "mint_to_sender",
173 vec![
174 CallArg::pure(&"example_nft_name"),
175 CallArg::pure(&"example_nft_description"),
176 CallArg::pure(&"https://iota.org/_nuxt/img/iota-logo.8d3c44e.svg"),
177 ],
178 )
179 }
180
181 pub fn call_nft_delete(self, package_id: ObjectId, nft_to_delete: ObjectRef) -> Self {
182 self.move_call(
183 package_id,
184 "testnet_nft",
185 "burn",
186 vec![CallArg::ImmutableOrOwned(nft_to_delete)],
187 )
188 }
189
190 pub fn call_staking(self, stake_coin: ObjectRef, validator: Address) -> Self {
191 self.move_call(
192 ObjectId::SYSTEM,
193 Identifier::IOTA_SYSTEM_MODULE.as_str(),
194 "request_add_stake",
195 vec![
196 CallArg::IOTA_SYSTEM_MUTABLE,
197 CallArg::ImmutableOrOwned(stake_coin),
198 CallArg::pure(&validator),
199 ],
200 )
201 }
202
203 pub fn call_emit_random(
204 self,
205 package_id: ObjectId,
206 randomness_initial_shared_version: SequenceNumber,
207 ) -> Self {
208 self.move_call(
209 package_id,
210 "random",
211 "new",
212 vec![CallArg::Shared(SharedObjectRef::new(
213 ObjectId::RANDOMNESS_STATE,
214 randomness_initial_shared_version,
215 false,
216 ))],
217 )
218 }
219
220 pub fn call_request_add_validator(self) -> Self {
221 self.move_call(
222 ObjectId::SYSTEM,
223 Identifier::IOTA_SYSTEM_MODULE.as_str(),
224 "request_add_validator",
225 vec![CallArg::IOTA_SYSTEM_MUTABLE],
226 )
227 }
228
229 pub fn call_request_add_validator_candidate(
230 self,
231 validator: &GenesisValidatorMetadata,
232 ) -> Self {
233 self.move_call(
234 ObjectId::SYSTEM,
235 Identifier::IOTA_SYSTEM_MODULE.as_str(),
236 "request_add_validator_candidate",
237 vec![
238 CallArg::IOTA_SYSTEM_MUTABLE,
239 CallArg::pure(&validator.authority_public_key),
240 CallArg::pure(&validator.network_public_key),
241 CallArg::pure(&validator.protocol_public_key),
242 CallArg::pure(&validator.proof_of_possession),
243 CallArg::pure(&validator.name),
244 CallArg::pure(&validator.description),
245 CallArg::pure(&validator.image_url),
246 CallArg::pure(&validator.project_url),
247 CallArg::pure(&validator.network_address),
248 CallArg::pure(&validator.p2p_address),
249 CallArg::pure(&validator.primary_address),
250 CallArg::pure(&DEFAULT_VALIDATOR_GAS_PRICE), CallArg::pure(&0u64), ],
253 )
254 }
255
256 pub fn call_request_remove_validator(self) -> Self {
257 self.move_call(
258 ObjectId::SYSTEM,
259 Identifier::IOTA_SYSTEM_MODULE.as_str(),
260 "request_remove_validator",
261 vec![CallArg::IOTA_SYSTEM_MUTABLE],
262 )
263 }
264
265 pub fn transfer(mut self, object: ObjectRef, recipient: Address) -> Self {
266 self.test_data = TestTransactionData::Transfer(TransferData { object, recipient });
267 self
268 }
269
270 pub fn transfer_iota(mut self, amount: Option<u64>, recipient: Address) -> Self {
271 self.test_data = TestTransactionData::TransferIota(TransferIotaData { amount, recipient });
272 self
273 }
274
275 pub fn split_coin(mut self, coin: ObjectRef, amounts: Vec<u64>) -> Self {
276 self.test_data = TestTransactionData::SplitCoin(SplitCoinData { coin, amounts });
277 self
278 }
279
280 pub fn publish(mut self, path: PathBuf) -> Self {
281 assert!(matches!(self.test_data, TestTransactionData::Empty));
282 self.test_data = TestTransactionData::Publish(PublishData::Source(path, false));
283 self
284 }
285
286 pub fn publish_with_deps(mut self, path: PathBuf) -> Self {
287 assert!(matches!(self.test_data, TestTransactionData::Empty));
288 self.test_data = TestTransactionData::Publish(PublishData::Source(path, true));
289 self
290 }
291
292 pub fn publish_with_data(mut self, data: PublishData) -> Self {
293 assert!(matches!(self.test_data, TestTransactionData::Empty));
294 self.test_data = TestTransactionData::Publish(data);
295 self
296 }
297
298 pub fn publish_examples(self, subpath: &'static str) -> Self {
299 let path = if let Ok(p) = std::env::var("MOVE_EXAMPLES_DIR") {
300 let mut path = PathBuf::from(p);
301 path.extend([subpath]);
302 path
303 } else {
304 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
305 path.extend(["..", "..", "examples", "move", subpath]);
306 path
307 };
308 self.publish(path)
309 }
310
311 pub fn programmable(mut self, programmable: ProgrammableTransaction) -> Self {
312 self.test_data = TestTransactionData::Programmable(programmable);
313 self
314 }
315
316 pub fn build(self) -> TransactionData {
317 let nonce = self.nonce;
318 let mut data = self.build_inner();
319 if let Some(nonce) = nonce {
320 if let TransactionKind::Programmable(pt) = data.kind_mut() {
324 pt.inputs.push(Input::Pure(nonce.to_le_bytes().to_vec()));
325 }
326 }
327 data
328 }
329
330 fn build_inner(self) -> TransactionData {
331 match self.test_data {
332 TestTransactionData::Move(data) => TransactionData::new_move_call(
333 self.sender,
334 data.package_id,
335 data.module,
336 data.function,
337 data.type_args,
338 self.gas_object,
339 data.args,
340 self.gas_budget
341 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE),
342 self.gas_price,
343 )
344 .unwrap(),
345 TestTransactionData::Transfer(data) => TransactionData::new_transfer(
346 data.recipient,
347 data.object,
348 self.sender,
349 self.gas_object,
350 self.gas_budget
351 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER),
352 self.gas_price,
353 ),
354 TestTransactionData::TransferIota(data) => TransactionData::new_transfer_iota(
355 data.recipient,
356 self.sender,
357 data.amount,
358 self.gas_object,
359 self.gas_budget
360 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER),
361 self.gas_price,
362 ),
363 TestTransactionData::SplitCoin(data) => TransactionData::new_split_coin(
364 self.sender,
365 data.coin,
366 data.amounts,
367 self.gas_object,
368 self.gas_budget
369 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER),
370 self.gas_price,
371 ),
372 TestTransactionData::Publish(data) => {
373 let (all_module_bytes, dependencies) = match data {
374 PublishData::Source(path, with_unpublished_deps) => {
375 let compiled_package = BuildConfig::new_for_testing().build(&path).unwrap();
376 let all_module_bytes =
377 compiled_package.get_package_bytes(with_unpublished_deps);
378 let dependencies = compiled_package.get_dependency_storage_package_ids();
379 (all_module_bytes, dependencies)
380 }
381 PublishData::ModuleBytes(bytecode) => (bytecode, vec![]),
382 PublishData::CompiledPackage(compiled_package) => {
383 let all_module_bytes = compiled_package.get_package_bytes(false);
384 let dependencies = compiled_package.get_dependency_storage_package_ids();
385 (all_module_bytes, dependencies)
386 }
387 };
388
389 TransactionData::new_module(
390 self.sender,
391 self.gas_object,
392 all_module_bytes,
393 dependencies,
394 self.gas_budget.unwrap_or(
395 self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE,
396 ),
397 self.gas_price,
398 )
399 }
400 TestTransactionData::Programmable(pt) => TransactionData::new_programmable(
401 self.sender,
402 vec![self.gas_object],
403 pt,
404 self.gas_budget
405 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE),
406 self.gas_price,
407 ),
408 TestTransactionData::Empty => {
409 panic!("Cannot build empty transaction");
410 }
411 }
412 }
413
414 pub fn build_and_sign(self, signer: &dyn Signer<Signature>) -> Transaction {
415 Transaction::from_data_and_signer(self.build(), vec![signer])
416 }
417
418 pub fn build_and_sign_multisig(
419 self,
420 multisig_pk: MultiSigPublicKey,
421 signers: &[&dyn SdkSigner<SimpleSignature>],
422 bitmap: BitmapUnit,
423 ) -> Transaction {
424 let data = self.build();
425 let digest = IntentMessage::new(Intent::iota_transaction(), data.clone()).signing_digest();
426 let signatures = signers.iter().map(|s| s.sign(&*digest).into()).collect();
427 let multisig =
428 GenericSignature::MultiSig(MultiSig::new_unchecked(signatures, bitmap, multisig_pk));
429
430 Transaction::from_generic_sig_data(data, vec![multisig])
431 }
432}
433
434#[expect(clippy::large_enum_variant)]
435enum TestTransactionData {
436 Move(MoveData),
437 Transfer(TransferData),
438 TransferIota(TransferIotaData),
439 SplitCoin(SplitCoinData),
440 Publish(PublishData),
441 Programmable(ProgrammableTransaction),
442 Empty,
443}
444
445struct MoveData {
446 package_id: ObjectId,
447 module: Identifier,
448 function: Identifier,
449 args: Vec<CallArg>,
450 type_args: Vec<TypeTag>,
451}
452
453#[expect(clippy::large_enum_variant)]
454pub enum PublishData {
455 Source(PathBuf, bool),
459 ModuleBytes(Vec<Vec<u8>>),
460 CompiledPackage(CompiledPackage),
461}
462
463struct TransferData {
464 object: ObjectRef,
465 recipient: Address,
466}
467
468struct TransferIotaData {
469 amount: Option<u64>,
470 recipient: Address,
471}
472
473struct SplitCoinData {
474 coin: ObjectRef,
475 amounts: Vec<u64>,
476}
477
478pub async fn batch_make_transfer_transactions(
489 context: &WalletContext,
490 max_txn_num: usize,
491) -> Vec<Transaction> {
492 let recipient = get_key_pair::<AccountKeyPair>().0;
493 let result = context.get_all_accounts_and_gas_objects().await;
494 let accounts_and_objs = result.unwrap();
495 let mut res = Vec::with_capacity(max_txn_num);
496
497 let gas_price = context.get_reference_gas_price().await.unwrap();
498 for (address, objs) in accounts_and_objs {
499 for obj in objs {
500 if res.len() >= max_txn_num {
501 return res;
502 }
503 let data = TransactionData::new_transfer_iota(
504 recipient,
505 address,
506 Some(2),
507 obj,
508 gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
509 gas_price,
510 );
511 let tx = context.sign_transaction(&data);
512 res.push(tx);
513 }
514 }
515 res
516}
517
518pub async fn make_transfer_iota_transaction(
519 context: &WalletContext,
520 recipient: Option<Address>,
521 amount: Option<u64>,
522) -> Transaction {
523 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
524 let gas_price = context.get_reference_gas_price().await.unwrap();
525 context.sign_transaction(
526 &TestTransactionBuilder::new(sender, gas_object, gas_price)
527 .transfer_iota(amount, recipient.unwrap_or(sender))
528 .build(),
529 )
530}
531
532pub async fn make_staking_transaction(
533 context: &WalletContext,
534 validator_address: Address,
535) -> Transaction {
536 let accounts_and_objs = context.get_all_accounts_and_gas_objects().await.unwrap();
537 let sender = accounts_and_objs[0].0;
538 let gas_object = accounts_and_objs[0].1[0];
539 let stake_object = accounts_and_objs[0].1[1];
540 let gas_price = context.get_reference_gas_price().await.unwrap();
541 context.sign_transaction(
542 &TestTransactionBuilder::new(sender, gas_object, gas_price)
543 .call_staking(stake_object, validator_address)
544 .build(),
545 )
546}
547
548pub async fn make_publish_transaction(context: &WalletContext, path: PathBuf) -> Transaction {
549 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
550 let gas_price = context.get_reference_gas_price().await.unwrap();
551 context.sign_transaction(
552 &TestTransactionBuilder::new(sender, gas_object, gas_price)
553 .publish(path)
554 .build(),
555 )
556}
557
558pub async fn make_publish_transaction_with_deps(
559 context: &WalletContext,
560 path: PathBuf,
561) -> Transaction {
562 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
563 let gas_price = context.get_reference_gas_price().await.unwrap();
564 context.sign_transaction(
565 &TestTransactionBuilder::new(sender, gas_object, gas_price)
566 .publish_with_deps(path)
567 .build(),
568 )
569}
570
571pub async fn publish_package(context: &WalletContext, path: PathBuf) -> ObjectRef {
572 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
573 let gas_price = context.get_reference_gas_price().await.unwrap();
574 let txn = context.sign_transaction(
575 &TestTransactionBuilder::new(sender, gas_object, gas_price)
576 .publish(path)
577 .build(),
578 );
579 let resp = context.execute_transaction_must_succeed(txn).await;
580 get_new_package_obj_from_response(&resp).unwrap()
581}
582
583pub async fn publish_basics_package(context: &WalletContext) -> ObjectRef {
586 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
587 let gas_price = context.get_reference_gas_price().await.unwrap();
588 let txn = context.sign_transaction(
589 &TestTransactionBuilder::new(sender, gas_object, gas_price)
590 .publish_examples("basics")
591 .build(),
592 );
593 let resp = context.execute_transaction_must_succeed(txn).await;
594 get_new_package_obj_from_response(&resp).unwrap()
595}
596
597pub async fn publish_basics_package_and_make_counter(
600 context: &WalletContext,
601) -> (ObjectRef, ObjectRef) {
602 let package_ref = publish_basics_package(context).await;
603 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
604 let gas_price = context.get_reference_gas_price().await.unwrap();
605 let counter_creation_txn = context.sign_transaction(
606 &TestTransactionBuilder::new(sender, gas_object, gas_price)
607 .call_counter_create(package_ref.object_id)
608 .build(),
609 );
610 let resp = context
611 .execute_transaction_must_succeed(counter_creation_txn)
612 .await;
613 let counter_ref = resp
614 .effects
615 .unwrap()
616 .created()
617 .iter()
618 .find(|obj_ref| matches!(obj_ref.owner, Owner::Shared(_)))
619 .unwrap()
620 .reference;
621 (package_ref, counter_ref)
622}
623
624pub async fn increment_counter(
627 context: &WalletContext,
628 sender: Address,
629 gas_object_id: Option<ObjectId>,
630 package_id: ObjectId,
631 counter_id: ObjectId,
632 initial_shared_version: SequenceNumber,
633) -> IotaTransactionBlockResponse {
634 let gas_object = if let Some(gas_object_id) = gas_object_id {
635 context.get_object_ref(gas_object_id).await.unwrap()
636 } else {
637 context
638 .get_one_gas_object_owned_by_address(sender)
639 .await
640 .unwrap()
641 .unwrap()
642 };
643 let rgp = context.get_reference_gas_price().await.unwrap();
644 let txn = context.sign_transaction(
645 &TestTransactionBuilder::new(sender, gas_object, rgp)
646 .call_counter_increment(package_id, counter_id, initial_shared_version)
647 .build(),
648 );
649 context.execute_transaction_must_succeed(txn).await
650}
651
652pub async fn emit_new_random_u128(
655 context: &WalletContext,
656 package_id: ObjectId,
657) -> IotaTransactionBlockResponse {
658 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
659 let rgp = context.get_reference_gas_price().await.unwrap();
660
661 let client = context.get_client().await.unwrap();
662 let random_obj = client
663 .read_api()
664 .get_object_with_options(
665 ObjectId::RANDOMNESS_STATE,
666 IotaObjectDataOptions::new().with_owner(),
667 )
668 .await
669 .unwrap()
670 .into_object()
671 .unwrap();
672 let random_obj_owner = random_obj
673 .owner
674 .expect("Expect Randomness object to have an owner");
675
676 let Owner::Shared(initial_shared_version) = random_obj_owner else {
677 panic!("Expect Randomness to be shared object")
678 };
679 let random_call_arg = CallArg::Shared(SharedObjectRef::new(
680 ObjectId::RANDOMNESS_STATE,
681 initial_shared_version,
682 false,
683 ));
684
685 let txn = context.sign_transaction(
686 &TestTransactionBuilder::new(sender, gas_object, rgp)
687 .move_call(package_id, "random", "new", vec![random_call_arg])
688 .build(),
689 );
690 context.execute_transaction_must_succeed(txn).await
691}
692
693pub async fn publish_example_package(
696 context: &WalletContext,
697 example_subpath: &'static str,
698 sender_key_pair: &AccountKeyPair,
699 sender: Address,
700 gas: ObjectRef,
701) -> (ObjectId, TransactionDigest) {
702 let gas_price = context.get_reference_gas_price().await.unwrap();
703 let tx = to_sender_signed_transaction(
704 TestTransactionBuilder::new(sender, gas, gas_price)
705 .publish_examples(example_subpath)
706 .build(),
707 sender_key_pair,
708 );
709
710 let resp = context.execute_transaction_must_succeed(tx).await;
711 let package_id = get_new_package_obj_from_response(&resp).unwrap().object_id;
712 (package_id, resp.digest)
713}
714
715pub async fn publish_nfts_package(
718 context: &WalletContext,
719) -> (ObjectId, ObjectId, TransactionDigest) {
720 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
721 let gas_id = gas_object.object_id;
722 let gas_price = context.get_reference_gas_price().await.unwrap();
723 let txn = context.sign_transaction(
724 &TestTransactionBuilder::new(sender, gas_object, gas_price)
725 .publish_examples("nft")
726 .build(),
727 );
728 let resp = context.execute_transaction_must_succeed(txn).await;
729 let package_id = get_new_package_obj_from_response(&resp).unwrap().object_id;
730 (package_id, gas_id, resp.digest)
731}
732
733pub async fn publish_simple_warrior_package(
736 context: &WalletContext,
737 sender_key_pair: &AccountKeyPair,
738 sender: Address,
739 gas: ObjectRef,
740) -> (ObjectId, TransactionDigest) {
741 publish_example_package(context, "simple_warrior", sender_key_pair, sender, gas).await
742}
743
744pub async fn create_nft(
748 context: &WalletContext,
749 package_id: ObjectId,
750) -> (Address, ObjectId, TransactionDigest) {
751 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
752 let rgp = context.get_reference_gas_price().await.unwrap();
753
754 let txn = context.sign_transaction(
755 &TestTransactionBuilder::new(sender, gas_object, rgp)
756 .call_nft_create(package_id)
757 .build(),
758 );
759 let resp = context.execute_transaction_must_succeed(txn).await;
760
761 let object_id = resp
762 .effects
763 .as_ref()
764 .unwrap()
765 .created()
766 .first()
767 .unwrap()
768 .reference
769 .object_id;
770
771 (sender, object_id, resp.digest)
772}
773
774pub async fn delete_nft(
776 context: &WalletContext,
777 sender: Address,
778 package_id: ObjectId,
779 nft_to_delete: ObjectRef,
780) -> IotaTransactionBlockResponse {
781 let gas = context
782 .get_one_gas_object_owned_by_address(sender)
783 .await
784 .unwrap()
785 .unwrap_or_else(|| panic!("Expect {sender} to have at least one gas object"));
786 let rgp = context.get_reference_gas_price().await.unwrap();
787 let txn = context.sign_transaction(
788 &TestTransactionBuilder::new(sender, gas, rgp)
789 .call_nft_delete(package_id, nft_to_delete)
790 .build(),
791 );
792 context.execute_transaction_must_succeed(txn).await
793}
794
795#[cfg(test)]
796mod tests {
797 use iota_types::base_types::{dbg_addr, random_object_ref};
798
799 use super::*;
800
801 #[test]
802 fn ensure_unique_changes_digest() {
803 let sender = dbg_addr(1);
804 let recipient = dbg_addr(2);
805 let gas = random_object_ref();
806 let build =
807 || TestTransactionBuilder::new(sender, gas, 1000).transfer_iota(Some(1), recipient);
808
809 assert_eq!(build().build().digest(), build().build().digest());
811
812 let base = build().build();
813 let unique = build().ensure_unique().build();
814
815 assert_ne!(base.digest(), unique.digest());
818 match (base.kind(), unique.kind()) {
819 (TransactionKind::Programmable(base), TransactionKind::Programmable(unique)) => {
820 assert_eq!(unique.inputs.len(), base.inputs.len() + 1);
821 assert!(matches!(unique.inputs.last(), Some(Input::Pure(_))));
822 }
823 _ => panic!("expected programmable transactions"),
824 }
825
826 assert_ne!(
828 build().ensure_unique().build().digest(),
829 build().ensure_unique().build().digest()
830 );
831 }
832}