dop-stick
Version:
Source control tooling for versionable-upgradeable smart contracts
1,012 lines • 41.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypechainProcessor = void 0;
const abi_1 = require("@ethersproject/abi");
const fs_1 = require("fs");
const fs_2 = require("fs");
const path_1 = __importDefault(require("path"));
const logger_1 = require("../logsAndMetrics/core/logger");
const types_1 = require("./types");
const abi_cache_1 = require("../cache/abi-cache");
class TypechainProcessor {
constructor(typechainPath = 'typechain-types', enableCache = true) {
this.initialized = false;
this.initializationPromise = null;
this.eventCache = new Map();
this.eventCacheTimestamps = new Map();
this.enableCache = enableCache;
this.typechainPath = path_1.default.join(process.cwd(), typechainPath);
this.abiCache = abi_cache_1.AbiCache.getInstance();
this.selectorLookupCache = new Map();
this.facetsCache = new Map();
this.factoryMap = new Map();
this.eventHandlers = new Map();
this.optimizedLookup = {
selectorToFacet: new Map(),
facetToSelectors: new Map(),
metadata: {
lastUpdate: new Date(),
version: TypechainProcessor.VERSION,
entries: 0
}
};
if (!(0, fs_2.existsSync)(this.typechainPath)) {
throw new types_1.TypechainProcessorError(`Typechain directory not found at: ${this.typechainPath}`, 'TYPECHAIN_DIR_NOT_FOUND');
}
}
static getInstance(typechainPath, enableCache = true) {
if (!TypechainProcessor.instance) {
TypechainProcessor.instance = new TypechainProcessor(typechainPath, enableCache);
}
return TypechainProcessor.instance;
}
async ensureInitialized() {
if (this.initializationPromise) {
await this.initializationPromise;
return;
}
if (!this.initialized) {
this.initializationPromise = this.initialize();
await this.initializationPromise;
}
}
async initialize() {
if (this.initialized)
return;
if (this.initializationPromise)
return this.initializationPromise;
this.initializationPromise = (async () => {
try {
const startTime = performance.now();
// Try to load from cache first
if (this.enableCache) {
const loaded = await this.loadFromCache();
if (loaded) {
// Rebuild event cache from facetsCache
for (const [contractName, processed] of this.facetsCache) {
if (processed.facetInfo.events.size > 0) {
const cleanedName = this.cleanContractName(contractName);
this.eventCache.set(cleanedName, processed.facetInfo.events);
this.eventCacheTimestamps.set(cleanedName, Date.now());
logger_1.Logger.debug(`Restored ${processed.facetInfo.events.size} events for facet: ${cleanedName}`);
}
}
this.initialized = true;
logger_1.Logger.info('Initialized from cache successfully');
return;
}
}
// If no cache or cache disabled, build from scratch
this.eventCache.clear();
this.eventCacheTimestamps.clear();
// First load factory mappings
await this.loadFactoryMappings();
logger_1.Logger.debug(`Loaded ${this.factoryMap.size} factory mappings`);
// Then build the optimized lookup
await this.buildOptimizedLookup();
// Save to cache if enabled
if (this.enableCache) {
await this.saveToCache();
}
const duration = ((performance.now() - startTime) / 1000).toFixed(1);
this.initialized = true;
logger_1.Logger.info(`TypechainProcessor initialized in ${duration}s`);
}
catch (error) {
logger_1.Logger.error('Failed to initialize TypechainProcessor:', error);
throw error;
}
finally {
this.initializationPromise = null;
}
})();
return this.initializationPromise;
}
async loadFactoryMappings() {
try {
const files = await this.findTypechainFiles();
for (const file of files) {
const content = await fs_1.promises.readFile(file, 'utf8');
const matches = Array.from(content.matchAll(TypechainProcessor.FACTORY_PATTERN));
for (const match of matches) {
const [_, contractName, importPath] = match;
if (contractName && importPath) {
// Clean the contract name before storing
const cleanedName = this.cleanContractName(contractName);
this.factoryMap.set(cleanedName, importPath);
}
}
}
logger_1.Logger.debug(`Loaded ${this.factoryMap.size} factory mappings`);
}
catch (error) {
logger_1.Logger.error('Failed to load factory mappings:', error);
throw error;
}
}
async findTypechainFiles() {
const files = [];
async function walk(dir) {
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path_1.default.join(dir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
}
else if (entry.isFile() && entry.name.endsWith('.ts')) {
files.push(fullPath);
}
}
}
await walk(this.typechainPath);
return files;
}
async getFactoryInfo(contractName) {
await this.ensureInitialized();
return this.factoryMap.get(contractName) || null;
}
async listFactories() {
await this.ensureInitialized();
return new Map(this.factoryMap);
}
async buildOptimizedLookup() {
try {
const startTime = performance.now();
const total = this.factoryMap.size;
let processed = 0;
let errors = 0;
const entries = Array.from(this.factoryMap.entries());
const chunks = entries.reduce((acc, curr, i) => {
const chunkIndex = Math.floor(i / TypechainProcessor.CONCURRENCY_LIMIT);
acc[chunkIndex] = [...(acc[chunkIndex] || []), curr];
return acc;
}, []);
for (const chunk of chunks) {
await Promise.all(chunk.map(async ([contractName, factoryPath]) => {
try {
logger_1.Logger.debug(`Processing facet: ${contractName} from ${factoryPath}`);
const facetInfo = await this.processFacet(contractName, factoryPath);
if (facetInfo) {
const cleanedName = this.cleanContractName(contractName);
this.updateLookupStructures(cleanedName, facetInfo);
// Store in facetsCache with events
this.facetsCache.set(cleanedName, {
facetInfo: {
...facetInfo,
events: facetInfo.events
},
timestamp: Date.now(),
metadata: {
lastProcessed: new Date(),
version: TypechainProcessor.VERSION,
functionCount: facetInfo.functions.size,
eventCount: facetInfo.events.size
}
});
// Cache events
if (facetInfo.events.size > 0) {
this.eventCache.set(cleanedName, facetInfo.events);
this.eventCacheTimestamps.set(cleanedName, Date.now());
logger_1.Logger.debug(`Cached ${facetInfo.events.size} events for facet: ${cleanedName}`);
}
processed++;
if (processed % Math.max(1, Math.floor(total / 10)) === 0) {
const progress = (processed / total * 100).toFixed(1);
logger_1.Logger.info(`Processing progress: ${progress}% (${processed}/${total})`);
}
}
else {
errors++;
logger_1.Logger.warn(`No facet info returned for ${contractName}`);
}
}
catch (error) {
errors++;
logger_1.Logger.error(`Error processing ${contractName} from ${factoryPath}:`, error);
}
}));
}
const duration = ((performance.now() - startTime) / 1000).toFixed(1);
logger_1.Logger.info(`Built optimized lookup in ${duration}s: ${processed}/${total} facets processed (${errors} errors)`);
if (errors > 0) {
logger_1.Logger.warn(`Failed to process ${errors} facets`);
}
}
catch (error) {
logger_1.Logger.error('Failed to build optimized lookup:', error);
throw error;
}
}
updateLookupStructures(contractName, facetInfo) {
if (!contractName || !facetInfo) {
logger_1.Logger.warn('Invalid input to updateLookupStructures');
return;
}
try {
// Update selector to facet mapping
for (const [selector, functionInfo] of facetInfo.enhancedFunctions) {
if (!selector || !functionInfo)
continue;
this.optimizedLookup.selectorToFacet.set(selector, {
facetName: contractName,
functionInfo
});
}
// Update facet to selectors mapping
this.optimizedLookup.facetToSelectors.set(contractName, new Set(facetInfo.enhancedFunctions.keys()));
// Update metadata
this.optimizedLookup.metadata.entries = this.optimizedLookup.selectorToFacet.size;
this.optimizedLookup.metadata.lastUpdate = new Date();
}
catch (error) {
logger_1.Logger.error(`Error updating lookup structures for ${contractName}:`, error);
}
}
async processFacet(contractName, factoryPath) {
try {
// Clean up the contract name by removing any function names and factory suffixes
const cleanedContractName = this.cleanContractName(contractName);
const fullPath = path_1.default.join(this.typechainPath, factoryPath.endsWith('.ts') ? factoryPath : `${factoryPath}.ts`);
if (!(0, fs_2.existsSync)(fullPath)) {
logger_1.Logger.warn(`Factory file not found: ${fullPath}`);
return null;
}
const content = await fs_1.promises.readFile(fullPath, 'utf8');
const abiString = await this.extractAndCleanAbi(content);
if (!abiString)
return null;
const abi = JSON.parse(abiString);
const iface = new abi_1.Interface(abi);
// Process functions and events in parallel
const [enhancedFunctions, enhancedEvents] = await Promise.all([
this.processContractFunctions(abi, iface),
this.processContractEvents(abi)
]);
const facetInfo = {
name: cleanedContractName,
path: factoryPath,
abi,
selectors: new Set(enhancedFunctions.keys()),
functions: new Map(),
events: new Map(),
enhancedFunctions,
enhancedEvents
};
// Convert enhanced functions to legacy format using Map
for (const [selector, func] of enhancedFunctions) {
facetInfo.functions.set(selector, {
name: func.name,
signature: func.signature,
fragment: func.fragment
});
}
// Convert events to Map format
for (const [eventName, event] of enhancedEvents) {
facetInfo.events.set(eventName, event.fragment);
}
return facetInfo;
}
catch (error) {
logger_1.Logger.error(`Failed to process facet ${contractName}:`, error);
return null;
}
}
cleanContractName(name) {
// Remove common suffixes and function names
return name
.replace(/__factory$/, '') // Remove factory suffix
.replace(/Factory$/, '') // Also remove 'Factory' suffix without underscores
.replace(/^get/, '') // Remove 'get' prefix if it exists
.replace(/([A-Z])/g, ' $1') // Add space before capital letters
.trim() // Remove extra spaces
.replace(/\s+/g, '') // Remove all spaces
.replace(/_+/g, '') // Remove underscores
.replace(/Function$/, '') // Remove 'Function' suffix
.replace(/Method$/, ''); // Remove 'Method' suffix
}
async extractAndCleanAbi(content) {
try {
// First try to match the const _abi declaration with 'as const'
const abiMatch = content.match(/const\s+_abi\s*=\s*(\[[\s\S]*?\])\s*as\s*const/);
if (abiMatch && abiMatch[1]) {
let abiString = abiMatch[1];
// Clean up the string
abiString = abiString
.replace(/\s+/g, ' ') // Normalize whitespace
.replace(/,(\s*[}\]])/g, '$1') // Remove trailing commas
.trim(); // Remove leading/trailing whitespace
try {
// Parse to validate
const abi = JSON.parse(abiString);
if (Array.isArray(abi) && abi.length > 0) {
// Validate ABI structure
const isValid = abi.every(item => typeof item === 'object' &&
item !== null &&
((item.type === 'function' && 'inputs' in item && 'outputs' in item) ||
(item.type === 'event' && 'inputs' in item && 'name' in item) ||
(item.type === 'constructor' && 'inputs' in item) ||
(item.type === 'fallback') ||
(item.type === 'receive')));
if (isValid) {
return JSON.stringify(abi);
}
}
}
catch (parseError) {
logger_1.Logger.debug('Initial parse failed, attempting cleanup:', parseError);
// Additional cleanup for common typechain formatting
abiString = abiString
.replace(/(\w+):/g, '"$1":') // Quote unquoted property names
.replace(/'/g, '"') // Replace single quotes with double quotes
.replace(/,(\s*[}\]])/g, '$1'); // Remove trailing commas again after modifications
try {
const cleanedAbi = JSON.parse(abiString);
if (Array.isArray(cleanedAbi) && cleanedAbi.length > 0) {
return JSON.stringify(cleanedAbi);
}
}
catch (secondError) {
logger_1.Logger.debug('Cleanup parse failed:', secondError);
}
}
}
logger_1.Logger.debug('No valid ABI found in content');
logger_1.Logger.debug('Content preview:', content.substring(0, 200) + '...');
return null;
}
catch (error) {
logger_1.Logger.error('Error extracting ABI:', error);
return null;
}
}
findCompleteJsonStructure(text) {
try {
let stack = [];
let start = text.indexOf('[') !== -1 ? text.indexOf('[') : text.indexOf('{');
if (start === -1)
return null;
let openChar = text[start];
let closeChar = openChar === '[' ? ']' : '}';
for (let i = start; i < text.length; i++) {
if (text[i] === openChar) {
stack.push(openChar);
}
else if (text[i] === closeChar) {
stack.pop();
if (stack.length === 0) {
// Found complete structure
return text.substring(start, i + 1);
}
}
}
return null;
}
catch (error) {
logger_1.Logger.debug('Error finding complete JSON structure:', error);
return null;
}
}
validateAndCleanAbi(abiString) {
try {
// Remove any trailing commas
abiString = abiString.replace(/,(\s*[}\]])/g, '$1');
// Handle 'as const' syntax
abiString = abiString.replace(/\s+as\s+const\s*$/, '');
// Parse to validate
const abi = JSON.parse(abiString);
// Validate it's an array or object with ABI-like structure
if (Array.isArray(abi)) {
if (abi.length === 0)
return null;
// Check if array contains ABI-like objects
const hasValidEntries = abi.some(entry => typeof entry === 'object' && entry !== null &&
('type' in entry || 'name' in entry || 'inputs' in entry || 'outputs' in entry));
if (!hasValidEntries)
return null;
}
else if (typeof abi === 'object' && abi !== null) {
// Check if object has ABI-like properties
const hasValidProps = Object.values(abi).some(value => typeof value === 'object' && value !== null &&
('type' in value || 'name' in value || 'inputs' in value || 'outputs' in value));
if (!hasValidProps)
return null;
}
else {
return null;
}
return JSON.stringify(abi);
}
catch (error) {
logger_1.Logger.debug('Failed to validate and clean ABI:', error);
return null;
}
}
async processContractFunctions(abi, iface) {
const functions = new Map();
for (const item of abi) {
if (item.type !== 'function')
continue;
if (!this.validateFunctionAbi(item)) {
logger_1.Logger.debug(`Skipping invalid function ABI:`, item);
continue;
}
try {
const fragment = abi_1.FunctionFragment.from(item);
const selector = iface.getSighash(fragment).toLowerCase();
functions.set(selector, {
name: fragment.name,
fullName: fragment.format('full'),
selector,
signature: fragment.format(),
fragment,
inputs: this.processParameters(item.inputs || []),
outputs: this.processParameters(item.outputs || []),
mutability: item.stateMutability,
visibility: item.visibility || 'external'
});
}
catch (error) {
logger_1.Logger.warn(`Failed to process function ${item.name}:`, error);
}
}
return functions;
}
async processContractEvents(abi) {
const events = new Map();
for (const item of abi) {
if (item.type !== 'event')
continue;
try {
const fragment = abi_1.EventFragment.from(item);
events.set(fragment.name, {
name: fragment.name,
signature: fragment.format(),
inputs: this.processParameters(item.inputs || []),
anonymous: !!item.anonymous,
fragment
});
}
catch (error) {
logger_1.Logger.warn(`Failed to process event ${item.name}:`, error);
}
}
return events;
}
// Optimized selector lookup
async findContractBySelectors(selectors) {
await this.ensureInitialized();
if (selectors.length === 0)
return null;
// Fast path: check in-memory cache first
const firstSelector = selectors[0].toLowerCase();
const cachedMapping = this.selectorLookupCache.get(firstSelector);
if (cachedMapping) {
// For single selector queries, return immediately
if (selectors.length === 1) {
return this.createContractInfo(cachedMapping.facetName, [firstSelector]);
}
// For multiple selectors, verify all belong to the same facet
const facetSelectors = this.optimizedLookup.facetToSelectors.get(cachedMapping.facetName);
if (facetSelectors && selectors.every(s => facetSelectors.has(s.toLowerCase()))) {
return this.createContractInfo(cachedMapping.facetName, selectors);
}
}
// Slower path: check optimized lookup
const mapping = this.optimizedLookup.selectorToFacet.get(firstSelector);
if (!mapping)
return null;
// Cache the result for future lookups
this.selectorLookupCache.set(firstSelector, mapping);
return this.createContractInfo(mapping.facetName, selectors);
}
// Efficient contract info creation with minimal object copying
async createContractInfo(facetName, selectors) {
const functions = {};
// Batch process selectors
for (const selector of selectors) {
const info = this.optimizedLookup.selectorToFacet.get(selector);
if (info) {
functions[selector] = info.functionInfo;
}
}
return {
name: facetName,
path: this.factoryMap.get(facetName) || '',
functions
};
}
// Efficient batch processing of parameters
processParameters(params) {
return params.map(param => ({
name: param.name || '',
type: param.type,
...(param.indexed !== undefined && { indexed: param.indexed }),
...(param.components && {
components: this.processParameters(param.components)
})
}));
}
// Memory-efficient ABI string cleaning with minimal string operations
cleanAbiString(abiString) {
const chunks = [];
let inString = false;
let current = '';
let depth = 0;
// Single-pass processing
for (let i = 0; i < abiString.length; i++) {
const char = abiString[i];
// Handle strings
if (char === '"' && abiString[i - 1] !== '\\') {
inString = !inString;
}
// Track nested structures
if (!inString) {
if (char === '{' || char === '[')
depth++;
if (char === '}' || char === ']')
depth--;
}
// Efficient string building
if (inString || /[^\s,]/.test(char) || depth > 0) {
current += char;
}
// Process complete chunks
if (depth === 0 && current.length > 0 &&
(char === '}' || char === ']')) {
chunks.push(current);
current = '';
}
}
return chunks.join(',');
}
// Fast cache operations
getCachedData(key) {
const cached = this.facetsCache.get(key);
if (!cached)
return null;
const age = Date.now() - cached.timestamp;
if (age > TypechainProcessor.CACHE_DURATION) {
this.facetsCache.delete(key);
return null;
}
return cached;
}
// Efficient batch operations
async batchProcessSelectors(selectorsBatch) {
await this.ensureInitialized();
// Process all batches in parallel with controlled concurrency
const results = await Promise.all(selectorsBatch.map(selectors => this.findContractBySelectors(selectors)));
return results;
}
// Fast contract lookup
async getContractInfo(contractName) {
await this.ensureInitialized();
const selectors = this.optimizedLookup.facetToSelectors.get(contractName);
if (!selectors)
return null;
const cached = this.getCachedData(contractName);
if (cached)
return cached.facetInfo;
return null;
}
// Performance metrics
getPerformanceMetrics() {
return {
totalContracts: this.facetsCache.size,
totalSelectors: this.optimizedLookup.selectorToFacet.size,
cacheSize: JSON.stringify(this.facetsCache).length,
lastUpdateTimestamp: this.optimizedLookup.metadata.lastUpdate.getTime()
};
}
// Memory management
clearCache() {
this.facetsCache.clear();
this.optimizedLookup.selectorToFacet.clear();
this.optimizedLookup.facetToSelectors.clear();
this.optimizedLookup.metadata.lastUpdate = new Date();
this.optimizedLookup.metadata.entries = 0;
}
// ABI Validation and Normalization
validateAndNormalizeAbi(abi) {
try {
for (const item of abi) {
if (!item.type) {
logger_1.Logger.warn('ABI item missing type property');
return false;
}
if (item.type === 'function') {
if (!this.validateFunctionAbi(item))
return false;
}
else if (item.type === 'event') {
if (!this.validateEventAbi(item))
return false;
}
}
return true;
}
catch (error) {
logger_1.Logger.error('ABI validation failed:', error);
return false;
}
}
validateFunctionAbi(item) {
return !!(item.name &&
Array.isArray(item.inputs) &&
Array.isArray(item.outputs) &&
item.stateMutability);
}
validateEventAbi(item) {
return !!(item.name &&
Array.isArray(item.inputs) &&
typeof item.anonymous === 'boolean');
}
// Error Recovery
async recoverFromError(error, context) {
logger_1.Logger.error(`Error in ${context}:`, error);
try {
// Attempt to recover cached data
if (this.enableCache) {
await this.loadFromCache();
}
// Rebuild necessary indexes
await this.rebuildIndexes();
logger_1.Logger.info('Recovery completed successfully');
}
catch (recoveryError) {
logger_1.Logger.error('Recovery failed:', recoveryError);
throw new types_1.TypechainProcessorError('Failed to recover from error', 'RECOVERY_FAILED');
}
}
// Event Handling
async subscribeToEvents(contractName, eventNames, callback) {
await this.ensureInitialized();
const facetInfo = await this.getContractInfo(contractName);
if (!facetInfo)
return false;
const events = Array.from(facetInfo.enhancedEvents.entries())
.filter(([name]) => eventNames.includes(name))
.map(([_, event]) => event);
if (events.length === 0)
return false;
// Register event handlers
events.forEach(event => {
this.eventHandlers.set(event.signature, callback);
});
return true;
}
// Interface Implementation
async getFacetInterfaces() {
await this.ensureInitialized();
const interfaces = new Map();
for (const [facetName, facetInfo] of this.facetsCache) {
try {
interfaces.set(facetName, new abi_1.Interface(facetInfo.facetInfo.abi));
}
catch (error) {
logger_1.Logger.warn(`Failed to create interface for ${facetName}:`, error);
}
}
return interfaces;
}
// Utility Methods
async getFunctionsBySelector(selector) {
await this.ensureInitialized();
const mapping = this.optimizedLookup.selectorToFacet.get(selector);
return mapping ? {
facetName: mapping.facetName,
functionInfo: mapping.functionInfo
} : null;
}
async getFacetSelectors(facetName) {
await this.ensureInitialized();
return this.optimizedLookup.facetToSelectors.get(facetName) || null;
}
async validateSelector(selector) {
return /^0x[0-9a-f]{8}$/i.test(selector);
}
// Diagnostic Methods
async getDiagnostics() {
await this.ensureInitialized();
return {
totalFacets: this.facetsCache.size,
totalSelectors: this.optimizedLookup.selectorToFacet.size,
totalEvents: Array.from(this.facetsCache.values())
.reduce((acc, curr) => acc + curr.facetInfo.enhancedEvents.size, 0),
cacheStatus: {
enabled: this.enableCache,
size: this.facetsCache.size,
lastUpdate: this.optimizedLookup.metadata.lastUpdate
},
version: TypechainProcessor.VERSION
};
}
async saveToCache() {
try {
await this.abiCache.save({
version: TypechainProcessor.VERSION,
timestamp: Date.now(),
lookup: this.optimizedLookup,
facets: this.facetsCache,
events: {
cache: this.eventCache,
timestamps: this.eventCacheTimestamps
}
});
logger_1.Logger.debug(`Saved cache with ${this.eventCache.size} event entries`);
}
catch (error) {
logger_1.Logger.warn('Failed to save to cache:', error);
}
}
async loadFromCache() {
try {
const cached = await this.abiCache.load();
if (!cached)
return false;
this.optimizedLookup = cached.lookup;
this.facetsCache = cached.facets;
// Restore event cache
if (cached.events) {
this.eventCache = cached.events.cache;
this.eventCacheTimestamps = cached.events.timestamps;
logger_1.Logger.debug(`Restored ${this.eventCache.size} event cache entries`);
}
else {
logger_1.Logger.debug('No event cache found in stored cache');
this.eventCache.clear();
this.eventCacheTimestamps.clear();
}
return true;
}
catch (error) {
logger_1.Logger.warn('Failed to load from cache:', error);
return false;
}
}
async rebuildIndexes() {
this.optimizedLookup.selectorToFacet.clear();
this.optimizedLookup.facetToSelectors.clear();
for (const [contractName, processed] of this.facetsCache) {
this.updateLookupStructures(contractName, processed.facetInfo);
}
}
async reloadContract(contractName) {
try {
const factoryPath = this.factoryMap.get(contractName);
if (!factoryPath)
return false;
const facetInfo = await this.processFacet(contractName, factoryPath);
if (!facetInfo)
return false;
this.updateLookupStructures(contractName, facetInfo);
return true;
}
catch (error) {
logger_1.Logger.error(`Failed to reload contract ${contractName}:`, error);
return false;
}
}
// Cache warmup for frequently used selectors
warmupSelectorCache() {
// Preload most frequently used selectors into memory
const frequentSelectors = Array.from(this.optimizedLookup.selectorToFacet.entries())
.slice(0, 100); // Cache first 100 selectors
for (const [selector, mapping] of frequentSelectors) {
this.selectorLookupCache.set(selector, mapping);
}
}
// Cache maintenance
async performCacheMaintenance() {
if (!this.enableCache)
return;
try {
// Clear in-memory selector cache periodically
this.selectorLookupCache.clear();
this.warmupSelectorCache();
// Maintain persistent cache
await this.abiCache.maintain();
}
catch (error) {
logger_1.Logger.warn('Cache maintenance failed:', error);
}
}
// Helper method to convert between Maps and objects if needed
convertFunctionsToMap(functions) {
return new Map(Object.entries(functions));
}
convertEventsToMap(events) {
return new Map(Object.entries(events));
}
// Helper method for the reverse conversion if needed
convertMapToObject(map) {
return Object.fromEntries(map.entries());
}
// Event handling methods
async registerEventHandler(contractName, eventNames, callback) {
try {
await this.ensureInitialized();
const contract = this.facetsCache.get(contractName);
if (!contract) {
logger_1.Logger.warn(`Contract ${contractName} not found`);
return false;
}
const events = [];
for (const eventName of eventNames) {
const event = contract.facetInfo.enhancedEvents.get(eventName);
if (event) {
events.push(event);
}
else {
logger_1.Logger.warn(`Event ${eventName} not found in contract ${contractName}`);
}
}
if (events.length === 0) {
logger_1.Logger.warn('No valid events found to register');
return false;
}
// Register event handlers
events.forEach(event => {
this.eventHandlers.set(event.signature, callback);
});
logger_1.Logger.info(`Registered ${events.length} event handlers for ${contractName}`);
return true;
}
catch (error) {
logger_1.Logger.error('Failed to register event handler:', error);
return false;
}
}
removeEventHandler(eventSignature) {
return this.eventHandlers.delete(eventSignature);
}
clearEventHandlers() {
this.eventHandlers.clear();
}
async handleEvent(eventSignature, eventData) {
const handler = this.eventHandlers.get(eventSignature);
if (!handler) {
logger_1.Logger.debug(`No handler found for event: ${eventSignature}`);
return;
}
try {
const event = await this.findEventBySignature(eventSignature);
if (!event) {
logger_1.Logger.warn(`Event info not found for signature: ${eventSignature}`);
return;
}
await handler(event, eventData);
}
catch (error) {
logger_1.Logger.error(`Error handling event ${eventSignature}:`, error);
}
}
async findEventBySignature(signature) {
for (const [_, contract] of this.facetsCache) {
for (const [_, event] of contract.facetInfo.enhancedEvents) {
if (event.signature === signature) {
return event;
}
}
}
return null;
}
// Helper method to get all registered event signatures
getRegisteredEventSignatures() {
return Array.from(this.eventHandlers.keys());
}
// Helper method to check if an event has a handler
hasEventHandler(eventSignature) {
return this.eventHandlers.has(eventSignature);
}
// Add cache maintenance method
async maintainCache() {
if (!this.enableCache)
return;
try {
this.cleanEventCache();
await this.abiCache.maintain();
logger_1.Logger.info('Cache maintenance completed');
}
catch (error) {
logger_1.Logger.error('Cache maintenance failed:', error);
}
}
// Add cache stats method
async getCacheStats() {
if (!this.enableCache) {
return { enabled: false };
}
try {
return await this.abiCache.getCacheStats();
}
catch (error) {
logger_1.Logger.error('Failed to get cache stats:', error);
return { error: 'Failed to get cache stats' };
}
}
async getEventsByAddress(address) {
await this.ensureInitialized();
try {
const contractInfo = this.facetsCache.get(address);
if (!contractInfo) {
logger_1.Logger.debug(`No contract info found for address: ${address}`);
return new Map();
}
return contractInfo.facetInfo.events;
}
catch (error) {
logger_1.Logger.error(`Failed to get events for address ${address}:`, error);
return new Map();
}
}
extractContractNameFromPath(filePath) {
// Get the base name without extension
const baseName = path_1.default.basename(filePath, '.ts');
// Remove common suffixes and pref
return this.cleanContractName(baseName);
}
async getEventsByFacetName(facetName) {
try {
// Check cache first
const cachedEvents = this.eventCache.get(facetName);
if (cachedEvents) {
// Ensure we're returning a Map even if the cached value isn't one
return cachedEvents instanceof Map
? cachedEvents
: new Map(Object.entries(cachedEvents));
}
// Look through the facetsCache for the matching facet name
for (const [_, contract] of this.facetsCache) {
if (contract.facetInfo.name === facetName) {
const events = contract.facetInfo.events;
// Ensure we're returning a Map with proper EventFragment typing
const eventMap = events instanceof Map
? events
: new Map(Object.entries(events || {}).map(([key, value]) => [
key,
value instanceof abi_1.EventFragment
? value
: abi_1.EventFragment.from(value)
]));
// Cache the result before returning
this.eventCache.set(facetName, eventMap);
this.eventCacheTimestamps.set(facetName, Date.now());
return eventMap;
}
}
logger_1.Logger.debug(`No events found for facet: ${facetName}`);
return new Map();
}
catch (error) {
logger_1.Logger.error(`Failed to get events for facet ${facetName}:`, error);
return new Map();
}
}
// Add cache cleanup method
cleanEventCache() {
const now = Date.now();
for (const [facetName, timestamp] of this.eventCacheTimestamps.entries()) {
if (now - timestamp > TypechainProcessor.EVENT_CACHE_DURATION) {
this.eventCache.delete(facetName);
this.eventCacheTimestamps.delete(facetName);
}
}
}
}
exports.TypechainProcessor = TypechainProcessor;
TypechainProcessor.VERSION = '2.0.0';
TypechainProcessor.CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
TypechainProcessor.ABI_PATTERNS = [
/const\s+_abi\s*=\s*(\[[\s\S]*?\])\s*as\s*const/,
/static\s+readonly\s+abi\s*=\s*(\[[\s\S]*?\])/,
/const\s+abi\s*=\s*(\[[\s\S]*?\])/,
/export\s+const\s+abi\s*=\s*(\[[\s\S]*?\])/
];
TypechainProcessor.CONCURRENCY_LIMIT = 5;
TypechainProcessor.FACTORY_PATTERN = /export\s+{\s*(\w+)\s*}\s*from\s*["'](.+?)["']/g;
TypechainProcessor.EVENT_CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
//# sourceMappingURL=typechain-processor.js.map