Basic Node + Express service and CLI to fetch an Ethereum address's transactions from Etherscan (normal, internal, ERC-20, ERC-721, ERC-1155), normalize them, and export to CSV.
- Prerequisites
- Node 18+
- Etherscan API key (
https://etherscan.io/apis)
- Install
npm install- Configure environment
Create a .env file in project root (not committed):
echo "ETHERSCAN_API_KEY=YOUR_KEY" >> .env
echo "BASE_URL=https://api.etherscan.io/api" >> .env- Run the server
npm run start
# Health check
curl http://localhost:3000/health
# Export CSV via HTTP (downloads file)
open "http://localhost:3000/export?address=0xa39b189482f984388a34460636fea9eb181ad1a6"- Use the CLI
# Export to CSV
npx eth-export 0xa39b189482f984388a34460636fea9eb181ad1a6
# Custom output dir
npx eth-export 0xd620AADaBaA20d2af700853C4504028cba7C3333 -o ./my-exports- Calls Etherscan account endpoints to fetch:
- Normal (external) txs
- Internal transactions
- ERC-20 token transfers
- ERC-721 NFT transfers
- ERC-1155 transfers
- Normalizes into a single CSV with columns:
transactionHash, dateTime, from, to, transactionType, assetContract, assetSymbol, tokenId, value, gasFeeEth
- This is intentionally minimal and coded in a day: Node + Express, no DB.
- Etherscan is the only provider used;
- Large addresses (e.g., 100k+ txs) are fetched in pages (offset=10000). The script will iterate until no more results or max pages reached. It is because of Etherscan rate limits.
- Rate limiting: The app includes 1-second delays between API calls to respect the FREE tier's 2 calls/second limit. This makes the export process take ~5-6 seconds but ensures it works reliably. These delays can be removed if you upgrade to a higher API tier with higher rate limits.
- Thin client per provider:
src/clients/etherscanClient.jsexposes simple methods and centralized pagination/rate delay. Makes it easy to swap providers. - Unified normalization layer:
src/utils/normalize.jsconverts provider-specific shapes into one row schema for CSV, so the exporter and API don't care about backend shape. - Dual entrypoints: REST endpoint
/exportand CLIeth-exportshare the same service (src/services/exportService.js).
GET /health→{ ok: true }GET /export?address=<ethAddress>→ returns a CSV file download
0xa39b189482f984388a34460636fea9eb181ad1a6— small accountEtherscan link0xd620AADaBaA20d2af700853C4504028cba7C3333— sample addressEtherscan link0xfb50526f49894b78541b776f5aaefe43e3bd8590— heavy activity (assumed)Etherscan link
- transactionHash
- dateTime (ISO string)
- from
- to
- transactionType (ETH, INTERNAL, ERC-20, ERC-721, ERC-1155)
- assetContract (if applicable)
- assetSymbol / name
- tokenId (NFTs)
- value (human-readable decimals)
- gasFeeEth (for normal txs)
- Etherscan address page used as data reference:
https://etherscan.io/address/0xa39b189482f984388a34460636fea9eb181ad1a6