Skip to content

Commit 4226151

Browse files
committed
refactor(sdk-coin-apt): changes to convert argument by abi method
Ticket: COIN-5799
1 parent 0aabda7 commit 4226151

File tree

3 files changed

+399
-71
lines changed

3 files changed

+399
-71
lines changed

modules/sdk-coin-apt/src/lib/transaction/customTransaction.ts

Lines changed: 104 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { Transaction } from './transaction';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
33
import { InvalidTransactionError, TransactionType } from '@bitgo/sdk-core';
44
import {
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
}

modules/sdk-coin-apt/src/lib/utils.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,24 @@ export class Utils implements BaseUtils {
199199
getTxnExpirationTimestamp(): number {
200200
return Math.floor(Date.now() / 1e3) + SECONDS_PER_WEEK;
201201
}
202+
203+
/**
204+
* Convert bytes array to hex string with 0x prefix
205+
*/
206+
bytesToHex(bytes: number[]): string {
207+
return '0x' + bytes.map((b) => b.toString(16).padStart(2, '0')).join('');
208+
}
209+
210+
/**
211+
* Try to convert hex string to AccountAddress, return original string if conversion fails
212+
*/
213+
tryParseAccountAddress(hexStr: string): AccountAddress | string {
214+
try {
215+
return AccountAddress.fromString(hexStr);
216+
} catch {
217+
return hexStr;
218+
}
219+
}
202220
}
203221

204222
const utils = new Utils();

0 commit comments

Comments
 (0)