@@ -2,13 +2,13 @@ import { Transaction } from './transaction';
22import { BaseCoin as CoinConfig } from '@bitgo/statics' ;
33import { InvalidTransactionError , TransactionType } from '@bitgo/sdk-core' ;
44import {
5+ AccountAddress ,
56 EntryFunctionABI ,
67 EntryFunctionArgumentTypes ,
78 SimpleEntryFunctionArgumentTypes ,
89 InputGenerateTransactionPayloadData ,
910 TransactionPayload ,
1011 TransactionPayloadEntryFunction ,
11- AccountAddress ,
1212 TypeTagAddress ,
1313 TypeTagBool ,
1414 TypeTagU8 ,
@@ -136,89 +136,119 @@ export class CustomTransaction extends Transaction {
136136 * Convert argument based on ABI type information
137137 */
138138 private convertArgumentByABI ( arg : any , paramType : any ) : any {
139- // Helper function to convert bytes to hex string
140- const bytesToHex = ( bytes : number [ ] ) : string => {
141- return '0x' + bytes . map ( ( b ) => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
142- } ;
143-
144- // Helper function to try converting a hex string to an AccountAddress
145- const tryToAddress = ( hexStr : string ) : any => {
146- try {
147- return AccountAddress . fromString ( hexStr ) ;
148- } catch {
149- return hexStr ;
150- }
151- } ;
152-
153139 // Handle primitive values (string, number, boolean)
154140 if ( typeof arg === 'string' || typeof arg === 'number' || typeof arg === 'boolean' ) {
155- // Address conversion for hex strings
156- if ( paramType instanceof TypeTagAddress && typeof arg === 'string' && arg . startsWith ( '0x' ) ) {
157- return tryToAddress ( arg ) ;
158- }
159-
160- // Type conversions based on parameter type
161- if ( paramType instanceof TypeTagBool ) return Boolean ( arg ) ;
162- if ( paramType instanceof TypeTagU8 || paramType instanceof TypeTagU16 || paramType instanceof TypeTagU32 )
163- return Number ( arg ) ;
164- if ( paramType instanceof TypeTagU64 || paramType instanceof TypeTagU128 || paramType instanceof TypeTagU256 )
165- return String ( arg ) ;
166-
167- return arg ;
141+ return this . convertPrimitiveArgument ( arg , paramType ) ;
168142 }
169143
170144 // Handle BCS-encoded data with 'data' property
171145 if ( arg && typeof arg === 'object' && 'data' in arg && arg . data ) {
172- const bytes = Array . from ( arg . data ) as number [ ] ;
173- const hexString = bytesToHex ( bytes ) ;
174-
175- return paramType instanceof TypeTagAddress ? tryToAddress ( hexString ) : hexString ;
146+ return this . convertBcsDataArgument ( arg , paramType ) ;
176147 }
177148
178149 // Handle nested BCS structures with 'value' property
179150 if ( arg && typeof arg === 'object' && 'value' in arg && arg . value ) {
180- // Check if inner value is a Uint8Array (common for U64 arguments)
181- if ( arg . value . value && arg . value . value instanceof Uint8Array ) {
182- const bytes = Array . from ( arg . value . value ) as number [ ] ;
183- if ( this . isNumericType ( paramType ) ) {
184- return this . convertNumericArgument ( bytes , paramType ) ;
185- }
186- // For non-numeric types, convert to hex string
187- return bytesToHex ( bytes ) ;
188- }
151+ return this . convertNestedBcsArgument ( arg , paramType ) ;
152+ }
189153
190- // Simple value wrapper
191- if ( ! ( 'value' in arg . value ) || typeof arg . value . value !== 'object' ) {
192- return this . convertArgumentByABI ( arg . value , paramType ) ;
193- }
154+ // For anything else, return as-is
155+ return arg ;
156+ }
157+
158+ /**
159+ * Convert primitive argument values based on parameter type
160+ */
161+ private convertPrimitiveArgument (
162+ arg : string | number | boolean ,
163+ paramType : any
164+ ) : string | number | boolean | AccountAddress {
165+ // Address conversion for hex strings
166+ if ( paramType instanceof TypeTagAddress && typeof arg === 'string' && arg . startsWith ( '0x' ) ) {
167+ return utils . tryParseAccountAddress ( arg ) ;
168+ }
169+
170+ // Type conversions based on parameter type
171+ if ( paramType instanceof TypeTagBool ) return Boolean ( arg ) ;
172+
173+ // Use unified numeric type handling
174+ if ( this . isNumericType ( paramType ) ) {
175+ return this . convertPrimitiveNumericArgument ( arg ) ;
176+ }
194177
195- // Double nested structure with numeric keys
196- const bytesObj = arg . value . value ;
197- const keys = Object . keys ( bytesObj )
198- . filter ( ( k ) => ! isNaN ( parseInt ( k , 10 ) ) )
199- . sort ( ( a , b ) => parseInt ( a , 10 ) - parseInt ( b , 10 ) ) ;
178+ return arg ;
179+ }
200180
201- if ( keys . length === 0 ) return arg ;
181+ /**
182+ * Convert primitive numeric arguments to string
183+ * - Big numbers break JavaScript (precision loss)
184+ * - String safer for large U64/U128/U256 values
185+ * - Aptos SDK converts string to correct type automatically
186+ */
187+ private convertPrimitiveNumericArgument ( arg : string | number | boolean ) : string {
188+ // Always string - safer for big numbers
189+ return String ( arg ) ;
190+ }
202191
203- const bytes = keys . map ( ( k ) => bytesObj [ k ] ) ;
204- let extractedValue : any ;
192+ /**
193+ * Convert BCS data argument with 'data' property
194+ */
195+ private convertBcsDataArgument ( arg : any , paramType : any ) : string | AccountAddress {
196+ const bytes = Array . from ( arg . data ) as number [ ] ;
197+ const hexString = utils . bytesToHex ( bytes ) ;
205198
206- // Convert bytes based on parameter type using unified approach
199+ return paramType instanceof TypeTagAddress ? utils . tryParseAccountAddress ( hexString ) : hexString ;
200+ }
201+
202+ /**
203+ * Convert nested BCS argument with 'value' property
204+ */
205+ private convertNestedBcsArgument ( arg : any , paramType : any ) : any {
206+ // Check if inner value is a Uint8Array (common for U64 arguments)
207+ if ( arg . value . value && arg . value . value instanceof Uint8Array ) {
208+ const bytes = Array . from ( arg . value . value ) as number [ ] ;
207209 if ( this . isNumericType ( paramType ) ) {
208- extractedValue = this . convertNumericArgument ( bytes , paramType ) ;
209- } else if ( paramType instanceof TypeTagAddress ) {
210- extractedValue = bytesToHex ( bytes ) ;
211- } else if ( paramType instanceof TypeTagBool ) {
212- extractedValue = bytes [ 0 ] === 1 ;
213- } else {
214- extractedValue = bytesToHex ( bytes ) ;
210+ return this . convertNumericArgument ( bytes , paramType ) ;
215211 }
212+ // For non-numeric types, convert to hex string
213+ return utils . bytesToHex ( bytes ) ;
214+ }
216215
217- return this . convertArgumentByABI ( extractedValue , paramType ) ;
216+ // Simple value wrapper - fix the bug in original implementation
217+ if ( typeof arg . value !== 'object' || ! ( 'value' in arg . value ) ) {
218+ return this . convertArgumentByABI ( arg . value , paramType ) ;
218219 }
219220
220- // For anything else, return as-is
221- return arg ;
221+ // Double nested structure with numeric keys
222+ const bytes = this . extractBytesFromBcsObject ( arg . value . value ) ;
223+ if ( bytes . length === 0 ) return arg ;
224+
225+ let extractedValue : any ;
226+
227+ // Convert bytes based on parameter type using unified approach
228+ if ( this . isNumericType ( paramType ) ) {
229+ extractedValue = this . convertNumericArgument ( bytes , paramType ) ;
230+ } else if ( paramType instanceof TypeTagAddress ) {
231+ extractedValue = utils . bytesToHex ( bytes ) ;
232+ } else if ( paramType instanceof TypeTagBool ) {
233+ extractedValue = bytes [ 0 ] === 1 ;
234+ } else {
235+ extractedValue = utils . bytesToHex ( bytes ) ;
236+ }
237+
238+ return this . convertArgumentByABI ( extractedValue , paramType ) ;
239+ }
240+
241+ /**
242+ * Extract bytes from BCS object with numeric keys
243+ */
244+ private extractBytesFromBcsObject ( bcsObject : any ) : number [ ] {
245+ const keys = Object . keys ( bcsObject )
246+ . filter ( ( k ) => ! isNaN ( parseInt ( k , 10 ) ) )
247+ . sort ( ( a , b ) => parseInt ( a , 10 ) - parseInt ( b , 10 ) ) ;
248+
249+ if ( keys . length === 0 ) return [ ] ;
250+
251+ return keys . map ( ( k ) => bcsObject [ k ] ) ;
222252 }
223253
224254 /**
@@ -282,24 +312,27 @@ export class CustomTransaction extends Transaction {
282312 }
283313
284314 /**
285- * Convert numeric argument using consistent little-endian byte handling
315+ * Convert byte arrays to string numbers
316+ * - Small types (U8/U16/U32): 1-4 bytes, parse manually
317+ * - Large types (U64/U128/U256): 8+ bytes, use existing utility
318+ * - Both return string for consistency
286319 */
287- private convertNumericArgument ( bytes : number [ ] , paramType : any ) : any {
320+ private convertNumericArgument ( bytes : number [ ] , paramType : any ) : string {
288321 if ( paramType instanceof TypeTagU8 || paramType instanceof TypeTagU16 || paramType instanceof TypeTagU32 ) {
289- // Small integers: use Number for compatibility
322+ // Small types: parse bytes manually (1-4 bytes)
290323 let result = 0 ;
291324 for ( let i = bytes . length - 1 ; i >= 0 ; i -- ) {
292325 result = result * 256 + bytes [ i ] ;
293326 }
294- return result ;
327+ return result . toString ( ) ;
295328 }
296329
297330 if ( paramType instanceof TypeTagU64 || paramType instanceof TypeTagU128 || paramType instanceof TypeTagU256 ) {
298- // Large integers: reuse the existing utility method
331+ // Large types: use existing method (needs 8+ bytes)
299332 return utils . getAmountFromPayloadArgs ( new Uint8Array ( bytes ) ) ;
300333 }
301334
302- // Fallback for unexpected numeric types
303- return '0x' + bytes . map ( ( b ) => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( '' ) ;
335+ // Unknown type: convert to hex
336+ return utils . bytesToHex ( bytes ) ;
304337 }
305338}
0 commit comments