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::{Deserialize, 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    /// Supports the full unsigned 256-bit range (`0..=2^256 - 1`), matching how
864    /// the Aptos JSON API encodes `u256` values (as a decimal string).
865    ///
866    /// # Errors
867    ///
868    /// Returns an error if `s` is empty, contains a non-digit character, or
869    /// represents a value that does not fit in 256 bits.
870    pub fn parse(s: &str) -> AptosResult<Self> {
871        let s = s.trim();
872        if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
873            return Err(AptosError::Transaction(format!("Invalid u256: {s}")));
874        }
875
876        // Accumulate the value in a little-endian byte array: acc = acc * 10 + digit.
877        let mut bytes = [0u8; 32];
878        for digit in s.bytes().map(|b| b - b'0') {
879            let mut carry = u16::from(digit);
880            for byte in &mut bytes {
881                let v = u16::from(*byte) * 10 + carry;
882                *byte = (v & 0xff) as u8;
883                carry = v >> 8;
884            }
885            if carry != 0 {
886                return Err(AptosError::Transaction(format!(
887                    "u256 overflow: {s} exceeds 2^256 - 1"
888                )));
889            }
890        }
891
892        Ok(Self(bytes))
893    }
894
895    /// Creates a `MoveU256` from a u128.
896    pub fn from_u128(val: u128) -> Self {
897        let mut bytes = [0u8; 32];
898        bytes[..16].copy_from_slice(&val.to_le_bytes());
899        Self(bytes)
900    }
901
902    /// Creates a `MoveU256` from raw bytes (little-endian).
903    pub fn from_le_bytes(bytes: [u8; 32]) -> Self {
904        Self(bytes)
905    }
906
907    /// Returns the value as a decimal string (matching the Aptos JSON encoding).
908    pub fn to_decimal_string(&self) -> String {
909        if self.0.iter().all(|&b| b == 0) {
910            return "0".to_string();
911        }
912
913        // Repeatedly divide the big-endian representation by 10, collecting remainders.
914        // Each remainder is 0..=9, so we push it straight into a String as an ASCII
915        // digit -- no fallible UTF-8 conversion (and therefore no panic path).
916        let mut be = self.0;
917        be.reverse();
918        let mut digits = String::with_capacity(78);
919        while be.iter().any(|&b| b != 0) {
920            let mut rem = 0u16;
921            for byte in &mut be {
922                let cur = (rem << 8) | u16::from(*byte);
923                *byte = (cur / 10) as u8;
924                rem = cur % 10;
925            }
926            digits.push(char::from(b'0' + rem as u8));
927        }
928        // Digits were produced least-significant first; reverse for normal order.
929        digits.chars().rev().collect()
930    }
931}
932
933impl std::fmt::Display for MoveU256 {
934    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
935        f.write_str(&self.to_decimal_string())
936    }
937}
938
939impl Serialize for MoveU256 {
940    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
941    where
942        S: serde::Serializer,
943    {
944        if serializer.is_human_readable() {
945            // JSON (and other human-readable formats): the Aptos API encodes
946            // u256 as a decimal string.
947            serializer.serialize_str(&self.to_decimal_string())
948        } else {
949            // BCS serializes u256 as 32 little-endian bytes (a fixed tuple of
950            // bytes, not a length-prefixed vector).
951            use serde::ser::SerializeTuple;
952            let mut tuple = serializer.serialize_tuple(32)?;
953            for byte in &self.0 {
954                tuple.serialize_element(byte)?;
955            }
956            tuple.end()
957        }
958    }
959}
960
961impl<'de> Deserialize<'de> for MoveU256 {
962    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
963    where
964        D: serde::Deserializer<'de>,
965    {
966        if deserializer.is_human_readable() {
967            struct HrVisitor;
968            impl serde::de::Visitor<'_> for HrVisitor {
969                type Value = MoveU256;
970
971                fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
972                    f.write_str("a u256 as a decimal string or unsigned integer")
973                }
974
975                fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<MoveU256, E> {
976                    MoveU256::parse(v).map_err(E::custom)
977                }
978
979                fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<MoveU256, E> {
980                    Ok(MoveU256::from_u128(u128::from(v)))
981                }
982
983                fn visit_u128<E: serde::de::Error>(self, v: u128) -> Result<MoveU256, E> {
984                    Ok(MoveU256::from_u128(v))
985                }
986            }
987            // Human-readable formats are self-describing, so `deserialize_any`
988            // dispatches to the string or integer visitor above.
989            deserializer.deserialize_any(HrVisitor)
990        } else {
991            // BCS: 32 little-endian bytes, matching the tuple serialization above.
992            let bytes = <[u8; 32]>::deserialize(deserializer)?;
993            Ok(Self(bytes))
994        }
995    }
996}
997
998/// An i128 value for Move arguments.
999///
1000/// Move's i128 is a 128-bit signed integer, represented as 16 bytes in little-endian
1001/// using two's complement representation.
1002#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1003pub struct MoveI128(pub i128);
1004
1005impl MoveI128 {
1006    /// Creates a new `MoveI128` from an i128 value.
1007    pub fn new(val: i128) -> Self {
1008        Self(val)
1009    }
1010}
1011
1012impl Serialize for MoveI128 {
1013    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1014    where
1015        S: serde::Serializer,
1016    {
1017        // BCS serializes i128 as 16 little-endian bytes (two's complement)
1018        use serde::ser::SerializeTuple;
1019        let bytes = self.0.to_le_bytes();
1020        let mut tuple = serializer.serialize_tuple(16)?;
1021        for byte in &bytes {
1022            tuple.serialize_element(byte)?;
1023        }
1024        tuple.end()
1025    }
1026}
1027
1028impl From<i128> for MoveI128 {
1029    fn from(val: i128) -> Self {
1030        Self(val)
1031    }
1032}
1033
1034/// An i256 value for Move arguments.
1035///
1036/// Move's i256 is a 256-bit signed integer, represented as 32 bytes in little-endian
1037/// using two's complement representation.
1038#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1039pub struct MoveI256(pub [u8; 32]);
1040
1041impl MoveI256 {
1042    /// Creates a `MoveI256` from an i128 value.
1043    pub fn from_i128(val: i128) -> Self {
1044        let mut bytes = [0u8; 32];
1045        let val_bytes = val.to_le_bytes();
1046        bytes[..16].copy_from_slice(&val_bytes);
1047        // Sign extend for negative values
1048        if val < 0 {
1049            bytes[16..].fill(0xFF);
1050        }
1051        Self(bytes)
1052    }
1053
1054    /// Creates a `MoveI256` from raw bytes (little-endian, two's complement).
1055    pub fn from_le_bytes(bytes: [u8; 32]) -> Self {
1056        Self(bytes)
1057    }
1058}
1059
1060impl Serialize for MoveI256 {
1061    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1062    where
1063        S: serde::Serializer,
1064    {
1065        // BCS serializes i256 as 32 little-endian bytes (as a tuple of bytes, not a vec)
1066        use serde::ser::SerializeTuple;
1067        let mut tuple = serializer.serialize_tuple(32)?;
1068        for byte in &self.0 {
1069            tuple.serialize_element(byte)?;
1070        }
1071        tuple.end()
1072    }
1073}
1074
1075impl From<i128> for MoveI256 {
1076    fn from(val: i128) -> Self {
1077        Self::from_i128(val)
1078    }
1079}
1080
1081/// Common function IDs for convenience.
1082pub mod functions {
1083    /// APT transfer function.
1084    pub const APT_TRANSFER: &str = "0x1::aptos_account::transfer";
1085    /// Coin transfer function.
1086    pub const COIN_TRANSFER: &str = "0x1::coin::transfer";
1087    /// Account creation function.
1088    pub const CREATE_ACCOUNT: &str = "0x1::aptos_account::create_account";
1089    /// Register a coin store.
1090    pub const REGISTER_COIN: &str = "0x1::managed_coin::register";
1091    /// Publish a package.
1092    pub const PUBLISH_PACKAGE: &str = "0x1::code::publish_package_txn";
1093    /// Rotate authentication key.
1094    pub const ROTATE_AUTH_KEY: &str = "0x1::account::rotate_authentication_key";
1095}
1096
1097/// Common type tags for convenience.
1098pub mod types {
1099    /// APT coin type.
1100    pub const APT_COIN: &str = "0x1::aptos_coin::AptosCoin";
1101}
1102
1103#[cfg(test)]
1104mod tests {
1105    use super::*;
1106
1107    #[test]
1108    fn test_simple_transfer() {
1109        let recipient = AccountAddress::from_hex("0x123").unwrap();
1110        let payload = InputEntryFunctionData::new("0x1::aptos_account::transfer")
1111            .arg(recipient)
1112            .arg(1_000_000u64)
1113            .build()
1114            .unwrap();
1115
1116        match payload {
1117            TransactionPayload::EntryFunction(ef) => {
1118                assert_eq!(ef.function, "transfer");
1119                assert_eq!(ef.module.name.as_str(), "aptos_account");
1120                assert!(ef.type_args.is_empty());
1121                assert_eq!(ef.args.len(), 2);
1122            }
1123            _ => panic!("Expected EntryFunction"),
1124        }
1125    }
1126
1127    #[test]
1128    fn test_with_type_args() {
1129        let payload = InputEntryFunctionData::new("0x1::coin::transfer")
1130            .type_arg("0x1::aptos_coin::AptosCoin")
1131            .arg(AccountAddress::ONE)
1132            .arg(1000u64)
1133            .build()
1134            .unwrap();
1135
1136        match payload {
1137            TransactionPayload::EntryFunction(ef) => {
1138                assert_eq!(ef.function, "transfer");
1139                assert_eq!(ef.type_args.len(), 1);
1140            }
1141            _ => panic!("Expected EntryFunction"),
1142        }
1143    }
1144
1145    #[test]
1146    fn test_invalid_function_id() {
1147        let result = InputEntryFunctionData::new("invalid").arg(42u64).build();
1148
1149        assert!(result.is_err());
1150    }
1151
1152    #[test]
1153    fn test_invalid_type_arg() {
1154        let result = InputEntryFunctionData::new("0x1::coin::transfer")
1155            .type_arg("not a type")
1156            .arg(AccountAddress::ONE)
1157            .arg(1000u64)
1158            .build();
1159
1160        assert!(result.is_err());
1161    }
1162
1163    #[test]
1164    fn test_transfer_apt_helper() {
1165        let recipient = AccountAddress::from_hex("0x456").unwrap();
1166        let payload = InputEntryFunctionData::transfer_apt(recipient, 5_000_000).unwrap();
1167
1168        match payload {
1169            TransactionPayload::EntryFunction(ef) => {
1170                assert_eq!(ef.function, "transfer");
1171                assert_eq!(ef.module.name.as_str(), "aptos_account");
1172            }
1173            _ => panic!("Expected EntryFunction"),
1174        }
1175    }
1176
1177    #[test]
1178    fn test_transfer_coin_helper() {
1179        let recipient = AccountAddress::from_hex("0x789").unwrap();
1180        let payload =
1181            InputEntryFunctionData::transfer_coin("0x1::aptos_coin::AptosCoin", recipient, 1000)
1182                .unwrap();
1183
1184        match payload {
1185            TransactionPayload::EntryFunction(ef) => {
1186                assert_eq!(ef.function, "transfer");
1187                assert_eq!(ef.module.name.as_str(), "coin");
1188                assert_eq!(ef.type_args.len(), 1);
1189            }
1190            _ => panic!("Expected EntryFunction"),
1191        }
1192    }
1193
1194    #[test]
1195    fn test_transfer_fungible_asset_helper() {
1196        let metadata = AccountAddress::from_hex("0xa").unwrap();
1197        let recipient = AccountAddress::from_hex("0x789").unwrap();
1198        let payload =
1199            InputEntryFunctionData::transfer_fungible_asset(metadata, recipient, 1000).unwrap();
1200
1201        match payload {
1202            TransactionPayload::EntryFunction(ef) => {
1203                assert_eq!(ef.function, "transfer");
1204                assert_eq!(ef.module.name.as_str(), "primary_fungible_store");
1205                // One type argument: the Metadata object type.
1206                assert_eq!(ef.type_args.len(), 1);
1207                // Three args: metadata object address, recipient, amount.
1208                assert_eq!(ef.args.len(), 3);
1209                // The metadata `Object<Metadata>` is BCS-encoded as its address.
1210                assert_eq!(ef.args[0], aptos_bcs::to_bytes(&metadata).unwrap());
1211                assert_eq!(ef.args[2], aptos_bcs::to_bytes(&1000u64).unwrap());
1212            }
1213            _ => panic!("Expected EntryFunction"),
1214        }
1215    }
1216
1217    #[test]
1218    fn test_transfer_object_helper() {
1219        let object = AccountAddress::from_hex("0xbeef").unwrap();
1220        let to = AccountAddress::from_hex("0x789").unwrap();
1221        let payload = InputEntryFunctionData::transfer_object(object, to).unwrap();
1222        match payload {
1223            TransactionPayload::EntryFunction(ef) => {
1224                assert_eq!(ef.module.name.as_str(), "object");
1225                assert_eq!(ef.function, "transfer_call");
1226                assert_eq!(ef.args.len(), 2);
1227                assert!(ef.type_args.is_empty());
1228            }
1229            _ => panic!("Expected EntryFunction"),
1230        }
1231    }
1232
1233    #[test]
1234    fn test_transfer_digital_asset_helper() {
1235        let token = AccountAddress::from_hex("0xdead").unwrap();
1236        let to = AccountAddress::from_hex("0x789").unwrap();
1237        let payload = InputEntryFunctionData::transfer_digital_asset(token, to).unwrap();
1238        match payload {
1239            TransactionPayload::EntryFunction(ef) => {
1240                assert_eq!(ef.module.name.as_str(), "object");
1241                assert_eq!(ef.function, "transfer");
1242                assert_eq!(ef.type_args.len(), 1, "should carry the Token object type");
1243                assert_eq!(ef.args.len(), 2);
1244            }
1245            _ => panic!("Expected EntryFunction"),
1246        }
1247    }
1248
1249    #[test]
1250    fn test_create_collection_arg_shape() {
1251        let payload = InputEntryFunctionData::create_collection(
1252            "desc",
1253            100,
1254            "My Collection",
1255            "https://example.com",
1256            CollectionConfig::default(),
1257            5,
1258            100,
1259        )
1260        .unwrap();
1261        match payload {
1262            TransactionPayload::EntryFunction(ef) => {
1263                assert_eq!(ef.module.address, AccountAddress::from_hex("0x4").unwrap());
1264                assert_eq!(ef.module.name.as_str(), "aptos_token");
1265                assert_eq!(ef.function, "create_collection");
1266                // 4 leading fields + 9 config flags + 2 royalty = 15 args.
1267                assert_eq!(ef.args.len(), 15);
1268            }
1269            _ => panic!("Expected EntryFunction"),
1270        }
1271    }
1272
1273    #[test]
1274    fn test_mint_digital_asset_arg_shape() {
1275        let payload = InputEntryFunctionData::mint_digital_asset(
1276            "My Collection",
1277            "desc",
1278            "Token #1",
1279            "https://example.com/1",
1280            vec!["level".to_string()],
1281            vec!["u64".to_string()],
1282            vec![aptos_bcs::to_bytes(&1u64).unwrap()],
1283        )
1284        .unwrap();
1285        match payload {
1286            TransactionPayload::EntryFunction(ef) => {
1287                assert_eq!(ef.function, "mint");
1288                assert_eq!(ef.args.len(), 7);
1289            }
1290            _ => panic!("Expected EntryFunction"),
1291        }
1292    }
1293
1294    #[test]
1295    fn test_delegation_helpers() {
1296        let pool = AccountAddress::from_hex("0x5").unwrap();
1297        for (payload, func) in [
1298            (
1299                InputEntryFunctionData::delegation_add_stake(pool, 1000).unwrap(),
1300                "add_stake",
1301            ),
1302            (
1303                InputEntryFunctionData::delegation_unlock(pool, 1000).unwrap(),
1304                "unlock",
1305            ),
1306            (
1307                InputEntryFunctionData::delegation_reactivate_stake(pool, 1000).unwrap(),
1308                "reactivate_stake",
1309            ),
1310            (
1311                InputEntryFunctionData::delegation_withdraw(pool, 1000).unwrap(),
1312                "withdraw",
1313            ),
1314        ] {
1315            match payload {
1316                TransactionPayload::EntryFunction(ef) => {
1317                    assert_eq!(ef.module.name.as_str(), "delegation_pool");
1318                    assert_eq!(ef.function, func);
1319                    assert_eq!(ef.args.len(), 2);
1320                }
1321                _ => panic!("Expected EntryFunction"),
1322            }
1323        }
1324    }
1325
1326    #[test]
1327    fn test_account_abstraction_helpers() {
1328        let module = AccountAddress::from_hex("0x123").unwrap();
1329        let add =
1330            InputEntryFunctionData::add_authentication_function(module, "my_auth", "authenticate")
1331                .unwrap();
1332        match add {
1333            TransactionPayload::EntryFunction(ef) => {
1334                assert_eq!(ef.module.name.as_str(), "account_abstraction");
1335                assert_eq!(ef.function, "add_authentication_function");
1336                assert_eq!(ef.args.len(), 3);
1337            }
1338            _ => panic!("Expected EntryFunction"),
1339        }
1340
1341        let remove_all = InputEntryFunctionData::remove_authenticator().unwrap();
1342        match remove_all {
1343            TransactionPayload::EntryFunction(ef) => {
1344                assert_eq!(ef.function, "remove_authenticator");
1345                assert!(ef.args.is_empty());
1346            }
1347            _ => panic!("Expected EntryFunction"),
1348        }
1349    }
1350
1351    #[test]
1352    fn test_various_arg_types() {
1353        let payload = InputEntryFunctionData::new("0x1::test::test_function")
1354            .arg(42u8)
1355            .arg(1000u64)
1356            .arg(true)
1357            .arg("hello".to_string())
1358            .arg(vec![1u8, 2u8, 3u8])
1359            .arg(AccountAddress::ONE)
1360            .build()
1361            .unwrap();
1362
1363        match payload {
1364            TransactionPayload::EntryFunction(ef) => {
1365                assert_eq!(ef.args.len(), 6);
1366            }
1367            _ => panic!("Expected EntryFunction"),
1368        }
1369    }
1370
1371    #[test]
1372    fn test_move_u256() {
1373        let val = MoveU256::from_u128(12345);
1374        let bytes = aptos_bcs::to_bytes(&val).unwrap();
1375        assert_eq!(bytes.len(), 32);
1376    }
1377
1378    #[test]
1379    fn test_move_some_none() {
1380        let some_bytes = move_some(42u64);
1381        assert_eq!(some_bytes[0], 0x01);
1382
1383        let none_bytes = move_none();
1384        assert_eq!(none_bytes, vec![0x00]);
1385    }
1386
1387    #[test]
1388    fn test_from_parts() {
1389        let module = MoveModuleId::from_str_strict("0x1::coin").unwrap();
1390        let payload = InputEntryFunctionData::from_parts(module, "transfer")
1391            .type_arg("0x1::aptos_coin::AptosCoin")
1392            .arg(AccountAddress::ONE)
1393            .arg(1000u64)
1394            .build()
1395            .unwrap();
1396
1397        match payload {
1398            TransactionPayload::EntryFunction(ef) => {
1399                assert_eq!(ef.function, "transfer");
1400                assert_eq!(ef.module.name.as_str(), "coin");
1401            }
1402            _ => panic!("Expected EntryFunction"),
1403        }
1404    }
1405
1406    #[test]
1407    fn test_build_entry_function() {
1408        let ef = InputEntryFunctionData::new("0x1::aptos_account::transfer")
1409            .arg(AccountAddress::ONE)
1410            .arg(1000u64)
1411            .build_entry_function()
1412            .unwrap();
1413
1414        assert_eq!(ef.function, "transfer");
1415        assert_eq!(ef.args.len(), 2);
1416    }
1417
1418    #[test]
1419    fn test_function_constants() {
1420        assert_eq!(functions::APT_TRANSFER, "0x1::aptos_account::transfer");
1421        assert_eq!(functions::COIN_TRANSFER, "0x1::coin::transfer");
1422    }
1423
1424    #[test]
1425    fn test_move_u256_from_u128() {
1426        let val = MoveU256::from_u128(123_456_789);
1427        // First 16 bytes should contain the value in little-endian
1428        let expected = 123_456_789_u128.to_le_bytes();
1429        assert_eq!(&val.0[..16], &expected);
1430        // Upper 16 bytes should be zero
1431        assert_eq!(&val.0[16..], &[0u8; 16]);
1432    }
1433
1434    #[test]
1435    fn test_move_u256_from_le_bytes() {
1436        let bytes = [0xab; 32];
1437        let val = MoveU256::from_le_bytes(bytes);
1438        assert_eq!(val.0, bytes);
1439    }
1440
1441    #[test]
1442    fn test_move_u256_parse() {
1443        let val = MoveU256::parse("12345678901234567890").unwrap();
1444        let expected = 12_345_678_901_234_567_890_u128;
1445        let mut expected_bytes = [0u8; 32];
1446        expected_bytes[..16].copy_from_slice(&expected.to_le_bytes());
1447        assert_eq!(val.0, expected_bytes);
1448    }
1449
1450    #[test]
1451    fn test_move_u256_parse_large_value_beyond_u128() {
1452        // A value larger than u128 (39+ digits) but within u256 range now parses.
1453        // 2^128 = 340282366920938463463374607431768211456
1454        let two_pow_128 = MoveU256::parse("340282366920938463463374607431768211456").unwrap();
1455        let mut expected = [0u8; 32];
1456        expected[16] = 1; // 2^128 sets the byte at little-endian offset 16
1457        assert_eq!(two_pow_128.0, expected);
1458    }
1459
1460    #[test]
1461    fn test_move_u256_parse_max_and_overflow() {
1462        // u256 max = 2^256 - 1 parses to all-0xff bytes.
1463        let max = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
1464        assert_eq!(MoveU256::parse(max).unwrap().0, [0xff; 32]);
1465
1466        // One past the max overflows.
1467        let over = "115792089237316195423570985008687907853269984665640564039457584007913129639936";
1468        assert!(MoveU256::parse(over).is_err());
1469
1470        // Non-digit and empty inputs are rejected.
1471        assert!(MoveU256::parse("12x3").is_err());
1472        assert!(MoveU256::parse("").is_err());
1473    }
1474
1475    #[test]
1476    fn test_move_u256_bcs_roundtrip() {
1477        let val = MoveU256::parse("340282366920938463463374607431768211457").unwrap();
1478        let bytes = aptos_bcs::to_bytes(&val).unwrap();
1479        assert_eq!(bytes.len(), 32);
1480        let back: MoveU256 = aptos_bcs::from_bytes(&bytes).unwrap();
1481        assert_eq!(back, val);
1482    }
1483
1484    #[test]
1485    fn test_move_u256_json_roundtrip_as_string() {
1486        let val = MoveU256::parse("340282366920938463463374607431768211457").unwrap();
1487        // Human-readable (JSON) encoding is the decimal string, matching the API.
1488        let json = serde_json::to_string(&val).unwrap();
1489        assert_eq!(json, "\"340282366920938463463374607431768211457\"");
1490        let back: MoveU256 = serde_json::from_str(&json).unwrap();
1491        assert_eq!(back, val);
1492        // The API sometimes sends small values as JSON numbers, too.
1493        let from_num: MoveU256 = serde_json::from_str("12345").unwrap();
1494        assert_eq!(from_num, MoveU256::from_u128(12345));
1495    }
1496
1497    #[test]
1498    fn test_move_u256_to_decimal_string() {
1499        assert_eq!(MoveU256::from_u128(0).to_decimal_string(), "0");
1500        assert_eq!(
1501            MoveU256::from_u128(12_345_678_901_234_567_890).to_decimal_string(),
1502            "12345678901234567890"
1503        );
1504        assert_eq!(
1505            MoveU256([0xff; 32]).to_decimal_string(),
1506            "115792089237316195423570985008687907853269984665640564039457584007913129639935"
1507        );
1508    }
1509
1510    #[test]
1511    fn test_move_u256_serialization() {
1512        let val = MoveU256::from_u128(0x0102_0304_0506_0708);
1513        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1514        // Should serialize as 32 bytes (tuple, not vector with length prefix)
1515        assert_eq!(bcs.len(), 32);
1516        // First 8 bytes should be our value in little-endian
1517        assert_eq!(&bcs[..8], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
1518    }
1519
1520    #[test]
1521    fn test_move_i128_new() {
1522        let val = MoveI128::new(42);
1523        assert_eq!(val.0, 42);
1524    }
1525
1526    #[test]
1527    fn test_move_i128_from_i128() {
1528        let val: MoveI128 = (-100i128).into();
1529        assert_eq!(val.0, -100);
1530    }
1531
1532    #[test]
1533    fn test_move_i128_serialization_positive() {
1534        let val = MoveI128::new(0x0102_0304_0506_0708);
1535        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1536        // Should serialize as 16 bytes (tuple, not vector)
1537        assert_eq!(bcs.len(), 16);
1538        // First 8 bytes should be our value in little-endian
1539        assert_eq!(&bcs[..8], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
1540        // Upper 8 bytes should be zeros for positive value
1541        assert_eq!(&bcs[8..], &[0, 0, 0, 0, 0, 0, 0, 0]);
1542    }
1543
1544    #[test]
1545    fn test_move_i128_serialization_negative() {
1546        let val = MoveI128::new(-1);
1547        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1548        assert_eq!(bcs.len(), 16);
1549        // -1 in two's complement is all 0xFF bytes
1550        assert_eq!(bcs, vec![0xFF; 16]);
1551    }
1552
1553    #[test]
1554    fn test_move_i256_from_i128_positive() {
1555        let val = MoveI256::from_i128(42);
1556        // First 16 bytes should contain the value
1557        let expected = 42i128.to_le_bytes();
1558        assert_eq!(&val.0[..16], &expected);
1559        // Upper 16 bytes should be zeros for positive value
1560        assert_eq!(&val.0[16..], &[0u8; 16]);
1561    }
1562
1563    #[test]
1564    fn test_move_i256_from_i128_negative() {
1565        let val = MoveI256::from_i128(-1);
1566        // -1 in two's complement should be all 0xFF bytes
1567        assert_eq!(val.0, [0xFF; 32]);
1568    }
1569
1570    #[test]
1571    fn test_move_i256_from_le_bytes() {
1572        let bytes = [0xcd; 32];
1573        let val = MoveI256::from_le_bytes(bytes);
1574        assert_eq!(val.0, bytes);
1575    }
1576
1577    #[test]
1578    fn test_move_i256_from_trait() {
1579        let val: MoveI256 = (-100i128).into();
1580        let expected = MoveI256::from_i128(-100);
1581        assert_eq!(val, expected);
1582    }
1583
1584    #[test]
1585    fn test_move_i256_serialization() {
1586        let val = MoveI256::from_i128(0x0102_0304_0506_0708);
1587        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1588        // Should serialize as 32 bytes (tuple, not vector)
1589        assert_eq!(bcs.len(), 32);
1590        // First 8 bytes should be our value in little-endian
1591        assert_eq!(&bcs[..8], &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
1592    }
1593
1594    #[test]
1595    fn test_move_i256_serialization_negative() {
1596        let val = MoveI256::from_i128(-1);
1597        let bcs = aptos_bcs::to_bytes(&val).unwrap();
1598        assert_eq!(bcs.len(), 32);
1599        // -1 in two's complement is all 0xFF bytes
1600        assert_eq!(bcs, vec![0xFF; 32]);
1601    }
1602
1603    /// Extracts the inner `EntryFunction` from a `TransactionPayload`, panicking
1604    /// for the test if the payload is not an entry-function variant.
1605    fn payload_as_entry_function(
1606        p: crate::transaction::TransactionPayload,
1607    ) -> crate::transaction::EntryFunction {
1608        match p {
1609            crate::transaction::TransactionPayload::EntryFunction(ef) => ef,
1610            other => panic!(
1611                "expected TransactionPayload::EntryFunction, got: {:?}",
1612                std::mem::discriminant(&other)
1613            ),
1614        }
1615    }
1616
1617    #[test]
1618    fn test_input_entry_function_data_new() {
1619        let builder = InputEntryFunctionData::new("0x1::coin::transfer");
1620        let entry_fn = payload_as_entry_function(builder.build().expect("build should succeed"));
1621        assert_eq!(entry_fn.module.name.as_str(), "coin");
1622        assert_eq!(entry_fn.function, "transfer");
1623        assert!(entry_fn.type_args.is_empty());
1624        assert!(entry_fn.args.is_empty());
1625    }
1626
1627    #[test]
1628    fn test_input_entry_function_data_invalid_function_id() {
1629        let builder = InputEntryFunctionData::new("invalid");
1630        let result = builder.build();
1631        assert!(result.is_err());
1632        assert!(
1633            result
1634                .unwrap_err()
1635                .to_string()
1636                .contains("Invalid function ID")
1637        );
1638    }
1639
1640    #[test]
1641    fn test_input_entry_function_data_type_arg() {
1642        let entry_fn = payload_as_entry_function(
1643            InputEntryFunctionData::new("0x1::coin::transfer")
1644                .type_arg("0x1::aptos_coin::AptosCoin")
1645                .build()
1646                .expect("build should succeed"),
1647        );
1648        assert_eq!(entry_fn.type_args.len(), 1);
1649        assert_eq!(
1650            entry_fn.type_args[0].to_string(),
1651            "0x1::aptos_coin::AptosCoin"
1652        );
1653    }
1654
1655    #[test]
1656    fn test_input_entry_function_data_invalid_type_arg() {
1657        let builder =
1658            InputEntryFunctionData::new("0x1::coin::transfer").type_arg("not a valid type");
1659        let result = builder.build();
1660        assert!(result.is_err());
1661        assert!(result.unwrap_err().to_string().contains("type argument"));
1662    }
1663
1664    #[test]
1665    fn test_input_entry_function_data_type_arg_typed() {
1666        use crate::types::TypeTag;
1667
1668        let entry_fn = payload_as_entry_function(
1669            InputEntryFunctionData::new("0x1::coin::transfer")
1670                .type_arg_typed(TypeTag::U64)
1671                .build()
1672                .expect("build should succeed"),
1673        );
1674        assert_eq!(entry_fn.type_args, vec![TypeTag::U64]);
1675    }
1676
1677    #[test]
1678    fn test_input_entry_function_data_type_args() {
1679        let entry_fn = payload_as_entry_function(
1680            InputEntryFunctionData::new("0x1::coin::transfer")
1681                .type_args(["u64", "u128"])
1682                .build()
1683                .expect("build should succeed"),
1684        );
1685        assert_eq!(entry_fn.type_args.len(), 2);
1686        assert_eq!(entry_fn.type_args[0].to_string(), "u64");
1687        assert_eq!(entry_fn.type_args[1].to_string(), "u128");
1688    }
1689
1690    #[test]
1691    fn test_input_entry_function_data_type_args_typed() {
1692        use crate::types::TypeTag;
1693
1694        let entry_fn = payload_as_entry_function(
1695            InputEntryFunctionData::new("0x1::coin::transfer")
1696                .type_args_typed([TypeTag::U64, TypeTag::Bool])
1697                .build()
1698                .expect("build should succeed"),
1699        );
1700        assert_eq!(entry_fn.type_args, vec![TypeTag::U64, TypeTag::Bool]);
1701    }
1702
1703    #[test]
1704    fn test_input_entry_function_data_arg() {
1705        let entry_fn = payload_as_entry_function(
1706            InputEntryFunctionData::new("0x1::coin::transfer")
1707                .arg(42u64)
1708                .arg(true)
1709                .arg("hello".to_string())
1710                .build()
1711                .expect("build should succeed"),
1712        );
1713        assert_eq!(entry_fn.args.len(), 3, "all three args must be present");
1714        // u64 BCS encodes 42 as 8 little-endian bytes: 0x2a 0x00 * 7
1715        assert_eq!(entry_fn.args[0][0], 42);
1716        // bool true is one byte: 0x01
1717        assert_eq!(entry_fn.args[1], vec![0x01]);
1718    }
1719
1720    #[test]
1721    fn test_input_entry_function_data_arg_raw() {
1722        let raw_bytes = vec![0x01, 0x02, 0x03];
1723        let entry_fn = payload_as_entry_function(
1724            InputEntryFunctionData::new("0x1::coin::transfer")
1725                .arg_raw(raw_bytes.clone())
1726                .build()
1727                .expect("build should succeed"),
1728        );
1729        assert_eq!(entry_fn.args.len(), 1);
1730        assert_eq!(entry_fn.args[0], raw_bytes);
1731    }
1732
1733    #[test]
1734    fn test_input_entry_function_data_args() {
1735        let entry_fn = payload_as_entry_function(
1736            InputEntryFunctionData::new("0x1::coin::transfer")
1737                .args([1u64, 2u64, 3u64])
1738                .build()
1739                .expect("build should succeed"),
1740        );
1741        assert_eq!(entry_fn.args.len(), 3);
1742        assert_eq!(entry_fn.args[0][0], 1);
1743        assert_eq!(entry_fn.args[1][0], 2);
1744        assert_eq!(entry_fn.args[2][0], 3);
1745    }
1746
1747    #[test]
1748    fn test_input_entry_function_data_transfer_apt() {
1749        use crate::types::AccountAddress;
1750
1751        let recipient = AccountAddress::from_hex("0x123").unwrap();
1752        let entry_fn = payload_as_entry_function(
1753            InputEntryFunctionData::transfer_apt(recipient, 1000)
1754                .expect("transfer_apt should succeed"),
1755        );
1756        assert_eq!(entry_fn.module.address, AccountAddress::ONE);
1757        assert_eq!(entry_fn.module.name.as_str(), "aptos_account");
1758        assert_eq!(entry_fn.function, "transfer");
1759        assert_eq!(entry_fn.args.len(), 2);
1760    }
1761
1762    #[test]
1763    fn test_input_entry_function_data_builder_debug() {
1764        let builder = InputEntryFunctionData::new("0x1::coin::transfer");
1765        let debug = format!("{builder:?}");
1766        assert!(debug.contains("InputEntryFunctionDataBuilder"));
1767    }
1768
1769    #[test]
1770    fn test_input_entry_function_data_builder_clone() {
1771        let builder = InputEntryFunctionData::new("0x1::coin::transfer").arg(42u64);
1772        let cloned = builder.clone();
1773        let original_built = payload_as_entry_function(builder.build().expect("original builds"));
1774        let cloned_built = payload_as_entry_function(cloned.build().expect("clone builds"));
1775        // Cloning produces a behaviourally-identical builder (same module, function, args).
1776        assert_eq!(original_built.module.name, cloned_built.module.name);
1777        assert_eq!(original_built.function, cloned_built.function);
1778        assert_eq!(original_built.args, cloned_built.args);
1779    }
1780
1781    #[test]
1782    fn test_move_u256_debug() {
1783        let val = MoveU256::from_u128(123_456_789);
1784        let debug = format!("{val:?}");
1785        assert!(debug.contains("MoveU256"));
1786    }
1787
1788    #[test]
1789    fn test_move_i128_debug() {
1790        let val = MoveI128::new(-42);
1791        let debug = format!("{val:?}");
1792        assert!(debug.contains("MoveI128"));
1793    }
1794
1795    #[test]
1796    fn test_move_i256_debug() {
1797        let val = MoveI256::from_i128(-42);
1798        let debug = format!("{val:?}");
1799        assert!(debug.contains("MoveI256"));
1800    }
1801
1802    #[test]
1803    fn test_move_u256_equality() {
1804        let val1 = MoveU256::from_u128(100);
1805        let val2 = MoveU256::from_u128(100);
1806        let val3 = MoveU256::from_u128(200);
1807        assert_eq!(val1, val2);
1808        assert_ne!(val1, val3);
1809    }
1810
1811    #[test]
1812    fn test_move_i256_equality() {
1813        let val1 = MoveI256::from_i128(-50);
1814        let val2 = MoveI256::from_i128(-50);
1815        let val3 = MoveI256::from_i128(50);
1816        assert_eq!(val1, val2);
1817        assert_ne!(val1, val3);
1818    }
1819
1820    #[test]
1821    fn test_move_u256_clone() {
1822        let val1 = MoveU256::from_u128(999);
1823        let val2 = val1;
1824        assert_eq!(val1, val2);
1825    }
1826
1827    #[test]
1828    fn test_move_i256_clone() {
1829        let val1 = MoveI256::from_i128(-999);
1830        let val2 = val1;
1831        assert_eq!(val1, val2);
1832    }
1833
1834    #[test]
1835    fn test_create_account_helper() {
1836        let auth_key = AccountAddress::from_hex("0x123").unwrap();
1837        let ef = payload_as_entry_function(
1838            InputEntryFunctionData::create_account(auth_key).expect("create_account should build"),
1839        );
1840        assert_eq!(ef.module.name.as_str(), "aptos_account");
1841        assert_eq!(ef.function, "create_account");
1842        assert_eq!(ef.args.len(), 1);
1843        assert_eq!(ef.args[0], aptos_bcs::to_bytes(&auth_key).unwrap());
1844    }
1845
1846    #[test]
1847    fn test_register_coin_helper() {
1848        let ef = payload_as_entry_function(
1849            InputEntryFunctionData::register_coin("0x1::aptos_coin::AptosCoin")
1850                .expect("register_coin should build"),
1851        );
1852        assert_eq!(ef.module.name.as_str(), "managed_coin");
1853        assert_eq!(ef.function, "register");
1854        assert_eq!(ef.type_args.len(), 1);
1855        assert!(ef.args.is_empty());
1856    }
1857
1858    #[test]
1859    fn test_register_coin_invalid_type() {
1860        let result = InputEntryFunctionData::register_coin("not a type");
1861        assert!(result.is_err());
1862    }
1863
1864    #[test]
1865    fn test_publish_package_helper() {
1866        let metadata = vec![1u8, 2, 3];
1867        let code = vec![vec![4u8, 5], vec![6u8]];
1868        let ef = payload_as_entry_function(
1869            InputEntryFunctionData::publish_package(metadata.clone(), code.clone())
1870                .expect("publish_package should build"),
1871        );
1872        assert_eq!(ef.module.name.as_str(), "code");
1873        assert_eq!(ef.function, "publish_package_txn");
1874        assert_eq!(ef.args.len(), 2);
1875        assert_eq!(ef.args[0], aptos_bcs::to_bytes(&metadata).unwrap());
1876        assert_eq!(ef.args[1], aptos_bcs::to_bytes(&code).unwrap());
1877    }
1878
1879    #[test]
1880    fn test_mint_soul_bound_digital_asset_helper() {
1881        let soul_bound_to = AccountAddress::from_hex("0xabc").unwrap();
1882        let ef = payload_as_entry_function(
1883            InputEntryFunctionData::mint_soul_bound_digital_asset(
1884                "My Collection",
1885                "desc",
1886                "Token #1",
1887                "https://example.com/1",
1888                vec!["level".to_string()],
1889                vec!["u64".to_string()],
1890                vec![aptos_bcs::to_bytes(&1u64).unwrap()],
1891                soul_bound_to,
1892            )
1893            .expect("mint_soul_bound should build"),
1894        );
1895        assert_eq!(ef.module.name.as_str(), "aptos_token");
1896        assert_eq!(ef.function, "mint_soul_bound");
1897        // 4 leading fields + 3 property vectors + soul_bound_to = 8 args.
1898        assert_eq!(ef.args.len(), 8);
1899        assert_eq!(
1900            ef.args[7],
1901            aptos_bcs::to_bytes(&soul_bound_to).unwrap(),
1902            "final arg is the soul-bound recipient"
1903        );
1904    }
1905
1906    #[test]
1907    fn test_burn_digital_asset_helper() {
1908        let token = AccountAddress::from_hex("0xdead").unwrap();
1909        let ef = payload_as_entry_function(
1910            InputEntryFunctionData::burn_digital_asset(token).expect("burn should build"),
1911        );
1912        assert_eq!(ef.module.name.as_str(), "aptos_token");
1913        assert_eq!(ef.function, "burn");
1914        assert_eq!(ef.type_args.len(), 1);
1915        assert_eq!(ef.args.len(), 1);
1916    }
1917
1918    #[test]
1919    fn test_freeze_and_unfreeze_digital_asset_transfer_helpers() {
1920        let token = AccountAddress::from_hex("0xbeef").unwrap();
1921
1922        let freeze = payload_as_entry_function(
1923            InputEntryFunctionData::freeze_digital_asset_transfer(token)
1924                .expect("freeze should build"),
1925        );
1926        assert_eq!(freeze.function, "freeze_transfer");
1927        assert_eq!(freeze.type_args.len(), 1);
1928        assert_eq!(freeze.args.len(), 1);
1929
1930        let unfreeze = payload_as_entry_function(
1931            InputEntryFunctionData::unfreeze_digital_asset_transfer(token)
1932                .expect("unfreeze should build"),
1933        );
1934        assert_eq!(unfreeze.function, "unfreeze_transfer");
1935        assert_eq!(unfreeze.type_args.len(), 1);
1936        assert_eq!(unfreeze.args.len(), 1);
1937    }
1938
1939    #[test]
1940    fn test_remove_authentication_function_helper() {
1941        let module = AccountAddress::from_hex("0x123").unwrap();
1942        let ef = payload_as_entry_function(
1943            InputEntryFunctionData::remove_authentication_function(
1944                module,
1945                "my_auth",
1946                "authenticate",
1947            )
1948            .expect("remove_authentication_function should build"),
1949        );
1950        assert_eq!(ef.module.name.as_str(), "account_abstraction");
1951        assert_eq!(ef.function, "remove_authentication_function");
1952        assert_eq!(ef.args.len(), 3);
1953    }
1954
1955    #[test]
1956    fn test_arg_serialization_error_is_reported() {
1957        // BCS cannot serialize floats, so `.arg` records the failure and `build`
1958        // surfaces it as an error.
1959        let result = InputEntryFunctionData::new("0x1::coin::transfer")
1960            .arg(1.5f64)
1961            .build();
1962        let err = result.unwrap_err();
1963        assert!(
1964            err.to_string().contains("Failed to serialize argument"),
1965            "unexpected error: {err}"
1966        );
1967    }
1968
1969    #[test]
1970    fn test_build_entry_function_reports_accumulated_errors() {
1971        // An invalid type argument is accumulated and reported by
1972        // build_entry_function (not just build).
1973        let result = InputEntryFunctionData::new("0x1::coin::transfer")
1974            .type_arg("not a valid type")
1975            .build_entry_function();
1976        assert!(result.is_err());
1977        assert!(result.unwrap_err().to_string().contains("type argument"));
1978    }
1979
1980    #[test]
1981    fn test_build_entry_function_reports_invalid_module() {
1982        // An invalid function ID makes the module a stored error, surfaced by
1983        // build_entry_function.
1984        let result = InputEntryFunctionData::new("invalid").build_entry_function();
1985        assert!(result.is_err());
1986        assert!(
1987            result
1988                .unwrap_err()
1989                .to_string()
1990                .contains("Invalid function ID")
1991        );
1992    }
1993
1994    #[test]
1995    fn test_into_move_arg_success() {
1996        let bytes = 42u64.into_move_arg().unwrap();
1997        assert_eq!(bytes, aptos_bcs::to_bytes(&42u64).unwrap());
1998
1999        let addr_bytes = AccountAddress::ONE.into_move_arg().unwrap();
2000        assert_eq!(
2001            addr_bytes,
2002            aptos_bcs::to_bytes(&AccountAddress::ONE).unwrap()
2003        );
2004    }
2005
2006    #[test]
2007    fn test_into_move_arg_error() {
2008        // Floats are unsupported by BCS; the mapped error must be surfaced.
2009        let result = 1.5f64.into_move_arg();
2010        assert!(result.is_err());
2011    }
2012
2013    #[test]
2014    fn test_move_vec_helper() {
2015        let items = [100u64, 200u64, 300u64];
2016        let encoded = move_vec(&items);
2017        assert_eq!(encoded, aptos_bcs::to_bytes(&items.as_slice()).unwrap());
2018        assert!(!encoded.is_empty());
2019    }
2020
2021    #[test]
2022    fn test_move_string_helper() {
2023        assert_eq!(move_string("Alice"), "Alice".to_string());
2024        assert_eq!(move_string(""), String::new());
2025    }
2026
2027    #[test]
2028    fn test_move_u256_display() {
2029        assert_eq!(format!("{}", MoveU256::from_u128(12345)), "12345");
2030        assert_eq!(format!("{}", MoveU256::from_u128(0)), "0");
2031        assert_eq!(
2032            MoveU256([0xff; 32]).to_string(),
2033            "115792089237316195423570985008687907853269984665640564039457584007913129639935"
2034        );
2035    }
2036
2037    #[test]
2038    fn test_move_u256_deserialize_from_string() {
2039        // Exercises the human-readable visit_str path.
2040        let val: MoveU256 = serde_json::from_str("\"999\"").unwrap();
2041        assert_eq!(val, MoveU256::from_u128(999));
2042    }
2043
2044    #[test]
2045    fn test_move_u256_deserialize_from_u64() {
2046        // Exercises the human-readable visit_u64 path.
2047        let val: MoveU256 = serde_json::from_str("42").unwrap();
2048        assert_eq!(val, MoveU256::from_u128(42));
2049    }
2050
2051    #[test]
2052    fn test_move_u256_deserialize_invalid_type_uses_expecting() {
2053        // A JSON bool is not a valid u256; the resulting error message must
2054        // include the visitor's `expecting` description.
2055        let result: Result<MoveU256, _> = serde_json::from_str("true");
2056        let err = result.unwrap_err();
2057        assert!(
2058            err.to_string().contains("u256"),
2059            "expecting message should mention u256, got: {err}"
2060        );
2061    }
2062}