--- package/package.json (excerpt) ---
{
"name": "ethsmith",
"version": "1.0.0",
"description": "Unified Ethereum dev toolkit — Ganache-compatible API powered by Foundry (Forge + Cast + Anvil + Chisel) with LevelDB persistence",
"main": "src/index.js",
"bin": {
"ethsmith": "bin/ethsmith.js"
},
"scripts": {
"start": "node bin/ethsmith.js",
"test": "node test/basic.js"
},
"keywords": [
"ethereum",
"ganache",
"foundry",
"anvil",
"forge",
"cast",
"chisel",
"blockchain",
"solidity",
"evm",
"hardhat",
"web3",
"development"
],
"author": "Lord1Egypt",
"license": "MIT",
"dependencies": {
"commander": "^12.0.0",
"level": "^8.0.0",
"axios": "^1.6.0",
"tar": "^7.0.0",
"ethers": "^6.0.0",
"winston": "^3.11.0",
"winston-daily-rotate-file": "^5.0.0"
},
"engines": {
"node": ">=20.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/Lord1Egypt/ethsmith.git"
},
"homepage": "https://github.com/Lord1Egypt/ethsmith#readme",
"bugs": {
"url": "https://github.com/Lord1Egypt/ethsmith/issues"
}
}
--- package/src/core/binary.js (excerpt) ---
const fs = require('fs')
const path = require('path')
const os = require('os')
const { execSync, spawnSync } = require('child_process')
const axios = require('axios')
const tar = require('tar')
const logger = require('./logger')
const BIN_DIR = path.join(os.homedir(), '.ethsmith', 'bin')
const TOOLS = ['forge', 'cast', 'anvil', 'chisel']
const FOUNDRY_RELEASES = 'https://github.com/foundry-rs/foundry/releases'
const FOUNDRY_VERSION = 'stable'
function getPlatform() {
const arch = os.arch() === 'arm64' ? 'arm64' : 'amd64'
const sys = os.platform()
if (sys === 'darwin') return `darwin_${arch}`
if (sys === 'linux') return `linux_${arch}`
if (sys === 'win32') return `windows_amd64`
throw new Error(`Unsupported platform: ${sys}`)
}
function getBinPath(tool) {
const ext = os.platform() === 'win32' ? '.exe' : ''
return path.join(BIN_DIR, tool + ext)
}
function isInstalled(tool) {
const p = getBinPath(tool)
if (fs.existsSync(p)) return true
try {
const r = spawnSync(tool, ['--version'], { stdio: 'pipe' })
return r.status === 0
} catch {
return false
}
}
function getSystemPath(tool) {
try {
const r = spawnSync('which', [tool], { stdio: 'pipe' })
if (r.status === 0) return r.stdout.toString().trim()
} catch {}
try {
const r = spawnSync('where', [tool], { stdio: 'pipe' })
if (r.status === 0) return r.stdout.toString().split('\n')[0].trim()
} catch {}
return null
}
function resolveBin(tool) {
// 1. our managed bin d
--- package/src/core/config.js (excerpt) ---
const fs = require('fs')
const path = require('path')
const os = require('os')
const CONFIG_DIR = path.join(os.homedir(), '.ethsmith')
const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json')
function loadConfig() {
if (!fs.existsSync(CONFIG_PATH)) return {}
try {
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'))
} catch {
return {}
}
}
function mergeConfig(cliOpts) {
const file = loadConfig()
// CLI opts win over file config — only merge file values not set by CLI
const merged = { ...file }
for (const [k, v] of Object.entries(cliOpts)) {
if (v !== undefined && v !== false && v !== null && v !== '') {
merged[k] = v
} else if (!(k in merged)) {
merged[k] = v
}
}
return merged
}
function ensureConfigDir() {
fs.mkdirSync(CONFIG_DIR, { recursive: true })
}
function writeDefaultConfig() {
ensureConfigDir()
if (fs.existsSync(CONFIG_PATH)) return
const defaults = {
port: 8545,
accounts: 10,
chainId: 1337,
gasLimit: 30000000,
balance: 1000,
stateInterval: 30,
logLevel: 'info'
}
fs.writeFileSync(CONFIG_PATH, JSON.stringify(defaults, null, 2))
}
module.exports = { loadConfig, mergeConfig, writeDefaultConfig, CONFIG_PATH, CONFIG_DIR }
--- package/src/core/db.js (excerpt) ---
const { Level } = require('level')
const zlib = require('zlib')
const path = require('path')
const os = require('os')
const fs = require('fs')
const logger = require('./logger')
const DB_ROOT = path.join(os.homedir(), '.ethsmith', 'db')
class EthsmithDB {
constructor(chainId, dbPath) {
this.chainId = chainId
this.dbPath = dbPath || path.join(DB_ROOT, String(chainId))
fs.mkdirSync(this.dbPath, { recursive: true })
this.db = new Level(this.dbPath, { valueEncoding: 'buffer' })
this._ready = false
}
async open() {
if (this._ready) return
await this.db.open()
this._ready = true
logger.debug('LevelDB opened', { path: this.dbPath, chainId: this.chainId })
}
async close() {
if (!this._ready) return
await this.db.close()
this._ready = false
logger.debug('LevelDB closed', { chainId: this.chainId })
}
// ── State persistence (Anvil state hex → gzip → LevelDB) ──────────────────
async saveState(stateHex) {
// anvil_dumpState returns 0x-prefixed hex — strip prefix before encoding
const raw = stateHex.startsWith('0x') ? stateHex.slice(2) : stateHex
const buf = Buffer.from(raw, 'hex')
const compressed = zlib.gzipSync(buf)
const ts = Date.now()
await this.db.put(`state:latest`, compressed)
await this.db.put(`state:${ts}`, compressed)
logger.info('State saved to LevelDB', {
chainId: this.chainId,
rawBytes: buf.length,
compressedBytes: compressed.length,
ratio: (com{
"name": "ethsmith",
"version": "1.0.0",
"description": "Unified Ethereum dev toolkit — Ganache-compatible API powered by Foundry (Forge + Cast + Anvil + Chisel) with LevelDB persistence",
"main": "src/index.js",
"bin": {
"ethsmith": "bin/ethsmith.js"
},
"scripts": {
"start": "node bin/ethsmith.js",
"test": "node test/basic.js"
},
"keywords": [
"ethereum",
"ganache",
"foundry",
"anvil",
"forge",
"cast",
"chisel",
"blockchain",
"solidity",
"evm",
"hardhat",
"web3",
"development"
],
"author": "Lord1Egypt",
"license": "MIT",
"dependencies": {
"commander": "^12.0.0",
"level": "^8.0.0",
"axios": "^1.6.0",
"tar": "^7.0.0",
"ethers": "^6.0.0",
"winston": "^3.11.0",
"winston-daily-rotate-file": "^5.0.0"
},
"engines": {
"node": ">=20.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/Lord1Egypt/ethsmith.git"
},
"homepage": "https://github.com/Lord1Egypt/ethsmith#readme",
"bugs": {
"url": "https://github.com/Lord1Egypt/ethsmith/issues"
}
}