af_ptbuilder/
lib.rs

1#![cfg_attr(all(doc, not(doctest)), feature(doc_cfg))]
2
3//! Builder for programmable transactions.
4//!
5//! Check out the [`ptb`](crate::ptb) and [`ptbuilder`](crate::ptbuilder) macros for an ergonomic
6//! way of building transactions, or
7//! [`ProgrammableTransactionBuilder`](crate::ProgrammableTransactionBuilder) for a macro-less
8//! approach.
9
10use indexmap::IndexMap;
11use serde::{Deserialize, Serialize};
12#[doc(no_inline)]
13pub use sui_sdk_types::Address;
14#[doc(no_inline)]
15pub use sui_sdk_types::Argument;
16#[doc(hidden)]
17pub use sui_sdk_types::Identifier;
18#[doc(inline)]
19pub use sui_sdk_types::Input;
20#[doc(no_inline)]
21pub use sui_sdk_types::MoveCall;
22use sui_sdk_types::ProgrammableTransaction;
23#[doc(no_inline)]
24pub use sui_sdk_types::TypeTag;
25
26#[cfg(test)]
27mod tests;
28
29pub type Result<T> = ::std::result::Result<T, Error>;
30
31#[derive(thiserror::Error, Debug)]
32pub enum Error {
33    #[error("Serializing to BCS: {0}")]
34    Bcs(#[from] bcs::Error),
35
36    #[error("invariant violation! object has pure argument")]
37    ObjInvariantViolation,
38
39    #[error("invariant violation! object has id does not match call arg")]
40    InvalidObjArgUpdate,
41
42    #[error(transparent)]
43    MismatchedObjArgKinds(Box<MismatchedObjArgKindsError>),
44
45    #[error("tried to use a pure argument as an object argument")]
46    NotAnObjectArg,
47}
48
49#[derive(thiserror::Error, Debug)]
50#[error(
51    "Mismatched Object argument kind for object {id}. \
52        {old_value:?} is not compatible with {new_value:?}"
53)]
54pub struct MismatchedObjArgKindsError {
55    pub id: Address,
56    pub old_value: Input,
57    pub new_value: Input,
58}
59
60/// Builder for a [`ProgrammableTransaction`].
61#[derive(Clone, Debug, Default)]
62pub struct ProgrammableTransactionBuilder {
63    inputs: IndexMap<BuilderArg, Input>,
64    commands: Vec<sui_sdk_types::Command>,
65}
66
67/// Base API.
68impl ProgrammableTransactionBuilder {
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    pub fn finish(self) -> ProgrammableTransaction {
74        let Self { inputs, commands } = self;
75        let inputs = inputs.into_values().collect();
76        ProgrammableTransaction { inputs, commands }
77    }
78
79    /// Potentially adds a pure argument to the PTB.
80    ///
81    /// May not create a new PTB input if a previous one already has the same contents.
82    pub fn pure<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<Argument> {
83        Ok(self.pure_bytes(bcs::to_bytes(value)?, false))
84    }
85
86    /// Like [`Self::pure`] but forces a separate input entry
87    pub fn force_separate_pure<T: Serialize>(&mut self, value: T) -> Result<Argument> {
88        Ok(self.pure_bytes(bcs::to_bytes(&value)?, true))
89    }
90
91    /// Adds a pure argument to the PTB.
92    ///
93    /// # Arguments
94    /// - `bytes`: the BCS-serialized contents of the argument
95    /// - `force_separate`: whether to force a separate input argument to the PTB, else the builder
96    ///   re-uses a previously declared input argument if it has the same contents.
97    pub fn pure_bytes(&mut self, bytes: Vec<u8>, force_separate: bool) -> Argument {
98        let key = if force_separate {
99            BuilderArg::ForcedNonUniquePure(self.inputs.len())
100        } else {
101            BuilderArg::Pure(bytes.clone())
102        };
103        let (i, _) = self.inputs.insert_full(key, Input::Pure { value: bytes });
104        Argument::Input(i as u16)
105    }
106
107    /// Adds an object input to the PTB, returning the corresponding argument which can be used in
108    /// the body.
109    ///
110    /// May fail if overriding a previously declared input.
111    pub fn obj<O: Into<Input>>(&mut self, obj_arg: O) -> Result<Argument> {
112        let obj_arg = obj_arg.into();
113        let id = match &obj_arg {
114            Input::Pure { .. } => return Err(Error::NotAnObjectArg),
115            Input::ImmutableOrOwned(object_reference) => *object_reference.object_id(),
116            Input::Shared { object_id, .. } => *object_id,
117            Input::Receiving(object_reference) => *object_reference.object_id(),
118            _ => panic!("unknown Input variant"),
119        };
120        let key = BuilderArg::Object(id);
121        let mut input_arg = obj_arg;
122
123        if let Some(old_value) = self.inputs.get(&key) {
124            // Check if the key hash didn't collide with a previous pure input
125            if matches!(old_value, Input::Pure { .. }) {
126                return Err(Error::ObjInvariantViolation);
127            }
128
129            input_arg = match (old_value, input_arg) {
130                // The only update allowed: changing the `mutable` flag for a shared object input
131                (
132                    Input::Shared {
133                        object_id: id1,
134                        initial_shared_version: v1,
135                        mutable: mut1,
136                    },
137                    Input::Shared {
138                        object_id: id2,
139                        initial_shared_version: v2,
140                        mutable: mut2,
141                    },
142                ) if v1 == &v2 => {
143                    if id1 != &id2 {
144                        return Err(Error::InvalidObjArgUpdate);
145                    }
146                    Input::Shared {
147                        object_id: id2,
148                        initial_shared_version: v2,
149                        mutable: *mut1 || mut2,
150                    }
151                }
152
153                // Changing anything else about an existing object input is disallowed
154                (old_value, new_value) if old_value != &new_value => {
155                    return Err(Error::MismatchedObjArgKinds(Box::new(
156                        MismatchedObjArgKindsError {
157                            id,
158                            old_value: old_value.clone(),
159                            new_value,
160                        },
161                    )));
162                }
163
164                // If we already declared this exact same object input in the transaction, it will
165                // be automatically reused
166                (_, new_value) => new_value,
167            };
168        }
169
170        let (i, _) = self.inputs.insert_full(key, input_arg);
171        Ok(Argument::Input(i as u16))
172    }
173
174    /// Add a command to the PTB.
175    ///
176    /// This will come after any commands that were previously declared.
177    pub fn command(&mut self, command: impl Into<sui_sdk_types::Command>) -> Argument {
178        let i = self.commands.len();
179        self.commands.push(command.into());
180        Argument::Result(i as u16)
181    }
182}
183
184/// Extensions to the base API.
185impl ProgrammableTransactionBuilder {
186    /// Like `.command(Command::SplitCoins(coin_arg, balances))`, but also takes care of unpacking
187    /// each entry in the returned vector as its own [`Argument`].
188    ///
189    /// # Panics
190    ///
191    /// Panics if the `balances` input vector has a length that exceeds [`u16::MAX`].
192    pub fn split_coins_into_vec(
193        &mut self,
194        coin: Argument,
195        amounts: Vec<Argument>,
196    ) -> Vec<Argument> {
197        let idxs = 0..amounts.len() as u16;
198        let Argument::Result(coin_vec) = self.command(Command::SplitCoins(coin, amounts)) else {
199            panic!("ProgrammableTransactionBuilder::command always gives an Argument::Result")
200        };
201        idxs.map(|i| Argument::NestedResult(coin_vec, i)).collect()
202    }
203}
204
205#[derive(Clone, Debug, PartialEq, Eq, Hash)]
206enum BuilderArg {
207    Object(Address),
208    Pure(Vec<u8>),
209    ForcedNonUniquePure(usize),
210}
211
212impl From<ProgrammableTransactionBuilder> for ProgrammableTransaction {
213    fn from(value: ProgrammableTransactionBuilder) -> Self {
214        value.finish()
215    }
216}
217
218impl TryFrom<ProgrammableTransaction> for ProgrammableTransactionBuilder {
219    type Error = Error;
220
221    fn try_from(
222        ProgrammableTransaction { inputs, commands }: ProgrammableTransaction,
223    ) -> Result<Self> {
224        use Input::*;
225        let mut self_ = Self::new();
226        for input in inputs {
227            match input {
228                Pure { value } => {
229                    self_.pure_bytes(value, true);
230                }
231                ImmutableOrOwned(object_reference) => {
232                    self_.obj(Input::ImmutableOrOwned(object_reference))?;
233                }
234                Shared {
235                    object_id,
236                    initial_shared_version,
237                    mutable,
238                } => {
239                    self_.obj(Input::Shared {
240                        object_id,
241                        initial_shared_version,
242                        mutable,
243                    })?;
244                }
245                Receiving(object_reference) => {
246                    self_.obj(Input::Receiving(object_reference))?;
247                }
248                _ => panic!("unknown Input variant"),
249            }
250        }
251        for command in commands {
252            self_.command(command);
253        }
254        Ok(self_)
255    }
256}
257
258// =============================================================================
259//  Command compat for migration
260// =============================================================================
261
262/// A single command in a programmable transaction.
263///
264/// This type is here for backwards compatibility purposes, as [`sui_sdk_types::Command`]
265/// has a different shape that would be incompatible with the [`ptb!`] syntax.
266///
267/// The actual resulting [`ProgrammableTransaction`] does not contain this type.
268#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
269pub enum Command {
270    /// A call to either an entry or a public Move function.
271    ///
272    /// Either an entry function or a public function (which cannot return references).
273    MoveCall(Box<MoveCall>),
274    /// `(Vec<forall T:key+store. T>, address)`
275    /// It sends n-objects to the specified address. These objects must have store
276    /// (public transfer) and either the previous owner must be an address or the object must
277    /// be newly created.
278    TransferObjects(Vec<Argument>, Argument),
279    /// `(&mut Coin<T>, Vec<u64>)` -> `Vec<Coin<T>>`
280    /// It splits off some amounts into a new coins with those amounts
281    SplitCoins(Argument, Vec<Argument>),
282    /// `(&mut Coin<T>, Vec<Coin<T>>)`
283    /// It merges n-coins into the first coin
284    MergeCoins(Argument, Vec<Argument>),
285    /// Publishes a Move package. It takes the package bytes and a list of the package's transitive
286    /// dependencies to link against on-chain.
287    Publish(Vec<Vec<u8>>, Vec<Address>),
288    /// `forall T: Vec<T> -> vector<T>`
289    /// Given n-values of the same type, it constructs a vector. For non objects or an empty vector,
290    /// the type tag must be specified.
291    MakeMoveVec(Option<TypeTag>, Vec<Argument>),
292    /// Upgrades a Move package
293    /// Takes (in order):
294    /// 1. A vector of serialized modules for the package.
295    /// 2. A vector of object ids for the transitive dependencies of the new package.
296    /// 3. The object ID of the package being upgraded.
297    /// 4. An argument holding the `UpgradeTicket` that must have been produced from an earlier command in the same
298    ///    programmable transaction.
299    Upgrade(Vec<Vec<u8>>, Vec<Address>, Address, Argument),
300}
301
302#[allow(clippy::fallible_impl_from)]
303impl From<sui_sdk_types::Command> for Command {
304    fn from(value: sui_sdk_types::Command) -> Self {
305        use sui_sdk_types::Command::*;
306        match value {
307            MoveCall(args) => Self::MoveCall(Box::new(args)),
308            TransferObjects(args) => Self::TransferObjects(args.objects, args.address),
309            SplitCoins(args) => Self::SplitCoins(args.coin, args.amounts),
310            MergeCoins(args) => Self::MergeCoins(args.coin, args.coins_to_merge),
311            Publish(args) => Self::Publish(args.modules, args.dependencies),
312            MakeMoveVector(args) => Self::MakeMoveVec(args.type_, args.elements),
313            Upgrade(args) => {
314                Self::Upgrade(args.modules, args.dependencies, args.package, args.ticket)
315            }
316            _ => panic!("unknown Command variant"),
317        }
318    }
319}
320
321impl From<Command> for sui_sdk_types::Command {
322    fn from(value: Command) -> Self {
323        use Command::*;
324        use sui_sdk_types::{
325            MakeMoveVector,
326            MergeCoins,
327            Publish,
328            SplitCoins,
329            TransferObjects,
330            Upgrade,
331        };
332        match value {
333            MoveCall(move_call) => Self::MoveCall(*move_call),
334            TransferObjects(objects, address) => {
335                Self::TransferObjects(TransferObjects { objects, address })
336            }
337            SplitCoins(coin, amounts) => Self::SplitCoins(SplitCoins { coin, amounts }),
338            MergeCoins(coin, coins_to_merge) => Self::MergeCoins(MergeCoins {
339                coin,
340                coins_to_merge,
341            }),
342            Publish(modules, dependencies) => Self::Publish(Publish {
343                modules,
344                dependencies,
345            }),
346            MakeMoveVec(type_, elements) => {
347                Self::MakeMoveVector(MakeMoveVector { type_, elements })
348            }
349            Upgrade(modules, dependencies, package, ticket) => Self::Upgrade(Upgrade {
350                modules,
351                dependencies,
352                package,
353                ticket,
354            }),
355        }
356    }
357}
358
359impl Command {
360    pub fn move_call(
361        package: Address,
362        module: Identifier,
363        function: Identifier,
364        type_arguments: Vec<TypeTag>,
365        arguments: Vec<Argument>,
366    ) -> Self {
367        Self::MoveCall(Box::new(MoveCall {
368            package,
369            module,
370            function,
371            type_arguments,
372            arguments,
373        }))
374    }
375
376    pub const fn make_move_vec(ty: Option<TypeTag>, args: Vec<Argument>) -> Self {
377        Self::MakeMoveVec(ty, args)
378    }
379}
380
381// =============================================================================
382//  Macro helper
383// =============================================================================
384
385/// Build a programmable transaction using Move-like syntax.
386///
387/// # Overview
388///
389/// This automatically creates and finishes a [`ProgrammableTransactionBuilder`] and allows users
390/// to declare:
391/// - packages the transaction uses
392/// - type arguments for functions
393/// - object/pure inputs for the transaction
394/// - Move calls
395/// - Built-in PTB commands
396///
397/// Every Move call and built-in PTB command declared withing the macro's scope can be thought of
398/// as happening in 'programmable transaction time'. In this way, the macro also helps users more
399/// clearly separate what's being executed at Rust's runtime and chain's runtime (once the
400/// transaction is execute by validators).
401///
402/// ## Packages
403///
404/// Move functions expect the [`Address`] of their package in the transaction payload (see
405/// [`MoveCall`]). One can declare the packages using the syntax
406/// ```no_run
407/// # use sui_sdk_types::Address;
408/// let package_name = Address::new(rand::random());
409/// let object_id = Address::new(rand::random());
410/// af_ptbuilder::ptb!(
411///     package package_name;
412///     package package_name: object_id;
413/// // ...
414/// );
415/// ```
416/// Similar to struct initialization syntax;
417///
418/// ## Type arguments
419///
420/// Move functions that have type arguments expect [`TypeTag`] arguments in the transaction payload
421/// (see [`MoveCall`]). One can declare these variables using the syntax
422/// ```no_run
423/// # use sui_sdk_types::TypeTag;
424/// let T = TypeTag::U8;
425/// let type_tag = TypeTag::U32;
426/// af_ptbuilder::ptb!(
427///     type T;
428///     type T = type_tag;
429/// // ...
430/// );
431/// ```
432///
433/// ## Object/Pure inputs
434///
435/// [`ProgrammableTransaction`]s need all their inputs declared upfront. One can
436/// declare the two types of inputs using the syntax
437/// ```no_run
438/// # use sui_sdk_types::{Address, Input};
439/// let clock = Input::Shared {
440///     object_id: Address::from_static("0x6"),
441///     initial_shared_version: 1,
442///     mutable: false
443/// };
444/// let object = Input::Shared {
445///     object_id: Address::new(rand::random()),
446///     initial_shared_version: 1,
447///     mutable: true
448/// };
449/// let count = &0_u64;
450/// af_ptbuilder::ptb!(
451///     input obj clock;
452///     input obj another: object;
453///     input pure count;
454///     input pure more: &1_u32;
455///     // ...
456/// );
457/// # eyre::Ok(())
458/// ```
459/// Similar to struct initialization syntax. `input obj`s expect [`Input`] values and
460/// become object [`Input`]s in the transaction payload. `input pure`s expect any type `T` that
461/// is [`Serialize`] `+ ?Sized` (see [`ProgrammableTransactionBuilder::pure`] for the internals) and
462/// become [`Input::Pure`]s in the transaction payload. Within the macro scope, both variables
463/// are [`Argument::Input`]s and can be used in Move/built-in calls.
464///
465/// ## Move calls
466///
467/// Use the syntax
468/// ```no_run
469/// # af_ptbuilder::ptb!(
470/// # package package: sui_sdk_types::Address::new(rand::random());
471/// # type T = sui_sdk_types::TypeTag::U8;
472/// # input pure arg: &0_u32;
473///     package::module::function<T>(arg);
474/// # );
475/// # eyre::Ok(())
476/// ````
477/// To include a [`MoveCall`] in the transaction payload. `package`,`T`, and `arg`
478/// must have been declared earlier. `module` and `function` are simply pure identifiers[^1]. One
479/// can of course declare more than one type argument if the function requires, or none if the
480/// function does not have type parameters.
481///
482/// Functions that return can have their results assigned to a value or unpacked into several ones:
483/// ```no_run
484/// # use sui_sdk_types::{Address, Input};
485/// # let clock = Input::Shared {
486/// #     object_id: Address::from_static("0x6"),
487/// #     initial_shared_version: 1,
488/// #     mutable: false
489/// # };
490/// # let clock2 = clock.clone();
491/// # let clock3 = clock.clone();
492/// # af_ptbuilder::ptb!(
493/// # package package: Address::new(rand::random());
494/// # input obj a: clock;
495/// # input obj b: clock2;
496/// # input obj arg: clock3;
497/// let result = package::module::function(a, b);
498/// let (a, b) = package::module::function(arg);
499/// # );
500/// # eyre::Ok(())
501/// ```
502/// These, of course, happen at 'programmable transaction time' and the result are
503/// [`Argument::Result`]s that can be passed to other functions.
504///
505/// ## Built-in commands
506///
507/// Sui PTBs have access to some calls that do not declare a package, module and function. These
508/// use the syntax:
509/// ```text
510/// command! Variant(x, y, ...);
511/// ```
512/// The result of the command can be optionally assigned or unpacked (`let a =` or
513/// `let (a, b) =`). `Variant` refers to the variant of [`Command`] to use. See its
514/// documentation for more information.
515///
516/// # Example
517///
518/// ```no_run
519/// use af_ptbuilder::ptb;
520/// use sui_sdk_types::{Address, Input, TypeTag};
521///
522/// let foo = Address::from_static("0xbeef");
523/// let otw: TypeTag = "0x2::sui::SUI".parse()?;
524/// let registry = Input::Shared {
525///     object_id: Address::from_static("0xdeed"),
526///     initial_shared_version: 1,
527///     mutable: true,
528/// };
529/// let sender = Address::from_static("0xabcd");
530///
531/// ptb!(
532///     package foo;
533///
534///     type T = otw;
535///
536///     input obj registry;
537///     input pure sender: &sender;
538///
539///     let account = foo::registry::create_account<T>(registry);
540///     command! TransferObjects(vec![account], sender);
541/// );
542/// # eyre::Ok(())
543/// ```
544///
545/// [^1]: [`Identifier`]
546#[macro_export]
547macro_rules! ptb {
548    ($($tt:tt)*) => {
549        {
550            let mut builder = $crate::ProgrammableTransactionBuilder::new();
551            $crate::ptbuilder!(builder { $($tt)* });
552            builder.finish()
553        }
554    };
555}
556
557/// Build a programmable transaction using Move-like syntax and an existing builder.
558///
559/// This will make the package, type, input and argument variables declared inside the macro
560/// available in the outer scope.
561///
562/// # Overview
563///
564/// This allows users to incrementally build a programmable transaction using an existing
565/// [`ProgrammableTransactionBuilder`] with the syntax
566/// ```no_run
567/// # use af_ptbuilder::ProgrammableTransactionBuilder;
568/// let mut builder = ProgrammableTransactionBuilder::new();
569/// af_ptbuilder::ptbuilder!(builder {
570///     // ...
571/// });
572/// ```
573/// where everything inside the braces uses the same syntax as [`ptb!`]. The user is responsible
574/// for initializing the builder and calling [`ProgrammableTransactionBuilder::finish`] at the end.
575///
576/// This can be useful if the number of calls is only known at runtime or if it's desirable to only
577/// include some calls based on some runtime logic. It still allows users to use a convenient
578/// syntax and separate what happens at 'programmable transaction time'.
579#[macro_export]
580macro_rules! ptbuilder {
581    ($builder:ident {}) => { };
582
583    ($builder:ident {
584        package $name:ident $value:literal;
585        $($tt:tt)*
586    }) => {
587        let $name: $crate::Address = $value.parse()?;
588
589        $crate::ptbuilder!($builder { $($tt)* });
590    };
591
592    ($builder:ident {
593        package $name:ident;
594        $($tt:tt)*
595    }) => {
596        let $name: $crate::Address = $name;
597
598        $crate::ptbuilder!($builder { $($tt)* });
599    };
600
601    ($builder:ident {
602        package $name:ident: $value:expr_2021;
603        $($tt:tt)*
604    }) => {
605        let $name: $crate::Address = $value;
606
607        $crate::ptbuilder!($builder { $($tt)* });
608    };
609
610    ($builder:ident {
611        input pure $name:ident;
612        $($tt:tt)*
613    }) => {
614        let $name = $builder.pure($name)?;
615
616        $crate::ptbuilder!($builder { $($tt)* });
617    };
618
619    ($builder:ident {
620        input pure $name:ident: $value:expr_2021;
621        $($tt:tt)*
622    }) => {
623        let $name = $builder.pure($value)?;
624
625        $crate::ptbuilder!($builder { $($tt)* });
626    };
627
628    ($builder:ident {
629        input obj $name:ident;
630        $($tt:tt)*
631    }) => {
632        let $name = $builder.obj($name)?;
633
634        $crate::ptbuilder!($builder { $($tt)* });
635    };
636
637    ($builder:ident {
638        input obj $name:ident: $value:expr_2021;
639        $($tt:tt)*
640    }) => {
641        let $name = $builder.obj($value)?;
642
643        $crate::ptbuilder!($builder { $($tt)* });
644    };
645
646    ($builder:ident {
647        type $T:ident;
648        $($tt:tt)*
649    }) => {
650        #[allow(non_snake_case)]
651        let $T: $crate::TypeTag = $T;
652
653        $crate::ptbuilder!($builder { $($tt)* });
654    };
655
656    ($builder:ident {
657        type $T:ident = $value:expr_2021;
658        $($tt:tt)*
659    }) => {
660        #[allow(non_snake_case)]
661        let $T: $crate::TypeTag = $value;
662
663        $crate::ptbuilder!($builder { $($tt)* });
664    };
665
666    ($builder:ident {
667        $package:ident::$module:ident::$fun:ident$(<$($T:ident),+>)?($($arg:ident),* $(,)?);
668        $($tt:tt)*
669    }) => {
670        let _module = stringify!($module);
671        let _fun = stringify!($fun);
672        $builder.command($crate::Command::move_call(
673            $package,
674            $crate::Identifier::from_static(_module),
675            $crate::Identifier::from_static(_fun),
676            vec![$($($T.clone()),+)?],
677            vec![$($arg),*]
678        ));
679
680        $crate::ptbuilder!($builder { $($tt)* });
681    };
682
683    ($builder:ident {
684        let $ret:ident = $package:ident::$module:ident::$fun:ident$(<$($T:ident),+>)?($($arg:ident),* $(,)?);
685        $($tt:tt)*
686    }) => {
687        let _module = stringify!($module);
688        let _fun = stringify!($fun);
689        let $ret = $builder.command($crate::Command::move_call(
690            $package,
691            $crate::Identifier::from_static(_module),
692            $crate::Identifier::from_static(_fun),
693            vec![$($($T.clone()),+)?],
694            vec![$($arg),*]
695        ));
696
697        $crate::ptbuilder!($builder { $($tt)* });
698    };
699
700    ($builder:ident {
701        let ($($ret:ident),+) = $package:ident::$module:ident::$fun:ident$(<$($T:ident),+>)?($($arg:ident),* $(,)?);
702        $($tt:tt)*
703    }) => {
704        let _module = stringify!($module);
705        let _fun = stringify!($fun);
706        let rets = $builder.command($crate::Command::move_call(
707            $package,
708            $crate::Identifier::from_static(_module),
709            $crate::Identifier::from_static(_fun),
710            vec![$($($T.clone()),+)?],
711            vec![$($arg),*]
712        ));
713        $crate::unpack_arg!(rets => { $($ret),+ });
714
715        $crate::ptbuilder!($builder { $($tt)* });
716    };
717
718    ($builder:ident {
719        $(let $ret:ident =)? command! $variant:ident($($args:tt)*);
720        $($tt:tt)*
721    }) => {
722        $(let $ret =)? $builder.command($crate::Command::$variant($($args)*));
723
724        $crate::ptbuilder!($builder { $($tt)* });
725    };
726
727    ($builder:ident {
728        let ($($ret:ident),+) = command! $variant:ident($($args:tt)*);
729        $($tt:tt)*
730    }) => {
731        let rets = $builder.command($crate::Command::$variant($($args)*));
732        $crate::unpack_arg!(rets => { $($ret),+ });
733
734        $crate::ptbuilder!($builder { $($tt)* });
735    };
736}
737
738/// Unpack the result of a programmable transaction call.
739///
740/// Useful for unpacking results from functions that return tuple or vector types.
741///
742/// # Example
743/// ```
744/// use af_ptbuilder::ProgrammableTransactionBuilder;
745/// use sui_sdk_types::Argument;
746///
747/// let mut builder = ProgrammableTransactionBuilder::new();
748/// let arg = Argument::Result(0);
749/// af_ptbuilder::unpack_arg!(arg => { sub1, sub2 });
750/// ```
751#[macro_export]
752macro_rules! unpack_arg {
753    ($arg:expr_2021 => {
754        $($name:ident),+ $(,)?
755    }) => {
756        let ($($name),+) = if let $crate::Argument::Result(tuple) = $arg {
757            let mut index = 0;
758            $(
759                let $name = $crate::Argument::NestedResult(
760                    tuple, index
761                );
762                index += 1;
763            )+
764            ($($name),+)
765        } else {
766            panic!(
767                "ProgrammableTransactionBuilder::command should always give a Argument::Result"
768            )
769        };
770    };
771}