From 655786e171bbedfe445db62f81dea4a0c17995c2 Mon Sep 17 00:00:00 2001 From: Logan Luo Date: Mon, 16 Jun 2025 14:25:06 +0800 Subject: [PATCH 1/6] chore: update eslint config --- .eslintrc.cjs | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index bb88d9d..b36deff 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -46,6 +46,5 @@ module.exports = { caughtErrorsIgnorePattern: '^_', }, ], - 'max-lines': ['error', { max: 400, skipBlankLines: true }], }, }; From d9868a79b4253e6685ee193986cba5c9c9a438cd Mon Sep 17 00:00:00 2001 From: Logan Luo Date: Mon, 16 Jun 2025 14:25:53 +0800 Subject: [PATCH 2/6] feat: implement watch feature --- src/contract-variable-tracer.ts | 277 +++++++++++++++++++++++++++++++- src/index.ts | 10 +- 2 files changed, 283 insertions(+), 4 deletions(-) diff --git a/src/contract-variable-tracer.ts b/src/contract-variable-tracer.ts index 96fd10b..208499a 100644 --- a/src/contract-variable-tracer.ts +++ b/src/contract-variable-tracer.ts @@ -1,5 +1,6 @@ import type { Address, PublicClient } from 'viem'; import { createPublicClient, getContract, http, parseAbi } from 'viem'; +import { watchEvent, type WatchEventReturnType } from 'viem/actions'; import { chunk } from './utils/chunk'; import { dedup } from './utils/dedup'; @@ -47,9 +48,20 @@ export interface ProgressInfo { current: number; total: number; } - export type OnProgressCallback = (progress: ProgressInfo) => void; +export interface WatchOptions { + filter?: (prev: TraceResult | undefined, curr: TraceResult) => boolean; + onError?: (error: Error) => void | Promise; + maxRetries?: number; + onReconnect?: () => void | Promise; + initialValue?: TraceResult; +} +export type OnNewValueCallback = ( + prev: TraceResult | undefined, + curr: TraceResult, +) => void | Promise; + /** * Generic smart contract variable tracer */ @@ -74,6 +86,40 @@ export class ContractVariableTracer { return fnName; } + /** + * Read variable value at a specific block + */ + private async readVariableAtBlock( + config: Pick< + ContractVariableTraceConfig, + 'contractAddress' | 'methodAbi' | 'methodParams' + >, + blockNumber: string, + ): Promise { + const abi: string[] = [config.methodAbi]; + const contract = getContract({ + address: config.contractAddress, + abi: parseAbi(abi), + client: this.publicClient, + }); + + const methodName = this.extractFunctionNameFromAbi(config.methodAbi); + const contractMethod = contract.read[methodName]; + + if (!contractMethod) { + throw new Error(`Method '${methodName}' not found in contract ABI`); + } + + const value = await contractMethod({ + ...(config.methodParams && config.methodParams.length > 0 + ? { args: config.methodParams } + : {}), + blockNumber: BigInt(blockNumber), + }); + + return (value as bigint).toString(); + } + /** * Get block numbers where specific events occurred that may have updated the variable */ @@ -275,4 +321,233 @@ export class ContractVariableTracer { return allValues; } + + /** + * Continuously monitors a smart contract variable for changes by watching for relevant events. + * + * This method establishes real-time monitoring of a contract variable by: + * 1. Setting up event listeners for specified events that may trigger variable changes + * 2. Reading the current variable value when events are detected + * 3. Comparing with the previous value to detect actual changes + * 4. Invoking callbacks only when the value has actually changed + * + * The method uses a dual-monitoring approach: + * - Primary: Event-based monitoring for immediate detection + * - Fallback: Periodic polling to ensure no changes are missed + * + * @param config - Configuration object specifying the contract, method, and events to monitor + * @param onNewValue - Callback function invoked when the variable value changes + * - `prev`: Previous TraceResult (undefined for the first detected value) + * - `curr`: Current TraceResult with the new value and block number + * @param opts - Optional configuration for monitoring behavior + * @param opts.filter - Optional predicate function to filter which changes trigger callbacks. + * Only calls `onNewValue` when this function returns `true`. Useful for: + * - Filtering out insignificant changes (e.g., < 1% change) + * - Applying custom business logic to determine relevance + * @param opts.onError - Callback for handling errors during monitoring. + * If not provided, errors are logged to console + * @param opts.maxRetries - Maximum retry attempts for failed operations (default: 3) + * @param opts.onReconnect - Callback invoked when connection is re-established + * after a disconnection + * @param opts.initialValue - Pre-fetched initial value to avoid extra RPC call. + * If not provided, the method will fetch the current value at startup + * + * @returns Promise that resolves to a cleanup function. Call this function to stop + * monitoring and clean up all resources (event listeners, timers, etc.) + * + * @throws {Error} If initial setup fails (invalid configuration, network issues, etc.) + * + * @example + * ```typescript + * const cleanup = await tracer.watch( + * config, + * (prev, curr) => { + * console.log(`Value: ${prev?.value || 'N/A'} → ${curr.value}`); + * } + * ); + * + * // Monitoring with filtering and error handling + * const cleanup = await tracer.watch( + * config, + * async (prev, curr) => { + * await sendAlert(`Critical change detected: ${curr.value}`); + * }, + * { + * filter: (prev, curr) => { + * // Only alert on significant changes (>5%) + * if (!prev) return true; + * const change = Math.abs(Number(curr.value) - Number(prev.value)); + * return change / Number(prev.value) > 0.05; + * }, + * onError: (error) => console.error('Monitoring failed:', error), + * onReconnect: () => console.log('Reconnected successfully') + * } + * ); + * + * // Stop monitoring when done + * cleanup(); + * ``` + * + * @see {@link collectBlockNumbers} For one-time block collection + * @see {@link trace} For historical variable tracing + */ + public async watch( + config: ContractVariableTraceConfig, + onNewValue: OnNewValueCallback, + opts: WatchOptions = {}, + ): Promise<() => void> { + const { filter, onError, maxRetries = 3, onReconnect, initialValue } = opts; + + let currentValue: TraceResult | undefined = initialValue; + let isWatching = true; + let unWatcher: WatchEventReturnType | undefined; + + // Get initial value if not provided + if (!currentValue) { + try { + const latestBlock = await this.publicClient.getBlockNumber(); + const value = await this.readVariableAtBlock( + config, + latestBlock.toString(), + ); + currentValue = { + blockNumber: latestBlock.toString(), + value, + }; + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + await this.handleError(err, onError, 'Failed to get initial value'); + } + } + + // Function to check and handle value changes + const checkValueChange = async ( + blockNumber: bigint, + retryCount = 0, + ): Promise => { + if (!isWatching) return; + + try { + const newValue = await this.readVariableAtBlock( + config, + blockNumber.toString(), + ); + const newResult: TraceResult = { + blockNumber: blockNumber.toString(), + value: newValue, + }; + + // If value not changed, return ealier + if (currentValue && currentValue.value === newResult.value) return; + + // Apply filter if provided + if (!filter || filter(currentValue, newResult)) { + const prevValue = currentValue; + currentValue = newResult; + + try { + await onNewValue(prevValue, newResult); + } catch (callbackError) { + const err = + callbackError instanceof Error + ? callbackError + : new Error(String(callbackError)); + await this.handleError( + err, + onError, + 'Error in onNewValue callback', + ); + } + } + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + + if (retryCount < maxRetries) { + console.warn( + `Retry ${retryCount + 1}/${maxRetries} for block ${blockNumber}:`, + err.message, + ); + await new Promise((resolve) => + setTimeout(resolve, 1000 * (retryCount + 1)), + ); // Exponential backoff + return checkValueChange(blockNumber, retryCount + 1); + } else { + await this.handleError( + err, + onError, + `Failed to check value change at block ${blockNumber}`, + ); + } + } + }; + + // Start event watching + const startEventWatcher = async (): Promise => { + unWatcher = watchEvent(this.publicClient, { + address: config.contractAddress, + events: parseAbi(config.events), + onLogs: async (logs) => { + if (!isWatching) return; + + // Process logs in order + const sortedLogs = logs + .filter((log) => log.blockNumber) + .sort((a, b) => Number(a.blockNumber!) - Number(b.blockNumber!)); + + for (const log of sortedLogs) { + await checkValueChange(log.blockNumber); + } + }, + onError: async (error) => { + await this.handleError(error, onError, 'Event watching error'); + if (!isWatching) return; + + // Try to reconnect + console.log('Attempting to reconnect event watcher...'); + await new Promise((resolve) => setTimeout(resolve, 5000)); + if (isWatching) { + await startEventWatcher(); + await onReconnect?.(); + } + }, + }); + }; + + // Start event watcher + await startEventWatcher(); + + // Return cleanup function + return () => { + isWatching = false; + if (unWatcher) { + unWatcher(); + unWatcher = undefined; + } + }; + } + + /** + * Handle errors consistently + */ + private async handleError( + error: Error, + onError?: (error: Error) => void | Promise, + context?: string, + ): Promise { + const errorMessage = context + ? `${context}: ${error.message}` + : error.message; + const wrappedError = new Error(errorMessage); + wrappedError.cause = error; + + if (onError) { + try { + await onError(wrappedError); + } catch (callbackError) { + console.error('Error in error callback:', callbackError); + } + } else { + console.error('Watch error:', wrappedError); + } + } } diff --git a/src/index.ts b/src/index.ts index fb10a61..6580c45 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,9 @@ -export { - type ContractVariableTraceConfig, - ContractVariableTracer, +export type { + ContractVariableTraceConfig, + OnNewValueCallback, + OnProgressCallback, + ProgressInfo, TraceResult, + WatchOptions, } from './contract-variable-tracer'; +export { ContractVariableTracer } from './contract-variable-tracer'; From 274d8934c58f90110cabd5dcbd9f90567abe0da0 Mon Sep 17 00:00:00 2001 From: Logan Luo Date: Mon, 16 Jun 2025 14:34:34 +0800 Subject: [PATCH 3/6] refactor: extract the readVariableAtBlock method --- src/contract-variable-tracer.ts | 38 +++++---------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/src/contract-variable-tracer.ts b/src/contract-variable-tracer.ts index 208499a..ac9c70b 100644 --- a/src/contract-variable-tracer.ts +++ b/src/contract-variable-tracer.ts @@ -229,8 +229,9 @@ export class ContractVariableTracer { blockNumbersOrOnProgress?: string[] | OnProgressCallback, onProgress?: OnProgressCallback, ): Promise { - let blockNumbers: string[] | undefined; + const { concurrentCallBatchSize = 10, dedup: isDedup = true } = config; + let blockNumbers: string[] | undefined; if (Array.isArray(blockNumbersOrOnProgress)) { blockNumbers = blockNumbersOrOnProgress; } else { @@ -238,28 +239,6 @@ export class ContractVariableTracer { blockNumbers = await this.collectBlockNumbers(config, onProgress); } - const { - contractAddress, - methodAbi, - methodParams = [], - concurrentCallBatchSize = 10, - dedup: isDedup = true, - } = config; - - const abi: string[] = [methodAbi]; - // Create contract instance - const contract = getContract({ - address: contractAddress, - abi: parseAbi(abi), - client: this.publicClient, - }); - - const methodName = this.extractFunctionNameFromAbi(methodAbi); - // Validate that the method exists on the contract - if (!contract.read[methodName]) { - throw new Error(`Method '${methodName}' not found in contract ABI`); - } - let allValues: TraceResult[] = []; const chunks = chunk(blockNumbers, concurrentCallBatchSize); const key = 'trace'; @@ -277,22 +256,15 @@ export class ContractVariableTracer { const blockChunk = chunks[i]; const requests = blockChunk.map(async (blockNumber) => { try { - const contractMethod = contract.read[methodName]; // Call the contract method with the specified parameters at the specified block number - const value = await contractMethod({ - ...(methodParams.length > 0 ? { args: methodParams } : {}), - blockNumber: BigInt(blockNumber), - }); + const value = await this.readVariableAtBlock(config, blockNumber); return { blockNumber, - value: (value as bigint).toString(), + value, }; } catch (error) { - console.warn( - `Failed to read ${methodName} at block ${blockNumber}:`, - error, - ); + console.error(`Failed to read value at block ${blockNumber}:`, error); return { blockNumber, value: 'ERROR', From ff4965d6b218d88f2fecd9f4f2709bec712fda7e Mon Sep 17 00:00:00 2001 From: Logan Luo Date: Mon, 16 Jun 2025 14:52:39 +0800 Subject: [PATCH 4/6] feat: support watch mode in cli --- src/cli.ts | 223 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 166 insertions(+), 57 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 4ec05d4..4a30b2f 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -21,6 +21,7 @@ interface CliArgs { rpc: string; output: string; // The output file path config: string; + watch?: boolean; // Watch mode verbose?: boolean; } @@ -29,6 +30,7 @@ interface CliArgs { */ async function loadConfig( configPath: string, + isWatchMode: boolean = false, ): Promise { try { // Resolve the config file path @@ -53,11 +55,14 @@ async function loadConfig( if (!config.events || !Array.isArray(config.events)) { throw new Error('Missing or invalid field: events (must be an array)'); } - if (typeof config.fromBlock === 'undefined') { - throw new Error('Missing required field: fromBlock'); - } - if (typeof config.toBlock === 'undefined') { - throw new Error('Missing required field: toBlock'); + // For watch mode, fromBlock and toBlock are not required + if (!isWatchMode) { + if (typeof config.fromBlock === 'undefined') { + throw new Error('Missing required field: fromBlock'); + } + if (typeof config.toBlock === 'undefined') { + throw new Error('Missing required field: toBlock'); + } } config.fromBlock = Number(config.fromBlock); @@ -78,6 +83,13 @@ async function loadConfig( } } +/** + * Format timestamp for logging + */ +function formatTimestamp(): string { + return new Date().toISOString(); +} + /** * Save data to a JSON file */ @@ -89,6 +101,139 @@ async function saveToFile(data: unknown, filename: string): Promise { } } +/** + * Watch mode handler + */ +async function handleWatchMode( + args: CliArgs, + config: ContractVariableTraceConfig, +) { + console.log('👀 Starting watch mode...'); + console.log(`Contract: ${config.contractAddress}`); + console.log(`Method: ${config.methodAbi}`); + console.log(`Events:\n\t${config.events.join('\n\t')}`); + console.log('✅ Watch mode active. Press Ctrl+C to stop.'); + console.log('---'); + + const tracer = new ContractVariableTracer({ + chainId: args.chainId, + rpcUrl: args.rpc, + }); + + let changeCount = 0; + const startTime = Date.now(); + + const cleanup = await tracer.watch( + config, + async (prev, curr) => { + changeCount++; + const timestamp = formatTimestamp(); + + // Simple output to stdout + if (prev) { + console.log( + `[${timestamp}] ${prev.value} → ${curr.value} (block ${curr.blockNumber})`, + ); + } else { + console.log( + `[${timestamp}] 🎯 Initial: ${curr.value} (block ${curr.blockNumber})`, + ); + } + }, + { + onError: async (error) => { + const timestamp = formatTimestamp(); + console.error(`[${timestamp}] ❌ Error: ${error.message}`); + + if (args.verbose) { + console.error('Details:', error); + } + }, + onReconnect: async () => { + const timestamp = formatTimestamp(); + console.log(`[${timestamp}] 🔄 Reconnected`); + }, + }, + ); + + // Handle graceful shutdown + const handleShutdown = async (_signal: string) => { + const timestamp = formatTimestamp(); + const duration = ((Date.now() - startTime) / 1000).toFixed(2); + + console.log( + `\n[${timestamp}] 🛑 Shutting down... (${duration}s, ${changeCount} changes)`, + ); + + cleanup(); + process.exit(0); + }; + + process.on('SIGINT', () => handleShutdown('SIGINT')); + process.on('SIGTERM', () => handleShutdown('SIGTERM')); + + // Keep the process alive + return new Promise(() => {}); // Never resolves, keeps watching +} + +/** + * Trace mode handler (original functionality) + */ +async function handleTraceMode( + args: CliArgs, + config: ContractVariableTraceConfig, +) { + const tracer = new ContractVariableTracer({ + chainId: args.chainId, + rpcUrl: args.rpc, + }); + + console.log('🚀 Starting contract variable trace...'); + const startTime = Date.now(); + + const bars: Record = {}; + const onProgress: OnProgressCallback = ({ + key, + description, + current, + total, + }) => { + if (!bars[key]) { + console.log(description); + bars[key] = new cliProgress.SingleBar( + { + clearOnComplete: true, + }, + cliProgress.Presets.shades_classic, + ); + bars[key].start(total, 0); + } else { + bars[key].update(current); + } + + if (total === current) { + bars[key].stop(); + } + }; + + const results = await tracer.trace(config, onProgress); + + if (args.output) { + await saveToFile(results, args.output); + console.log(`Results saved to: ${args.output}`); + } else { + console.log('\n================Tracing Result START===================='); + console.log(JSON.stringify(results, null, 2)); + console.log('================Tracing Result END======================\n'); + } + + const endTime = Date.now(); + const duration = ((endTime - startTime) / 1000).toFixed(2); + + console.log(`Trace completed successfully!`); + console.log(`Traced ${results.length} data points in ${duration}s`); +} + /** * Main CLI handler */ @@ -98,63 +243,18 @@ async function main(args: CliArgs) { console.log('Loading configuration...'); } - const config = await loadConfig(args.config); + const config = await loadConfig(args.config, args.watch); + if (args.verbose) { console.log(`Connecting to chain ${args.chainId} via ${args.rpc}`); console.log(`Config loaded successfully from: ${args.config}`); - console.log(`Contract: ${config.contractAddress}`); - console.log(`Method: ${config.methodAbi}`); - console.log(`Block range: ${config.fromBlock} to ${config.toBlock}`); } - const tracer = new ContractVariableTracer({ - chainId: args.chainId, - rpcUrl: args.rpc, - }); - - console.log('🚀 Starting contract variable trace...'); - const startTime = Date.now(); - - const bars: Record = {}; - const onProgress: OnProgressCallback = ({ - key, - description, - current, - total, - }) => { - if (!bars[key]) { - console.log(description); - bars[key] = new cliProgress.SingleBar( - { - clearOnComplete: true, - }, - cliProgress.Presets.shades_classic, - ); - bars[key].start(total, 0); - } else { - bars[key].update(current); - } - - if (total === current) { - bars[key].stop(); - } - }; - - const results = await tracer.trace(config, onProgress); - - if (args.output) { - saveToFile(results, args.output); + if (args.watch) { + await handleWatchMode(args, config); } else { - console.log('\n================Tracing Result START===================='); - console.log(JSON.stringify(results, null, 2)); - console.log('================Tracing Result EDN======================\n'); + await handleTraceMode(args, config); } - - const endTime = Date.now(); - const duration = ((endTime - startTime) / 1000).toFixed(2); - - console.log(`Trace completed successfully!`); - console.log(`Traced ${results.length} data points in ${duration}s`); } catch (error) { console.error( '❌ Error:', @@ -194,8 +294,7 @@ const cli = yargs(hideBin(process.argv)) alias: 'o', type: 'string', demandOption: false, - description: - 'Path to the output file, the tracing result to be save to this file when provided', + description: 'Path to the output file for trace mode results', }) .option('verbose', { alias: 'v', @@ -203,10 +302,20 @@ const cli = yargs(hideBin(process.argv)) default: false, description: 'Enable verbose logging', }) + .option('watch', { + alias: 'w', + type: 'boolean', + default: false, + description: 'Enable watch mode for real-time monitoring', + }) .example( '$0 -c 1 -r https://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY -f ./config.json', 'Trace contract variables on Ethereum mainnet', ) + .example( + '$0 --watch --chainId 1 --rpc https://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY -f ./config.json', + 'Start real-time monitoring (watch mode)', + ) .example( '$0 --chainId 42161 --rpc https://arb1.arbitrum.io/rpc --config ./arbitrum-config.json --verbose', 'Trace on Arbitrum with verbose logging', From b4bda9a0441c1978c22d4ebba3ca6efb96f534fc Mon Sep 17 00:00:00 2001 From: Logan Luo Date: Mon, 16 Jun 2025 15:03:40 +0800 Subject: [PATCH 5/6] docs: update docs --- README.md | 84 ++++++++++++++++++++++++++------- package.json | 2 +- src/contract-variable-tracer.ts | 4 -- 3 files changed, 68 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 7592953..1116874 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) -A simple library and CLI tool for tracing EVM smart contract variable changes over time by analyzing events. +A CLI tool for tracing and monitoring EVM smart contract variable changes over time by analyzing events ## ✨ Features @@ -49,9 +49,13 @@ npm install contract-variable-tracer 2. **Run the CLI**: ```bash +# trace mode cvt -# or -cvt -c 1 -r rpc-url -f ./cvt.config.json +# watch mode +cvt -w + +# specific more options +cvt -c 1 -r YOUR_RPC_URL -f ./cvt.config.json ``` ### Library Usage @@ -77,6 +81,35 @@ const config: ContractVariableTraceConfig = { // Run the trace const results = await tracer.trace(config); console.log(`Traced ${results.length} data points`); + +// Watch/Monitoring mode +const cleanup = await tracer.watch( + config, + (prev, curr) => { + console.log(`Value: ${prev?.value || 'N/A'} → ${curr.value}`); + } +); + +// Monitoring with filtering and error handling +const cleanup = await tracer.watch( + config, + async (prev, curr) => { + await sendAlert(`Critical change detected: ${curr.value}`); + }, + { + filter: (prev, curr) => { + // Only alert on significant changes (>5%) + if (!prev) return true; + const change = Math.abs(Number(curr.value) - Number(prev.value)); + return change / Number(prev.value) > 0.05; + }, + onError: (error) => console.error('Monitoring failed:', error), + onReconnect: () => console.log('Reconnected successfully') + } +); + +// Stop monitoring when done +cleanup(); ``` ## 📖 API Documentation @@ -89,6 +122,20 @@ The main class for tracing contract variables. ```typescript new ContractVariableTracer(opts?: { chainId?: number, rpcUrl?: string }) ``` +### Configuration Interface + +```typescript +interface ContractVariableTraceConfig { + contractAddress: Address; // Contract address to trace + methodAbi: string; // Contract ABI + methodParams?: unknown[]; // Method parameters (optional) + events: string[]; // Events that trigger variable changes + fromBlock: number; // Starting block number + toBlock: number; // Ending block number + maxBlockRangePerLogQuery: number; // Max blocks per RPC call (usually 500) + concurrentCallBatchSize?: number; // Concurrent contract calls (default: 10) +} +``` #### Methods @@ -111,6 +158,22 @@ public async trace( ``` Traces a contract variable over time with optional progress reporting.Can either generate block numbers automatically or use provided block numbers. +##### `watch` +```ts +public async watch( + config: ContractVariableTraceConfig, + onNewValue: OnNewValueCallback, + opts: WatchOptions = {}, +): Promise<() => void>; +``` +Continuously monitors a smart contract variable for changes by watching for relevant events. +This method establishes real-time monitoring of a contract variable by: + +1. Setting up event listeners for specified events that may trigger variable changes +2. Reading the current variable value when events are detected +3. Comparing with the previous value to detect actual changes +4. Invoking callbacks only when the value has actually changed + ##### `collectBlockNumbers` ```ts public async collectBlockNumbers( @@ -127,20 +190,6 @@ public async collectBlockNumbers( ``` Collects block numbers where specified events occurred. -### Configuration Interface - -```typescript -interface ContractVariableTraceConfig { - contractAddress: Address; // Contract address to trace - methodAbi: string; // Contract ABI - methodParams?: unknown[]; // Method parameters (optional) - events: string[]; // Events that trigger variable changes - fromBlock: number; // Starting block number - toBlock: number; // Ending block number - maxBlockRangePerLogQuery: number; // Max blocks per RPC call (usually 500) - concurrentCallBatchSize?: number; // Concurrent contract calls (default: 10) -} -``` ### Result Interface @@ -188,6 +237,7 @@ const config = { - `-r, --rpc `: RPC URL for blockchain connection - `-o, --output `: Output file path. If not specified, results are written to stdout - `-f, --config `: Path to configuration JSON file +- `-w, --watch` : Enable watch mode for real-time monitoring - `-v, --verbose`: Enable verbose logging - `-h, --help`: Show help information - `-V, --version`: Show version number diff --git a/package.json b/package.json index f60d76f..a4466ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "contract-variable-tracer", - "description": "A CLI tool for tracing EVM smart contract variable changes over time by analyzing events", + "description": "A CLI tool for tracing and monitoring EVM smart contract variable changes over time by analyzing events", "author": "Logan Luo ", "version": "0.4.0", "license": "MIT", diff --git a/src/contract-variable-tracer.ts b/src/contract-variable-tracer.ts index ac9c70b..04bc9af 100644 --- a/src/contract-variable-tracer.ts +++ b/src/contract-variable-tracer.ts @@ -303,10 +303,6 @@ export class ContractVariableTracer { * 3. Comparing with the previous value to detect actual changes * 4. Invoking callbacks only when the value has actually changed * - * The method uses a dual-monitoring approach: - * - Primary: Event-based monitoring for immediate detection - * - Fallback: Periodic polling to ensure no changes are missed - * * @param config - Configuration object specifying the contract, method, and events to monitor * @param onNewValue - Callback function invoked when the variable value changes * - `prev`: Previous TraceResult (undefined for the first detected value) From 60bd28272d76f1ac1671b6fa94da3e6b7933cffa Mon Sep 17 00:00:00 2001 From: Logan Luo Date: Mon, 16 Jun 2025 15:04:27 +0800 Subject: [PATCH 6/6] chore: bump version to 0.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a4466ec..5d70012 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "contract-variable-tracer", "description": "A CLI tool for tracing and monitoring EVM smart contract variable changes over time by analyzing events", "author": "Logan Luo ", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "keywords": [ "EVM",