sui_transaction_builder/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4mod error;
5pub mod unresolved;
6
7use error::Error;
8use sui_types::Address;
9use sui_types::Argument;
10use sui_types::Command;
11use sui_types::GasPayment;
12use sui_types::Identifier;
13use sui_types::Input;
14use sui_types::MakeMoveVector;
15use sui_types::MergeCoins;
16use sui_types::MoveCall;
17use sui_types::ObjectReference;
18use sui_types::Publish;
19use sui_types::SharedInput;
20use sui_types::SplitCoins;
21use sui_types::Transaction;
22use sui_types::TransactionExpiration;
23use sui_types::TransferObjects;
24use sui_types::TypeTag;
25use sui_types::Upgrade;
26
27use base64ct::Encoding;
28use serde::Serialize;
29
30/// A builder for creating transactions. Use `resolve` to finalize the transaction data.
31#[derive(Clone, Default, Debug)]
32pub struct TransactionBuilder {
33    /// The inputs to the transaction.
34    inputs: Vec<unresolved::Input>,
35    /// The list of commands in the transaction. A command is a single operation in a programmable
36    /// transaction.
37    commands: Vec<Command>,
38    /// The gas objects that will be used to pay for the transaction. The most common way is to
39    /// use [`unresolved::Input::owned`] function to create a gas object and use the [`add_gas`]
40    /// method to set the gas objects.
41    gas: Vec<unresolved::Input>,
42    /// The gas budget for the transaction.
43    gas_budget: Option<u64>,
44    /// The gas price for the transaction.
45    gas_price: Option<u64>,
46    /// The sender of the transaction.
47    sender: Option<Address>,
48    /// The sponsor of the transaction. If None, the sender is also the sponsor.
49    sponsor: Option<Address>,
50    /// The expiration of the transaction. The default value of this type is no expiration.
51    expiration: TransactionExpiration,
52}
53
54/// A transaction input that bypasses serialization. The input contents is already BCS serialized
55/// and is put verbatim into the transaction.
56struct RawBytes(Vec<u8>);
57
58/// A transaction input that will be serialized from BCS.
59pub struct Serialized<'a, T: Serialize>(pub &'a T);
60
61/// A separate type to support denoting a function by a more structured representation.
62pub struct Function {
63    /// The package that contains the module with the function.
64    package: Address,
65    /// The module that contains the function.
66    module: Identifier,
67    /// The function name.
68    function: Identifier,
69    /// The type arguments for the function.
70    type_args: Vec<TypeTag>,
71}
72
73/// A transaction builder to build transactions.
74impl TransactionBuilder {
75    /// Create a new transaction builder and initialize its elements to default.
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    // Transaction Inputs
81
82    /// Make a value available to the transaction as an input.
83    pub fn input(&mut self, i: impl Into<unresolved::Input>) -> Argument {
84        let input = i.into();
85        self.inputs.push(input);
86        Argument::Input((self.inputs.len() - 1) as u16)
87    }
88
89    /// Return the argument to be the gas object.
90    pub fn gas(&self) -> Argument {
91        Argument::Gas
92    }
93
94    // Metadata
95
96    /// Add one or more gas objects to use to pay for the transaction.
97    ///
98    /// Most commonly the gas can be passed as a reference to an owned/immutable `Object`,
99    /// or can created using one of the of the constructors of the [`unresolved::Input`] enum,
100    /// e.g., [`unresolved::Input::owned`].
101    pub fn add_gas_objects<O, I>(&mut self, gas: I)
102    where
103        O: Into<unresolved::Input>,
104        I: IntoIterator<Item = O>,
105    {
106        self.gas.extend(gas.into_iter().map(|x| x.into()));
107    }
108
109    /// Set the gas budget for the transaction.
110    pub fn set_gas_budget(&mut self, budget: u64) {
111        self.gas_budget = Some(budget);
112    }
113
114    /// Set the gas price for the transaction.
115    pub fn set_gas_price(&mut self, price: u64) {
116        self.gas_price = Some(price);
117    }
118
119    /// Set the sender of the transaction.
120    pub fn set_sender(&mut self, sender: Address) {
121        self.sender = Some(sender);
122    }
123
124    /// Set the sponsor of the transaction.
125    pub fn set_sponsor(&mut self, sponsor: Address) {
126        self.sponsor = Some(sponsor);
127    }
128
129    /// Set the expiration of the transaction to be a specific epoch.
130    pub fn set_expiration(&mut self, epoch: u64) {
131        self.expiration = TransactionExpiration::Epoch(epoch);
132    }
133
134    // Commands
135
136    /// Call a Move function with the given arguments.
137    ///
138    /// - `function` is a structured representation of a package::module::function argument,
139    ///   optionally with type arguments.
140    ///
141    /// The return value is a result argument that can be used in subsequent commands.
142    /// If the move call returns multiple results, you can access them using the
143    /// [`Argument::nested`] method.
144    pub fn move_call(&mut self, function: Function, arguments: Vec<Argument>) -> Argument {
145        let cmd = Command::MoveCall(MoveCall {
146            package: function.package,
147            module: function.module,
148            function: function.function,
149            type_arguments: function.type_args,
150            arguments,
151        });
152        self.commands.push(cmd);
153        Argument::Result(self.commands.len() as u16 - 1)
154    }
155
156    /// Transfer a list of objects to the given address, without producing any result.
157    pub fn transfer_objects(&mut self, objects: Vec<Argument>, address: Argument) {
158        let cmd = Command::TransferObjects(TransferObjects { objects, address });
159        self.commands.push(cmd);
160    }
161
162    /// Split a coin by the provided amounts, returning multiple results (as many as there are
163    /// amounts). To access the results, use the [`Argument::nested`] method to access the desired
164    /// coin by its index.
165    pub fn split_coins(&mut self, coin: Argument, amounts: Vec<Argument>) -> Argument {
166        let cmd = Command::SplitCoins(SplitCoins { coin, amounts });
167        self.commands.push(cmd);
168        Argument::Result(self.commands.len() as u16 - 1)
169    }
170
171    /// Merge a list of coins into a single coin, without producing any result.
172    pub fn merge_coins(&mut self, coin: Argument, coins_to_merge: Vec<Argument>) {
173        let cmd = Command::MergeCoins(MergeCoins {
174            coin,
175            coins_to_merge,
176        });
177        self.commands.push(cmd);
178    }
179
180    /// Make a move vector from a list of elements. If the elements are not objects, or the vector
181    /// is empty, a type must be supplied.
182    /// It returns the Move vector as an argument, that can be used in subsequent commands.
183    pub fn make_move_vec(&mut self, type_: Option<TypeTag>, elements: Vec<Argument>) -> Argument {
184        let cmd = Command::MakeMoveVector(MakeMoveVector { type_, elements });
185        self.commands.push(cmd);
186        Argument::Result(self.commands.len() as u16 - 1)
187    }
188
189    /// Publish a list of modules with the given dependencies. The result is the
190    /// `0x2::package::UpgradeCap` Move type. Note that the upgrade capability needs to be handled
191    /// after this call:
192    ///  - transfer it to the transaction sender or another address
193    ///  - burn it
194    ///  - wrap it for access control
195    ///  - discard the it to make a package immutable
196    ///
197    /// The arguments required for this command are:
198    ///  - `modules`: is the modules' bytecode to be published
199    ///  - `dependencies`: is the list of IDs of the transitive dependencies of the package
200    pub fn publish(&mut self, modules: Vec<Vec<u8>>, dependencies: Vec<Address>) -> Argument {
201        let cmd = Command::Publish(Publish {
202            modules,
203            dependencies,
204        });
205        self.commands.push(cmd);
206        Argument::Result(self.commands.len() as u16 - 1)
207    }
208
209    /// Upgrade a Move package.
210    ///
211    ///  - `modules`: is the modules' bytecode for the modules to be published
212    ///  - `dependencies`: is the list of IDs of the transitive dependencies of the package to be
213    ///    upgraded
214    ///  - `package`: is the ID of the current package being upgraded
215    ///  - `ticket`: is the upgrade ticket
216    ///
217    ///  To get the ticket, you have to call the `0x2::package::authorize_upgrade` function,
218    ///  and pass the package ID, the upgrade policy, and package digest.
219    pub fn upgrade(
220        &mut self,
221        modules: Vec<Vec<u8>>,
222        dependencies: Vec<Address>,
223        package: Address,
224        ticket: Argument,
225    ) -> Argument {
226        let cmd = Command::Upgrade(Upgrade {
227            modules,
228            dependencies,
229            package,
230            ticket,
231        });
232        self.commands.push(cmd);
233        Argument::Result(self.commands.len() as u16 - 1)
234    }
235
236    /// Assuming everything is resolved, convert this transaction into the
237    /// resolved form. Returns a [`Transaction`] if successful, or an `Error` if not.
238    pub fn finish(self) -> Result<Transaction, Error> {
239        let Some(sender) = self.sender else {
240            return Err(Error::MissingSender);
241        };
242        if self.gas.is_empty() {
243            return Err(Error::MissingGasObjects);
244        }
245        let Some(budget) = self.gas_budget else {
246            return Err(Error::MissingGasBudget);
247        };
248        let Some(price) = self.gas_price else {
249            return Err(Error::MissingGasPrice);
250        };
251
252        Ok(Transaction {
253            kind: sui_types::TransactionKind::ProgrammableTransaction(
254                sui_types::ProgrammableTransaction {
255                    inputs: self
256                        .inputs
257                        .into_iter()
258                        .map(try_from_unresolved_input_arg)
259                        .collect::<Result<Vec<_>, _>>()?,
260                    commands: self.commands,
261                },
262            ),
263            sender,
264            gas_payment: {
265                GasPayment {
266                    objects: self
267                        .gas
268                        .into_iter()
269                        .map(try_from_gas_unresolved_input_to_unresolved_obj_ref)
270                        .collect::<Result<Vec<_>, _>>()?
271                        .into_iter()
272                        .map(try_from_unresolved_obj_ref)
273                        .collect::<Result<Vec<_>, _>>()?,
274                    owner: self.sponsor.unwrap_or(sender),
275                    price,
276                    budget,
277                }
278            },
279            expiration: self.expiration,
280        })
281    }
282}
283
284impl Function {
285    /// Constructor for the function type.
286    pub fn new(
287        package: Address,
288        module: Identifier,
289        function: Identifier,
290        type_args: Vec<TypeTag>,
291    ) -> Self {
292        Self {
293            package,
294            module,
295            function,
296            type_args,
297        }
298    }
299}
300
301impl From<RawBytes> for unresolved::Input {
302    fn from(raw: RawBytes) -> Self {
303        Self {
304            kind: Some(unresolved::InputKind::Pure),
305            value: Some(unresolved::Value::String(base64ct::Base64::encode_string(
306                &raw.0,
307            ))),
308            object_id: None,
309            version: None,
310            digest: None,
311            mutable: None,
312        }
313    }
314}
315
316impl<'a, T: Serialize> From<Serialized<'a, T>> for unresolved::Input {
317    fn from(value: Serialized<'a, T>) -> Self {
318        Self::from(RawBytes(bcs::to_bytes(value.0).unwrap()))
319    }
320}
321
322/// Convert from an [`unresolved::Input`] to a [`unresolved::ObjectReference`]. This is used to
323/// convert gas objects into unresolved object references.
324fn try_from_gas_unresolved_input_to_unresolved_obj_ref(
325    input: unresolved::Input,
326) -> Result<unresolved::ObjectReference, Error> {
327    match input.kind {
328        Some(unresolved::InputKind::ImmutableOrOwned) => {
329            let object_id = input.object_id.ok_or(Error::MissingObjectId)?;
330            let version = input.version;
331            let digest = input.digest;
332            Ok(unresolved::ObjectReference {
333                object_id,
334                version,
335                digest,
336            })
337        }
338        _ => Err(Error::WrongGasObject),
339    }
340}
341
342/// Convert from an [`unresolved::ObjectReference`] to a [`ObjectReference`].
343fn try_from_unresolved_obj_ref(obj: unresolved::ObjectReference) -> Result<ObjectReference, Error> {
344    let obj_id = obj.object_id;
345    let version = obj.version.ok_or(Error::MissingVersion(obj_id))?;
346    let digest = obj.digest.ok_or(Error::MissingDigest(obj_id))?;
347    Ok(ObjectReference::new(obj_id, version, digest))
348}
349
350/// Convert from an [`unresolved::Input`] into an [`Input`] for resolving the
351/// transaction.
352fn try_from_unresolved_input_arg(value: unresolved::Input) -> Result<Input, Error> {
353    if let Some(kind) = value.kind {
354        match kind {
355            unresolved::InputKind::Pure => {
356                let Some(value) = value.value else {
357                    return Err(Error::MissingPureValue);
358                };
359
360                match value {
361                    unresolved::Value::String(v) => {
362                        let bytes = base64ct::Base64::decode_vec(&v).map_err(Error::Decoding)?;
363                        Ok(Input::Pure(bytes))
364                    }
365                    _ => Err(Error::Input(
366                        "expected a base64 string value for the Pure input argument".to_string(),
367                    )),
368                }
369            }
370            unresolved::InputKind::ImmutableOrOwned => {
371                let Some(object_id) = value.object_id else {
372                    return Err(Error::MissingObjectId);
373                };
374                let Some(version) = value.version else {
375                    return Err(Error::MissingVersion(object_id));
376                };
377                let Some(digest) = value.digest else {
378                    return Err(Error::MissingDigest(object_id));
379                };
380                Ok(Input::ImmutableOrOwned(ObjectReference::new(
381                    object_id, version, digest,
382                )))
383            }
384            unresolved::InputKind::Shared => {
385                let Some(object_id) = value.object_id else {
386                    return Err(Error::MissingObjectId);
387                };
388                let Some(initial_shared_version) = value.version else {
389                    return Err(Error::MissingInitialSharedVersion(object_id));
390                };
391                let Some(mutable) = value.mutable else {
392                    return Err(Error::SharedObjectMutability(object_id));
393                };
394
395                Ok(Input::Shared(SharedInput::new(
396                    object_id,
397                    initial_shared_version,
398                    mutable,
399                )))
400            }
401            unresolved::InputKind::Receiving => {
402                let Some(object_id) = value.object_id else {
403                    return Err(Error::MissingObjectId);
404                };
405                let Some(version) = value.version else {
406                    return Err(Error::MissingVersion(object_id));
407                };
408                let Some(digest) = value.digest else {
409                    return Err(Error::MissingDigest(object_id));
410                };
411                Ok(Input::Receiving(ObjectReference::new(
412                    object_id, version, digest,
413                )))
414            }
415            unresolved::InputKind::Literal => Err(Error::UnsupportedLiteral),
416        }
417    } else {
418        Err(Error::Input(
419            "unresolved::Input must have a kind that is not None".to_string(),
420        ))
421    }
422}
423
424// #[cfg(test)]
425// mod tests {
426//     use std::str::FromStr;
427
428//     use anyhow::Context;
429//     use base64ct::Encoding;
430//     use serde::de;
431//     use serde::Deserialize;
432//     use serde::Deserializer;
433
434//     use sui_crypto::ed25519::Ed25519PrivateKey;
435//     use sui_crypto::SuiSigner;
436//     use sui_graphql_client::faucet::FaucetClient;
437//     use sui_graphql_client::Client;
438//     use sui_graphql_client::PaginationFilter;
439
440//     use sui_types::framework::Coin;
441//     use sui_types::Address;
442//     use sui_types::Digest;
443//     use sui_types::ExecutionStatus;
444//     use sui_types::IdOperation;
445//     use sui_types::ObjectType;
446//     use sui_types::TransactionEffects;
447//     use sui_types::TypeTag;
448
449//     use crate::unresolved::Input;
450//     use crate::Function;
451//     use crate::Serialized;
452//     use crate::TransactionBuilder;
453
454//     /// Type corresponding to the output of `sui move build --dump-bytecode-as-base64`
455//     #[derive(serde::Deserialize, Debug)]
456//     struct MovePackageData {
457//         #[serde(deserialize_with = "bcs_from_str")]
458//         modules: Vec<Vec<u8>>,
459//         #[serde(deserialize_with = "deps_from_str")]
460//         dependencies: Vec<Address>,
461//         digest: Vec<u8>,
462//     }
463
464//     fn bcs_from_str<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
465//     where
466//         D: Deserializer<'de>,
467//     {
468//         let bcs = Vec::<String>::deserialize(deserializer)?;
469//         bcs.into_iter()
470//             .map(|s| base64ct::Base64::decode_vec(&s).map_err(de::Error::custom))
471//             .collect()
472//     }
473
474//     fn deps_from_str<'de, D>(deserializer: D) -> Result<Vec<Address>, D::Error>
475//     where
476//         D: Deserializer<'de>,
477//     {
478//         let deps = Vec::<String>::deserialize(deserializer)?;
479//         deps.into_iter()
480//             .map(|s| Address::from_str(&s).map_err(de::Error::custom))
481//             .collect()
482//     }
483
484//     /// This is used to read the json file that contains the modules/deps/digest generated with sui
485//     /// move build --dump-bytecode-as-base64 on the `test_example_v1 and test_example_v2` projects
486//     /// in the tests directory.
487//     /// The json files are generated automatically when running `make test-with-localnet` in the
488//     /// root of the sui-transaction-builder crate.
489//     fn move_package_data(file: &str) -> MovePackageData {
490//         let data = std::fs::read_to_string(file)
491//             .with_context(|| {
492//                 format!(
493//                     "Failed to read {file}. \
494//                     Run `make test-with-localnet` from the root of the repository that will \
495//                     generate the right json files with the package data and then run the tests."
496//                 )
497//             })
498//             .unwrap();
499//         serde_json::from_str(&data).unwrap()
500//     }
501
502//     /// Generate a random private key and its corresponding address
503//     fn helper_address_pk() -> (Address, Ed25519PrivateKey) {
504//         let pk = Ed25519PrivateKey::generate(rand::thread_rng());
505//         let address = pk.public_key().derive_address();
506//         (address, pk)
507//     }
508
509//     /// Helper to:
510//     /// - generate a private key and its corresponding address
511//     /// - set the sender for the tx to this newly created address
512//     /// - set gas price
513//     /// - set gas budget
514//     /// - call faucet which returns 5 coin objects (by default)
515//     /// - set the gas object (last coin from the list of the 5 objects returned by faucet)
516//     /// - return the address, private key, and coins.
517//     async fn helper_setup(
518//         tx: &mut TransactionBuilder,
519//         client: &Client,
520//     ) -> (Address, Ed25519PrivateKey, Vec<Coin<'static>>) {
521//         let (address, pk) = helper_address_pk();
522//         let faucet = FaucetClient::local();
523//         let faucet_resp = faucet.request(address).await.unwrap();
524//         wait_for_tx(
525//             client,
526//             faucet_resp
527//                 .coins_sent
528//                 .unwrap()
529//                 .first()
530//                 .unwrap()
531//                 .transfer_tx_digest,
532//         )
533//         .await;
534
535//         let coins = client
536//             .coins(address, None, PaginationFilter::default())
537//             .await
538//             .unwrap();
539//         let coins = coins.data();
540
541//         let gas = coins.last().unwrap().id();
542//         // TODO when we have tx resolution, we can just pass an ObjectId
543//         let gas_obj: Input = (&client.object(*gas, None).await.unwrap().unwrap()).into();
544//         tx.add_gas_objects(vec![gas_obj.with_owned_kind()]);
545//         tx.set_gas_budget(500000000);
546//         tx.set_gas_price(1000);
547//         tx.set_sender(address);
548
549//         (address, pk, coins.to_vec())
550//     }
551
552//     /// Wait for the transaction to be finalized and indexed. This queries the GraphQL server until
553//     /// it retrieves the requested transaction.
554//     async fn wait_for_tx(client: &Client, digest: Digest) {
555//         while client.transaction(digest).await.unwrap().is_none() {
556//             tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
557//         }
558//     }
559
560//     /// Wait for the transaction to be finalized and indexed, and check the effects' to ensure the
561//     /// transaction was successfully executed.
562//     async fn wait_for_tx_and_check_effects_status_success(
563//         client: &Client,
564//         digest: Digest,
565//         effects: Result<Option<TransactionEffects>, sui_graphql_client::error::Error>,
566//     ) {
567//         assert!(effects.is_ok(), "Execution failed. Effects: {effects:?}");
568//         // wait for the transaction to be finalized
569//         wait_for_tx(client, digest).await;
570//         // check that it succeeded
571//         let status = effects.unwrap();
572//         let expected_status = ExecutionStatus::Success;
573//         assert_eq!(&expected_status, status.as_ref().unwrap().status());
574//     }
575
576//     #[tokio::test]
577//     async fn test_finish() {
578//         let mut tx = TransactionBuilder::new();
579//         let coin_obj_id = "0x19406ea4d9609cd9422b85e6bf2486908f790b778c757aff805241f3f609f9b4";
580//         let coin_digest = "7opR9rFUYivSTqoJHvFb9p6p54THyHTatMG6id4JKZR9";
581//         let coin_version = 2;
582//         let coin = tx.input(Input::owned(
583//             coin_obj_id.parse().unwrap(),
584//             coin_version,
585//             coin_digest.parse().unwrap(),
586//         ));
587
588//         let addr = Address::generate(rand::thread_rng());
589//         let recipient = tx.input(Serialized(&addr));
590
591//         let result = tx.clone().finish();
592//         assert!(result.is_err());
593
594//         tx.transfer_objects(vec![coin], recipient);
595//         tx.set_gas_budget(500000000);
596//         tx.set_gas_price(1000);
597//         tx.add_gas_objects(vec![Input::immutable(
598//             "0xd8792bce2743e002673752902c0e7348dfffd78638cb5367b0b85857bceb9821"
599//                 .parse()
600//                 .unwrap(),
601//             2,
602//             "2ZigdvsZn5BMeszscPQZq9z8ebnS2FpmAuRbAi9ednCk"
603//                 .parse()
604//                 .unwrap(),
605//         )]);
606//         tx.set_sender(
607//             "0xc574ea804d9c1a27c886312e96c0e2c9cfd71923ebaeb3000d04b5e65fca2793"
608//                 .parse()
609//                 .unwrap(),
610//         );
611
612//         let tx = tx.finish();
613//         assert!(tx.is_ok());
614//     }
615
616//     #[tokio::test]
617//     async fn test_transfer_obj_execution() {
618//         let mut tx = TransactionBuilder::new();
619//         let client = Client::new_localhost();
620//         let (_, pk, coins) = helper_setup(&mut tx, &client).await;
621
622//         // get the object information from the client
623//         let first = coins.first().unwrap().id();
624//         let coin: Input = (&client.object(*first, None).await.unwrap().unwrap()).into();
625//         let coin_input = tx.input(coin.with_owned_kind());
626//         let recipient = Address::generate(rand::thread_rng());
627//         let recipient_input = tx.input(Serialized(&recipient));
628//         tx.transfer_objects(vec![coin_input], recipient_input);
629
630//         let tx = tx.finish().unwrap();
631//         let sig = pk.sign_transaction(&tx).unwrap();
632
633//         let effects = client.execute_tx(vec![sig], &tx).await;
634//         wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
635
636//         // check that recipient has 1 coin
637//         let recipient_coins = client
638//             .coins(recipient, None, PaginationFilter::default())
639//             .await
640//             .unwrap();
641//         assert_eq!(recipient_coins.data().len(), 1);
642//     }
643
644//     #[tokio::test]
645//     async fn test_move_call() {
646//         // Check that `0x1::option::is_none` move call works when passing `1`
647//         let client = Client::new_localhost();
648//         let mut tx = TransactionBuilder::new();
649//         // set up the sender, gas object, gas budget, and gas price and return the pk to sign
650//         let (_, pk, _) = helper_setup(&mut tx, &client).await;
651//         let function = Function::new(
652//             "0x1".parse().unwrap(),
653//             "option".parse().unwrap(),
654//             "is_none".parse().unwrap(),
655//             vec![TypeTag::U64],
656//         );
657//         let input = tx.input(Serialized(&vec![1u64]));
658//         tx.move_call(function, vec![input]);
659
660//         let tx = tx.finish().unwrap();
661//         let sig = pk.sign_transaction(&tx).unwrap();
662//         let effects = client.execute_tx(vec![sig], &tx).await;
663//         wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
664//     }
665
666//     #[tokio::test]
667//     async fn test_split_transfer() {
668//         let client = Client::new_localhost();
669//         let mut tx = TransactionBuilder::new();
670//         let (_, pk, _) = helper_setup(&mut tx, &client).await;
671
672//         // transfer 1 SUI from Gas coin
673//         let amount = tx.input(Serialized(&1_000_000_000u64));
674//         let result = tx.split_coins(tx.gas(), vec![amount]);
675//         let recipient_address = Address::generate(rand::thread_rng());
676//         let recipient = tx.input(Serialized(&recipient_address));
677//         tx.transfer_objects(vec![result], recipient);
678
679//         let tx = tx.finish().unwrap();
680//         let sig = pk.sign_transaction(&tx).unwrap();
681
682//         let effects = client.execute_tx(vec![sig], &tx).await;
683//         wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
684
685//         // check that recipient has 1 coin
686//         let recipient_coins = client
687//             .coins(recipient_address, None, PaginationFilter::default())
688//             .await
689//             .unwrap();
690//         assert_eq!(recipient_coins.data().len(), 1);
691//     }
692
693//     #[tokio::test]
694//     async fn test_split_without_transfer_should_fail() {
695//         let client = Client::new_localhost();
696//         let mut tx = TransactionBuilder::new();
697//         let (_, pk, coins) = helper_setup(&mut tx, &client).await;
698
699//         let coin = coins.first().unwrap().id();
700//         let coin_obj: Input = (&client.object(*coin, None).await.unwrap().unwrap()).into();
701//         let coin_input = tx.input(coin_obj.with_owned_kind());
702
703//         // transfer 1 SUI
704//         let amount = tx.input(Serialized(&1_000_000_000u64));
705//         tx.split_coins(coin_input, vec![amount]);
706
707//         let tx = tx.finish().unwrap();
708//         let sig = pk.sign_transaction(&tx).unwrap();
709
710//         let effects = client.execute_tx(vec![sig], &tx).await;
711//         assert!(effects.is_ok());
712
713//         // wait for the transaction to be finalized
714//         loop {
715//             let tx_digest = client.transaction(tx.digest()).await.unwrap();
716//             if tx_digest.is_some() {
717//                 break;
718//             }
719//         }
720//         assert!(effects.is_ok());
721//         let status = effects.unwrap();
722//         let expected_status = ExecutionStatus::Success;
723//         // The tx failed, so we expect Failure instead of Success
724//         assert_ne!(&expected_status, status.as_ref().unwrap().status());
725//     }
726
727//     #[tokio::test]
728//     async fn test_merge_coins() {
729//         let client = Client::new_localhost();
730//         let mut tx = TransactionBuilder::new();
731//         let (address, pk, coins) = helper_setup(&mut tx, &client).await;
732
733//         let coin1 = coins.first().unwrap().id();
734//         let coin1_obj: Input = (&client.object(*coin1, None).await.unwrap().unwrap()).into();
735//         let coin_to_merge = tx.input(coin1_obj.with_owned_kind());
736
737//         let mut coins_to_merge = vec![];
738//         // last coin is used for gas, first coin is the one we merge into
739//         for c in coins[1..&coins.len() - 1].iter() {
740//             let coin: Input = (&client.object(*c.id(), None).await.unwrap().unwrap()).into();
741//             coins_to_merge.push(tx.input(coin.with_owned_kind()));
742//         }
743
744//         tx.merge_coins(coin_to_merge, coins_to_merge);
745//         let tx = tx.finish().unwrap();
746//         let sig = pk.sign_transaction(&tx).unwrap();
747
748//         let effects = client.execute_tx(vec![sig], &tx).await;
749//         wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
750
751//         // check that there are two coins
752//         let coins_after = client
753//             .coins(address, None, PaginationFilter::default())
754//             .await
755//             .unwrap();
756//         assert_eq!(coins_after.data().len(), 2);
757//     }
758
759//     #[tokio::test]
760//     async fn test_make_move_vec() {
761//         let client = Client::new_localhost();
762//         let mut tx = TransactionBuilder::new();
763//         let (_, pk, _) = helper_setup(&mut tx, &client).await;
764
765//         let input = tx.input(Serialized(&1u64));
766//         tx.make_move_vec(Some(TypeTag::U64), vec![input]);
767
768//         let tx = tx.finish().unwrap();
769//         let sig = pk.sign_transaction(&tx).unwrap();
770
771//         let effects = client.execute_tx(vec![sig], &tx).await;
772//         wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
773//     }
774
775//     #[tokio::test]
776//     async fn test_publish() {
777//         let client = Client::new_localhost();
778//         let mut tx = TransactionBuilder::new();
779//         let (address, pk, _) = helper_setup(&mut tx, &client).await;
780
781//         let package = move_package_data("package_test_example_v1.json");
782//         let sender = tx.input(Serialized(&address));
783//         let upgrade_cap = tx.publish(package.modules, package.dependencies);
784//         tx.transfer_objects(vec![upgrade_cap], sender);
785//         let tx = tx.finish().unwrap();
786//         let sig = pk.sign_transaction(&tx).unwrap();
787//         let effects = client.execute_tx(vec![sig], &tx).await;
788//         wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
789//     }
790
791//     #[tokio::test]
792//     async fn test_upgrade() {
793//         let client = Client::new_localhost();
794//         let mut tx = TransactionBuilder::new();
795//         let (address, pk, coins) = helper_setup(&mut tx, &client).await;
796
797//         let package = move_package_data("package_test_example_v2.json");
798//         let sender = tx.input(Serialized(&address));
799//         let upgrade_cap = tx.publish(package.modules, package.dependencies);
800//         tx.transfer_objects(vec![upgrade_cap], sender);
801//         let tx = tx.finish().unwrap();
802//         let sig = pk.sign_transaction(&tx).unwrap();
803//         let effects = client.execute_tx(vec![sig], &tx).await;
804//         let mut package_id: Option<Address> = None;
805//         let mut created_objs = vec![];
806//         if let Ok(Some(ref effects)) = effects {
807//             match effects {
808//                 TransactionEffects::V2(e) => {
809//                     for obj in e.changed_objects.clone() {
810//                         if obj.id_operation == IdOperation::Created {
811//                             let change = obj.output_state;
812//                             match change {
813//                                 sui_types::ObjectOut::PackageWrite { .. } => {
814//                                     package_id = Some(obj.object_id);
815//                                 }
816//                                 sui_types::ObjectOut::ObjectWrite { .. } => {
817//                                     created_objs.push(obj.object_id);
818//                                 }
819//                                 _ => {}
820//                             }
821//                         }
822//                     }
823//                 }
824//                 _ => panic!("Expected V2 effects"),
825//             }
826//         }
827//         wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
828
829//         let mut tx = TransactionBuilder::new();
830//         let mut upgrade_cap = None;
831//         for o in created_objs {
832//             let obj = client.object(o, None).await.unwrap().unwrap();
833//             match obj.object_type() {
834//                 ObjectType::Struct(x) if x.name.to_string() == "UpgradeCap" => {
835//                     match obj.owner() {
836//                         sui_types::Owner::Address(_) => {
837//                             let obj: Input = (&obj).into();
838//                             upgrade_cap = Some(tx.input(obj.with_owned_kind()))
839//                         }
840//                         sui_types::Owner::Shared(_) => {
841//                             upgrade_cap = Some(tx.input(&obj))
842//                         }
843//                         // If the capability is owned by an object, then the module defining the owning
844//                         // object gets to decide how the upgrade capability should be used.
845//                         sui_types::Owner::Object(_) => {
846//                             panic!("Upgrade capability controlled by object")
847//                         }
848//                         sui_types::Owner::Immutable => panic!("Upgrade capability is stored immutably and cannot be used for upgrades"),
849//                         sui_types::Owner::ConsensusAddress { .. } => {
850//                             upgrade_cap = Some(tx.input(&obj))
851//                         }
852//                         _ => panic!("unknwon owner"),
853//                     };
854//                     break;
855//                 }
856//                 _ => {}
857//             };
858//         }
859
860//         let upgrade_policy = tx.input(Serialized(&0u8));
861//         let updated_package = move_package_data("package_test_example_v2.json");
862//         let digest_arg = tx.input(Serialized(&updated_package.digest));
863
864//         // we need this ticket to authorize the upgrade
865//         let upgrade_ticket = tx.move_call(
866//             Function::new(
867//                 "0x2".parse().unwrap(),
868//                 "package".parse().unwrap(),
869//                 "authorize_upgrade".parse().unwrap(),
870//                 vec![],
871//             ),
872//             vec![upgrade_cap.unwrap(), upgrade_policy, digest_arg],
873//         );
874//         // now we can upgrade the package
875//         let upgrade_receipt = tx.upgrade(
876//             updated_package.modules,
877//             updated_package.dependencies,
878//             package_id.unwrap(),
879//             upgrade_ticket,
880//         );
881
882//         // commit the upgrade
883//         tx.move_call(
884//             Function::new(
885//                 "0x2".parse().unwrap(),
886//                 "package".parse().unwrap(),
887//                 "commit_upgrade".parse().unwrap(),
888//                 vec![],
889//             ),
890//             vec![upgrade_cap.unwrap(), upgrade_receipt],
891//         );
892
893//         let gas = coins.last().unwrap().id();
894//         let gas_obj: Input = (&client.object(*gas, None).await.unwrap().unwrap()).into();
895//         tx.add_gas_objects(vec![gas_obj.with_owned_kind()]);
896//         tx.set_gas_budget(500000000);
897//         tx.set_gas_price(1000);
898//         tx.set_sender(address);
899//         let tx = tx.finish().unwrap();
900//         let sig = pk.sign_transaction(&tx).unwrap();
901//         let effects = client.execute_tx(vec![sig], &tx).await;
902//         wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
903//     }
904// }