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