Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 5 additions & 46 deletions modules/sdk-coin-apt/src/lib/transaction/customTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
107 changes: 107 additions & 0 deletions modules/sdk-coin-apt/src/lib/utils/validation.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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 () {
Expand Down