Skip to content

feat: add status and accounts commands#1

Merged
Lord1Egypt merged 1 commit into
mainfrom
feat/healthcheck
Jun 9, 2026
Merged

feat: add status and accounts commands#1
Lord1Egypt merged 1 commit into
mainfrom
feat/healthcheck

Conversation

@Lord1Egypt

Copy link
Copy Markdown
Owner

Summary

  • ethsmith status — queries the running node (default port 8545), shows chain ID, current block number, account count, and top-5 balances
  • ethsmith accounts — lists every account with ETH balance; --json flag for machine-readable output
  • Both commands: built-in http module only (zero new dependencies), --port flag to target any port, exit 1 with helpful message when no node is running

Test plan

  • Start a node with ethsmith and run ethsmith status — verify chain ID, block, accounts print
  • Run ethsmith accounts — verify all 10 Ganache accounts listed
  • Run ethsmith accounts --json — verify valid JSON array
  • Run ethsmith status --port 9999 with no node — verify exit 1 message
  • Verify no new npm dependencies added

🤖 Generated with Claude Code

ethsmith status — shows chain ID, block number, account count + balances
ethsmith accounts — lists all accounts with ETH balances, supports --json

Both use built-in http module (zero new deps), query JSON-RPC on
configurable --port (default 8545), and exit 1 with helpful message
when no node is running.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Lord1Egypt Lord1Egypt merged commit 42eb668 into main Jun 9, 2026
5 checks passed
@Lord1Egypt Lord1Egypt deleted the feat/healthcheck branch June 9, 2026 22:30

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the status and accounts commands to the CLI to query node status and list accounts with their balances. The review feedback highlights three main issues: code duplication of the rpc helper function, incorrect JSON-RPC error handling that could lead to runtime crashes, and a lack of port validation. The reviewer provided a refactored implementation that extracts a shared createRpcClient helper to address these concerns.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/cli/index.js
Comment on lines +391 to +510
// ── STATUS ────────────────────────────────────────────────────────────────
program
.command('status')
.description('Show running node status: block number, chain ID, accounts, port')
.option('-p, --port <port>', 'RPC port to query', '8545')
.action(async (opts) => {
const http = require('http')
const port = parseInt(opts.port, 10)

function rpc (method, params = []) {
return new Promise((resolve, reject) => {
const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
const req = http.request({ host: '127.0.0.1', port, path: '/', method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
}, res => {
let data = ''
res.on('data', c => { data += c })
res.on('end', () => {
try { resolve(JSON.parse(data).result) } catch { reject(new Error('bad response')) }
})
})
req.on('error', reject)
req.setTimeout(3000, () => { req.destroy(); reject(new Error('timeout')) })
req.write(body)
req.end()
})
}

const ok = s => ` \x1b[32m✔\x1b[0m ${s}`
const err = s => ` \x1b[31m✖\x1b[0m ${s}`

console.log(`\nethsmith status — port ${port}\n`)

try {
const [blockHex, chainHex, accounts] = await Promise.all([
rpc('eth_blockNumber'),
rpc('eth_chainId'),
rpc('eth_accounts'),
])
const block = parseInt(blockHex, 16)
const chain = parseInt(chainHex, 16)

console.log(ok(`Node running on http://127.0.0.1:${port}`))
console.log(ok(`Chain ID ${chain}`))
console.log(ok(`Block #${block}`))
console.log(ok(`Accounts ${accounts.length}`))
if (accounts.length) {
const bals = await Promise.all(accounts.slice(0, 5).map(a => rpc('eth_getBalance', [a, 'latest'])))
console.log()
accounts.slice(0, 5).forEach((a, i) => {
const eth = (BigInt(bals[i]) * 100n / 10n ** 18n)
const display = `${eth / 100n}.${String(eth % 100n).padStart(2, '0')}`
console.log(` ${a} ${display} ETH`)
})
if (accounts.length > 5) console.log(` ... and ${accounts.length - 5} more`)
}
console.log()
} catch (e) {
console.log(err(`No node running on port ${port} (${e.message})`))
console.log(`\n Start one with: \x1b[33methsmith\x1b[0m\n`)
process.exit(1)
}
})

// ── ACCOUNTS ──────────────────────────────────────────────────────────────
program
.command('accounts')
.description('List accounts and ETH balances from the running node')
.option('-p, --port <port>', 'RPC port to query', '8545')
.option('--json', 'output as JSON')
.action(async (opts) => {
const http = require('http')
const port = parseInt(opts.port, 10)

function rpc (method, params = []) {
return new Promise((resolve, reject) => {
const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
const req = http.request({ host: '127.0.0.1', port, path: '/', method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
}, res => {
let data = ''
res.on('data', c => { data += c })
res.on('end', () => {
try { resolve(JSON.parse(data).result) } catch { reject(new Error('bad response')) }
})
})
req.on('error', reject)
req.setTimeout(3000, () => { req.destroy(); reject(new Error('timeout')) })
req.write(body)
req.end()
})
}

try {
const accounts = await rpc('eth_accounts')
const bals = await Promise.all(accounts.map(a => rpc('eth_getBalance', [a, 'latest'])))

const result = accounts.map((a, i) => {
const wei = BigInt(bals[i])
const eth = Number(wei) / 1e18
return { address: a, balance_wei: bals[i], balance_eth: eth.toFixed(4) }
})

if (opts.json) {
console.log(JSON.stringify(result, null, 2))
return
}

console.log(`\n Accounts (${result.length} total)\n`)
result.forEach((r, i) => {
console.log(` [${i}] ${r.address}`)
console.log(` ${r.balance_eth} ETH`)
})
console.log()
} catch (e) {
console.error(` No node on port ${port}: ${e.message}`)
console.error(` Start one with: ethsmith`)
process.exit(1)
}
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There are three main issues in the newly added commands:

  1. Code Duplication: The rpc helper function is duplicated verbatim in both the status and accounts commands.
  2. Incorrect JSON-RPC Error Handling: The rpc helper resolves with undefined when the node returns a JSON-RPC error (since it only checks JSON.parse(data).result and doesn't check for the error field). This causes downstream TypeError crashes (e.g., when trying to read accounts.length or converting bals[i] to BigInt).
  3. Missing Port Validation: The port option is parsed using parseInt but never validated, which can lead to NaN and unexpected behavior if an invalid port is provided.

Extracting a shared createRpcClient helper inside buildCLI resolves the duplication, handles JSON-RPC errors correctly by rejecting on error, and allows for clean port validation in both commands.

  // ── RPC HELPER ────────────────────────────────────────────────────────────
  function createRpcClient(port) {
    const http = require('http')
    return function rpc(method, params = []) {
      return new Promise((resolve, reject) => {
        const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
        const req = http.request({
          host: '127.0.0.1',
          port,
          path: '/',
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(body)
          }
        }, res => {
          let data = ''
          res.on('data', c => { data += c })
          res.on('end', () => {
            try {
              const parsed = JSON.parse(data)
              if (parsed.error) {
                reject(new Error(parsed.error.message || 'RPC error'))
              } else {
                resolve(parsed.result)
              }
            } catch {
              reject(new Error('bad response'))
            }
          })
        })
        req.on('error', reject)
        req.setTimeout(3000, () => {
          req.destroy()
          reject(new Error('timeout'))
        })
        req.write(body)
        req.end()
      })
    }
  }

  // ── STATUS ────────────────────────────────────────────────────────────────
  program
    .command('status')
    .description('Show running node status: block number, chain ID, accounts, port')
    .option('-p, --port <port>', 'RPC port to query', '8545')
    .action(async (opts) => {
      const port = parseInt(opts.port, 10)
      if (isNaN(port)) {
        console.error('  \\x1b[31m✖\\x1b[0m  Invalid port number')
        process.exit(1)
      }
      const rpc = createRpcClient(port)

      const ok  = s => `  \\x1b[32m✔\\x1b[0m  ${s}`
      const err = s => `  \\x1b[31m✖\\x1b[0m  ${s}`

      console.log(`\\nethsmith status — port ${port}\\n`)

      try {
        const [blockHex, chainHex, accounts] = await Promise.all([
          rpc('eth_blockNumber'),
          rpc('eth_chainId'),
          rpc('eth_accounts'),
        ])
        const block = parseInt(blockHex, 16)
        const chain = parseInt(chainHex, 16)

        console.log(ok(`Node     running on http://127.0.0.1:${port}`))
        console.log(ok(`Chain ID ${chain}`))
        console.log(ok(`Block    #${block}`))
        console.log(ok(`Accounts ${accounts ? accounts.length : 0}`))
        if (accounts && accounts.length) {
          const bals = await Promise.all(accounts.slice(0, 5).map(a => rpc('eth_getBalance', [a, 'latest'])))
          console.log()
          accounts.slice(0, 5).forEach((a, i) => {
            const eth = (BigInt(bals[i]) * 100n / 10n ** 18n)
            const display = `${eth / 100n}.${String(eth % 100n).padStart(2, '0')}`
            console.log(`       ${a}  ${display} ETH`)
          })
          if (accounts.length > 5) console.log(`       ... and ${accounts.length - 5} more`)
        }
        console.log()
      } catch (e) {
        console.log(err(`No node running on port ${port}  (${e.message})`))
        console.log(`\\n  Start one with: \\x1b[33methsmith\\x1b[0m\\n`)
        process.exit(1)
      }
    })

  // ── ACCOUNTS ──────────────────────────────────────────────────────────────
  program
    .command('accounts')
    .description('List accounts and ETH balances from the running node')
    .option('-p, --port <port>', 'RPC port to query', '8545')
    .option('--json', 'output as JSON')
    .action(async (opts) => {
      const port = parseInt(opts.port, 10)
      if (isNaN(port)) {
        console.error('  Invalid port number')
        process.exit(1)
      }
      const rpc = createRpcClient(port)

      try {
        const accounts = await rpc('eth_accounts')
        if (!accounts || !Array.isArray(accounts)) {
          throw new Error('No accounts returned from node')
        }
        const bals = await Promise.all(accounts.map(a => rpc('eth_getBalance', [a, 'latest'])))

        const result = accounts.map((a, i) => {
          const wei = BigInt(bals[i] || 0)
          const eth = Number(wei) / 1e18
          return { address: a, balance_wei: bals[i], balance_eth: eth.toFixed(4) }
        })

        if (opts.json) {
          console.log(JSON.stringify(result, null, 2))
          return
        }

        console.log(`\\n  Accounts (${result.length} total)\\n`)
        result.forEach((r, i) => {
          console.log(`  [${i}] ${r.address}`)
          console.log(`       ${r.balance_eth} ETH`)
        })
        console.log()
      } catch (e) {
        console.error(`  No node on port ${port}: ${e.message}`)
        console.error(`  Start one with: ethsmith`)
        process.exit(1)
      }
    })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant