Skip to main content

af_ptbuilder/
lib.rs

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