Skip to content

nova ens: check wallet balance before submitting setContenthash tx #1

@TippyFlitsUK

Description

@TippyFlitsUK

Summary

nova ens <cid> --ens <name> submits the setContenthash transaction without first checking the wallet's ETH balance. When the wallet is underfunded:

  1. The RPC accepts the raw tx and returns a hash (most public RPCs skip balance checks at submission time).
  2. Block builders reject the tx when trying to include it (not enough ETH to cover gas × gasPrice).
  3. Nova waits 120s for confirmation, times out, and reports: "Transaction not confirmed within 120s. It may have been dropped (gas too low) or is still pending."

That message lists plausible causes but misses the actual one. In CI with an automatic retry loop, this wastes 120s × N retries per deploy without ever succeeding — and each retry sends a new tx with the next nonce, burning through any remaining balance trying.

Reproduction

Wallet with <0.0005 ETH on mainnet:

NOVA_ENS_KEY=0x... NOVA_RPC_URL=https://ethereum-rpc.publicnode.com \
  nova ens bafybeicvntbhmsx3ybwd7brva6upxz4mabm7ei72of2k44endly53myfo4 \
  --ens <yourname>.eth

Output:

┃  ✔ Connected to Ethereum
┃  ✔ Wallet 0x...
┃  ✔ Resolver 0x231b0Ee14048e9dCcD1d247744d114a4EB5E8E63
┃  ⏳ Updating <yourname>.eth
┃  tx: 0x...
┃  ⏳ Waiting for confirmation...
┗━━
✘ ENS update failed: Transaction not confirmed within 120s.
  It may have been dropped (gas too low) or is still pending.

Check the tx hash on etherscan or via eth_getTransactionByHash — it's nowhere. The correct error should have been "wallet is underfunded" before any tx was ever broadcast.

Submitting the same tx with explicit gas overrides via a standalone script surfaces the real error:

insufficient funds for intrinsic transaction cost

Expected behavior

Before calling resolverContract.setContenthash(node, contenthash) in src/ens.ts, compute the upper bound on gas cost (gasEstimate × maxFeePerGas) and compare against provider.getBalance(wallet.address). If insufficient, throw a clear error including:

  • Current balance in ETH
  • Required balance in ETH
  • Wallet address (so the user can send ETH to it)

Example:

✘ Wallet 0x244d2454... has 0.000114 ETH, needs ~0.00056 ETH for gas.
  Top up: send at least 0.001 ETH to 0x244d2454...

Proposed fix

In src/ens.ts updateEnsContenthash, between the resolver-is-found check and the staticCall dry-run, add:

const gasEstimate = await resolverContract.setContenthash.estimateGas(node, contenthash)
const feeData = await provider.getFeeData()
const maxCost = gasEstimate * (feeData.maxFeePerGas ?? feeData.gasPrice ?? 0n)
const balance = await provider.getBalance(wallet.address)
if (balance < maxCost) {
  throw new Error(
    `Wallet ${wallet.address} is underfunded.\n\n` +
    `  Balance: ${ethers.formatEther(balance)} ETH\n` +
    `  Needed:  ~${ethers.formatEther(maxCost)} ETH\n\n` +
    `  Top up by sending ETH to ${wallet.address}.`
  )
}

This also makes CI fail fast with actionable output instead of wasting retry budget on doomed submissions.

Additional context

Observed in a GitHub Actions CI loop that retries nova ens 3× with 15s backoff on failure. Three consecutive 120s waits (≈6 minutes of CI time) produced three tx hashes, none ever mined, wallet balance unchanged, before the workflow gave up.

NOVA_RPC_URL=https://ethereum-rpc.publicnode.com was used, but the issue is not RPC-specific — any node that accepts raw txs without balance validation shows the same symptom.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions