diff --git a/modules/sdk-coin-apt/src/lib/transaction/customTransaction.ts b/modules/sdk-coin-apt/src/lib/transaction/customTransaction.ts index 78997d73bf..501f8115f4 100644 --- a/modules/sdk-coin-apt/src/lib/transaction/customTransaction.ts +++ b/modules/sdk-coin-apt/src/lib/transaction/customTransaction.ts @@ -10,6 +10,7 @@ import { TransactionPayloadEntryFunction, } from '@aptos-labs/ts-sdk'; import { CustomTransactionParams } from '../iface'; +import { validateModuleName, validateFunctionName } from '../utils/validation'; /** * Transaction class for custom Aptos transactions with entry function payloads. @@ -32,8 +33,8 @@ export class CustomTransaction extends Transaction { * @param {CustomTransactionParams} params - Custom transaction parameters */ setCustomTransactionParams(params: CustomTransactionParams): void { - this.validateModuleName(params.moduleName); - this.validateFunctionName(params.functionName); + validateModuleName(params.moduleName); + validateFunctionName(params.functionName); this.validateAbi(params.abi); this._moduleName = params.moduleName; @@ -84,8 +85,8 @@ export class CustomTransaction extends Transaction { const moduleName = `${moduleAddress}::${moduleIdentifier}`; // Validate the extracted names using our existing validation - this.validateModuleName(moduleName); - this.validateFunctionName(functionIdentifier); + validateModuleName(moduleName); + validateFunctionName(functionIdentifier); this._moduleName = moduleName; this._functionName = functionIdentifier; @@ -128,48 +129,6 @@ export class CustomTransaction extends Transaction { }; } - /** - * Validate module name format - * - * @param {string} moduleName - Module name to validate - * @throws {Error} If module name format is invalid - */ - private validateModuleName(moduleName: string): void { - if (!moduleName || typeof moduleName !== 'string') { - throw new Error('Module name is required and must be a non-empty string'); - } - - // Aptos module name format: address::module_name - // Supports both SHORT (0x1) and LONG (0x0000...0001) address formats - // Also supports named addresses (resolved at deployment time) - const moduleNamePattern = /^(0x[a-fA-F0-9]{1,64}|[a-zA-Z_][a-zA-Z0-9_]*)::[a-zA-Z_][a-zA-Z0-9_]*$/; - if (!moduleNamePattern.test(moduleName)) { - throw new Error( - `Invalid module name format: "${moduleName}". Expected format: "0xaddress::module_name" or "named_address::module_name"` - ); - } - } - - /** - * Validate function name format - * - * @param {string} functionName - Function name to validate - * @throws {Error} If function name format is invalid - */ - private validateFunctionName(functionName: string): void { - if (!functionName || typeof functionName !== 'string') { - throw new Error('Function name is required and must be a non-empty string'); - } - - // Aptos function name pattern: valid identifier (letters, numbers, underscores, starting with letter/underscore) - const functionNamePattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/; - if (!functionNamePattern.test(functionName)) { - throw new Error( - `Invalid function name format: "${functionName}". Function names must be valid identifiers (letters, numbers, underscores, starting with letter or underscore)` - ); - } - } - /** * Override the deprecated recipient getter to handle custom transactions gracefully * Custom transactions may not have traditional recipients diff --git a/modules/sdk-coin-apt/src/lib/transactionBuilder/customTransactionBuilder.ts b/modules/sdk-coin-apt/src/lib/transactionBuilder/customTransactionBuilder.ts index 94e1fcba09..aff8a5bd3d 100644 --- a/modules/sdk-coin-apt/src/lib/transactionBuilder/customTransactionBuilder.ts +++ b/modules/sdk-coin-apt/src/lib/transactionBuilder/customTransactionBuilder.ts @@ -5,6 +5,7 @@ import { Transaction } from '../transaction/transaction'; import { TransactionBuilder } from './transactionBuilder'; import { CustomTransaction } from '../transaction/customTransaction'; import { CustomTransactionParams } from '../iface'; +import { isValidModuleName, isValidFunctionName } from '../utils/validation'; /** * Builder for Aptos custom transactions. @@ -77,23 +78,13 @@ export class CustomTransactionBuilder extends TransactionBuilder { } try { const entryFunction = payload.entryFunction; - // Validate module address format - const moduleAddress = entryFunction.module_name.address.toString(); - if (!moduleAddress.startsWith('0x')) { - return false; - } // Validate module and function identifiers + const moduleAddress = entryFunction.module_name.address.toString(); const moduleIdentifier = entryFunction.module_name.name.identifier; const functionIdentifier = entryFunction.function_name.identifier; - // Check identifier format (letters, numbers, underscores, starting with letter/underscore) - const identifierPattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/; - if (!identifierPattern.test(moduleIdentifier) || !identifierPattern.test(functionIdentifier)) { - return false; - } - // Validate module name format const moduleName = `${moduleAddress}::${moduleIdentifier}`; - const moduleNamePattern = /^0x[a-fA-F0-9]{1,64}::[a-zA-Z_][a-zA-Z0-9_]*$/; - if (!moduleNamePattern.test(moduleName)) { + + if (!isValidModuleName(moduleName) || !isValidFunctionName(functionIdentifier)) { return false; } return true; diff --git a/modules/sdk-coin-apt/src/lib/utils/validation.ts b/modules/sdk-coin-apt/src/lib/utils/validation.ts new file mode 100644 index 0000000000..4e2e6957dc --- /dev/null +++ b/modules/sdk-coin-apt/src/lib/utils/validation.ts @@ -0,0 +1,107 @@ +/** + * Validation utilities for Aptos transactions + */ + +interface ValidationResult { + isValid: boolean; + errorMessage?: string; +} + +/** + * Core validation logic for module names + * + * @param {string} moduleName - Module name to validate + * @returns {ValidationResult} Validation result with isValid flag and optional error message + */ +function validateModuleNameCore(moduleName: string): ValidationResult { + if (!moduleName || typeof moduleName !== 'string') { + return { + isValid: false, + errorMessage: 'Module name is required and must be a non-empty string', + }; + } + + // Aptos module name format: address::module_name + // Supports both SHORT (0x1) and LONG (0x0000...0001) address formats + const moduleNamePattern = /^0x[a-fA-F0-9]{1,64}::[a-zA-Z_][a-zA-Z0-9_]*$/; + if (!moduleNamePattern.test(moduleName)) { + return { + isValid: false, + errorMessage: `Invalid module name format: "${moduleName}". Expected format: "0xaddress::module_name" (hex addresses only)`, + }; + } + + return { isValid: true }; +} + +/** + * Core validation logic for function names + * + * @param {string} functionName - Function name to validate + * @returns {ValidationResult} Validation result with isValid flag and optional error message + */ +function validateFunctionNameCore(functionName: string): ValidationResult { + if (!functionName || typeof functionName !== 'string') { + return { + isValid: false, + errorMessage: 'Function name is required and must be a non-empty string', + }; + } + + // Aptos function name pattern: valid identifier (letters, numbers, underscores, starting with letter/underscore) + const functionNamePattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/; + if (!functionNamePattern.test(functionName)) { + return { + isValid: false, + errorMessage: `Invalid function name format: "${functionName}". Function names must be valid identifiers (letters, numbers, underscores, starting with letter or underscore)`, + }; + } + + return { isValid: true }; +} + +/** + * Validate module name format (throwing version) + * + * @param {string} moduleName - Module name to validate + * @throws {Error} If module name format is invalid + */ +export function validateModuleName(moduleName: string): void { + const result = validateModuleNameCore(moduleName); + if (!result.isValid) { + throw new Error(result.errorMessage); + } +} + +/** + * Validate function name format (throwing version) + * + * @param {string} functionName - Function name to validate + * @throws {Error} If function name format is invalid + */ +export function validateFunctionName(functionName: string): void { + const result = validateFunctionNameCore(functionName); + if (!result.isValid) { + throw new Error(result.errorMessage); + } +} + +/** + * Check if a module name matches the expected pattern (non-throwing version) + * + * @param {string} moduleName - Module name to check + * @returns {boolean} True if valid, false otherwise + */ +export function isValidModuleName(moduleName: string): boolean { + return validateModuleNameCore(moduleName).isValid; +} + +/** + * Check if a function name matches the expected pattern (non-throwing version) + * + * @param {string} functionName - Function name to check + * @returns {boolean} True if valid, false otherwise + */ +export function isValidFunctionName(functionName: string): boolean { + return validateFunctionNameCore(functionName).isValid; +} diff --git a/modules/sdk-coin-apt/test/unit/transactionBuilder/customTransactionBuilder.ts b/modules/sdk-coin-apt/test/unit/transactionBuilder/customTransactionBuilder.ts index c61c91e878..5db308ad82 100644 --- a/modules/sdk-coin-apt/test/unit/transactionBuilder/customTransactionBuilder.ts +++ b/modules/sdk-coin-apt/test/unit/transactionBuilder/customTransactionBuilder.ts @@ -284,7 +284,7 @@ describe('Apt Custom Transaction Builder', () => { ).not.throw(); }); - it('should accept named addresses', async function () { + it('should reject named addresses', async function () { const builder = factory.getCustomTransactionBuilder(); should(() => builder.customTransaction({ @@ -294,7 +294,7 @@ describe('Apt Custom Transaction Builder', () => { functionArguments: [], abi: basicAbi, }) - ).not.throw(); + ).throwError(/Invalid module name format.*hex addresses only/); }); it('should validate transaction payloads properly', async function () {