Ganache: launched with deterministic accounts so private keys are predictable: npx ganache-cli -p 8545 -h 127.0.0.1 -d (or ganache --port 8545 --deterministic).
Geth: ran a dev node with RPC enabled: geth --http --http.addr 127.0.0.1 --http.port 8545 --http.api eth,net,web3,personal --dev. (Allow the personal API if you want to unlock accounts.)
Used Remix (Injected Web3) and a tiny ethers.js front end to call a simple Storage contract, then observed MetaMask popups and transaction receipts.
Commands I used to verify the node: curl with an eth_chainId JSON-RPC request. Example:
curl -s -X POST --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' http://127.0.0.1:8545
Why these choices? Deterministic Ganache simplifies private-key import; geth dev gives a closer match to a real node for testing account unlocking and chain configuration. You can reproduce the same steps on macOS, Linux, or Windows.
Connect localhost to MetaMask: step-by-step
- Open MetaMask, click the network dropdown, and choose "Add Network" or "Custom RPC". (You can also follow add-networks-custom-rpc.)
- Enter RPC URL:
http://127.0.0.1:8545 (or wherever your node is listening).
- For Chain ID, query the node using the curl command above and paste the decimal or hex value returned. MetaMask will reject a mismatch.
- Save and switch to that network.
And yes, if MetaMask warns about unknown chain ID, stop and verify the chainId from the node first. Incorrect chain IDs are a common blocker.
Import Ganache account to MetaMask (fast method)
Ganache exposes private keys and/or a mnemonic. The fastest, safest local workflow is:
- Start Ganache with deterministic keys (
-d flag) or check the GUI's accounts list for private keys.
- In MetaMask click the account circle → Import Account → Private Key.
- Paste the private key exactly (no 0x prefix is required by some UIs, but MetaMask accepts both formats). Confirm.
This imported account will mirror the Ganache account and let you sign transactions for that address. You can now open Remix (Injected Web3) or your dApp and deploy or call contracts using MetaMask. Example: sending a simple transaction will pop up the MetaMask confirm dialog and show gas estimate, gas limit, and nonce.
Screenshot note: when I did this, Ganache GUI showed account #0 private key; importing into MetaMask produced identical addresses and balances that matched the Ganache UI's balance (see placeholder image).

Security tip: only use test accounts on a local node. But don't import mainnet private keys into a test node — ever.
Import Geth account to MetaMask (options and safety)
Importing a Geth account is conceptually the same: MetaMask needs the raw private key. Geth stores keys as encrypted keystore JSON files under the keystore folder. Here are safe options:
Option A — transfer funds from Geth account to a MetaMask-created local account (recommended):
- Start geth with
--http --http.api personal,eth,net,web3 and unlock the account in the geth console: personal.unlockAccount('0xYourAddr', 'password', 60).
- Send a transaction to the MetaMask address:
eth.sendTransaction({from:'0xYourAddr', to:'0xMetaMaskAddr', value: '1000000000000000000'}) (1 ETH in wei). Now your MetaMask account has funds and can interact with contracts.
Option B — export the private key from the keystore (only do this on an isolated machine and offline):
- Use a local, offline tool to decrypt the keystore JSON with your keystore passphrase and extract the private key. A common programmatic route is to use an offline Node script with
ethereumjs-wallet to fromV3() the keystore and then print getPrivateKeyString() — run this code offline and delete the output after importing.
But be careful. If you extract private keys, store them only in memory or a secure temporary file, and erase after importing. I believe transferring funds to a MetaMask account you control (Option A) is safer for routine local testing.
Interact with smart contract MetaMask (deploy and call)
Want to call contract MetaMask will sign transactions for? Two easy approaches:
A) Remix + Injected Web3
- Open Remix, choose the Injected Web3 environment, select your Localhost network in MetaMask, and deploy from the MetaMask account. When you invoke a function that modifies state, MetaMask will prompt you to confirm the transaction.
B) Minimal web page + ethers.js (example snippet)
- Your dApp can use the injected provider to call contract methods. Example (pseudocode):
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send('eth_requestAccounts', []);
const signer = provider.getSigner();
const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, signer);
await contract.setValue(42); // MetaMask will show a confirm popup
This pattern works locally (localhost connecting to MetaMask) and is the same code you'd run against a testnet. The only difference is the RPC URL and chainId configured in MetaMask.
Call contract MetaMask to run read-only methods? Use provider.getBalance() or contract.callStatic.someView() and you won't trigger a signature popup because reads are free (no on-chain state change).
Common issues & troubleshooting tips
- MetaMask rejects connection: check chainId via eth_chainId and use that value in the custom RPC settings.
- Transactions stuck pending: on local nodes, miners may not be running. For Ganache this isn't an issue (it mines instantly). For geth dev, ensure mining is enabled or run with
--dev.
- CORS errors in the browser console: set
--http.corsdomain on geth or configure your local dev server appropriately.
- Account mismatch after importing: double-check the private key and that you imported into the correct MetaMask profile.
If you hit a weird error, see troubleshooting and developer-workflow for additional patterns I use every day.
Quick comparison: Ganache vs Geth vs Running your own localhost node
| Feature |
Ganache |
Geth (dev) |
Your own geth/private chain |
| Quick start |
Very fast |
Moderate |
Complex |
| Deterministic accounts |
Yes (with -d) |
Usually no |
Depends |
| Mines instantly |
Yes |
With --dev |
Depends (you manage miners) |
| Best for |
rapid contract iteration |
integration testing |
production-like simulation |
FAQ: short answers to common questions
Q: Is it safe to keep crypto in a hot wallet used for local development?
A: Keep real funds out of your local development accounts. Use disposable test accounts or transfer small amounts from a throwaway test wallet.
Q: How do I revoke a token approval when testing locally?
A: Use your dApp front end or a small script calling approve(tokenAddress, spender, 0) from the same MetaMask account. No need to revoke on an ephemeral test node unless you want to reproduce a bug.
Q: What happens if I lose my laptop with test accounts?
A: If the seed phrase or private keys are lost for non-production accounts, you can recreate deterministic Ganache accounts. But for real funds, see backup-and-recovery-seed-phrase.
Wrap-up and next steps (CTA)
Localhost connecting to MetaMask is one of the fastest ways to iterate on smart contracts and front-end dApps. Try starting Ganache with deterministic keys, import Ganache account to MetaMask, and deploy a contract from Remix — you'll see how quickly you can go from code to signed transaction. But be cautious: never import production private keys into a local node, and always run key-export steps offline if you must extract a private key.
If you want a step-by-step installer checklist, check install-metamask-chrome and add-networks-custom-rpc. For troubleshooting common errors I encountered while testing, see troubleshooting-common-errors.
Happy building. And if you run into a specific error while connecting a node, tell me the exact JSON-RPC response (or paste the eth_chainId curl output) and I can help diagnose.