Skip to main content

aptos_sdk/transaction/
input.rs

1//! Type-safe entry function payload builders.
2//!
3//! This module provides ergonomic builders for constructing entry function
4//! payloads with automatic BCS encoding of arguments.
5//!
6//! # Overview
7//!
8//! `InputEntryFunctionData` provides a builder pattern that:
9//! - Accepts Rust types directly (no manual BCS encoding)
10//! - Validates function IDs at construction
11//! - Supports all Move types
12//!
13//! # Example
14//!
15//! ```rust,ignore
16//! use aptos_sdk::transaction::InputEntryFunctionData;
17//!
18//! // Simple transfer
19//! let payload = InputEntryFunctionData::new("0x1::aptos_account::transfer")
20//!     .arg(recipient_address)
21//!     .arg(1_000_000u64)
22//!     .build()?;
23//!
24//! // Generic function with type args
25//! let payload = InputEntryFunctionData::new("0x1::coin::transfer")
26//!     .type_arg("0x1::aptos_coin::AptosCoin")
27//!     .arg(recipient_address)
28//!     .arg(amount)
29//!     .build()?;
30//! ```
31
32use crate::error::{AptosError, AptosResult};
33use crate::transaction::{EntryFunction, TransactionPayload};
34use crate::types::{AccountAddress, EntryFunctionId, MoveModuleId, TypeTag};
35use serde::Serialize;
36
37/// A type-safe builder for entry function payloads.
38///
39/// This builder provides an ergonomic way to construct entry function calls
40/// with automatic BCS encoding of arguments.
41///
42/// # Example
43///
44/// ```rust,ignore
45/// use aptos_sdk::transaction::InputEntryFunctionData;
46/// use aptos_sdk::types::AccountAddress;
47///
48/// let payload = InputEntryFunctionData::new("0x1::aptos_account::transfer")
49///     .arg(AccountAddress::from_hex("0x123").unwrap())
50///     .arg(1_000_000u64)  // 0.01 APT in octas
51///     .build()?;
52/// ```
53#[allow(dead_code)] // Public API struct - fields used via builder pattern
54#[derive(Debug, Clone)]
55pub struct InputEntryFunctionData {
56    module: MoveModuleId,
57    function: String,
58    type_args: Vec<TypeTag>,
59    args: Vec<Vec<u8>>,
60}
61
62impl InputEntryFunctionData {
63    /// Creates a new entry function data builder.
64    ///
65    /// # Arguments
66    ///
67    /// * `function_id` - The full function identifier (e.g., "`0x1::coin::transfer`")
68    ///
69    /// # Example
70    ///
71    /// ```rust,ignore
72    /// let builder = InputEntryFunctionData::new("0x1::aptos_account::transfer");
73    /// ```
74    #[allow(clippy::new_ret_no_self)] // Returns builder pattern intentionally
75    pub fn new(function_id: &str) -> InputEntryFunctionDataBuilder {
76        InputEntryFunctionDataBuilder::new(function_id)
77    }
78
79    /// Creates a builder from module and function name.
80    ///
81    /// # Arguments
82    ///
83    /// * `module` - The module ID
84    /// * `function` - The function name
85    pub fn from_parts(
86        module: MoveModuleId,
87        function: impl Into<String>,
88    ) -> InputEntryFunctionDataBuilder {
89        InputEntryFunctionDataBuilder {
90            module: Ok(module),
91            function: function.into(),
92            type_args: Vec::new(),
93            args: Vec::new(),
94            errors: Vec::new(),
95        }
96    }
97
98    /// Builds an APT transfer payload.
99    ///
100    /// # Arguments
101    ///
102    /// * `recipient` - The recipient address
103    /// * `amount` - Amount in octas (1 APT = 10^8 octas)
104    ///
105    /// # Example
106    ///
107    /// ```rust,ignore
108    /// let payload = InputEntryFunctionData::transfer_apt(recipient, 1_000_000)?;
109    /// ```
110    ///
111    /// # Errors
112    ///
113    /// Returns an error if the function ID is invalid or if BCS encoding of arguments fails.
114    pub fn transfer_apt(recipient: AccountAddress, amount: u64) -> AptosResult<TransactionPayload> {
115        InputEntryFunctionData::new("0x1::aptos_account::transfer")
116            .arg(recipient)
117            .arg(amount)
118            .build()
119    }
120
121    /// Builds a coin transfer payload for any coin type.
122    ///
123    /// # Arguments
124    ///
125    /// * `coin_type` - The coin type (e.g., "`0x1::aptos_coin::AptosCoin`")
126    /// * `recipient` - The recipient address
127    /// * `amount` - Amount in the coin's smallest unit
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if the function ID is invalid, the coin type is invalid, or if BCS encoding of arguments fails.
132    pub fn transfer_coin(
133        coin_type: &str,
134        recipient: AccountAddress,
135        amount: u64,
136    ) -> AptosResult<TransactionPayload> {
137        InputEntryFunctionData::new("0x1::coin::transfer")
138            .type_arg(coin_type)
139            .arg(recipient)
140            .arg(amount)
141            .build()
142    }
143
144    /// Builds a fungible-asset transfer payload.
145    ///
146    /// Calls `0x1::primary_fungible_store::transfer`, moving `amount` units of
147    /// the fungible asset identified by `metadata` (the address of its
148    /// `0x1::fungible_asset::Metadata` object) from the sender's primary store
149    /// to the recipient's primary store, creating the recipient's store if
150    /// needed. This is the current (FA standard) counterpart to
151    /// [`transfer_coin`](Self::transfer_coin) and matches the TypeScript SDK's
152    /// `transferFungibleAsset`.
153    ///
154    /// # Arguments
155    ///
156    /// * `metadata` - Address of the fungible asset's `Metadata` object (e.g.
157    ///   `0xa` for APT as a fungible asset).
158    /// * `recipient` - The recipient address.
159    /// * `amount` - Amount in the asset's smallest unit.
160    ///
161    /// # Errors
162    ///
163    /// Returns an error if the function ID or type tag is invalid, or if BCS
164    /// encoding of arguments fails.
165    pub fn transfer_fungible_asset(
166        metadata: AccountAddress,
167        recipient: AccountAddress,
168        amount: u64,
169    ) -> AptosResult<TransactionPayload> {
170        InputEntryFunctionData::new("0x1::primary_fungible_store::transfer")
171            .type_arg("0x1::fungible_asset::Metadata")
172            .arg(metadata)
173            .arg(recipient)
174            .arg(amount)
175            .build()
176    }
177
178    /// Builds an account creation payload.
179    ///
180    /// # Arguments
181    ///
182    /// * `auth_key` - The authentication key (32 bytes)
183    ///
184    /// # Errors
185    ///
186    /// Returns an error if the function ID is invalid or if BCS encoding of arguments fails.
187    pub fn create_account(auth_key: AccountAddress) -> AptosResult<TransactionPayload> {
188        InputEntryFunctionData::new("0x1::aptos_account::create_account")
189            .arg(auth_key)
190            .build()
191    }
192
193    /// Builds a payload to rotate an account's authentication key.
194    ///
195    /// # Arguments
196    ///
197    /// * Various rotation parameters
198    ///
199    /// # Errors
200    ///
201    /// Returns an error if the function ID is invalid or if BCS encoding of arguments fails.
202    pub fn rotate_authentication_key(
203        from_scheme: u8,
204        from_public_key_bytes: Vec<u8>,
205        to_scheme: u8,
206        to_public_key_bytes: Vec<u8>,
207        cap_rotate_key: Vec<u8>,
208        cap_update_table: Vec<u8>,
209    ) -> AptosResult<TransactionPayload> {
210        InputEntryFunctionData::new("0x1::account::rotate_authentication_key")
211            .arg(from_scheme)
212            .arg(from_public_key_bytes)
213            .arg(to_scheme)
214            .arg(to_public_key_bytes)
215            .arg(cap_rotate_key)
216            .arg(cap_update_table)
217            .build()
218    }
219
220    /// Builds a payload to register a coin store.
221    ///
222    /// # Arguments
223    ///
224    /// * `coin_type` - The coin type to register
225    ///
226    /// # Errors
227    ///
228    /// Returns an error if the function ID is invalid, the coin type is invalid, or if building the payload fails.
229    pub fn register_coin(coin_type: &str) -> AptosResult<TransactionPayload> {
230        InputEntryFunctionData::new("0x1::managed_coin::register")
231            .type_arg(coin_type)
232            .build()
233    }
234
235    /// Builds a payload to publish a module.
236    ///
237    /// # Arguments
238    ///
239    /// * `metadata_serialized` - Serialized module metadata
240    /// * `code` - Vector of module bytecode
241    ///
242    /// # Errors
243    ///
244    /// Returns an error if the function ID is invalid or if BCS encoding of arguments fails.
245    pub fn publish_package(
246        metadata_serialized: Vec<u8>,
247        code: Vec<Vec<u8>>,
248    ) -> AptosResult<TransactionPayload> {
249        InputEntryFunctionData::new("0x1::code::publish_package_txn")
250            .arg(metadata_serialized)
251            .arg(code)
252            .build()
253    }
254
255    // === Objects ===
256
257    /// Builds a payload to transfer ownership of an object.
258    ///
259    /// Calls `0x1::object::transfer_call`, which moves the object at address
260    /// `object` to `to`. Works for any object (including digital assets); for a
261    /// typed transfer use [`transfer_digital_asset`](Self::transfer_digital_asset).
262    ///
263    /// # Errors
264    ///
265    /// Returns an error if the function ID is invalid or if BCS encoding fails.
266    pub fn transfer_object(
267        object: AccountAddress,
268        to: AccountAddress,
269    ) -> AptosResult<TransactionPayload> {
270        InputEntryFunctionData::new("0x1::object::transfer_call")
271            .arg(object)
272            .arg(to)
273            .build()
274    }
275
276    // === Digital Assets (Token Objects) ===
277
278    /// Builds a payload to transfer a digital asset (NFT) to another address.
279    ///
280    /// Calls `0x1::object::transfer` with the `0x4::token::Token` type, matching
281    /// the TypeScript SDK's `transferDigitalAsset`.
282    ///
283    /// # Errors
284    ///
285    /// Returns an error if the function ID or type tag is invalid or if BCS
286    /// encoding fails.
287    pub fn transfer_digital_asset(
288        token: AccountAddress,
289        to: AccountAddress,
290    ) -> AptosResult<TransactionPayload> {
291        InputEntryFunctionData::new("0x1::object::transfer")
292            .type_arg("0x4::token::Token")
293            .arg(token)
294            .arg(to)
295            .build()
296    }
297
298    /// Builds a payload to create a digital-asset collection
299    /// (`0x4::aptos_token::create_collection`).
300    ///
301    /// `config` controls the collection/token mutability and burn/freeze
302    /// permissions ([`CollectionConfig::default`] enables all of them, matching
303    /// the most permissive TypeScript SDK defaults). `royalty_numerator /
304    /// royalty_denominator` express the royalty as a fraction (use `0 / 1` for
305    /// none).
306    ///
307    /// # Errors
308    ///
309    /// Returns an error if the function ID is invalid or if BCS encoding fails.
310    #[allow(clippy::too_many_arguments)]
311    pub fn create_collection(
312        description: &str,
313        max_supply: u64,
314        name: &str,
315        uri: &str,
316        config: CollectionConfig,
317        royalty_numerator: u64,
318        royalty_denominator: u64,
319    ) -> AptosResult<TransactionPayload> {
320        InputEntryFunctionData::new("0x4::aptos_token::create_collection")
321            .arg(description)
322            .arg(max_supply)
323            .arg(name)
324            .arg(uri)
325            .arg(config.mutable_description)
326            .arg(config.mutable_royalty)
327            .arg(config.mutable_uri)
328            .arg(config.mutable_token_description)
329            .arg(config.mutable_token_name)
330            .arg(config.mutable_token_properties)
331            .arg(config.mutable_token_uri)
332            .arg(config.tokens_burnable_by_creator)
333            .arg(config.tokens_freezable_by_creator)
334            .arg(royalty_numerator)
335            .arg(royalty_denominator)
336            .build()
337    }
338
339    /// Builds a payload to mint a digital asset into a collection
340    /// (`0x4::aptos_token::mint`).
341    ///
342    /// The three property vectors must be equal length (a key, a Move type
343    /// string, and BCS-encoded value per property); pass empty vectors for no
344    /// properties.
345    ///
346    /// # Errors
347    ///
348    /// Returns an error if the function ID is invalid or if BCS encoding fails.
349    pub fn mint_digital_asset(
350        collection: &str,
351        description: &str,
352        name: &str,
353        uri: &str,
354        property_keys: Vec<String>,
355        property_types: Vec<String>,
356        property_values: Vec<Vec<u8>>,
357    ) -> AptosResult<TransactionPayload> {
358        InputEntryFunctionData::new("0x4::aptos_token::mint")
359            .arg(collection)
360            .arg(description)
361            .arg(name)
362            .arg(uri)
363            .arg(property_keys)
364            .arg(property_types)
365            .arg(property_values)
366            .build()
367    }
368
369    /// Builds a payload to mint a soul-bound (non-transferable) digital asset
370    /// (`0x4::aptos_token::mint_soul_bound`), bound to `soul_bound_to`.
371    ///
372    /// # Errors
373    ///
374    /// Returns an error if the function ID is invalid or if BCS encoding fails.
375    pub fn mint_soul_bound_digital_asset(
376        collection: &str,
377        description: &str,
378        name: &str,
379        uri: &str,
380        property_keys: Vec<String>,
381        property_types: Vec<String>,
382        property_values: Vec<Vec<u8>>,
383        soul_bound_to: AccountAddress,
384    ) -> AptosResult<TransactionPayload> {
385        InputEntryFunctionData::new("0x4::aptos_token::mint_soul_bound")
386            .arg(collection)
387            .arg(description)
388            .arg(name)
389            .arg(uri)
390            .arg(property_keys)
391            .arg(property_types)
392            .arg(property_values)
393            .arg(soul_bound_to)
394            .build()
395    }
396
397    /// Builds a payload to burn a digital asset (`0x4::aptos_token::burn`).
398    ///
399    /// # Errors
400    ///
401    /// Returns an error if the function ID or type tag is invalid or if BCS
402    /// encoding fails.
403    pub fn burn_digital_asset(token: AccountAddress) -> AptosResult<TransactionPayload> {
404        InputEntryFunctionData::new("0x4::aptos_token::burn")
405            .type_arg("0x4::token::Token")
406            .arg(token)
407            .build()
408    }
409
410    /// Builds a payload to freeze transfers of a digital asset
411    /// (`0x4::aptos_token::freeze_transfer`).
412    ///
413    /// # Errors
414    ///
415    /// Returns an error if the function ID or type tag is invalid or if BCS
416    /// encoding fails.
417    pub fn freeze_digital_asset_transfer(token: AccountAddress) -> AptosResult<TransactionPayload> {
418        InputEntryFunctionData::new("0x4::aptos_token::freeze_transfer")
419            .type_arg("0x4::token::Token")
420            .arg(token)
421            .build()
422    }
423
424    /// Builds a payload to unfreeze transfers of a digital asset
425    /// (`0x4::aptos_token::unfreeze_transfer`).
426    ///
427    /// # Errors
428    ///
429    /// Returns an error if the function ID or type tag is invalid or if BCS
430    /// encoding fails.
431    pub fn unfreeze_digital_asset_transfer(
432        token: AccountAddress,
433    ) -> AptosResult<TransactionPayload> {
434        InputEntryFunctionData::new("0x4::aptos_token::unfreeze_transfer")
435            .type_arg("0x4::token::Token")
436            .arg(token)
437            .build()
438    }
439
440    // === Staking (delegation pool) ===
441
442    /// Builds a payload to add stake to a delegation pool
443    /// (`0x1::delegation_pool::add_stake`).
444    ///
445    /// # Errors
446    ///
447    /// Returns an error if the function ID is invalid or if BCS encoding fails.
448    pub fn delegation_add_stake(
449        pool_address: AccountAddress,
450        amount: u64,
451    ) -> AptosResult<TransactionPayload> {
452        InputEntryFunctionData::new("0x1::delegation_pool::add_stake")
453            .arg(pool_address)
454            .arg(amount)
455            .build()
456    }
457
458    /// Builds a payload to unlock stake from a delegation pool
459    /// (`0x1::delegation_pool::unlock`). Unlocked stake becomes withdrawable
460    /// after the pool's lockup expires.
461    ///
462    /// # Errors
463    ///
464    /// Returns an error if the function ID is invalid or if BCS encoding fails.
465    pub fn delegation_unlock(
466        pool_address: AccountAddress,
467        amount: u64,
468    ) -> AptosResult<TransactionPayload> {
469        InputEntryFunctionData::new("0x1::delegation_pool::unlock")
470            .arg(pool_address)
471            .arg(amount)
472            .build()
473    }
474
475    /// Builds a payload to reactivate previously-unlocked stake
476    /// (`0x1::delegation_pool::reactivate_stake`).
477    ///
478    /// # Errors
479    ///
480    /// Returns an error if the function ID is invalid or if BCS encoding fails.
481    pub fn delegation_reactivate_stake(
482        pool_address: AccountAddress,
483        amount: u64,
484    ) -> AptosResult<TransactionPayload> {
485        InputEntryFunctionData::new("0x1::delegation_pool::reactivate_stake")
486            .arg(pool_address)
487            .arg(amount)
488            .build()
489    }
490
491    /// Builds a payload to withdraw unlocked stake from a delegation pool
492    /// (`0x1::delegation_pool::withdraw`).
493    ///
494    /// # Errors
495    ///
496    /// Returns an error if the function ID is invalid or if BCS encoding fails.
497    pub fn delegation_withdraw(
498        pool_address: AccountAddress,
499        amount: u64,
500    ) -> AptosResult<TransactionPayload> {
501        InputEntryFunctionData::new("0x1::delegation_pool::withdraw")
502            .arg(pool_address)
503            .arg(amount)
504            .build()
505    }
506
507    // === Account Abstraction (AIP-113) ===
508
509    /// Builds a payload to enable account abstraction by registering a custom
510    /// authentication function (`0x1::account_abstraction::add_authentication_function`).
511    ///
512    /// The function is identified by the module it lives in (`module_address` +
513    /// `module_name`) and its `function_name`.
514    ///
515    /// # Errors
516    ///
517    /// Returns an error if the function ID is invalid or if BCS encoding fails.
518    pub fn add_authentication_function(
519        module_address: AccountAddress,
520        module_name: &str,
521        function_name: &str,
522    ) -> AptosResult<TransactionPayload> {
523        InputEntryFunctionData::new("0x1::account_abstraction::add_authentication_function")
524            .arg(module_address)
525            .arg(module_name)
526            .arg(function_name)
527            .build()
528    }
529
530    /// Builds a payload to remove a previously-registered authentication
531    /// function (`0x1::account_abstraction::remove_authentication_function`).
532    ///
533    /// # Errors
534    ///
535    /// Returns an error if the function ID is invalid or if BCS encoding fails.
536    pub fn remove_authentication_function(
537        module_address: AccountAddress,
538        module_name: &str,
539        function_name: &str,
540    ) -> AptosResult<TransactionPayload> {
541        InputEntryFunctionData::new("0x1::account_abstraction::remove_authentication_function")
542            .arg(module_address)
543            .arg(module_name)
544            .arg(function_name)
545            .build()
546    }
547
548    /// Builds a payload to fully disable account abstraction, removing all
549    /// registered authentication functions
550    /// (`0x1::account_abstraction::remove_authenticator`).
551    ///
552    /// # Errors
553    ///
554    /// Returns an error if the function ID is invalid or if BCS encoding fails.
555    pub fn remove_authenticator() -> AptosResult<TransactionPayload> {
556        InputEntryFunctionData::new("0x1::account_abstraction::remove_authenticator").build()
557    }
558}
559
560/// Mutability and permission configuration for
561/// [`InputEntryFunctionData::create_collection`].
562///
563/// [`Default`] enables every mutability flag and both burn/freeze permissions,
564/// matching the most permissive collection configuration. Set individual fields
565/// to `false` to lock down a collection.
566#[derive(Debug, Clone, Copy, PartialEq, Eq)]
567pub struct CollectionConfig {
568    /// Whether the collection description can be changed later.
569    pub mutable_description: bool,
570    /// Whether the collection royalty can be changed later.
571    pub mutable_royalty: bool,
572    /// Whether the collection URI can be changed later.
573    pub mutable_uri: bool,
574    /// Whether token descriptions can be changed later.
575    pub mutable_token_description: bool,
576    /// Whether token names can be changed later.
577    pub mutable_token_name: bool,
578    /// Whether token properties can be changed later.
579    pub mutable_token_properties: bool,
580    /// Whether token URIs can be changed later.
581    pub mutable_token_uri: bool,
582    /// Whether the creator may burn tokens in this collection.
583    pub tokens_burnable_by_creator: bool,
584    /// Whether the creator may freeze token transfers in this collection.
585    pub tokens_freezable_by_creator: bool,
586}
587
588impl Default for CollectionConfig {
589    fn default() -> Self {
590        Self {
591            mutable_description: true,
592            mutable_royalty: true,
593            mutable_uri: true,
594            mutable_token_description: true,
595            mutable_token_name: true,
596            mutable_token_properties: true,
597            mutable_token_uri: true,
598            tokens_burnable_by_creator: true,
599            tokens_freezable_by_creator: true,
600        }
601    }
602}
603
604/// Builder for `InputEntryFunctionData`.
605#[derive(Debug, Clone)]
606pub struct InputEntryFunctionDataBuilder {
607    module: Result<MoveModuleId, String>,
608    function: String,
609    type_args: Vec<TypeTag>,
610    args: Vec<Vec<u8>>,
611    errors: Vec<String>,
612}
613
614impl InputEntryFunctionDataBuilder {
615    /// Creates a new builder from a function ID string.
616    #[must_use]
617    fn new(function_id: &str) -> Self {
618        match EntryFunctionId::from_str_strict(function_id) {
619            Ok(func_id) => Self {
620                module: Ok(func_id.module),
621                function: func_id.name.as_str().to_string(),
622                type_args: Vec::new(),
623                args: Vec::new(),
624                errors: Vec::new(),
625            },
626            Err(e) => Self {
627                module: Err(format!("Invalid function ID '{function_id}': {e}")),
628                function: String::new(),
629                type_args: Vec::new(),
630                args: Vec::new(),
631                errors: Vec::new(),
632            },
633        }
634    }
635
636    /// Adds a type argument.
637    ///
638    /// # Arguments
639    ///
640    /// * `type_arg` - A type tag string (e.g., "`0x1::aptos_coin::AptosCoin`")
641    ///
642    /// # Example
643    ///
644    /// ```rust,ignore
645    /// let builder = InputEntryFunctionData::new("0x1::coin::transfer")
646    ///     .type_arg("0x1::aptos_coin::AptosCoin");
647    /// ```
648    #[must_use]
649    pub fn type_arg(mut self, type_arg: &str) -> Self {
650        match TypeTag::from_str_strict(type_arg) {
651            Ok(tag) => self.type_args.push(tag),
652            Err(e) => self
653                .errors
654                .push(format!("Invalid type argument '{type_arg}': {e}")),
655        }
656        self
657    }
658
659    /// Adds a type argument from a `TypeTag`.
660    #[must_use]
661    pub fn type_arg_typed(mut self, type_arg: TypeTag) -> Self {
662        self.type_args.push(type_arg);
663        self
664    }
665
666    /// Adds multiple type arguments.
667    #[must_use]
668    pub fn type_args(mut self, type_args: impl IntoIterator<Item = &'static str>) -> Self {
669        for type_arg in type_args {
670            self = self.type_arg(type_arg);
671        }
672        self
673    }
674
675    /// Adds multiple typed type arguments.
676    #[must_use]
677    pub fn type_args_typed(mut self, type_args: impl IntoIterator<Item = TypeTag>) -> Self {
678        self.type_args.extend(type_args);
679        self
680    }
681
682    /// Adds a BCS-encodable argument.
683    ///
684    /// Accepts any type that implements `Serialize` (BCS encoding).
685    ///
686    /// # Supported Types
687    ///
688    /// - Integers: `u8`, `u16`, `u32`, `u64`, `u128`
689    /// - Boolean: `bool`
690    /// - Strings: `String`, `&str`
691    /// - Addresses: `AccountAddress`
692    /// - Vectors: `Vec<T>` where T is serializable
693    /// - Bytes: `Vec<u8>`, `&[u8]`
694    ///
695    /// # Example
696    ///
697    /// ```rust,ignore
698    /// let builder = InputEntryFunctionData::new("0x1::my_module::my_function")
699    ///     .arg(42u64)
700    ///     .arg(true)
701    ///     .arg(AccountAddress::ONE)
702    ///     .arg("hello".to_string());
703    /// ```
704    #[must_use]
705    pub fn arg<T: Serialize>(mut self, value: T) -> Self {
706        match aptos_bcs::to_bytes(&value) {
707            Ok(bytes) => self.args.push(bytes),
708            Err(e) => self
709                .errors
710                .push(format!("Failed to serialize argument: {e}")),
711        }
712        self
713    }
714
715    /// Adds a raw BCS-encoded argument.
716    ///
717    /// Use this when you have pre-encoded bytes.
718    #[must_use]
719    pub fn arg_raw(mut self, bytes: Vec<u8>) -> Self {
720        self.args.push(bytes);
721        self
722    }
723
724    /// Adds multiple BCS-encodable arguments.
725    #[must_use]
726    pub fn args<T: Serialize>(mut self, values: impl IntoIterator<Item = T>) -> Self {
727        for value in values {
728            self = self.arg(value);
729        }
730        self
731    }
732
733    /// Builds the transaction payload.
734    ///
735    /// # Returns
736    ///
737    /// The constructed `TransactionPayload`, or an error if any
738    /// validation or serialization failed.
739    ///
740    /// # Errors
741    ///
742    /// Returns an error if the function ID is invalid, any type argument is invalid, or if any argument serialization failed.
743    pub fn build(self) -> AptosResult<TransactionPayload> {
744        // Check for module parsing error
745        let module = self.module.map_err(AptosError::Transaction)?;
746
747        // Check for any accumulated errors
748        if !self.errors.is_empty() {
749            return Err(AptosError::Transaction(self.errors.join("; ")));
750        }
751
752        Ok(TransactionPayload::EntryFunction(EntryFunction {
753            module,
754            function: self.function,
755            type_args: self.type_args,
756            args: self.args,
757        }))
758    }
759
760    /// Builds just the entry function (without wrapping in `TransactionPayload`).
761    ///
762    /// # Errors
763    ///
764    /// Returns an error if the function ID is invalid, any type argument is invalid, or if any argument serialization failed.
765    pub fn build_entry_function(self) -> AptosResult<EntryFunction> {
766        let module = self.module.map_err(AptosError::Transaction)?;
767
768        if !self.errors.is_empty() {
769            return Err(AptosError::Transaction(self.errors.join("; ")));
770        }
771
772        Ok(EntryFunction {
773            module,
774            function: self.function,
775            type_args: self.type_args,
776            args: self.args,
777        })
778    }
779}
780
781/// Trait for types that can be converted to entry function arguments.
782///
783/// This trait is automatically implemented for types that implement `Serialize`.
784pub trait IntoMoveArg {
785    /// Converts this value into BCS-encoded bytes.
786    ///
787    /// # Errors
788    ///
789    /// Returns an error if BCS serialization fails.
790    fn into_move_arg(self) -> AptosResult<Vec<u8>>;
791}
792
793impl<T: Serialize> IntoMoveArg for T {
794    fn into_move_arg(self) -> AptosResult<Vec<u8>> {
795        aptos_bcs::to_bytes(&self).map_err(AptosError::bcs)
796    }
797}
798
799/// Helper to create a vector argument for Move functions.
800///
801/// Move vectors are BCS-encoded with a length prefix followed by elements.
802///
803/// # Example
804///
805/// ```rust,ignore
806/// let addresses = move_vec(&[addr1, addr2, addr3]);
807/// let amounts = move_vec(&[100u64, 200u64, 300u64]);
808/// ```
809pub fn move_vec<T: Serialize>(items: &[T]) -> Vec<u8> {
810    aptos_bcs::to_bytes(items).unwrap_or_default()
811}
812
813/// Helper to create a string argument for Move functions.
814///
815/// Move strings are UTF-8 encoded vectors of bytes.
816///
817/// # Example
818///
819/// ```rust,ignore
820/// let name = move_string("Alice");
821/// ```
822pub fn move_string(s: &str) -> String {
823    s.to_string()
824}
825
826/// Helper to create an `Option::Some` argument for Move.
827///
828/// # Example
829///
830/// ```rust,ignore
831/// let maybe_value = move_some(42u64);
832/// ```
833pub fn move_some<T: Serialize>(value: T) -> Vec<u8> {
834    // BCS encodes Option as: 0x01 followed by the value bytes for Some
835    let mut bytes = vec![0x01];
836    if let Ok(value_bytes) = aptos_bcs::to_bytes(&value) {
837        bytes.extend(value_bytes);
838    }
839    bytes
840}
841
842/// Helper to create an `Option::None` argument for Move.
843///
844/// # Example
845///
846/// ```rust,ignore
847/// let maybe_value: Vec<u8> = move_none();
848/// ```
849pub fn move_none() -> Vec<u8> {
850    // BCS encodes Option as: 0x00 for None
851    vec![0x00]
852}
853
854/// A u256 value for Move arguments.
855///
856/// Move's u256 is a 256-bit unsigned integer, represented as 32 bytes in little-endian.
857#[derive(Debug, Clone, Copy, PartialEq, Eq)]
858pub struct MoveU256(pub [u8; 32]);
859
860impl MoveU256 {
861    /// Creates a `MoveU256` from a decimal string.
862    ///
863    /// # Errors
864    ///
865    /// Returns an error if the string cannot be parsed as a u256 value.
866    pub fn parse(s: &str) -> AptosResult<Self> {
867        // Parse as big integer and convert to little-endian bytes
868        let mut bytes = [0u8; 32];
869
870        // Simple parsing for small values
871        if let Ok(val) = s.parse::<u128>() {
872            bytes[..16].copy_from_slice(&val.to_le_bytes());
873            return Ok(Self(bytes));
874        }
875
876        Err(AptosError::Transaction(format!("Invalid u256: {s}")))
877    }
878
879    /// Creates a `MoveU256` from a u128.
880    pub fn from_u128(val: u128) -> Self {
881        let mut bytes = [0u8; 32];
882        bytes[..16].copy_from_slice(&val.to_le_bytes());
883        Self(bytes)
884    }
885
886    /// Creates a `MoveU256` from raw bytes (little-endian).
887    pub fn from_le_bytes(bytes: [u8; 32]) -> Self {
888        Self(bytes)
889    }
890}
891
892impl Serialize for MoveU256 {
893    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
894    where
895        S: serde::Serializer,
896    {
897        // BCS serializes u256 as 32 little-endian bytes (as a tuple of bytes, not a vec)
898        use serde::ser::SerializeTuple;
899        let mut tuple = serializer.serialize_tuple(32)?;
900        for byte in &self.0 {
901            tuple.serialize_element(byte)?;
902        }
903        tuple.end()
904    }
905}
906
907/// An i128 value for Move arguments.
908///
909/// Move's i128 is a 128-bit signed integer, represented as 16 bytes in little-endian
910/// using two's complement representation.
911#[derive(Debug, Clone, Copy, PartialEq, Eq)]
912pub struct MoveI128(pub i128);
913
914impl MoveI128 {
915    /// Creates a new `MoveI128` from an i128 value.
916    pub fn new(val: i128) -> Self {
917        Self(val)
918    }
919}
920
921impl Serialize for MoveI128 {
922    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
923    where
924        S: serde::Serializer,
925    {
926        // BCS serializes i128 as 16 little-endian bytes (two's complement)
927        use serde::ser::SerializeTuple;
928        let bytes = self.0.to_le_bytes();
929        let mut tuple = serializer.serialize_tuple(16)?;
930        for byte in &bytes {
931            tuple.serialize_element(byte)?;
932        }
933        tuple.end()
934    }
935}
936
937impl From<i128> for MoveI128 {
938    fn from(val: i128) -> Self {
939        Self(val)
940    }
941}
942
943/// An i256 value for Move arguments.
944///
945/// Move's i256 is a 256-bit signed integer, represented as 32 bytes in little-endian
946/// using two's complement representation.
947#[derive(Debug, Clone, Copy, PartialEq, Eq)]
948pub struct MoveI256(pub [u8; 32]);
949
950impl MoveI256 {
951    /// Creates a `MoveI256` from an i128 value.
952    pub fn from_i128(val: i128) -> Self {
953        let mut bytes = [0u8; 32];
954        let val_bytes = val.to_le_bytes();
955        bytes[..16].copy_from_slice(&val_bytes);
956        // Sign extend for negative values
957        if val < 0 {
958            bytes[16..].fill(0xFF);
959        }
960        Self(bytes)
961    }
962
963    /// Creates a `MoveI256` from raw bytes (little-endian, two's complement).
964    pub fn from_le_bytes(bytes: [u8; 32]) -> Self {
965        Self(bytes)
966    }
967}
968
969impl Serialize for MoveI256 {
970    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
971    where
972        S: serde::Serializer,
973    {
974        // BCS serializes i256 as 32 little-endian bytes (as a tuple of bytes, not a vec)
975        use serde::ser::SerializeTuple;
976        let mut tuple = serializer.serialize_tuple(32)?;
977        for byte in &self.0 {
978            tuple.serialize_element(byte)?;
979        }
980        tuple.end()
981    }
982}
983
984impl From<i128> for MoveI256 {
985    fn from(val: i128) -> Self {
986        Self::from_i128(val)
987    }
988}
989
990/// Common function IDs for convenience.
991pub mod functions {
992    /// APT transfer function.
993    pub const APT_TRANSFER: &str = "0x1::aptos_account::transfer";
994    /// Coin transfer function.
995    pub const COIN_TRANSFER: &str = "0x1::coin::transfer";
996    /// Account creation function.
997    pub const CREATE_ACCOUNT: &str = "0x1::aptos_account::create_account";
998    /// Register a coin store.
999    pub const REGISTER_COIN: &str = "0x1::managed_coin::register";
1000    /// Publish a package.
1001    pub const PUBLISH_PACKAGE: &str = "0x1::code::publish_package_txn";
1002    /// Rotate authentication key.
1003    pub const ROTATE_AUTH_KEY: &str = "0x1::account::rotate_authentication_key";
1004}
1005
1006/// Common type tags for convenience.
1007pub mod types {
1008    /// APT coin type.
1009    pub const APT_COIN: &str = "0x1::aptos_coin::AptosCoin";
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014    use super::*;
1015
1016    #[test]
1017    fn test_simple_transfer() {
1018        let recipient = AccountAddress::from_hex("0x123").unwrap();
1019        let payload = InputEntryFunctionData::new("0x1::aptos_account::transfer")
1020            .arg(recipient)
1021            .arg(1_000_000u64)
1022            .build()
1023            .unwrap();
1024
1025        match payload {
1026            TransactionPayload::EntryFunction(ef) => {
1027                assert_eq!(ef.function, "transfer");
1028                assert_eq!(ef.module.name.as_str(), "aptos_account");
1029                assert!(ef.type_args.is_empty());
1030                assert_eq!(ef.args.len(), 2);
1031            }
1032            _ => panic!("Expected EntryFunction"),
1033        }
1034    }
1035
1036    #[test]
1037    fn test_with_type_args() {
1038        let payload = InputEntryFunctionData::new("0x1::coin::transfer")
1039            .type_arg("0x1::aptos_coin::AptosCoin")
1040            .arg(AccountAddress::ONE)
1041            .arg(1000u64)
1042            .build()
1043            .unwrap();
1044
1045        match payload {
1046            TransactionPayload::EntryFunction(ef) => {
1047                assert_eq!(ef.function, "transfer");
1048                assert_eq!(ef.type_args.len(), 1);
1049            }
1050            _ => panic!("Expected EntryFunction"),
1051        }
1052    }
1053
1054    #[test]
1055    fn test_invalid_function_id() {
1056        let result = InputEntryFunctionData::new("invalid").arg(42u64).build();
1057
1058        assert!(result.is_err());
1059    }
1060
1061    #[test]
1062    fn test_invalid_type_arg() {
1063        let result = InputEntryFunctionData::new("0x1::coin::transfer")
1064            .type_arg("not a type")
1065            .arg(AccountAddress::ONE)
1066            .arg(1000u64)
1067            .build();
1068
1069        assert!(result.is_err());
1070    }
1071
1072    #[test]
1073    fn test_transfer_apt_helper() {
1074        let recipient = AccountAddress::from_hex("0x456").unwrap();
1075        let payload = InputEntryFunctionData::transfer_apt(recipient, 5_000_000).unwrap();
1076
1077        match payload {
1078            TransactionPayload::EntryFunction(ef) => {
1079                assert_eq!(ef.function, "transfer");
1080                assert_eq!(ef.module.name.as_str(), "aptos_account");
1081            }
1082            _ => panic!("Expected EntryFunction"),
1083        }
1084    }
1085
1086    #[test]
1087    fn test_transfer_coin_helper() {
1088        let recipient = AccountAddress::from_hex("0x789").unwrap();
1089        let payload =
1090            InputEntryFunctionData::transfer_coin("0x1::aptos_coin::AptosCoin", recipient, 1000)
1091                .unwrap();
1092
1093        match payload {
1094            TransactionPayload::EntryFunction(ef) => {
1095                assert_eq!(ef.function, "transfer");
1096                assert_eq!(ef.module.name.as_str(), "coin");
1097                assert_eq!(ef.type_args.len(), 1);
1098            }
1099            _ => panic!("Expected EntryFunction"),
1100        }
1101    }
1102
1103    #[test]
1104    fn test_transfer_fungible_asset_helper() {
1105        let metadata = AccountAddress::from_hex("0xa").unwrap();
1106        let recipient = AccountAddress::from_hex("0x789").unwrap();
1107        let payload =
1108            InputEntryFunctionData::transfer_fungible_asset(metadata, recipient, 1000).unwrap();
1109
1110        match payload {
1111            TransactionPayload::EntryFunction(ef) => {
1112                assert_eq!(ef.function, "transfer");
1113                assert_eq!(ef.module.name.as_str(), "primary_fungible_store");
1114                // One type argument: the Metadata object type.
1115                assert_eq!(ef.type_args.len(), 1);
1116                // Three args: metadata object address, recipient, amount.
1117                assert_eq!(ef.args.len(), 3);
1118                // The metadata `Object<Metadata>` is BCS-encoded as its address.
1119                assert_eq!(ef.args[0], aptos_bcs::to_bytes(&metadata).unwrap());
1120                assert_eq!(ef.args[2], aptos_bcs::to_bytes(&1000u64).unwrap());
1121            }
1122            _ => panic!("Expected EntryFunction"),
1123        }
1124    }
1125
1126    #[test]
1127    fn test_transfer_object_helper() {
1128        let object = AccountAddress::from_hex("0xbeef").unwrap();
1129        let to = AccountAddress::from_hex("0x789").unwrap();
1130        let payload = InputEntryFunctionData::transfer_object(object, to).unwrap();
1131        match payload {
1132            TransactionPayload::EntryFunction(ef) => {
1133                assert_eq!(ef.module.name.as_str(), "object");
1134                assert_eq!(ef.function, "transfer_call");
1135                assert_eq!(ef.args.len(), 2);
1136                assert!(ef.type_args.is_empty());
1137            }
1138            _ => panic!("Expected EntryFunction"),
1139        }
1140    }
1141
1142    #[test]
1143    fn test_transfer_digital_asset_helper() {
1144        let token = AccountAddress::from_hex("0xdead").unwrap();
1145        let to = AccountAddress::from_hex("0x789").unwrap();
1146        let payload = InputEntryFunctionData::transfer_digital_asset(token, to).unwrap();
1147        match payload {
1148            TransactionPayload::EntryFunction(ef) => {
1149                assert_eq!(ef.module.name.as_str(), "object");
1150                assert_eq!(ef.function, "transfer");
1151                assert_eq!(ef.type_args.len(), 1, "should carry the Token object type");
1152                assert_eq!(ef.args.len(), 2);
1153            }
1154            _ => panic!("Expected EntryFunction"),
1155        }
1156    }
1157
1158    #[test]
1159    fn test_create_collection_arg_shape() {
1160        let payload = InputEntryFunctionData::create_collection(
1161            "desc",
1162            100,
1163            "My Collection",
1164            "https://example.com",
1165            CollectionConfig::default(),
1166            5,
1167            100,
1168        )
1169        .unwrap();
1170        match payload {
1171            TransactionPayload::EntryFunction(ef) => {
1172                assert_eq!(ef.module.address, AccountAddress::from_hex("0x4").unwrap());
1173                assert_eq!(ef.module.name.as_str(), "aptos_token");
1174                assert_eq!(ef.function, "create_collection");
1175                // 4 leading fields + 9 config flags + 2 royalty = 15 args.
1176                assert_eq!(ef.args.len(), 15);
1177            }
1178            _ => panic!("Expected EntryFunction"),
1179        }
1180    }
1181
1182    #[test]
1183    fn test_mint_digital_asset_arg_shape() {
1184        let payload = InputEntryFunctionData::mint_digital_asset(
1185            "My Collection",
1186            "desc",
1187            "Token #1",
1188            "https://example.com/1",
1189            vec!["level".to_string()],
1190            vec!["u64".to_string()],
1191            vec![aptos_bcs::to_bytes(&1u64).unwrap()],
1192        )
1193        .unwrap();
1194        match payload {
1195            TransactionPayload::EntryFunction(ef) => {
1196                assert_eq!(ef.function, "mint");
1197                assert_eq!(ef.args.len(), 7);
1198            }
1199            _ => panic!("Expected EntryFunction"),
1200        }
1201    }
1202
1203    #[test]
1204    fn test_delegation_helpers() {
1205        let pool = AccountAddress::from_hex("0x5").unwrap();
1206        for (payload, func) in [
1207            (
1208                InputEntryFunctionData::delegation_add_stake(pool, 1000).unwrap(),
1209                "add_stake",
1210            ),
1211            (
1212                InputEntryFunctionData::delegation_unlock(pool, 1000).unwrap(),
1213                "unlock",
1214            ),
1215            (
1216                InputEntryFunctionData::delegation_reactivate_stake(pool, 1000).unwrap(),
1217                "reactivate_stake",
1218            ),
1219            (
1220                InputEntryFunctionData::delegation_withdraw(pool, 1000).unwrap(),
1221                "withdraw",
1222            ),
1223        ] {
1224            match payload {
1225                TransactionPayload::EntryFunction(ef) => {
1226                    assert_eq!(ef.module.name.as_str(), "delegation_pool");
1227                    assert_eq!(ef.function, func);
1228                    assert_eq!(ef.args.len(), 2);
1229                }
1230                _ => panic!("Expected EntryFunction"),
1231            }
1232        }
1233    }
1234
1235    #[test]
1236    fn test_account_abstraction_helpers() {
1237        let module = AccountAddress::from_hex("0x123").unwrap();
1238        let add =
1239            InputEntryFunctionData::add_authentication_function(module, "my_auth", "authenticate")
1240                .unwrap();
1241        match add {
1242            TransactionPayload::EntryFunction(ef) => {
1243                assert_eq!(ef.module.name.as_str(), "account_abstraction");
1244                assert_eq!(ef.function, "add_authentication_function");
1245                assert_eq!(ef.args.len(), 3);
1246            }
1247            _ => panic!("Expected EntryFunction"),
1248        }
1249
1250        let remove_all = InputEntryFunctionData::remove_authenticator().unwrap();
1251        match remove_all {
1252            TransactionPayload::EntryFunction(ef) => {
1253                assert_eq!(ef.function, "remove_authenticator");
1254                assert!(ef.args.is_empty());
1255            }
1256            _ => panic!("Expected EntryFunction"),
1257        }
1258    }
1259
1260    #[test]
1261    fn test_various_arg_types() {
1262        let payload = InputEntryFunctionData::new("0x1::test::test_function")
1263            .arg(42u8)
1264            .arg(1000u64)
1265            .arg(true)
1266            .arg("hello".to_string())
1267            .arg(vec![1u8, 2u8, 3u8])
1268            .arg(AccountAddress::ONE)
1269            .build()
1270            .unwrap();
1271
1272        match payload {
1273            TransactionPayload::EntryFunction(ef) => {
1274                assert_eq!(ef.args.len(), 6);
1275            }
1276            _ => panic!("Expected EntryFunction"),
1277        }
1278    }
1279
1280    #[test]
1281    fn test_move_u256() {
1282        let val = MoveU256::from_u128(12345);
1283        let bytes = aptos_bcs::to_bytes(&val).unwrap();
1284        assert_eq!(bytes.len(), 32);
1285    }
1286
1287    #[test]
1288    fn test_move_some_none() {
1289        let some_bytes = move_some(42u64);
1290        assert_eq!(some_bytes[0], 0x01);
1291
1292        let none_bytes = move_none();
1293        assert_eq!(none_bytes, vec![0x00]);
1294    }
1295
1296    #[test]
1297    fn test_from_parts() {
1298        let module = MoveModuleId::from_str_strict("0x1::coin").unwrap();
1299        let payload = InputEntryFunctionData::from_parts(module, "transfer")
1300            .type_arg("0x1::aptos_coin::AptosCoin")
1301            .arg(AccountAddress::ONE)
1302            .arg(1000u64)
1303            .build()
1304            .unwrap();
1305
1306        match payload {
1307            TransactionPayload::EntryFunction(ef) => {
1308                assert_eq!(ef.function, "transfer");
1309                assert_eq!(ef.module.name.as_str(), "coin");
1310            }
1311            _ => panic!("Expected EntryFunction"),
1312        }
1313    }
1314
1315    #[test]
1316    fn test_build_entry_function() {
1317        let ef = InputEntryFunctionData::new("0x1::aptos_account::transfer")
1318            .arg(AccountAddress::ONE)
1319            .arg(1000u64)
1320            .build_entry_function()
1321            .unwrap();
1322
1323        assert_eq!(ef.function, "transfer");
1324        assert_eq!(ef.args.len(), 2);
1325    }
1326
1327    #[test]
1328    fn test_function_constants() {
1329        assert_eq!(functions::APT_TRANSFER, "0x1::aptos_account::transfer");
1330        assert_eq!(functions::COIN_TRANSFER, "0x1::coin::transfer");
1331    }
1332
1333    #[test]
1334    fn test_move_u256_from_u128() {
1335        let val = MoveU256::from_u128(123_456_789);
1336        // First 16 bytes should contain the value in little-endian
1337        let expected = 123_456_789_u128.to_le_bytes();
1338        assert_eq!(&val.0[..16], &expected);
1339        // Upper 16 bytes should be zero
1340        assert_eq!(&val.0[16..], &[0u8; 16]);
1341    }
1342
1343    #[test]
1344    fn test_move_u256_from_le_bytes() {
1345        let bytes = [0xab; 32];
1346        let val = MoveU256::from_le_bytes(bytes);
1347        assert_eq!(val.0, bytes);
1348    }
1349
1350    #[test]
1351    fn test_move_u256_parse() {
1352        let val = MoveU256::parse("12345678901234567890").unwrap();
1353        let expected = 12_345_678_901_234_567_890_u128;
1354        let mut expected_bytes = [0u8; 32];
1355        expected_bytes[..16].copy_from_slice(&expected.to_le_bytes());
1356        assert_eq!(val.0, expected_bytes);
1357    }
1358
1359    #[test]
1360    fn test_move_u256_parse_invalid() {
1361        // Value larger than u128 currently returns error
1362        let result = MoveU256::parse("999999999999999999999999999999999999999999999");
1363        assert!(result.is_err());
1364    }
1365
1366    #[test]
1367    fn test_move_u256_serialization() {
1368        let val = MoveU256::from_u128(0x0102_0304_0506_0708);
1369        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1370        // Should serialize as 32 bytes (tuple, not vector with length prefix)
1371        assert_eq!(bcs.len(), 32);
1372        // First 8 bytes should be our value in little-endian
1373        assert_eq!(&bcs[..8], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
1374    }
1375
1376    #[test]
1377    fn test_move_i128_new() {
1378        let val = MoveI128::new(42);
1379        assert_eq!(val.0, 42);
1380    }
1381
1382    #[test]
1383    fn test_move_i128_from_i128() {
1384        let val: MoveI128 = (-100i128).into();
1385        assert_eq!(val.0, -100);
1386    }
1387
1388    #[test]
1389    fn test_move_i128_serialization_positive() {
1390        let val = MoveI128::new(0x0102_0304_0506_0708);
1391        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1392        // Should serialize as 16 bytes (tuple, not vector)
1393        assert_eq!(bcs.len(), 16);
1394        // First 8 bytes should be our value in little-endian
1395        assert_eq!(&bcs[..8], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
1396        // Upper 8 bytes should be zeros for positive value
1397        assert_eq!(&bcs[8..], &[0, 0, 0, 0, 0, 0, 0, 0]);
1398    }
1399
1400    #[test]
1401    fn test_move_i128_serialization_negative() {
1402        let val = MoveI128::new(-1);
1403        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1404        assert_eq!(bcs.len(), 16);
1405        // -1 in two's complement is all 0xFF bytes
1406        assert_eq!(bcs, vec![0xFF; 16]);
1407    }
1408
1409    #[test]
1410    fn test_move_i256_from_i128_positive() {
1411        let val = MoveI256::from_i128(42);
1412        // First 16 bytes should contain the value
1413        let expected = 42i128.to_le_bytes();
1414        assert_eq!(&val.0[..16], &expected);
1415        // Upper 16 bytes should be zeros for positive value
1416        assert_eq!(&val.0[16..], &[0u8; 16]);
1417    }
1418
1419    #[test]
1420    fn test_move_i256_from_i128_negative() {
1421        let val = MoveI256::from_i128(-1);
1422        // -1 in two's complement should be all 0xFF bytes
1423        assert_eq!(val.0, [0xFF; 32]);
1424    }
1425
1426    #[test]
1427    fn test_move_i256_from_le_bytes() {
1428        let bytes = [0xcd; 32];
1429        let val = MoveI256::from_le_bytes(bytes);
1430        assert_eq!(val.0, bytes);
1431    }
1432
1433    #[test]
1434    fn test_move_i256_from_trait() {
1435        let val: MoveI256 = (-100i128).into();
1436        let expected = MoveI256::from_i128(-100);
1437        assert_eq!(val, expected);
1438    }
1439
1440    #[test]
1441    fn test_move_i256_serialization() {
1442        let val = MoveI256::from_i128(0x0102_0304_0506_0708);
1443        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1444        // Should serialize as 32 bytes (tuple, not vector)
1445        assert_eq!(bcs.len(), 32);
1446        // First 8 bytes should be our value in little-endian
1447        assert_eq!(&bcs[..8], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
1448    }
1449
1450    #[test]
1451    fn test_move_i256_serialization_negative() {
1452        let val = MoveI256::from_i128(-1);
1453        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1454        assert_eq!(bcs.len(), 32);
1455        // -1 in two's complement is all 0xFF bytes
1456        assert_eq!(bcs, vec![0xFF; 32]);
1457    }
1458
1459    /// Extracts the inner `EntryFunction` from a `TransactionPayload`, panicking
1460    /// for the test if the payload is not an entry-function variant.
1461    fn payload_as_entry_function(
1462        p: crate::transaction::TransactionPayload,
1463    ) -> crate::transaction::EntryFunction {
1464        match p {
1465            crate::transaction::TransactionPayload::EntryFunction(ef) => ef,
1466            other => panic!(
1467                "expected TransactionPayload::EntryFunction, got: {:?}",
1468                std::mem::discriminant(&other)
1469            ),
1470        }
1471    }
1472
1473    #[test]
1474    fn test_input_entry_function_data_new() {
1475        let builder = InputEntryFunctionData::new("0x1::coin::transfer");
1476        let entry_fn = payload_as_entry_function(builder.build().expect("build should succeed"));
1477        assert_eq!(entry_fn.module.name.as_str(), "coin");
1478        assert_eq!(entry_fn.function, "transfer");
1479        assert!(entry_fn.type_args.is_empty());
1480        assert!(entry_fn.args.is_empty());
1481    }
1482
1483    #[test]
1484    fn test_input_entry_function_data_invalid_function_id() {
1485        let builder = InputEntryFunctionData::new("invalid");
1486        let result = builder.build();
1487        assert!(result.is_err());
1488        assert!(
1489            result
1490                .unwrap_err()
1491                .to_string()
1492                .contains("Invalid function ID")
1493        );
1494    }
1495
1496    #[test]
1497    fn test_input_entry_function_data_type_arg() {
1498        let entry_fn = payload_as_entry_function(
1499            InputEntryFunctionData::new("0x1::coin::transfer")
1500                .type_arg("0x1::aptos_coin::AptosCoin")
1501                .build()
1502                .expect("build should succeed"),
1503        );
1504        assert_eq!(entry_fn.type_args.len(), 1);
1505        assert_eq!(
1506            entry_fn.type_args[0].to_string(),
1507            "0x1::aptos_coin::AptosCoin"
1508        );
1509    }
1510
1511    #[test]
1512    fn test_input_entry_function_data_invalid_type_arg() {
1513        let builder =
1514            InputEntryFunctionData::new("0x1::coin::transfer").type_arg("not a valid type");
1515        let result = builder.build();
1516        assert!(result.is_err());
1517        assert!(result.unwrap_err().to_string().contains("type argument"));
1518    }
1519
1520    #[test]
1521    fn test_input_entry_function_data_type_arg_typed() {
1522        use crate::types::TypeTag;
1523
1524        let entry_fn = payload_as_entry_function(
1525            InputEntryFunctionData::new("0x1::coin::transfer")
1526                .type_arg_typed(TypeTag::U64)
1527                .build()
1528                .expect("build should succeed"),
1529        );
1530        assert_eq!(entry_fn.type_args, vec![TypeTag::U64]);
1531    }
1532
1533    #[test]
1534    fn test_input_entry_function_data_type_args() {
1535        let entry_fn = payload_as_entry_function(
1536            InputEntryFunctionData::new("0x1::coin::transfer")
1537                .type_args(["u64", "u128"])
1538                .build()
1539                .expect("build should succeed"),
1540        );
1541        assert_eq!(entry_fn.type_args.len(), 2);
1542        assert_eq!(entry_fn.type_args[0].to_string(), "u64");
1543        assert_eq!(entry_fn.type_args[1].to_string(), "u128");
1544    }
1545
1546    #[test]
1547    fn test_input_entry_function_data_type_args_typed() {
1548        use crate::types::TypeTag;
1549
1550        let entry_fn = payload_as_entry_function(
1551            InputEntryFunctionData::new("0x1::coin::transfer")
1552                .type_args_typed([TypeTag::U64, TypeTag::Bool])
1553                .build()
1554                .expect("build should succeed"),
1555        );
1556        assert_eq!(entry_fn.type_args, vec![TypeTag::U64, TypeTag::Bool]);
1557    }
1558
1559    #[test]
1560    fn test_input_entry_function_data_arg() {
1561        let entry_fn = payload_as_entry_function(
1562            InputEntryFunctionData::new("0x1::coin::transfer")
1563                .arg(42u64)
1564                .arg(true)
1565                .arg("hello".to_string())
1566                .build()
1567                .expect("build should succeed"),
1568        );
1569        assert_eq!(entry_fn.args.len(), 3, "all three args must be present");
1570        // u64 BCS encodes 42 as 8 little-endian bytes: 0x2a 0x00 * 7
1571        assert_eq!(entry_fn.args[0][0], 42);
1572        // bool true is one byte: 0x01
1573        assert_eq!(entry_fn.args[1], vec![0x01]);
1574    }
1575
1576    #[test]
1577    fn test_input_entry_function_data_arg_raw() {
1578        let raw_bytes = vec![0x01, 0x02, 0x03];
1579        let entry_fn = payload_as_entry_function(
1580            InputEntryFunctionData::new("0x1::coin::transfer")
1581                .arg_raw(raw_bytes.clone())
1582                .build()
1583                .expect("build should succeed"),
1584        );
1585        assert_eq!(entry_fn.args.len(), 1);
1586        assert_eq!(entry_fn.args[0], raw_bytes);
1587    }
1588
1589    #[test]
1590    fn test_input_entry_function_data_args() {
1591        let entry_fn = payload_as_entry_function(
1592            InputEntryFunctionData::new("0x1::coin::transfer")
1593                .args([1u64, 2u64, 3u64])
1594                .build()
1595                .expect("build should succeed"),
1596        );
1597        assert_eq!(entry_fn.args.len(), 3);
1598        assert_eq!(entry_fn.args[0][0], 1);
1599        assert_eq!(entry_fn.args[1][0], 2);
1600        assert_eq!(entry_fn.args[2][0], 3);
1601    }
1602
1603    #[test]
1604    fn test_input_entry_function_data_transfer_apt() {
1605        use crate::types::AccountAddress;
1606
1607        let recipient = AccountAddress::from_hex("0x123").unwrap();
1608        let entry_fn = payload_as_entry_function(
1609            InputEntryFunctionData::transfer_apt(recipient, 1000)
1610                .expect("transfer_apt should succeed"),
1611        );
1612        assert_eq!(entry_fn.module.address, AccountAddress::ONE);
1613        assert_eq!(entry_fn.module.name.as_str(), "aptos_account");
1614        assert_eq!(entry_fn.function, "transfer");
1615        assert_eq!(entry_fn.args.len(), 2);
1616    }
1617
1618    #[test]
1619    fn test_input_entry_function_data_builder_debug() {
1620        let builder = InputEntryFunctionData::new("0x1::coin::transfer");
1621        let debug = format!("{builder:?}");
1622        assert!(debug.contains("InputEntryFunctionDataBuilder"));
1623    }
1624
1625    #[test]
1626    fn test_input_entry_function_data_builder_clone() {
1627        let builder = InputEntryFunctionData::new("0x1::coin::transfer").arg(42u64);
1628        let cloned = builder.clone();
1629        let original_built = payload_as_entry_function(builder.build().expect("original builds"));
1630        let cloned_built = payload_as_entry_function(cloned.build().expect("clone builds"));
1631        // Cloning produces a behaviourally-identical builder (same module, function, args).
1632        assert_eq!(original_built.module.name, cloned_built.module.name);
1633        assert_eq!(original_built.function, cloned_built.function);
1634        assert_eq!(original_built.args, cloned_built.args);
1635    }
1636
1637    #[test]
1638    fn test_move_u256_debug() {
1639        let val = MoveU256::from_u128(123_456_789);
1640        let debug = format!("{val:?}");
1641        assert!(debug.contains("MoveU256"));
1642    }
1643
1644    #[test]
1645    fn test_move_i128_debug() {
1646        let val = MoveI128::new(-42);
1647        let debug = format!("{val:?}");
1648        assert!(debug.contains("MoveI128"));
1649    }
1650
1651    #[test]
1652    fn test_move_i256_debug() {
1653        let val = MoveI256::from_i128(-42);
1654        let debug = format!("{val:?}");
1655        assert!(debug.contains("MoveI256"));
1656    }
1657
1658    #[test]
1659    fn test_move_u256_equality() {
1660        let val1 = MoveU256::from_u128(100);
1661        let val2 = MoveU256::from_u128(100);
1662        let val3 = MoveU256::from_u128(200);
1663        assert_eq!(val1, val2);
1664        assert_ne!(val1, val3);
1665    }
1666
1667    #[test]
1668    fn test_move_i256_equality() {
1669        let val1 = MoveI256::from_i128(-50);
1670        let val2 = MoveI256::from_i128(-50);
1671        let val3 = MoveI256::from_i128(50);
1672        assert_eq!(val1, val2);
1673        assert_ne!(val1, val3);
1674    }
1675
1676    #[test]
1677    fn test_move_u256_clone() {
1678        let val1 = MoveU256::from_u128(999);
1679        let val2 = val1;
1680        assert_eq!(val1, val2);
1681    }
1682
1683    #[test]
1684    fn test_move_i256_clone() {
1685        let val1 = MoveI256::from_i128(-999);
1686        let val2 = val1;
1687        assert_eq!(val1, val2);
1688    }
1689}