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

# Get a wallet's DeFi positions

> Retrieve lending, staking, liquidity, and farming positions across DeFi protocols for any wallet, grouped by protocol and chain, using Zerion API.

**What you'll build:**

* Fetch all DeFi positions (staked, deposited, LP, locked, rewards)
* Filter by position type and chain
* Group positions by protocol and calculate per-protocol totals
* Build a complete runnable DeFi dashboard

```
Position                  | Protocol     | Module         | Amount    | Value
Supplied USDC             | Aave V3      | lending        | 5,000.00  | $5,000.00
Staked ETH                | Lido         | staked         | 1.20      | $2,543.88
ETH/USDC LP              | Uniswap V3   | liquidity_pool | 0.85      | $1,801.15
Locked CRV                | Curve        | locked         | 10,000.00 | $450.00
Unclaimed ARB             | Arbitrum     | rewards        | 200.00    | $230.00
```

**Time:** \~10 minutes

## Prerequisites

* A Zerion API key ([get one here](https://dashboard.zerion.io))
* A wallet address to query

## Steps

<Steps>
  <Step title="Fetch DeFi positions">
    Call the [positions endpoint](/api-reference/wallets/get-wallet-fungible-positions) with `filter[positions]=only_complex` to get only DeFi protocol positions, excluding regular wallet tokens.

    <CodeGroup>
      ```javascript JavaScript theme={null}
      const API_KEY = process.env.ZERION_API_KEY;
      const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
      const headers = {
        accept: "application/json",
        authorization: `Basic ${btoa(API_KEY + ":")}`,
      };

      const response = await fetch(
        `https://api.zerion.io/v1/wallets/${address}/positions/?currency=usd&filter[positions]=only_complex&sort=value`,
        { headers }
      );
      if (!response.ok) throw new Error(`API error: ${response.status}`);

      const { data } = await response.json();

      for (const position of data) {
        const { name, protocol, protocol_module, quantity, value } = position.attributes;
        console.log(
          `${name} | ${protocol} (${protocol_module}) | ${quantity?.float} | ${value != null ? `$${value.toFixed(2)}` : "N/A"}`
        );
      }
      ```

      ```python Python theme={null}
      import os, requests

      api_key = os.environ["ZERION_API_KEY"]
      address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

      response = requests.get(
          f"https://api.zerion.io/v1/wallets/{address}/positions/",
          params={
              "currency": "usd",
              "filter[positions]": "only_complex",
              "sort": "value",
          },
          auth=(api_key, ""),
      )

      for position in response.json()["data"]:
          attrs = position["attributes"]
          print(
              f"{attrs['name']} | "
              f"{attrs.get('protocol', 'N/A')} ({attrs.get('protocol_module', 'N/A')}) | "
              f"{attrs['quantity']['float']} | "
              f"{'${:.2f}'.format(attrs['value']) if attrs.get('value') is not None else 'N/A'}"
          )
      ```

      ```bash cURL theme={null}
      curl -g -u "YOUR_API_KEY:" \
        "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/positions/?currency=usd&filter[positions]=only_complex&sort=value"
      ```
    </CodeGroup>

    <Tip>
      **Key parameters:** `filter[positions]=only_complex` (DeFi positions only), `sort=value` (highest value first), `currency=usd` (also supports `eur`, `gbp`, etc.)
    </Tip>
  </Step>

  <Step title="Filter by position type (optional)">
    Use `filter[position_types]` to narrow results to specific DeFi categories.

    <CodeGroup>
      ```javascript JavaScript theme={null}
      const response = await fetch(
        `https://api.zerion.io/v1/wallets/${address}/positions/?currency=usd&filter[positions]=only_complex&filter[position_types]=deposit,staked`,
        { headers }
      );

      const { data } = await response.json();
      ```

      ```python Python theme={null}
      response = requests.get(
          f"https://api.zerion.io/v1/wallets/{address}/positions/",
          params={
              "currency": "usd",
              "filter[positions]": "only_complex",
              "filter[position_types]": "deposit,staked",
          },
          auth=(api_key, ""),
      )
      ```

      ```bash cURL theme={null}
      curl -g -u "YOUR_API_KEY:" \
        "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/positions/?currency=usd&filter[positions]=only_complex&filter[position_types]=deposit,staked"
      ```
    </CodeGroup>

    Available position types:

    | Type | Description |
    | - | - |
    | `deposit` | Assets deposited into lending pools, vaults, or liquidity pools |
    | `staked` | Assets staked for rewards, governance, or consensus |
    | `reward` | Unclaimed rewards earned from protocols |
    | `locked` | Vote-escrowed or time-locked positions |
    | `loan` | Borrowed assets. `value` is positive, so subtract it when you compute a net total |
    | `investment` | Tokenized funds, indices or structured products |
    | `wallet` | Regular wallet assets (excluded by `only_complex`) |
  </Step>

  <Step title="Filter by chain (optional)">
    To get DeFi positions on specific chains only, add `filter[chain_ids]`.

    ```bash theme={null}
    curl -g -u "YOUR_API_KEY:" \
      "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/positions/?currency=usd&filter[positions]=only_complex&filter[chain_ids]=ethereum,arbitrum"
    ```
  </Step>

  <Step title="Full working example">
    Save as `defi-dashboard.mjs` and run with `node defi-dashboard.mjs`:

    ```javascript theme={null}
    const API_KEY = process.env.ZERION_API_KEY;
    const BASE_URL = "https://api.zerion.io/v1";
    const headers = {
      accept: "application/json",
      authorization: `Basic ${btoa(API_KEY + ":")}`,
    };

    // The positions endpoint isn't paginated: one call returns every position
    async function getDeFiPositions(address) {
      const res = await fetch(
        `${BASE_URL}/wallets/${address}/positions/?currency=usd&filter[positions]=only_complex&sort=value`,
        { headers }
      );
      if (!res.ok) throw new Error(`API error: ${res.status}`);
      const { data } = await res.json();
      return data;
    }

    // Loans are debt: the API returns their value as a positive number, so subtract it
    function netValue(pos) {
      const value = pos.attributes.value || 0;
      return pos.attributes.position_type === "loan" ? -value : value;
    }

    async function displayDeFiDashboard(address) {
      const positions = await getDeFiPositions(address);

      // Group by protocol
      const byProtocol = {};
      for (const pos of positions) {
        const protocol = pos.attributes.protocol || "Unknown";
        if (!byProtocol[protocol]) byProtocol[protocol] = { total: 0, positions: [] };
        byProtocol[protocol].total += netValue(pos);
        byProtocol[protocol].positions.push(pos);
      }

      // Sort protocols by total value
      const sorted = Object.entries(byProtocol).sort((a, b) => b[1].total - a[1].total);

      const grandTotal = positions.reduce((sum, p) => sum + netValue(p), 0);
      console.log(`=== DeFi OVERVIEW ===`);
      console.log(`Net DeFi value: $${grandTotal.toFixed(2)}`);
      console.log(`Protocols: ${sorted.length}`);
      console.log(`Positions: ${positions.length}\n`);

      for (const [protocol, data] of sorted) {
        console.log(`--- ${protocol} ($${data.total.toFixed(2)}) ---`);
        for (const pos of data.positions) {
          const { name, position_type, value, quantity } = pos.attributes;
          const chain = pos.relationships.chain.data.id;
          console.log(
            `  [${position_type}] ${name} on ${chain}: ${quantity.float} (${value != null ? `$${value.toFixed(2)}` : "N/A"})`
          );
        }
        console.log();
      }
    }

    displayDeFiDashboard("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
    ```
  </Step>
</Steps>

## Response fields reference

Each DeFi position includes:

| Field | Description |
| - | - |
| `name` | Human-readable position name |
| `protocol` | Protocol name (e.g., "Aave V3", "Lido") |
| `protocol_module` | Type of DeFi interaction: `deposit`, `lending`, `yield`, `liquidity_pool`, `staked`, `farming`, `leveraged_farming`, `nft_staked`, `locked`, `vesting`, `rewards`, `investment`. Can be `null` |
| `position_type` | Position category (`deposit`, `staked`, `reward`, `locked`, `loan`, `investment`) |
| `quantity.float` | Token amount |
| `value` | Value of the position in the requested `currency` |
| `group_id` | ID to group related positions (e.g., both sides of an LP) |
| `relationships.chain` | Which chain the position is on |

## Deduplicating share tokens

Many DeFi positions come with a share token in the wallet, such as an aToken, a vault share or an LP token. The position names it in `receipt`: `receipt.fungible_info` is the share token and `receipt.quantity` is the amount held. The position's own `quantity` and `value` are the underlying asset.

With `filter[positions]=no_filter`, Zerion API often leaves out the share token's `wallet` row, but not always. To count each holding once, drop a `wallet` row when either is true:

* **Receipt match:** Its chain and contract address match the `receipt.fungible_info` of a returned position.
* **Not displayable:** `flags.displayable` is `false`.

Positions that share a `group_id`, such as the legs of a liquidity pool, can carry the same receipt. Count `receipt.quantity` once per `group_id`.

For example, on October 1, 2026, wallet address `0x9026A229b535ecF0162Dfe48fDeb3c75f7b2A7AE` returned a Compound V2 `deposit` with a `receipt` of 0.2442 cETH, and a separate `wallet` row for the same 0.2442 cETH. Each is worth about \$13. The code below drops the duplicate row.

```javascript theme={null}
const API_KEY = process.env.ZERION_API_KEY;
const address = "0x9026A229b535ecF0162Dfe48fDeb3c75f7b2A7AE";
const headers = {
  accept: "application/json",
  authorization: `Basic ${btoa(API_KEY + ":")}`,
};

const response = await fetch(
  `https://api.zerion.io/v1/wallets/${address}/positions/?currency=usd&filter[positions]=no_filter&filter[chain_ids]=ethereum`,
  { headers }
);
if (!response.ok) throw new Error(`API error: ${response.status}`);
const { data } = await response.json();

// Chain and contract address of every share token a returned position carries as its receipt
const receiptKeys = new Set();
for (const position of data) {
  const receipt = position.attributes.receipt?.fungible_info;
  if (!receipt) continue;
  const chain = position.relationships.chain.data.id;
  for (const impl of receipt.implementations ?? []) {
    if (impl.chain_id === chain && impl.address) {
      receiptKeys.add(`${chain}:${impl.address.toLowerCase()}`);
    }
  }
}

function isDuplicate(position) {
  if (position.attributes.position_type !== "wallet") return false;
  if (!position.attributes.flags.displayable) return true;
  const chain = position.relationships.chain.data.id;
  return (position.attributes.fungible_info.implementations ?? []).some(
    (impl) => impl.chain_id === chain && impl.address &&
      receiptKeys.has(`${chain}:${impl.address.toLowerCase()}`)
  );
}

const kept = data.filter((position) => !isDuplicate(position));
for (const position of data.filter(isDuplicate)) {
  const { fungible_info, quantity, value } = position.attributes;
  console.log(`Dropped ${fungible_info.symbol}: ${quantity.float} ($${(value ?? 0).toFixed(2)})`);
}
console.log(`${data.length} rows returned, ${kept.length} kept`);
```

## Next steps

* Use `filter[positions]=no_filter` to get DeFi and wallet positions in one call, then [deduplicate share tokens](#deduplicating-share-tokens)
* Combine with the [portfolio endpoint](/api-reference/wallets/get-wallet-portfolio) to see total value distribution by type


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