Summary
nova ens <cid> --ens <name> submits the setContenthash transaction without first checking the wallet's ETH balance. When the wallet is underfunded:
- The RPC accepts the raw tx and returns a hash (most public RPCs skip balance checks at submission time).
- Block builders reject the tx when trying to include it (not enough ETH to cover
gas × gasPrice).
- 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.
Summary
nova ens <cid> --ens <name>submits thesetContenthashtransaction without first checking the wallet's ETH balance. When the wallet is underfunded:gas × gasPrice).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:
Output:
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:
Expected behavior
Before calling
resolverContract.setContenthash(node, contenthash)insrc/ens.ts, compute the upper bound on gas cost (gasEstimate × maxFeePerGas) and compare againstprovider.getBalance(wallet.address). If insufficient, throw a clear error including:Example:
Proposed fix
In
src/ens.tsupdateEnsContenthash, between the resolver-is-found check and thestaticCalldry-run, add: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 ens3× 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.comwas used, but the issue is not RPC-specific — any node that accepts raw txs without balance validation shows the same symptom.