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