TL;DR
CoinGecko API provides a single, multi-chain solution for tracking a crypto wallet's balances, trades, token transfers, and PnL using a single provider.
- Pull balances with the Token Balances by Wallet Address endpoint to see every token a wallet holds, valued in USD, across multiple networks in one call.
- Pull trades with the Trades by Wallet Address endpoint. Each trade is labeled
buyorselland includes USD pricing. - Use the Token Transfers by Wallet Address endpoint to capture token movements that a trade-only feed misses.
- Merge trades and transfers into one chronological activity feed, then add realized and unrealized PnL from the PnL by Wallet Address endpoint.
A wallet balance shows the assets a wallet currently holds. To understand its activity, portfolio dashboards, tax tools, and wallet analytics also need trade and transfer data. The trades endpoint shows what was swapped and at what price, while the transfers endpoint shows which tokens moved in or out and in which direction. This provides the data needed to track both trades and asset movements.
This guide uses CoinGecko API's Onchain Wallet endpoints to retrieve both data sets for a wallet address and combine them into a single activity feed, sorted by time and labeled by type. It also covers how to add realized and unrealized PnL. Trade responses include USD pricing, so no separate price feed is needed to calculate trade values.

Prerequisites
You’ll need a CoinGecko API key on the Analyst plan or above to use all four endpoints.
This guide uses the same Ethereum wallet address throughout, so the balance snapshot, trade history, transfer history, and PnL summary all describe the same wallet. A trade represents a swap with a buy or sell side and price, while a transfer represents a token moving into or out of a wallet through a swap, send, deposit, or airdrop. A single swap appears in both data sets as a trade and as the token transfers that moved the assets.
To get started, install the dependencies.
1 2
requests>=2.31.0 python-dotenv>=1.0.0
pip install -r requirements.txt1
COINGECKO_PRO_API_KEY=CG-your_api_key_here
How to Fetch a Wallet's Balances Across Multiple Networks
CoinGecko API’s Token Balances by Wallet Address endpoint returns every token a wallet holds across one or more networks in a single call. Pass a comma-separated networks list, such as eth, base, or arbitrum, to see a wallet’s full multi-chain portfolio in one request instead of querying each chain’s RPC separately and merging the results yourself.
Each holding includes price_usd and value_usd, so you don’t need a separate price feed. Use token_type to filter for native or non_native (ERC-20 / SPL) tokens, and value_usd_min or reserve_in_usd_min to filter out dust and low-liquidity spam tokens.
Verify the endpoint with a curl request:
curl --request GET \
--url "https://pro-api.coingecko.com/api/v3/onchain/wallets/0x9642b23ed1e01df1092b92641051881a322f5d4e/balances?networks=eth,base,arbitrum" \
--header "x-cg-pro-api-key: CG-your_api_key_here"
This gives you a wallet’s current holdings and USD value to pair with the trade, transfer, and PnL history covered next. Every call in this guide uses the same API client, so set it up once before working through the endpoints.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
import os import requests from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("COINGECKO_PRO_API_KEY") BASE_URL = "https://pro-api.coingecko.com/api/v3" HEADERS = {"x-cg-pro-api-key": API_KEY} def get_balances(address, networks): """Fetch a wallet's token balances across networks.""" url = f"{BASE_URL}/onchain/wallets/{address}/balances" response = requests.get(url, headers=HEADERS, params={"networks": networks}, timeout=20) response.raise_for_status() return response.json() def get_trades(network, address, **params): """Fetch a wallet's trades on a single network.""" url = f"{BASE_URL}/onchain/networks/{network}/wallets/{address}/trades" response = requests.get(url, headers=HEADERS, params=params, timeout=20) response.raise_for_status() return response.json() def get_transfers(network, address, **params): """Fetch a wallet's token transfers on a single network.""" # SDK equivalent (pip install coingecko_sdk): # from coingecko_sdk import Coingecko # client = Coingecko() # reads COINGECKO_PRO_API_KEY automatically # response = client.onchain.networks.wallets.transfers.get(address, network=network, **params) url = f"{BASE_URL}/onchain/networks/{network}/wallets/{address}/transfers" response = requests.get(url, headers=HEADERS, params=params, timeout=20) response.raise_for_status() return response.json() def get_pnl(address, networks): """Fetch a wallet's realized and unrealized PnL across networks in one VM family.""" url = f"{BASE_URL}/onchain/wallets/{address}/pnl" response = requests.get(url, headers=HEADERS, params={"networks": networks}, timeout=20) response.raise_for_status() return response.json()
Calling get_balances("0x9642b23ed1e01df1092b92641051881a322f5d4e", "eth,base,arbitrum") returns a data object. The top-level networks array shows the USD value and holding count per chain, so you can see how a wallet’s assets are distributed before drilling into individual balances:
{
"data": {
"id": "0x9642b23ed1e01df1092b92641051881a322f5d4e",
"type": "wallet_balances",
"attributes": {
"wallet_address": "0x9642b23ed1e01df1092b92641051881a322f5d4e",
"total_value_usd": "8452.10",
"total_holdings": 14,
"networks": [
{
"network": "eth",
"value_usd": "6210.55",
"holdings": 9,
"last_updated_at": "2026-09-24T09:00:00Z"
},
{
"network": "base",
"value_usd": "2241.55",
"holdings": 5,
"last_updated_at": "2026-09-24T09:00:00Z"
}
],
"balances": [
{
"network": "eth",
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"token_type": "erc20",
"name": "Wrapped Ether",
"symbol": "WETH",
"decimals": 18,
"coingecko_coin_id": "weth",
"balance_raw": "412000000000000000",
"balance": "0.412",
"price_usd": "2521.47",
"value_usd": "1038.85",
"h24_price_change_percentage": "1.32",
"total_reserve_in_usd": "48210233.10"
}
]
}
}
}
How to Fetch a Wallet's Trade History
CoinGecko API’s Trades by Wallet Address endpoint returns trade history for a specified network and wallet address. Each result is labeled as a buy or sell and includes the USD price for both sides of the swap. The endpoint supports 250+ networks, including Ethereum (network="eth") and Solana (network="solana")
Pass from and to together to query a fixed window of up to 30 days, or omit both parameters to default to the most recent 7 days. If the response contains more results, pass the next_cursor value from meta in your next request to fetch the next page.
Each entry's attributes object contains kind, which is either buy or sell, along with price_from_in_usd and price_to_in_usd for both sides of the swap and volume_in_usd for the total trade volume in USD. The response looks like this:
{
"id": "eth_26043243_0x3a1c...142",
"type": "trade",
"attributes": {
"pool_address": "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640",
"pool_dex": "uniswap_v3",
"block_number": 26043243,
"block_timestamp": "2026-09-23T22:03:47Z",
"tx_hash": "0x3a1c...142",
"tx_from_address": "0x9642b23ed1e01df1092b92641051881a322f5d4e",
"kind": "buy",
"from_token_address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"to_token_address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"from_token_amount": "500.000000",
"to_token_amount": "0.198342",
"price_from_in_usd": "1.0002",
"price_to_in_usd": "2521.47",
"volume_in_usd": "500.10"
}
}
How to Fetch a Wallet's Transfers
CoinGecko API’s Token Transfers by Wallet Address endpoint returns token transfer history for a specified network and wallet address. Each result includes a transfer direction and a resolved token amount, separate from its price. This captures activity that a trades-only view misses, such as deposits, sends, and airdrops that move tokens without a swap.
The from and to parameters define the date range, while the cursor fetches additional pages of results. The direction parameter filters results to incoming (in) or outgoing (out) transfers.
Each entry includes direction, which is out when the wallet is the from_address and in when it is the to_address. It also includes amount, the token amount transferred. Here is the response:
{
"id": "eth_26046243_0x08550aecf7a762599ceaac52bddfdb42cec1ebfc14cb9bfd9c895e7278502134_413_out",
"type": "transfer",
"attributes": {
"block_number": 26046243,
"tx_hash": "0x08550aecf7a762599ceaac52bddfdb42cec1ebfc14cb9bfd9c895e7278502134",
"direction": "out",
"token_address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"amount_raw": "99598300",
"amount": "99.5983",
"block_timestamp": "2026-09-24T08:51:11Z",
"from_address": "0x9642b23ed1e01df1092b92641051881a322f5d4e",
"to_address": "0xb24c9abdeb1b06c19dece441fa259d69631e1e3b",
"name": "Tether USD",
"symbol": "USDT",
"decimals": 6
}
}
How to Fetch a Wallet's PnL Across Multiple Networks
The PnL by Wallet Address endpoint returns a wallet’s realized and unrealized profit and loss across one or more networks in a single call. It uses the same multi-chain design as the balances endpoint, providing a wallet’s full trading performance without stitching together per-chain results.
Pass a comma-separated networks list, as long as every network belongs to the same VM family. For example, eth, base, and arbitrum can be queried together because they’re all EVM, while Solana requires a separate request. The response includes total_realized_pnl_usd and total_unrealized_pnl_usd for the requested networks combined, a networks array with the PnL breakdown by chain, and a token_stats array with the breakdown by token.
The endpoint returns a combined PnL summary across the requested networks, along with a per-network breakdown, giving you a consolidated view of wallet performance without requiring separate PnL requests and manual aggregation for each chain. The API returns the following:
{
"data": {
"id": "0x9642b23ed1e01df1092b92641051881a322f5d4e",
"type": "wallet_pnl",
"attributes": {
"wallet_address": "0x9642b23ed1e01df1092b92641051881a322f5d4e",
"total_realized_pnl_usd": "1284.52",
"total_unrealized_pnl_usd": "312.90",
"total_tokens": 14,
"networks": [
{
"network": "eth",
"realized_pnl_usd": "980.15",
"unrealized_pnl_usd": "150.40",
"tokens": 9
},
{
"network": "base",
"realized_pnl_usd": "304.37",
"unrealized_pnl_usd": "162.50",
"tokens": 5
}
],
"token_stats": [
{
"network": "eth",
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"name": "Wrapped Ether",
"symbol": "WETH",
"decimals": 18,
"realized_pnl_usd": "540.22",
"unrealized_pnl_usd": "88.10",
"token_balance": "0.412",
"average_buy_price_usd": "2410.00",
"average_sell_price_usd": "2601.55",
"total_buy_count": 6,
"total_sell_count": 4,
"total_buy_usd": "2901.84",
"total_sell_usd": "2061.63"
}
]
}
}
}
How to Build a Multi-Chain Crypto Wallet Portfolio Dashboard and Feed
Balances, trades, transfers, and PnL each provide a different view of a wallet. Pulling all four for the same wallet address gives you a complete multi-chain view of holdings, activity, and performance from one API. Trades and transfers combine into one chronological activity feed, while balances and PnL provide separate portfolio and performance summaries.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
from wallet_client import get_balances, get_trades, get_transfers, get_pnl WALLET = "0x9642b23ed1e01df1092b92641051881a322f5d4e" NETWORK = "eth" NETWORKS = "eth,base,arbitrum" def fetch_all_trades(network, address): """Page through every trade using the cursor CoinGecko returns.""" trades = [] cursor = None while True: params = {"per_page": 100} if cursor: params["cursor"] = cursor payload = get_trades(network, address, **params) trades.extend(payload["data"]) cursor = payload["meta"]["next_cursor"] if not cursor: break return trades def fetch_all_transfers(network, address): """Page through every transfer using the cursor CoinGecko returns.""" transfers = [] cursor = None while True: params = {"per_page": 100} if cursor: params["cursor"] = cursor payload = get_transfers(network, address, **params) transfers.extend(payload["data"]) cursor = payload["meta"]["next_cursor"] if not cursor: break return transfers def build_activity_feed(trades, transfers): """Merge trades and transfers into one feed, labeled and sorted by time.""" feed = [] for trade in trades: attrs = trade["attributes"] feed.append({ "type": "trade", "timestamp": attrs["block_timestamp"], "tx_hash": attrs["tx_hash"], "kind": attrs["kind"], "volume_usd": attrs["volume_in_usd"], }) for transfer in transfers: attrs = transfer["attributes"] feed.append({ "type": "transfer", "timestamp": attrs["block_timestamp"], "tx_hash": attrs["tx_hash"], "direction": attrs["direction"], "token": attrs["symbol"], "amount": attrs["amount"], }) feed.sort(key=lambda entry: entry["timestamp"], reverse=True) return feed if __name__ == "__main__": balances = get_balances(WALLET, NETWORKS) portfolio = balances["data"]["attributes"] print(f"Portfolio value: ${portfolio['total_value_usd']} across {len(portfolio['networks'])} networks\n") trades = fetch_all_trades(NETWORK, WALLET) transfers = fetch_all_transfers(NETWORK, WALLET) feed = build_activity_feed(trades, transfers) for entry in feed[:10]: if entry["type"] == "trade": print(f"{entry['timestamp']} TRADE {entry['kind']:<4} ${entry['volume_usd']}") else: print(f"{entry['timestamp']} TRANSFER {entry['direction']:<3} {entry['amount']} {entry['token']}") pnl = get_pnl(WALLET, NETWORKS) summary = pnl["data"]["attributes"] print(f"\nRealized PnL: ${summary['total_realized_pnl_usd']}") print(f"Unrealized PnL: ${summary['total_unrealized_pnl_usd']}") for network in summary["networks"]: print(f" {network['network']:<10} realized ${network['realized_pnl_usd']} unrealized ${network['unrealized_pnl_usd']}")
fetch_all_trades and fetch_all_transfers fetch data for one network at a time. To build a feed across multiple networks, run both for each network and merge the results by timestamp.
Once trades and transfers are in the same list, the type field identifies each event as a trade or transfer. This matters because one swap can produce one trade entry and two transfer entries for the tokens that moved.
PnL stays separate from the activity feed. Trades and transfers are individual events with timestamps, while realized and unrealized PnL are totals. get_pnl therefore returns a separate PnL summary instead of adding PnL to the event list.
fetch_all_trades with network="solana" and add PnL separately with get_pnl.Running activity_feed.py prints the portfolio value, merged activity feed, and PnL summary, including the per-network breakdown:
Portfolio value: $8452.10 across 2 networks
2026-09-24T09:14:02Z TRADE sell $312.40
2026-09-24T08:51:11Z TRANSFER out 99.5983 USDT
2026-09-24T08:51:11Z TRANSFER out 1803.28 LINK
2026-09-23T22:03:47Z TRADE buy $500.10
2026-09-23T19:40:15Z TRANSFER in 250.0 USDC
Realized PnL: $1284.52
Unrealized PnL: $312.90
eth realized $980.15 unrealized $150.40
base realized $304.37 unrealized $162.50
The per-network breakdown enables a multi-chain dashboard to be built from a single wallet address. The API provides balances, trades, transfers, and aggregated PnL across the requested networks, all through a single provider.
The dashboard below is built directly from the data above:

Conclusion
CoinGecko API provides the data to build a complete, multi-chain view of any wallet, including balances, trade history, token transfers, and realized and unrealized PnL, across 250+ networks through one API. Query balances and PnL across multiple chains in a single call, then combine trades and transfers into one onchain activity feed with USD pricing.
Once you have wallet activity covered, combine it with CoinGecko’s market data endpoints to build a multi-chain crypto platform with real-time prices and market data alongside wallet balances, trades, transfers, and PnL. Create a Crypto Portfolio Dashboard shows how to bring these data points together in a portfolio dashboard. You can also track newly created pools across networks or stream real-time crypto prices with REST and WebSocket
Ready to start building? The CoinGecko API Analyst plan unlocks the onchain wallet endpoints used in this guide, along with 500K monthly API call credits and a 500 RPM rate limit. If you are not ready to subscribe yet, start with a free Demo API key to explore 60+ endpoints with 10K free monthly API call credits.
