logtui
Version:
A terminal-based UI for monitoring blockchain events using Hypersync
395 lines (371 loc) • 11.9 kB
JavaScript
/**
* Configuration module for logtui
* Provides network endpoints and event signature presets
*/
import fetch from "node-fetch";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
// Get the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Path to store cached networks
const CACHE_FILE = path.join(__dirname, "../.networks-cache.json");
// Default network endpoint configuration (used as fallback if API fetch fails)
export const DEFAULT_NETWORKS = {
eth: "http://eth.hypersync.xyz",
arbitrum: "http://arbitrum.hypersync.xyz",
optimism: "http://optimism.hypersync.xyz",
base: "http://base.hypersync.xyz",
polygon: "http://polygon.hypersync.xyz",
};
// Runtime networks object that will be populated
export let NETWORKS = { ...DEFAULT_NETWORKS };
// API endpoint to fetch available networks
const NETWORKS_API_URL = "https://chains.hyperquery.xyz/active_chains";
/**
* Load networks from cache file
* @returns {Object} Cached networks or default networks if cache not found
*/
function loadNetworksFromCache() {
try {
if (fs.existsSync(CACHE_FILE)) {
const data = fs.readFileSync(CACHE_FILE, "utf8");
const networks = JSON.parse(data);
console.debug("Loaded networks from cache");
return networks;
}
} catch (error) {
console.warn(`Failed to load networks from cache: ${error.message}`);
}
return DEFAULT_NETWORKS;
}
/**
* Save networks to cache file
* @param {Object} networks - Networks to cache
*/
function saveNetworksToCache(networks) {
try {
const data = JSON.stringify(networks, null, 2);
fs.writeFileSync(CACHE_FILE, data);
console.debug("Saved networks to cache");
} catch (error) {
console.warn(`Failed to save networks to cache: ${error.message}`);
}
}
/**
* Fetches all available networks from the Hypersync API
* @param {boolean} forceRefresh - Whether to force a refresh from API
* @returns {Promise<Object>} Object with network names as keys and URLs as values
*/
export async function fetchNetworks(forceRefresh = false) {
// If not forcing refresh, try to load from cache first
if (!forceRefresh) {
const cachedNetworks = loadNetworksFromCache();
if (
Object.keys(cachedNetworks).length > Object.keys(DEFAULT_NETWORKS).length
) {
// If cache has more networks than default, use it
NETWORKS = { ...cachedNetworks };
return cachedNetworks;
}
}
try {
console.debug("Fetching networks from API...");
const response = await fetch(NETWORKS_API_URL);
if (!response.ok) {
throw new Error(`API responded with status: ${response.status}`);
}
const networks = await response.json();
const result = {};
// Process the API response into our format
networks.forEach((network) => {
// Skip non-EVM networks for now
if (network.ecosystem !== "evm") return;
// Convert network name to URL format
const url = `http://${network.name}.hypersync.xyz`;
result[network.name] = url;
});
// Update the NETWORKS object
NETWORKS = { ...result };
// Save to cache
saveNetworksToCache(result);
return result;
} catch (error) {
console.warn(`Warning: Failed to fetch networks: ${error.message}`);
console.warn("Using previously cached or default networks instead.");
// Fall back to cached networks if available, or defaults
const cachedNetworks = loadNetworksFromCache();
NETWORKS = { ...cachedNetworks };
return cachedNetworks;
}
}
// Try to load networks from cache immediately
try {
const cachedNetworks = loadNetworksFromCache();
if (Object.keys(cachedNetworks).length > 0) {
NETWORKS = { ...cachedNetworks };
}
} catch (err) {
console.warn(`Failed to load networks from cache: ${err.message}`);
}
// Event signature presets
export const EVENT_PRESETS = {
"uniswap-v3": {
name: "Uniswap V3",
description: "Uniswap V3 core events",
signatures: [
"PoolCreated(address,address,uint24,int24,address)",
"Burn(address,int24,int24,uint128,uint256,uint256)",
"Initialize(uint160,int24)",
"Mint(address,address,int24,int24,uint128,uint256,uint256)",
"Swap(address,address,int256,int256,uint160,uint128,int24)",
],
},
"uniswap-v4": {
name: "Uniswap V4",
description: "Uniswap V4 PoolManager events",
signatures: [
"Donate(bytes32,address,uint256,uint256)",
"Initialize(bytes32,address,address,uint24,int24,address,uint160,int24)",
"ModifyLiquidity(bytes32,address,int24,int24,int256,bytes32)",
"Swap(bytes32,address,int128,int128,uint160,uint128,int24,uint24)",
"Transfer(address,address,address,uint256,uint256)",
],
},
erc20: {
name: "ERC-20",
description: "Standard ERC-20 token events",
signatures: [
"Transfer(address,address,uint256)",
"Approval(address,address,uint256)",
],
},
erc721: {
name: "ERC-721",
description: "Standard ERC-721 NFT events",
signatures: [
"Transfer(address,address,uint256)",
"Approval(address,address,uint256)",
"ApprovalForAll(address,address,bool)",
],
},
// Oracle Presets
"chainlink-price-feeds": {
name: "Chainlink Price Feeds",
description: "Chainlink price oracle events",
signatures: [
"AnswerUpdated(int256,uint256,uint256)",
"NewRound(uint256,address,uint256)",
"ResponseReceived(int256,uint256,address)",
"AggregatorConfigSet(address,address,address)",
"RoundDetailsUpdated(uint128,uint32,int192,uint32)",
],
},
"chainlink-vrf": {
name: "Chainlink VRF",
description: "Chainlink Verifiable Random Function events",
signatures: [
"RandomWordsRequested(bytes32,uint256,uint256,uint64,uint16,uint32,uint32,address)",
"RandomWordsFulfilled(uint256,uint256,uint96,bool)",
"ConfigSet(uint16,uint32,uint32,uint32,uint32)",
"SubscriptionCreated(uint64,address)",
"SubscriptionFunded(uint64,uint256,uint256)",
],
},
pyth: {
name: "Pyth Network",
description: "Pyth Network oracle events",
signatures: [
"BatchPriceFeedUpdate(bytes32[],bytes[])",
"PriceFeedUpdate(bytes32,bytes)",
"BatchPriceUpdate(bytes32[],int64[],uint64[],int32[],uint32[])",
"PriceUpdate(bytes32,int64,uint64,int32,uint32)",
],
},
uma: {
name: "UMA Protocol",
description: "UMA Oracle events",
signatures: [
"PriceProposed(address,uint256,uint256,int256)",
"PriceDisputed(address,uint256,uint256,int256)",
"PriceSettled(address,uint256,int256,uint256)",
],
},
// DeFi Presets
aave: {
name: "Aave V3",
description: "Aave V3 lending protocol events",
signatures: [
"Supply(address,address,address,uint256,uint16)",
"Withdraw(address,address,address,uint256)",
"Borrow(address,address,address,uint256,uint256,uint16)",
"Repay(address,address,address,uint256,bool)",
"LiquidationCall(address,address,address,uint256,uint256,address,bool)",
],
},
curve: {
name: "Curve Finance",
description: "Curve pool events",
signatures: [
"TokenExchange(address,int128,uint256,int128,uint256)",
"AddLiquidity(address,uint256[],uint256,uint256)",
"RemoveLiquidity(address,uint256,uint256[])",
"RemoveLiquidityOne(address,uint256,uint256)",
],
},
// Cross-chain & Bridges
layerzero: {
name: "LayerZero",
description: "LayerZero cross-chain messaging events",
signatures: [
"MessageSent(bytes,uint64,bytes32,bytes)",
"PacketReceived(uint16,bytes,address,uint64,bytes32)",
"RelayerAdded(address,uint16)",
"RelayerRemoved(address,uint16)",
],
},
weth: {
name: "WETH",
description: "Wrapped Ether events",
signatures: [
"Deposit(address,uint256)",
"Withdrawal(address,uint256)",
"Transfer(address,address,uint256)",
"Approval(address,address,uint256)",
],
},
// L2 Infrastructure
arbitrum: {
name: "Arbitrum",
description: "Arbitrum sequencer and bridge events",
signatures: [
"SequencerBatchDelivered(uint256,bytes32,address,uint256)",
"MessageDelivered(uint256,bytes32,address,uint8,address,bytes32)",
"BridgeCallTriggered(address,address,uint256,bytes)",
],
},
// Gaming/NFTs
blur: {
name: "Blur",
description: "Blur NFT marketplace events",
signatures: [
"OrdersMatched(address,address,bytes32,bytes32)",
"NonceIncremented(address,uint256)",
"NewBlurExchange(address)",
"NewExecutionDelegate(address)",
],
},
axie: {
name: "Axie Infinity",
description: "Axie Infinity game events",
signatures: [
"AxieSpawned(uint256,uint256,uint256,uint256,uint256)",
"AxieBred(address,uint256,uint256,uint256)",
"Transfer(address,address,uint256)",
"BreedingApproval(address,uint256,address)",
],
},
// Stablecoins
usdc: {
name: "USDC",
description: "USD Coin stablecoin events",
signatures: [
"Transfer(address,address,uint256)",
"Approval(address,address,uint256)",
"Mint(address,uint256)",
"Burn(address,uint256)",
"BlacklistAdded(address)",
"BlacklistRemoved(address)",
],
},
// DAOs/Governance
ens: {
name: "ENS",
description: "Ethereum Name Service registry events",
signatures: [
"NewOwner(bytes32,bytes32,address)",
"Transfer(bytes32,address)",
"NewResolver(bytes32,address)",
"NewTTL(bytes32,uint64)",
"NameRegistered(string,bytes32,address,uint256,uint256)",
],
},
// Emerging/Trending
erc4337: {
name: "ERC-4337",
description: "Account Abstraction events",
signatures: [
"UserOperationEvent(bytes32,address,address,uint256,bool,uint256,uint256)",
"AccountDeployed(bytes32,address,address,address)",
],
},
universalRouter: {
name: "Uniswap Universal Router",
description: "Uniswap's intent-based router events",
signatures: [
"RewardsSent(address,uint256)",
"ERC20Transferred(address,address,uint256)",
"ERC721Transferred(address,address,address,uint256)",
"ERC1155Transferred(address,address,address,uint256,uint256)",
],
},
};
/**
* Get network URL from network name
* @param {string} network - Network name
* @returns {string} Network URL
*/
export function getNetworkUrl(network) {
if (!NETWORKS[network]) {
throw new Error(
`Network '${network}' not supported. Available networks: ${Object.keys(
NETWORKS
)
.slice(0, 10)
.join(", ")}... (Use --list-networks to see all)`
);
}
return NETWORKS[network];
}
/**
* Get event signatures from preset name
* @param {string} presetName - Preset name
* @returns {Array<string>} Array of event signatures
*/
export function getEventSignatures(presetName) {
if (!EVENT_PRESETS[presetName]) {
throw new Error(
`Preset '${presetName}' not found. Available presets: ${Object.keys(
EVENT_PRESETS
).join(", ")}`
);
}
return EVENT_PRESETS[presetName].signatures;
}
/**
* Check if a preset exists
* @param {string} presetName - Preset name
* @returns {boolean} Whether the preset exists
*/
export function hasPreset(presetName) {
return Boolean(EVENT_PRESETS[presetName]);
}
/**
* List all available presets
* @returns {Object} All presets with name and description
*/
export function listPresets() {
return Object.entries(EVENT_PRESETS).map(([id, preset]) => ({
id,
name: preset.name,
description: preset.description,
}));
}
/**
* Force refresh networks from API
* @returns {Promise<Object>} Updated networks list
*/
export async function refreshNetworks() {
return await fetchNetworks(true);
}