logtui
Version:
A terminal-based UI for monitoring blockchain events using Hypersync
289 lines (257 loc) • 9.02 kB
JavaScript
/**
* LogTUI - Command Line Interface
*
* A terminal-based UI for monitoring blockchain events using Hypersync
*/
// Force terminal compatibility mode
process.env.FORCE_COLOR = "3";
process.env.NCURSES_NO_UTF8_ACS = "1";
// Handle terminal capability errors before imports
const originalError = console.error;
console.error = function (msg) {
// Ignore specific terminal capability errors
if (
typeof msg === "string" &&
(msg.includes("Error on xterm") || msg.includes("Setulc"))
) {
return;
}
originalError.apply(console, arguments);
};
// Disable debug logging
console.debug = () => {};
import { Command } from "commander";
import chalk from "chalk";
import { createScanner } from "../lib/scanner.js";
import {
getNetworkUrl,
getEventSignatures,
hasPreset,
NETWORKS,
DEFAULT_NETWORKS,
fetchNetworks,
listPresets,
} from "../lib/config.js";
// Create a new command instance
const program = new Command();
// Setup program metadata
program
.name("logtui")
.description("A terminal UI for monitoring blockchain events with Hypersync")
.version("0.1.0");
// Main command
program
.argument(
"[preset]",
"Event preset to use (e.g., uniswap-v3, erc20, erc721)",
"uniswap-v3"
)
.argument(
"[network]",
"Network to connect to (e.g., eth, arbitrum, optimism)",
"eth"
)
.option("-e, --events <events...>", "Custom event signatures to monitor")
.option("-n, --network <network>", "Network to connect to")
.option(
"-t, --title <title>",
"Custom title for the scanner",
"Blockchain Event Scanner"
)
.option("-l, --list-presets", "List available event presets and exit")
.option("-N, --list-networks", "List all available networks and exit")
.option("-v, --verbose", "Show additional info in the console")
.option("--refresh-networks", "Force refresh network list from API")
.action(async (presetArg, networkArg, options) => {
try {
// Always fetch networks at startup to ensure we have the latest
// This uses the cache by default unless --refresh-networks is specified
if (options.refreshNetworks) {
console.log(chalk.blue("Refreshing networks from API..."));
await fetchNetworks(true);
console.log(chalk.green("Networks refreshed successfully!"));
} else {
// Silently ensure networks are loaded (uses cache if available)
await fetchNetworks();
}
// If the user requested to list networks, show them and exit
if (options.listNetworks) {
console.log(chalk.bold.blue("\nAvailable Networks:"));
console.log(chalk.blue("──────────────────────────────────────────"));
// Separate into categories for better display
const mainnetNetworks = [];
const testnetNetworks = [];
const otherNetworks = [];
Object.entries(NETWORKS).forEach(([name, url]) => {
// Categorize networks by name patterns
if (
name.includes("sepolia") ||
name.includes("goerli") ||
name.includes("testnet") ||
name.includes("test")
) {
testnetNetworks.push({ name, url });
} else if (Object.keys(DEFAULT_NETWORKS).includes(name)) {
mainnetNetworks.push({ name, url });
} else {
otherNetworks.push({ name, url });
}
});
console.log(chalk.yellow("\nPopular Mainnets:"));
mainnetNetworks.forEach(({ name, url }) => {
console.log(`${chalk.green(name)}: ${url}`);
});
console.log(chalk.yellow("\nTestnets:"));
testnetNetworks.forEach(({ name, url }) => {
console.log(`${chalk.green(name)}: ${url}`);
});
console.log(chalk.yellow("\nOther Networks:"));
otherNetworks.forEach(({ name, url }) => {
console.log(`${chalk.green(name)}: ${url}`);
});
console.log(
chalk.yellow(
`\nTotal ${Object.keys(NETWORKS).length} networks available`
)
);
console.log(chalk.blue("\nUsage Examples:"));
console.log(
`${chalk.yellow("logtui uniswap-v3 arbitrum")} - Use Arbitrum network`
);
console.log(
`${chalk.yellow(
"logtui -n optimism-sepolia"
)} - Use Optimism Sepolia testnet`
);
console.log();
process.exit(0);
}
// If the user requested to list presets, show them and exit
if (options.listPresets) {
console.log(chalk.bold.blue("\nAvailable Event Presets:"));
console.log(chalk.blue("──────────────────────────────────────────"));
listPresets().forEach((preset) => {
console.log(
`${chalk.green(preset.id)}: ${chalk.yellow(preset.name)} - ${
preset.description
}`
);
});
console.log(chalk.blue("\nAvailable Networks:"));
console.log(chalk.blue("──────────────────────────────────────────"));
// Show just the default networks for simplicity
Object.keys(DEFAULT_NETWORKS).forEach((network) => {
console.log(`${chalk.green(network)}: ${DEFAULT_NETWORKS[network]}`);
});
console.log(
chalk.yellow(
`(${
Object.keys(NETWORKS).length -
Object.keys(DEFAULT_NETWORKS).length
} more networks available. Run with --list-networks to see all)`
)
);
console.log(chalk.blue("\nUsage Examples:"));
console.log(chalk.blue("──────────────────────────────────────────"));
console.log(
`${chalk.yellow("logtui")} - Scan for Uniswap V3 events on Ethereum`
);
console.log(
`${chalk.yellow(
"logtui uniswap-v3 arbitrum"
)} - Scan for Uniswap V3 events on Arbitrum`
);
console.log(
`${chalk.yellow(
"logtui erc20 optimism"
)} - Scan for ERC-20 events on Optimism`
);
console.log(
`${chalk.yellow(
'logtui -e "Transfer(address,address,uint256)" -n eth'
)} - Scan for custom events`
);
console.log();
process.exit(0);
}
// Determine the network to use
const network = options.network || networkArg || "eth";
let networkUrl;
try {
networkUrl = getNetworkUrl(network);
} catch (err) {
console.error(chalk.red(`Error: ${err.message}`));
console.log(
chalk.yellow(
"Run 'logtui --list-networks' to see all available networks."
)
);
process.exit(1);
}
// Determine the event signatures to use
let eventSignatures = [];
if (options.events && options.events.length > 0) {
// Use custom event signatures
eventSignatures = options.events;
if (options.verbose) {
console.log(chalk.blue("Using custom event signatures:"));
eventSignatures.forEach((sig) => console.log(`- ${sig}`));
}
} else {
// Use preset
const preset = presetArg || "uniswap-v3";
if (!hasPreset(preset)) {
console.error(chalk.red(`Error: Preset '${preset}' not found.`));
console.log(
chalk.yellow(
`Run 'logtui --list-presets' to see available presets.`
)
);
process.exit(1);
}
eventSignatures = getEventSignatures(preset);
if (options.verbose) {
console.log(
chalk.blue(
`Using '${preset}' preset with ${eventSignatures.length} event signatures`
)
);
}
}
// Set the title
const title = `${options.title} (${network})`;
if (options.verbose) {
console.log(
chalk.blue(`Starting scanner on ${network}: ${networkUrl}`)
);
console.log(
chalk.blue(`Monitoring ${eventSignatures.length} event types`)
);
}
// Start the scanner
await createScanner({
networkUrl,
eventSignatures,
title,
});
} catch (err) {
console.error(chalk.red(`Error: ${err.message}`));
if (err.stack) {
console.error(chalk.red(err.stack));
}
process.exit(1);
}
});
// Execute the CLI
async function main() {
try {
// Ensure networks are loaded before parsing arguments
await fetchNetworks();
program.parse(process.argv);
} catch (err) {
console.error(chalk.red(`Fatal error: ${err.message}`));
process.exit(1);
}
}
main();