> ## Documentation Index
> Fetch the complete documentation index at: https://goldrush.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Measuring token holder concentration (Gini & HHI)

> Compute Gini and HHI concentration metrics for any token's holders with the GoldRush token_holders_v2 endpoint, and how it compares to Etherscan's token holder API.

## The use case

Two questions come up constantly in token research, risk scoring, and airdrop design:

* **How concentrated is this token?** A handful of wallets controlling most of the supply is a governance and liquidity risk.
* **Is it getting more or less concentrated over time?** Concentration trending up after an unlock or an airdrop is a red flag.

Two standard metrics answer this:

* **Gini coefficient**: inequality across all holders. `0` means every holder owns an equal amount; `1` means a single wallet owns everything.
* **HHI (Herfindahl-Hirschman Index)**: the sum of squared ownership shares. It ranges from `1/N` (perfectly even across `N` holders) to `1` (one holder owns everything), and it is dominated by the largest holders, so it is the sharper "whale detector" of the two.

Both metrics need the same raw inputs, for **every** holder of the token:

* the holder's **balance**,
* the token's **total supply** (to turn balances into ownership shares),
* the **holder count** (`N`),
* and, ideally, the ability to pull all of the above **as of a past block** so you can chart the trend.

This guide computes both metrics with the GoldRush [Get token holders (v2)](https://goldrush.dev/docs/api-reference/foundational-api/balances/get-token-holders-as-of-any-block-height-v2.md) endpoint, then compares the approach to Etherscan's token holder API.

## The formulas

With holder balances `bᵢ` and shares `sᵢ = bᵢ / Σ bⱼ`:

* **HHI** `= Σ sᵢ²`
* **Gini** `= Σᵢ (2i − N − 1) · b₍ᵢ₎ / (N · Σ b₍ⱼ₎)`, where `b₍ᵢ₎` are balances sorted ascending and `i` runs from `1` to `N`.

Worked sanity check for three holders owning `[20, 30, 50]`: shares are `[0.2, 0.3, 0.5]`, so **HHI = 0.04 + 0.09 + 0.25 = 0.38** and **Gini = 0.20**.

## What you need from an API

| Requirement | Why it matters |
| - | - |
| Every holder's balance | Both metrics are sums over all holders, so a truncated list understates concentration. |
| Total supply | Converts raw balances into ownership shares. |
| Holder count (`N`) | Sets the HHI floor (`1/N`) and normalizes the Gini. |
| Point-in-time holders | Required to chart concentration over time, not just today. |

## Fetching holders and computing Gini & HHI with GoldRush

The [GoldRush TypeScript SDK](https://goldrush.dev/docs/goldrush-sdks.md) exposes `getTokenHoldersV2ForTokenAddress` as an async iterator that pages through **all** holders for you. Each item carries `balance`, `total_supply`, and `contract_decimals` inline, so you never need a second request to normalize the data.

```typescript Gini & HHI theme={null}
import { GoldRushClient } from "@covalenthq/client-sdk";

const client = new GoldRushClient("<GOLDRUSH_API_KEY>");

// Pull every holder. The SDK auto-paginates via the async iterator;
// page-size 1000 (the max) keeps the number of round-trips low.
async function getAllBalances(chain: string, token: string) {
  const balances: number[] = [];
  let totalSupply = 0;
  let decimals = 0;

  for await (const page of client.BalanceService.getTokenHoldersV2ForTokenAddress(
    chain,
    token,
    { pageSize: 1000 },
  )) {
    if (page.error) throw new Error(page.error_message);
    for (const holder of page.data.items ?? []) {
      decimals = holder.contract_decimals ?? 0;
      totalSupply = Number(holder.total_supply);          // returned inline
      balances.push(Number(holder.balance) / 10 ** decimals);
    }
  }
  return { balances, totalSupply: totalSupply / 10 ** decimals };
}

function hhi(values: number[]): number {
  const sum = values.reduce((a, b) => a + b, 0);
  if (sum === 0) return 0;
  return values.reduce((acc, v) => acc + (v / sum) ** 2, 0);
}

function gini(values: number[]): number {
  const xs = [...values].sort((a, b) => a - b);
  const n = xs.length;
  const sum = xs.reduce((a, b) => a + b, 0);
  if (n === 0 || sum === 0) return 0;
  let weighted = 0;
  for (let i = 0; i < n; i++) weighted += (2 * (i + 1) - n - 1) * xs[i];
  return weighted / (n * sum);
}

const { balances, totalSupply } = await getAllBalances(
  "eth-mainnet",
  "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC
);

// Optional: share of total supply held by the top 10 holders.
const top10 =
  [...balances].sort((a, b) => b - a).slice(0, 10).reduce((a, b) => a + b, 0) /
  totalSupply;

console.log({
  holders: balances.length,
  gini: gini(balances).toFixed(4),
  hhi: hhi(balances).toFixed(6),
  top10ShareOfSupply: (top10 * 100).toFixed(2) + "%",
});
```

```typescript Concentration over time theme={null}
// Pass block-height (or date) to snapshot holders in the past, then
// recompute the same metrics to build a time series. Historical holders
// are supported on Foundational Chains.
async function giniAtBlock(chain: string, token: string, blockHeight: number) {
  const balances: number[] = [];
  for await (const page of client.BalanceService.getTokenHoldersV2ForTokenAddress(
    chain,
    token,
    { pageSize: 1000, blockHeight },
  )) {
    if (page.error) throw new Error(page.error_message);
    for (const holder of page.data.items ?? []) {
      const decimals = holder.contract_decimals ?? 0;
      balances.push(Number(holder.balance) / 10 ** decimals);
    }
  }
  return gini(balances);
}

const blocks = [18_000_000, 19_000_000, 20_000_000];
const series = await Promise.all(
  blocks.map(async (b) => ({ block: b, gini: await giniAtBlock("eth-mainnet", "0xa0b8...eb48", b) })),
);
console.table(series);
```

> [!NOTE]
>
> `balance` and `total_supply` are returned as raw strings in the token's base units. The examples divide by `10 ^ contract_decimals` for readability; for tokens whose supply exceeds JavaScript's safe-integer range, sum the raw values with `BigInt` before taking ratios if you need exact precision.

## The Etherscan approach

Etherscan exposes the equivalent data through `tokenholderlist` (module `token`, action `tokenholderlist`), paginated with `page` and `offset`:

```bash theme={null}
curl "https://api.etherscan.io/api?module=token&action=tokenholderlist\
&contractaddress=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48\
&page=1&offset=1000&apikey=<KEY>"
```

Each row returns only `TokenHolderAddress` and `TokenHolderQuantity`. To compute Gini or HHI you then have to:

* fetch the **total supply separately** (the `stats/tokensupply` endpoint), since it is not in the holder response;
* apply the token's **decimals separately** (also not in the holder response);
* accept that the list is **current holders only**, with no way to snapshot holders at a past block, so a concentration-over-time chart is not possible from this endpoint.

Note also that `tokenholderlist` and `tokenholdercount` are **PRO endpoints**, available only on Etherscan's paid Standard plan and above.

## Side-by-side

| For a Gini/HHI pipeline | GoldRush `token_holders_v2` | Etherscan `tokenholderlist` |
| - | - | - |
| Total supply in the same response | ✅ `total_supply` on every item | ❌ separate `tokensupply` call |
| Decimals in the same response | ✅ `contract_decimals` | ❌ separate lookup |
| Holder count (`N`) | ✅ `pagination.total_count` | Separate `tokenholdercount` call |
| Point-in-time (historical) holders | ✅ `block-height` / `date` | ❌ current only |
| Max page size | 1000 | 1000 |
| Auto-pagination in the SDK | ✅ async iterator | ❌ manual `page`/`offset` loop |
| Multichain under one key | ✅ 100+ chains | Per-chain access |
| Access tier | Included (0.02 credits/item) | PRO (paid) endpoint |

## Where GoldRush pulls ahead

1. **One response has everything the math needs.** `balance`, `total_supply`, and `contract_decimals` arrive together, so shares are a one-line calculation, with no second request and no separate supply/decimals bookkeeping.
2. **Concentration over time, not just a snapshot.** `block-height` and `date` let you recompute Gini/HHI at any historical point on [Foundational Chains](https://goldrush.dev/chains/) and chart the trend across unlocks, airdrops, and listings.
3. **`N` comes for free.** `pagination.total_count` gives the holder count in the same call, so the HHI floor (`1/N`) and Gini normalization need no extra request.
4. **Fewer round-trips.** A page size of 1000 plus SDK auto-pagination enumerates every holder with minimal code.
5. **Same shape across 100+ chains.** One API key and one code path cover Ethereum, Polygon, BSC, Base, Arbitrum, and more.

## Where to start

> [card] [Get token holders (v2)](https://goldrush.dev/docs/api-reference/foundational-api/balances/get-token-holders-as-of-any-block-height-v2.md)
>
> Full parameter and response reference for the endpoint used above, including historical `block-height` and `date` queries.

> [card] [Foundational API quickstart](https://goldrush.dev/docs/goldrush-foundational-api/quickstart.md)
>
> Install the TypeScript SDK and make your first authenticated call in minutes.

> [card] [Estimate your cost](https://goldrush.dev/docs/pricing-calculator.md?endpoint=%2Fapi-reference%2Ffoundational-api%2Fbalances%2Fget-token-holders-as-of-any-block-height-v2)
>
> Project the monthly cost of pulling full holder lists at 0.02 credits per item.

> [card] [Supported chains](https://goldrush.dev/docs/goldrush-foundational-api/supported-chains.md)
>
> See which chains support the latest snapshot and which support historical holder queries.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.