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