polybrainz_etherscan

pub package License: MIT

A production-ready Dart wrapper for the Etherscan API V2, supporting 60+ EVM chains with a single API key.

Features

  • Multi-chain Support: Ethereum, Polygon, Arbitrum, Optimism, Base, BSC, Avalanche, and 50+ more chains
  • Type-safe: Extension types for addresses, hashes, and Wei amounts with compile-time validation
  • Resilient: Built-in rate limiting, circuit breaker, and retry with exponential backoff
  • Cached: In-memory LRU cache with configurable TTL
  • No Magic Strings: All constants are strongly typed enums
  • Code Generation: Freezed models with JSON serialization

Supported Chains

Mainnets Testnets
Ethereum, Polygon, Arbitrum One, Optimism, Base, BSC, Avalanche, Fantom, Cronos, Gnosis, Linea, Scroll, zkSync Era, Polygon zkEVM, Mantle, Celo, Moonbeam, Moonriver, Blast, Fraxtal, Taiko, WEMIX, Kroma, opBNB, Zora Sepolia, Holesky, Goerli, BSC Testnet, Polygon Amoy, Arbitrum Sepolia, Optimism Sepolia, Base Sepolia, Blast Sepolia

Installation

dependencies:
  polybrainz_etherscan: ^1.0.0

Quick Start

import 'package:polybrainz_etherscan/polybrainz_etherscan.dart';

void main() async {
  // Create API instance
  final api = await EtherscanApi.create(apiKey: 'YOUR_API_KEY');

  // Get ETH balance
  final result = await api.account.getBalance(
    EthereumAddress('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
  );

  result.when(
    success: (balance) => print('Balance: ${balance.balanceInEther} ETH'),
    failure: (error) => print('Error: ${error.message}'),
  );

  // Switch to Polygon
  final polygonApi = api.forChain(Chain.polygon);
  final polygonBalance = await polygonApi.account.getBalance(
    EthereumAddress('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
  );
}

API Modules

Account

// Single balance
final balance = await api.account.getBalance(address);

// Multiple balances (up to 20 addresses)
final balances = await api.account.getBalanceMulti([address1, address2]);

// Normal transactions
final txs = await api.account.getTransactions(address, page: 1, offset: 10);

// Internal transactions
final internalTxs = await api.account.getInternalTransactions(address);

// ERC-20 token transfers
final tokens = await api.account.getTokenTransfers(address);

// ERC-721 NFT transfers
final nfts = await api.account.getNftTransfers(address);

Contract

// Get verified contract ABI
final abi = await api.contract.getAbi(contractAddress);

// Get source code
final source = await api.contract.getSourceCode(contractAddress);

Gas

// Get gas oracle (safe, propose, fast prices)
final gas = await api.gas.getGasOracle();

gas.when(
  success: (oracle) {
    print('Safe: ${oracle.safeGasPrice.toGwei} Gwei');
    print('Propose: ${oracle.proposeGasPrice.toGwei} Gwei');
    print('Fast: ${oracle.fastGasPrice.toGwei} Gwei');
  },
  failure: (e) => print(e),
);

// Estimate confirmation time for a gas price
final time = await api.gas.estimateConfirmationTime(Wei.fromGwei(50));

Stats

// Get ETH price
final price = await api.stats.getEthPrice();

// Get total ETH supply
final supply = await api.stats.getEthSupply();

Transaction

// Check execution status
final status = await api.transaction.getStatus(txHash);

// Check receipt status
final receipt = await api.transaction.getReceiptStatus(txHash);

Logs

// Get event logs
final logs = await api.logs.getLogs(
  address: contractAddress,
  fromBlock: BlockNumber(18000000),
  toBlock: BlockNumber(18001000),
  topic0: '0xddf252ad...', // Transfer event signature
);

Type-Safe Values

// Ethereum address with checksum validation
final address = EthereumAddress('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045');

// Transaction hash validation
final txHash = TransactionHash('0x15f8e5ea1079d9a0bb04a4c58ae5fe7654b5b2b4463375ff7ffb490aa0032f3a');

// Wei with safe arithmetic
final wei = Wei.fromEther(1.5);
print(wei.toGwei); // 1500000000.0
print(wei.toEther); // 1.5

// Block tags
final latest = BlockTagName.latest;
final specific = BlockNumber(18000000);

Error Handling

The library uses a Result<T> type for explicit error handling:

final result = await api.account.getBalance(address);

// Pattern matching
result.when(
  success: (balance) => print(balance.balanceInEther),
  failure: (error) => print(error.message),
);

// Or use isSuccess/isFailure
if (result.isSuccess) {
  final balance = result.value;
}

// Exception hierarchy
try {
  // ...
} on RateLimitException catch (e) {
  print('Rate limited, retry after: ${e.retryAfter}');
} on ApiErrorException catch (e) {
  print('API error: ${e.message}');
} on NetworkException catch (e) {
  print('Network error: ${e.message}');
} on EtherscanException catch (e) {
  print('General error: ${e.message}');
}

Configuration

final api = await EtherscanApi.create(
  apiKey: 'YOUR_API_KEY',
  chain: Chain.ethereum,
  config: EtherscanConfig(
    connectTimeout: Duration(seconds: 30),
    receiveTimeout: Duration(seconds: 30),
    rateLimitConfig: RateLimitConfig(
      requestsPerSecond: 5,
      burstSize: 10,
    ),
    circuitBreakerConfig: CircuitBreakerConfig(
      failureThreshold: 5,
      resetTimeout: Duration(minutes: 1),
    ),
    cacheConfig: CacheConfig(
      memoryCacheSize: 1000,
    ),
  ),
);

Multi-Chain Usage

// Start with Ethereum
final api = await EtherscanApi.create(apiKey: 'YOUR_API_KEY');

// Switch chains easily
final polygonApi = api.forChain(Chain.polygon);
final arbitrumApi = api.forChain(Chain.arbitrumOne);
final baseApi = api.forChain(Chain.base);

// Each instance shares the same HTTP client and cache
final ethBalance = await api.account.getBalance(address);
final maticBalance = await polygonApi.account.getBalance(address);

Getting an API Key

  1. Create a free account at Etherscan.io
  2. Go to API Keys
  3. Create a new API key
  4. The same key works for all 60+ supported chains

License

MIT License - see LICENSE for details.