Appendix E: Bitcoin and Ethereum RPC API Reference
This appendix provides a quick reference for the blockchain APIs and libraries used in the textbook's code exercises and data analysis chapters. It covers the raw RPC interfaces for Bitcoin and Ethereum, the two most popular Ethereum client libraries (web3.py and ethers.js), the Etherscan data API, and The Graph's GraphQL subgraph queries.
E.1 Bitcoin Core RPC
Bitcoin Core exposes a JSON-RPC interface on port 8332 (mainnet) or 18332 (testnet). Authentication uses the rpcuser and rpcpassword configured in bitcoin.conf.
Connection
import requests
import json
def bitcoin_rpc(method, params=None):
"""Call Bitcoin Core RPC."""
url = "http://127.0.0.1:8332"
headers = {"Content-Type": "application/json"}
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params or [],
}
response = requests.post(
url,
data=json.dumps(payload),
headers=headers,
auth=("rpcuser", "rpcpassword"),
)
result = response.json()
if result.get("error"):
raise Exception(result["error"])
return result["result"]
Common Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
getblockchaininfo |
— | Object | Chain state: blocks, difficulty, chain name, verification progress |
getblockcount |
— | Integer | Current block height |
getblockhash |
height |
String | Block hash at given height |
getblock |
blockhash, verbosity |
Object | Block data. Verbosity: 0=hex, 1=JSON, 2=JSON with full tx |
getrawtransaction |
txid, verbose, blockhash |
Object/String | Transaction data. verbose=true for decoded JSON |
decoderawtransaction |
hex_string |
Object | Decode a raw transaction hex without broadcasting |
getmempoolinfo |
— | Object | Mempool size, bytes, fee statistics |
getrawmempool |
verbose |
Array/Object | List of unconfirmed transaction IDs |
getnetworkinfo |
— | Object | P2P network: version, connections, relay fee |
getpeerinfo |
— | Array | Connected peers with latency, services, and version |
estimatesmartfee |
conf_target |
Object | Fee estimate (BTC/kvB) for confirmation within n blocks |
gettxout |
txid, n, include_mempool |
Object | UTXO data for a specific output |
getbalance |
*args |
Number | Wallet balance (requires wallet loaded) |
sendrawtransaction |
hex_string |
String | Broadcast a signed raw transaction, returns txid |
validateaddress |
address |
Object | Check if an address is valid |
Example Calls
# Get the latest block height
height = bitcoin_rpc("getblockcount")
# 831542
# Get a block by height
block_hash = bitcoin_rpc("getblockhash", [831542])
block = bitcoin_rpc("getblock", [block_hash, 2]) # verbosity=2 includes full transactions
print(f"Block {block['height']}: {len(block['tx'])} transactions")
# Get a transaction
tx = bitcoin_rpc("getrawtransaction", ["txid_here", True])
print(f"Inputs: {len(tx['vin'])}, Outputs: {len(tx['vout'])}")
print(f"Size: {tx['vsize']} vbytes")
# Estimate fee for 6-block confirmation
fee = bitcoin_rpc("estimatesmartfee", [6])
print(f"Fee rate: {fee['feerate']} BTC/kvB")
# Get mempool statistics
mempool = bitcoin_rpc("getmempoolinfo")
print(f"Mempool: {mempool['size']} txs, {mempool['bytes']} bytes")
E.2 Ethereum JSON-RPC
Ethereum nodes (Geth, Nethermind, Besu, Erigon) expose a JSON-RPC interface over HTTP (port 8545), WebSocket (port 8546), or IPC. Hosted providers (Infura, Alchemy, QuickNode) expose the same interface over HTTPS.
Connection
import requests
import json
def eth_rpc(method, params=None, provider_url="http://127.0.0.1:8545"):
"""Call Ethereum JSON-RPC."""
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": method,
"params": params or [],
}
response = requests.post(provider_url, json=payload)
result = response.json()
if "error" in result:
raise Exception(result["error"])
return result["result"]
Common Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
eth_blockNumber |
— | Hex string | Latest block number |
eth_getBlockByNumber |
block_number, full_txs |
Object | Block data; full_txs=true includes transaction objects |
eth_getBlockByHash |
block_hash, full_txs |
Object | Block data by hash |
eth_getBalance |
address, block |
Hex string | Account balance in wei |
eth_getTransactionByHash |
tx_hash |
Object | Transaction data |
eth_getTransactionReceipt |
tx_hash |
Object | Receipt with status, gas used, logs |
eth_getTransactionCount |
address, block |
Hex string | Nonce (transaction count) for address |
eth_call |
{to, data}, block |
Hex string | Execute a read-only call (no state change) |
eth_sendRawTransaction |
signed_tx_hex |
String | Broadcast a signed transaction, returns tx hash |
eth_estimateGas |
{from, to, data, value} |
Hex string | Estimate gas for a transaction |
eth_gasPrice |
— | Hex string | Current gas price in wei |
eth_getLogs |
{fromBlock, toBlock, address, topics} |
Array | Event logs matching filter criteria |
eth_getCode |
address, block |
Hex string | Contract bytecode at address (0x for EOAs) |
eth_getStorageAt |
address, slot, block |
Hex string | Raw storage value at slot |
eth_chainId |
— | Hex string | Network chain ID |
net_version |
— | String | Network ID |
web3_clientVersion |
— | String | Node client and version |
Block Parameter Values
Many methods accept a block parameter:
| Value | Meaning |
|---|---|
"latest" |
Most recent mined block |
"pending" |
Pending block (includes mempool transactions) |
"earliest" |
Genesis block |
"finalized" |
Most recent finalized block (PoS) |
"safe" |
Most recent safe block (PoS) |
"0x..." |
Specific block number in hex |
Example Calls
provider = "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
# Get latest block number
block_num = eth_rpc("eth_blockNumber", provider_url=provider)
print(f"Block: {int(block_num, 16)}")
# Get ETH balance
balance_wei = eth_rpc(
"eth_getBalance",
["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest"],
provider_url=provider,
)
balance_eth = int(balance_wei, 16) / 1e18
print(f"Balance: {balance_eth:.4f} ETH")
# Get event logs (ERC-20 Transfer events from a token)
transfer_topic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
logs = eth_rpc("eth_getLogs", [{
"fromBlock": hex(19000000),
"toBlock": hex(19000100),
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC
"topics": [transfer_topic],
}], provider_url=provider)
print(f"Found {len(logs)} Transfer events")
E.3 Web3.py Common Operations
Web3.py is the primary Python library for Ethereum interaction (used in Chapters 11, 13, 14, 19, 20, 22, 33, 35).
Setup
from web3 import Web3
# Connect to a provider
w3 = Web3(Web3.HTTPProvider("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"))
# For WebSocket (real-time subscriptions)
# w3 = Web3(Web3.WebSocketProvider("wss://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"))
# For local node
# w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
assert w3.is_connected()
Account and Balance Operations
# Get balance
balance = w3.eth.get_balance("0xAddress")
print(w3.from_wei(balance, "ether"))
# Get transaction count (nonce)
nonce = w3.eth.get_transaction_count("0xAddress")
# Get current gas price
gas_price = w3.eth.gas_price
# Get chain ID
chain_id = w3.eth.chain_id
Transaction Operations
from eth_account import Account
# Build and sign a transaction
acct = Account.from_key("0xPrivateKey")
tx = {
"nonce": w3.eth.get_transaction_count(acct.address),
"to": "0xRecipient",
"value": w3.to_wei(0.01, "ether"),
"gas": 21000,
"maxFeePerGas": w3.to_wei(30, "gwei"),
"maxPriorityFeePerGas": w3.to_wei(2, "gwei"),
"chainId": w3.eth.chain_id,
}
signed = acct.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Status: {'success' if receipt.status == 1 else 'failed'}")
Contract Interactions
# Load a contract
contract = w3.eth.contract(address="0xContractAddress", abi=contract_abi)
# Read (call) - no gas, no signing
balance = contract.functions.balanceOf("0xAddress").call()
name = contract.functions.name().call()
total_supply = contract.functions.totalSupply().call()
# Write (transact) - requires signing
tx = contract.functions.transfer(
"0xRecipient",
w3.to_wei(100, "ether")
).build_transaction({
"from": acct.address,
"nonce": w3.eth.get_transaction_count(acct.address),
"maxFeePerGas": w3.to_wei(30, "gwei"),
"maxPriorityFeePerGas": w3.to_wei(2, "gwei"),
})
signed = acct.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
Event Log Queries
# Get past events
events = contract.events.Transfer.get_logs(
from_block=19000000,
to_block=19001000,
argument_filters={"from": "0xSenderAddress"},
)
for event in events:
print(f"{event.args['from']} -> {event.args['to']}: {event.args['value']}")
# Create a filter for new events
event_filter = contract.events.Transfer.create_filter(from_block="latest")
# Poll for new events
new_events = event_filter.get_new_entries()
Utility Functions
# Unit conversion
w3.to_wei(1.5, "ether") # 1500000000000000000
w3.from_wei(1500000000000000000, "ether") # Decimal('1.5')
# Address utilities
w3.is_address("0xABC...") # True/False
w3.to_checksum_address("0xabc...") # Mixed-case checksummed
# Hashing
w3.keccak(text="Hello").hex() # Keccak-256
w3.solidity_keccak(["address", "uint256"], [addr, amount]).hex()
# ENS resolution (if connected to mainnet)
address = w3.ens.address("vitalik.eth")
E.4 Ethers.js v6 Common Operations
Ethers.js is the dominant JavaScript library for Ethereum. Version 6 is the current major release. Used in textbook JavaScript/frontend examples.
Setup
import { ethers } from "ethers";
// Connect to provider
const provider = new ethers.JsonRpcProvider(
"https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"
);
// For MetaMask (browser)
// const provider = new ethers.BrowserProvider(window.ethereum);
// Create a signer (for sending transactions)
const signer = new ethers.Wallet("0xPrivateKey", provider);
Account and Balance
// Get balance
const balance = await provider.getBalance("0xAddress");
console.log(ethers.formatEther(balance)); // "1.5"
// Get nonce
const nonce = await provider.getTransactionCount("0xAddress");
// Get gas price
const feeData = await provider.getFeeData();
console.log(ethers.formatUnits(feeData.gasPrice, "gwei"));
// Get block
const block = await provider.getBlock("latest");
console.log(block.number, block.timestamp);
Contract Interactions
// Read-only contract (no signer needed)
const contract = new ethers.Contract(contractAddress, abi, provider);
const name = await contract.name();
const balance = await contract.balanceOf("0xAddress");
// Read-write contract (signer required)
const contractWithSigner = new ethers.Contract(contractAddress, abi, signer);
const tx = await contractWithSigner.transfer("0xRecipient", ethers.parseEther("100"));
const receipt = await tx.wait();
console.log(`Tx confirmed in block ${receipt.blockNumber}`);
Event Queries
// Query past events
const filter = contract.filters.Transfer("0xFromAddress", null);
const events = await contract.queryFilter(filter, fromBlock, toBlock);
events.forEach((event) => {
console.log(`${event.args.from} -> ${event.args.to}: ${event.args.value}`);
});
// Listen for new events (real-time)
contract.on("Transfer", (from, to, value, event) => {
console.log(`Transfer: ${from} -> ${to}: ${ethers.formatEther(value)}`);
});
Utilities
// Unit conversion
ethers.parseEther("1.5"); // 1500000000000000000n (BigInt)
ethers.formatEther(1500000000000000000n); // "1.5"
ethers.parseUnits("30", "gwei"); // 30000000000n
ethers.formatUnits(30000000000n, "gwei"); // "30.0"
// Hashing
ethers.keccak256(ethers.toUtf8Bytes("Hello"));
ethers.solidityPackedKeccak256(["address", "uint256"], [addr, amount]);
// ABI encoding
const iface = new ethers.Interface(abi);
const calldata = iface.encodeFunctionData("transfer", [recipient, amount]);
const decoded = iface.decodeFunctionResult("balanceOf", returnData);
// Address validation
ethers.isAddress("0xABC...");
ethers.getAddress("0xabc..."); // Checksummed
E.5 Etherscan API Endpoints
The Etherscan API provides indexed blockchain data without running a full node. Rate limit: 5 calls/second (free tier). API key required.
Base URLs
| Network | URL |
|---|---|
| Ethereum Mainnet | https://api.etherscan.io/api |
| Sepolia Testnet | https://api-sepolia.etherscan.io/api |
| Polygon | https://api.polygonscan.com/api |
| Arbitrum | https://api.arbiscan.io/api |
| Optimism | https://api-optimistic.etherscan.io/api |
| Base | https://api.basescan.org/api |
Common Endpoints
import requests
BASE = "https://api.etherscan.io/api"
API_KEY = "YOUR_KEY"
def etherscan(module, action, **params):
params.update({"module": module, "action": action, "apikey": API_KEY})
response = requests.get(BASE, params=params)
return response.json()["result"]
| Module | Action | Parameters | Returns |
|---|---|---|---|
account |
balance |
address, tag |
Balance in wei |
account |
balancemulti |
address (comma-sep), tag |
Multiple balances |
account |
txlist |
address, startblock, endblock, sort |
Normal transactions |
account |
txlistinternal |
address, startblock, endblock |
Internal transactions |
account |
tokentx |
address, contractaddress |
ERC-20 transfers |
account |
tokennfttx |
address, contractaddress |
ERC-721 transfers |
contract |
getabi |
address |
Contract ABI JSON |
contract |
getsourcecode |
address |
Verified source code |
transaction |
getstatus |
txhash |
Transaction execution status |
transaction |
gettxreceiptstatus |
txhash |
Receipt status (1=success, 0=fail) |
block |
getblockreward |
blockno |
Block and uncle rewards |
stats |
ethprice |
— | Current ETH price (USD, BTC) |
stats |
ethsupply |
— | Total ETH supply |
gastracker |
gasoracle |
— | Gas price estimates (low, medium, high) |
logs |
getLogs |
fromBlock, toBlock, address, topic0... |
Event logs |
Example Queries
# Get transaction history for an address
txs = etherscan("account", "txlist",
address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
startblock=0,
endblock=99999999,
sort="desc"
)
print(f"Found {len(txs)} transactions")
# Get ERC-20 token transfers
transfers = etherscan("account", "tokentx",
address="0xAddress",
contractaddress="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", # USDC
sort="desc"
)
# Get verified contract ABI (for use with web3.py)
import json
abi_json = etherscan("contract", "getabi", address="0xContractAddress")
abi = json.loads(abi_json)
# Get current gas prices
gas = etherscan("gastracker", "gasoracle")
print(f"Low: {gas['SafeGasPrice']} | Medium: {gas['ProposeGasPrice']} | High: {gas['FastGasPrice']} Gwei")
E.6 The Graph — GraphQL Queries
The Graph is a decentralized indexing protocol that indexes blockchain events into queryable subgraphs. Many DeFi protocols publish subgraphs providing rich, structured data.
Query Endpoint
Subgraphs are queried via HTTP POST to a GraphQL endpoint:
https://gateway.thegraph.com/api/YOUR_API_KEY/subgraphs/id/SUBGRAPH_ID
Or for the hosted service (being sunset):
https://api.thegraph.com/subgraphs/name/ORGANIZATION/SUBGRAPH_NAME
Python Client
import requests
def query_subgraph(subgraph_url, query, variables=None):
"""Execute a GraphQL query against a subgraph."""
response = requests.post(
subgraph_url,
json={"query": query, "variables": variables or {}},
)
data = response.json()
if "errors" in data:
raise Exception(data["errors"])
return data["data"]
Example: Uniswap V3 Queries
UNISWAP_V3 = "https://gateway.thegraph.com/api/KEY/subgraphs/id/5zvR82..."
# Top pools by TVL
result = query_subgraph(UNISWAP_V3, """
{
pools(first: 10, orderBy: totalValueLockedUSD, orderDirection: desc) {
id
token0 { symbol }
token1 { symbol }
feeTier
totalValueLockedUSD
volumeUSD
}
}
""")
for pool in result["pools"]:
print(f"{pool['token0']['symbol']}/{pool['token1']['symbol']}: "
f"TVL ${float(pool['totalValueLockedUSD']):,.0f}")
Example: Aave V3 Queries
AAVE_V3 = "https://gateway.thegraph.com/api/KEY/subgraphs/id/..."
# Get market data
result = query_subgraph(AAVE_V3, """
{
markets(first: 10, orderBy: totalValueLockedUSD, orderDirection: desc) {
name
inputToken { symbol }
totalValueLockedUSD
totalBorrowBalanceUSD
rates {
rate
side
type
}
}
}
""")
Example: ENS Queries
ENS_SUBGRAPH = "https://gateway.thegraph.com/api/KEY/subgraphs/id/..."
# Look up an ENS name
result = query_subgraph(ENS_SUBGRAPH, """
{
domains(where: { name: "vitalik.eth" }) {
name
resolvedAddress { id }
owner { id }
expiryDate
registrant { id }
createdAt
}
}
""")
Popular Subgraphs
| Protocol | Data Available |
|---|---|
| Uniswap V2/V3 | Pools, swaps, positions, token prices, volume |
| Aave V2/V3 | Markets, borrows, liquidations, flash loans |
| Compound V2/V3 | Markets, accounts, proposals, governance |
| Lido | Staking deposits, withdrawals, oracle reports |
| ENS | Domains, registrations, transfers, resolvers |
| OpenSea (Seaport) | Sales, orders, collections |
| Chainlink | Price feeds, oracle updates, VRF requests |
| Maker/DAI | Vaults, liquidations, stability fees |
Pagination
The Graph limits results to 1,000 per query. Use cursor-based pagination for larger datasets:
all_swaps = []
last_id = ""
while True:
result = query_subgraph(UNISWAP_V3, """
query ($lastId: String!) {
swaps(first: 1000, where: { id_gt: $lastId }, orderBy: id) {
id
timestamp
amountUSD
pool { id }
}
}
""", variables={"lastId": last_id})
swaps = result["swaps"]
if not swaps:
break
all_swaps.extend(swaps)
last_id = swaps[-1]["id"]
print(f"Fetched {len(all_swaps)} total swaps")
This reference covers the primary APIs for interacting with Bitcoin and Ethereum programmatically. For hands-on practice, work through the exercises in Chapters 6, 11, 13, 33, and 35, which use these APIs extensively.