@@ -108,6 +108,66 @@ export function standardizeTypeTags(typeArguments?: Array<TypeArgument>): Array<
108108 */
109109const MODULE_ABI_CACHE_TTL_MS = 5 * 60 * 1000 ;
110110
111+ /**
112+ * Represents a bundle of a module ABI along with all struct ABIs it references.
113+ * This allows for offline struct/enum encoding without additional network calls.
114+ */
115+ export type ModuleAbiBundle = {
116+ /** The main module ABI */
117+ module : MoveModule ;
118+ /** Map of referenced struct ABIs: "address::module::struct" -> MoveModule */
119+ referencedStructModules : Map < string , MoveModule > ;
120+ } ;
121+
122+ /**
123+ * Extracts all struct type references from a module's struct fields and function parameters.
124+ * Returns a set of unique module identifiers (address::moduleName) that need to be fetched.
125+ *
126+ * @param module - The module to extract struct references from
127+ * @returns Set of module IDs referenced by this module (e.g., "0x1::string", "0x123::my_module")
128+ */
129+ function extractReferencedStructModules ( module : MoveModule ) : Set < string > {
130+ const referencedModules = new Set < string > ( ) ;
131+ const moduleId = `${ module . address } ::${ module . name } ` ;
132+
133+ // Helper to parse a type string and extract struct references
134+ const parseTypeForStructs = ( typeStr : string ) => {
135+ // Match patterns like: 0x1::module::Struct, address::module::Struct<T>
136+ // Handles generic types and vectors: vector<0x1::module::Struct<T>>
137+ const structPattern = / ( 0 x [ a - f A - F 0 - 9 ] + ) : : ( [ \w _ ] + ) : : ( [ \w _ ] + ) / g;
138+ let match ;
139+
140+ while ( ( match = structPattern . exec ( typeStr ) ) !== null ) {
141+ const [ , address , modName ] = match ;
142+ const refModuleId = `${ address } ::${ modName } ` ;
143+
144+ // Don't include self-references or standard library types that don't need fetching
145+ if ( refModuleId !== moduleId && ! refModuleId . startsWith ( "0x1::" ) ) {
146+ referencedModules . add ( refModuleId ) ;
147+ }
148+ }
149+ } ;
150+
151+ // Extract from struct fields
152+ for ( const struct of module . structs ) {
153+ for ( const field of struct . fields ) {
154+ parseTypeForStructs ( field . type ) ;
155+ }
156+ }
157+
158+ // Extract from function parameters and return types
159+ for ( const func of module . exposed_functions ) {
160+ for ( const param of func . params ) {
161+ parseTypeForStructs ( param ) ;
162+ }
163+ for ( const ret of func . return ) {
164+ parseTypeForStructs ( ret ) ;
165+ }
166+ }
167+
168+ return referencedModules ;
169+ }
170+
111171/**
112172 * Fetches the ABI of a specified module from the on-chain module ABI.
113173 * Results are cached for 5 minutes to reduce redundant network calls.
@@ -135,6 +195,70 @@ export async function fetchModuleAbi(
135195 ) ( ) ;
136196}
137197
198+ /**
199+ * Fetches a module ABI along with all struct ABIs it references.
200+ * This optimization minimizes nested network calls when encoding struct/enum arguments.
201+ *
202+ * Strategy:
203+ * - Fetches the main module ABI
204+ * - Parses all type references in struct fields and function parameters
205+ * - Fetches ABIs for all referenced struct modules in parallel
206+ * - Caches the complete bundle together
207+ *
208+ * @param moduleAddress - The address of the module from which to fetch the ABI.
209+ * @param moduleName - The name of the module containing the ABI.
210+ * @param aptosConfig - The configuration settings for Aptos.
211+ * @returns ModuleAbiBundle containing the module and all referenced struct modules
212+ * @group Implementation
213+ * @category Transactions
214+ */
215+ export async function fetchModuleAbiWithStructs (
216+ moduleAddress : string ,
217+ moduleName : string ,
218+ aptosConfig : AptosConfig ,
219+ ) : Promise < ModuleAbiBundle > {
220+ const cacheKey = `module-abi-bundle-${ aptosConfig . network } -${ moduleAddress } -${ moduleName } ` ;
221+
222+ return memoizeAsync (
223+ async ( ) => {
224+ // Fetch the main module ABI
225+ const module = await fetchModuleAbi ( moduleAddress , moduleName , aptosConfig ) ;
226+ if ( ! module ) {
227+ throw new Error ( `Module not found: ${ moduleAddress } ::${ moduleName } ` ) ;
228+ }
229+
230+ // Extract all struct modules referenced by this module
231+ const referencedModuleIds = extractReferencedStructModules ( module ) ;
232+ const referencedStructModules = new Map < string , MoveModule > ( ) ;
233+
234+ // Fetch all referenced struct modules in parallel
235+ if ( referencedModuleIds . size > 0 ) {
236+ const fetchPromises = Array . from ( referencedModuleIds ) . map ( async ( moduleId ) => {
237+ const [ addr , modName ] = moduleId . split ( "::" ) ;
238+ try {
239+ const structModule = await fetchModuleAbi ( addr , modName , aptosConfig ) ;
240+ if ( structModule ) {
241+ referencedStructModules . set ( moduleId , structModule ) ;
242+ }
243+ } catch ( error ) {
244+ // Log warning but don't fail - the struct might not be used in this execution path
245+ console . warn ( `Failed to fetch referenced module ${ moduleId } : ${ error } ` ) ;
246+ }
247+ } ) ;
248+
249+ await Promise . all ( fetchPromises ) ;
250+ }
251+
252+ return {
253+ module,
254+ referencedStructModules,
255+ } ;
256+ } ,
257+ cacheKey ,
258+ MODULE_ABI_CACHE_TTL_MS ,
259+ ) ( ) ;
260+ }
261+
138262/**
139263 * Fetches the ABI of a specified function from the on-chain module ABI. This function allows you to access the details of a
140264 * specific function within a module.
@@ -1035,19 +1159,34 @@ async function parseArgAsync(
10351159 ) ;
10361160 }
10371161
1038- // Instantiate the parser
1039- const parser = new StructEnumArgumentParser ( aptosConfig ) ;
1162+ // Fetch the module ABI bundle with all referenced struct modules
1163+ // This optimization minimizes nested network calls
1164+ const moduleAddress = param . value . address . toString ( ) ;
1165+ const moduleName = param . value . moduleName . identifier ;
10401166
1041- // Check if this is an enum or struct by examining the structure
1042- // Enums have format: { "VariantName": {...} } with a single key
1043- const keys = Object . keys ( arg as Record < string , any > ) ;
1044- const isLikelyEnumVariant = keys . length === 1 && typeof ( arg as Record < string , any > ) [ keys [ 0 ] ] === "object" ;
1167+ try {
1168+ const abiBundle = await fetchModuleAbiWithStructs ( moduleAddress , moduleName , aptosConfig ) ;
10451169
1046- // For ambiguous cases, check the ABI if available
1047- const structDef = moduleAbi ?. structs . find ( ( s ) => s . name === param . value . name . identifier ) ;
1048- const isEnumType = structDef ?. is_enum ?? false ;
1170+ // Instantiate the parser and preload it with all referenced struct modules
1171+ const parser = new StructEnumArgumentParser ( aptosConfig ) ;
1172+
1173+ // Convert the bundle's modules to MoveModuleBytecode format for preloading
1174+ const modulesToPreload = new Map < string , any > ( ) ;
1175+ modulesToPreload . set ( `${ moduleAddress } ::${ moduleName } ` , { abi : abiBundle . module } ) ;
1176+ for ( const [ moduleId , module ] of abiBundle . referencedStructModules . entries ( ) ) {
1177+ modulesToPreload . set ( moduleId , { abi : module } ) ;
1178+ }
1179+ parser . preloadModules ( modulesToPreload ) ;
1180+
1181+ // Check if this is an enum or struct by examining the structure
1182+ // Enums have format: { "VariantName": {...} } with a single key
1183+ const keys = Object . keys ( arg as Record < string , any > ) ;
1184+ const isLikelyEnumVariant = keys . length === 1 && typeof ( arg as Record < string , any > ) [ keys [ 0 ] ] === "object" ;
1185+
1186+ // For ambiguous cases, check the ABI from the bundle
1187+ const structDef = abiBundle . module . structs . find ( ( s ) => s . name === param . value . name . identifier ) ;
1188+ const isEnumType = structDef ?. is_enum ?? false ;
10491189
1050- try {
10511190 if ( isEnumType || isLikelyEnumVariant ) {
10521191 // Encode as enum
10531192 return await parser . encodeEnumArgument ( param , arg ) ;
0 commit comments