> ## 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.

# From Moralis

> Map Moralis Web3 Data API endpoints to Zerion API equivalents for token balances, net worth, wallet history, PnL, DeFi positions, and prices, with code samples.

If you've been calling the Moralis Web3 Data API for wallet balances, net worth, transaction history, PnL, DeFi positions, or token prices, the same data is available on Zerion API across [60+ EVM chains and Solana](/supported-blockchains), usually in a single call.

This guide shows the direct mapping for the main Moralis wallet endpoints, with copy-pasteable code for each.

What you get with Zerion:

* **One address, all chains:** Most Moralis endpoints take a single `chain` parameter per call, so a multi-chain read means one request per chain. Zerion returns every supported chain for an address in one call, filterable with `filter[chain_ids]`.
* **One API for EVM and Solana:** Moralis serves Solana from a separate base URL (`solana-gateway.moralis.io`) with a different response schema. Zerion accepts Solana addresses on the same `/wallets/{address}/...` endpoints used for EVM.
* **One call for tokens + DeFi:** Moralis splits wallet tokens (`/wallets/{address}/tokens`) and DeFi positions (a separate API) into different products. Zerion serves both from `/positions/?filter[positions]=no_filter`.

## Endpoint parity

Moralis paths below are relative to `https://deep-index.moralis.io/api/v2.2` unless noted.

| Use case                         | Moralis Web3 Data API                                             | Zerion API                                                                                                                              |
| -------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Tokens + DeFi (one call)         | Two products (`/wallets/{address}/tokens` + DeFi positions)       | [`GET /v1/wallets/{address}/positions/?filter[positions]=no_filter`](/api-reference/wallets/get-wallet-fungible-positions)              |
| Token balances (with prices)     | `GET /wallets/{address}/tokens`                                   | [`GET /v1/wallets/{address}/positions/?filter[positions]=only_simple`](/api-reference/wallets/get-wallet-fungible-positions)            |
| ERC-20 + native balance (legacy) | `GET /{address}/erc20` + `GET /{address}/balance`                 | Same `/positions/` call (native tokens included)                                                                                        |
| Net worth                        | `GET /wallets/{address}/net-worth`                                | [`GET /v1/wallets/{address}/portfolio`](/api-reference/wallets/get-wallet-portfolio)                                                    |
| Net worth over time              | (not available)                                                   | [`GET /v1/wallets/{address}/charts/{period}`](/api-reference/wallets/get-wallet-balance-chart)                                          |
| Wallet history                   | `GET /wallets/{address}/history`                                  | [`GET /v1/wallets/{address}/transactions/`](/api-reference/wallets/get-wallet-transactions)                                             |
| Swaps only                       | `GET /wallets/{address}/swaps`                                    | [`GET /v1/wallets/{address}/transactions/?filter[operation_types]=trade`](/api-reference/wallets/get-wallet-transactions)               |
| Wallet PnL                       | `GET /wallets/{address}/profitability` (+ `/summary`)             | [`GET /v1/wallets/{address}/pnl`](/api-reference/wallets/get-wallet-pnl)                                                                |
| DeFi positions                   | `GET https://api.moralis.com/v1/wallets/{address}/defi/positions` | [`GET /v1/wallets/{address}/positions/?filter[positions]=only_complex`](/api-reference/wallets/get-wallet-fungible-positions)           |
| Token price                      | `GET /erc20/{address}/price`                                      | [`GET /v1/fungibles/by-implementation?implementation={chain}:{address}`](/api-reference/fungibles/get-fungible-asset-by-implementation) |
| Token metadata                   | `GET /erc20/metadata`                                             | [`GET /v1/fungibles/`](/api-reference/fungibles/get-list-of-fungible-assets)                                                            |
| NFTs                             | `GET /{address}/nft`                                              | [`GET /v1/wallets/{address}/nft-positions/`](/api-reference/wallets/get-wallet-nft-positions)                                           |
| Solana wallet data               | Separate Solana API (`solana-gateway.moralis.io`)                 | Same `/wallets/{address}/...` endpoints                                                                                                 |
| Realtime updates                 | Moralis Streams                                                   | [Transaction webhooks](/webhooks)                                                                                                       |

<Tip>
  Prefer not to write code? The [Zerion CLI](/build-with-ai/zerion-cli) wraps the same endpoints with a one-shot `npx @zerion/cli init` flow, useful for quick experiments and AI agents.
</Tip>

## A note on chains

The biggest structural change is how you address chains:

* **Moralis:** Most endpoints take one `chain` query parameter per call (`chain=eth`, `chain=0x1`), so covering a wallet across five chains means five requests. Only net worth (and the newer Universal API endpoints) accept multiple chains at once.
* **Zerion:** `GET /v1/wallets/{address}/...` returns every supported chain for the address by default, narrowed with `filter[chain_ids]=ethereum,base`. Chain IDs are full lowercase names (`ethereum`, not `eth` or `0x1`); see the [full list](/supported-blockchains).

So a per-chain fan-out loop over `chain=eth`, `chain=polygon`, `chain=base` becomes a single Zerion call, with the chain of each row in `relationships.chain.data.id`.

## Token balances

Moralis's `/wallets/{address}/tokens` returns `result[]` with `balance` (raw integer), `balance_formatted`, `usd_price`, and `usd_value` per token, for one chain per call. Zerion returns a [JSON:API](https://jsonapi.org/) collection covering all chains at once, with `attributes.fungible_info` for metadata, `attributes.quantity` for amounts, and `attributes.value` for USD values. The same endpoint accepts both EVM and Solana addresses.

<CodeGroup>
  ```javascript JavaScript (EVM) theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/positions/?currency=usd&filter[positions]=only_simple&filter[trash]=only_non_trash&sort=value`,
    {
      headers: {
        accept: "application/json",
        authorization: `Basic ${btoa(API_KEY + ":")}`,
      },
    }
  );
  const { data } = await res.json();

  for (const pos of data) {
    const { fungible_info, quantity, price, value } = pos.attributes;
    const chain = pos.relationships.chain.data.id;
    console.log(`${fungible_info.symbol} on ${chain}: ${quantity.float} @ $${price} = $${value?.toFixed(2) ?? "N/A"}`);
  }
  ```

  ```javascript JavaScript (Solana) theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "6sEk1enayZBGFyNvvJMTP7qs5S3uC7KLrQWaEk38hSHH";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/positions/?currency=usd&filter[chain_ids]=solana&filter[trash]=only_non_trash&sort=value`,
    {
      headers: {
        accept: "application/json",
        authorization: `Basic ${btoa(API_KEY + ":")}`,
      },
    }
  );
  const { data } = await res.json();

  for (const pos of data) {
    const { fungible_info, quantity, price, value } = pos.attributes;
    const chain = pos.relationships.chain.data.id;
    console.log(`${fungible_info.symbol} on ${chain}: ${quantity.float} @ $${price} = $${value?.toFixed(2) ?? "N/A"}`);
  }
  ```

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

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

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/positions/",
      params={
          "currency": "usd",
          "filter[positions]": "only_simple",
          "filter[trash]": "only_non_trash",
          "sort": "-value",
      },
      auth=(api_key, ""),
  )
  res.raise_for_status()

  for pos in res.json()["data"]:
      info = pos["attributes"]["fungible_info"]
      qty = pos["attributes"]["quantity"]["float"]
      value = pos["attributes"]["value"]
      chain = pos["relationships"]["chain"]["data"]["id"]
      print(f"{info['symbol']} on {chain}: {qty} = ${value:.2f}" if value else f"{info['symbol']} on {chain}: {qty}")
  ```

  ```bash cURL (EVM) theme={null}
  curl -g -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/positions/?currency=usd&filter[positions]=only_simple&filter[trash]=only_non_trash&sort=value"
  ```

  ```bash cURL (Solana) theme={null}
  curl -g -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/6sEk1enayZBGFyNvvJMTP7qs5S3uC7KLrQWaEk38hSHH/positions/?currency=usd&filter[chain_ids]=solana&filter[trash]=only_non_trash&sort=value"
  ```
</CodeGroup>

### Field mapping

| Moralis (`result[].…`)                                 | Zerion (`data[].attributes.…`)                                                                                                                                           |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `token_address` (`native_token: true` for gas tokens)  | `fungible_info.implementations[].address` (`null` for native)                                                                                                            |
| `symbol`, `name`                                       | `fungible_info.symbol`, `fungible_info.name`                                                                                                                             |
| `decimals`                                             | `fungible_info.implementations[].decimals`                                                                                                                               |
| `logo`, `thumbnail`                                    | `fungible_info.icon.url`                                                                                                                                                 |
| `balance` (raw integer string)                         | `quantity.int`                                                                                                                                                           |
| `balance_formatted`                                    | `quantity.float` (decimal number). Also `quantity.numeric` (decimal string).                                                                                             |
| `usd_price`                                            | `price`                                                                                                                                                                  |
| `usd_price_24hr_percent_change`                        | `changes.percent_1d`                                                                                                                                                     |
| `usd_value`                                            | `value` (USD, precomputed)                                                                                                                                               |
| `usd_value_24hr_usd_change`                            | `changes.absolute_1d`                                                                                                                                                    |
| `portfolio_percentage`                                 | Compute from `value` / [`/portfolio`](/api-reference/wallets/get-wallet-portfolio) → `total.positions`                                                                   |
| `possible_spam`                                        | `flags.is_trash`, or filter server-side with `filter[trash]=only_non_trash`                                                                                              |
| `verified_contract`                                    | `fungible_info.flags.verified`                                                                                                                                           |
| `exclude_spam`, `exclude_unverified_contracts` (query) | `filter[trash]=only_non_trash` (query)                                                                                                                                   |
| `total_supply`, `percentage_relative_to_total_supply`  | Supply data lives on the asset: [`/v1/fungibles/{id}`](/api-reference/fungibles/get-fungible-asset-by-id) → `market_data.total_supply`, `market_data.circulating_supply` |

<Note>
  `price` and `value` are `null` for tokens without a reliable price. Guard for `null` before summing or formatting.
</Note>

<Note>
  If you're on the legacy `GET /{address}/erc20` + `GET /{address}/balance` pair, the same `/positions/` call replaces both: native tokens come back as regular rows (implementation `address: null`), already decimal-adjusted and priced.
</Note>

## Net worth

Moralis's `net-worth` returns `total_networth_usd` with a per-chain breakdown of native vs token value. Zerion's [`/portfolio`](/api-reference/wallets/get-wallet-portfolio) returns the total, the 24h change, a breakdown by chain, and a breakdown by position type (wallet, deposited, borrowed, locked, staked) in one response.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/portfolio?currency=usd`,
    { headers: { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` } }
  );
  const { data } = await res.json();
  const a = data.attributes;

  console.log(`Net worth: $${a.total.positions.toFixed(2)}`);
  console.log(`24h change: $${a.changes.absolute_1d?.toFixed(2)} (${a.changes.percent_1d?.toFixed(2)}%)`);
  console.log("By chain:", a.positions_distribution_by_chain);
  console.log("By type:", a.positions_distribution_by_type);
  ```

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

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

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/portfolio",
      params={"currency": "usd"},
      auth=(api_key, ""),
  )
  a = res.json()["data"]["attributes"]
  print(f"Net worth: ${a['total']['positions']:.2f}")
  print(f"24h change: {a['changes']['percent_1d']}%")
  print("By chain:", a["positions_distribution_by_chain"])
  print("By type:", a["positions_distribution_by_type"])
  ```

  ```bash cURL theme={null}
  curl -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/portfolio?currency=usd"
  ```
</CodeGroup>

### Field mapping

| Moralis (`net-worth`)                                        | Zerion (`/portfolio` → `data.attributes.…`)                                                                                                                    |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `total_networth_usd`                                         | `total.positions`                                                                                                                                              |
| `chains[].networth_usd`                                      | `positions_distribution_by_chain` (keyed by chain ID)                                                                                                          |
| `chains[].native_balance_usd` / `chains[].token_balance_usd` | No native-vs-token split. The breakdown is by position type instead: `positions_distribution_by_type` (`wallet`, `deposited`, `borrowed`, `locked`, `staked`). |
| (no equivalent)                                              | `changes.absolute_1d`, `changes.percent_1d` (24h change)                                                                                                       |
| (no equivalent on Moralis)                                   | Net worth over time: [`/wallets/{address}/charts/{period}`](/api-reference/wallets/get-wallet-balance-chart) (periods from `hour` to `max`)                    |

## Wallet history

Moralis's `/wallets/{address}/history` returns decoded transactions for one chain per call, with a `category` label, a `summary` sentence, and transfers split into `erc20_transfers[]`, `native_transfers[]`, and `nft_transfers[]`. Zerion's [`/transactions/`](/api-reference/wallets/get-wallet-transactions) returns enriched transactions across all chains at once, with a single decoded `operation_type`, one unified `transfers[]` array, fees, and the dApp when Zerion recognizes it. The same endpoint accepts both EVM and Solana addresses.

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

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/transactions/?currency=usd&page[size]=20`,
    { headers }
  );
  const { data } = await res.json();

  for (const tx of data) {
    const { operation_type, mined_at, transfers, fee } = tx.attributes;
    const chain = tx.relationships.chain.data.id;
    const dappId = tx.relationships.dapp?.data?.id;

    console.log(`[${mined_at}] ${operation_type} on ${chain}`);
    if (dappId) console.log(`  via ${dappId}`);
    for (const t of transfers) {
      const sign = t.direction === "out" ? "-" : "+";
      const symbol = t.fungible_info?.symbol ?? "NFT";
      console.log(`  ${sign}${t.quantity.float} ${symbol} ($${t.value?.toFixed(2) ?? "?"})`);
    }
    console.log(`  Fee: $${fee.value?.toFixed(2) ?? "?"}`);
  }
  ```

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

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

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/transactions/",
      params={"currency": "usd", "page[size]": 20},
      auth=(api_key, ""),
  )
  for tx in res.json()["data"]:
      attrs = tx["attributes"]
      chain = tx["relationships"]["chain"]["data"]["id"]
      dapp_id = (tx["relationships"].get("dapp") or {}).get("data", {}).get("id")
      print(f"[{attrs['mined_at']}] {attrs['operation_type']} on {chain}")
      if dapp_id:
          print(f"  via {dapp_id}")
      for t in attrs["transfers"]:
          sign = "-" if t["direction"] == "out" else "+"
          symbol = (t.get("fungible_info") or {}).get("symbol", "NFT")
          val = t.get("value")
          print(f"  {sign}{t['quantity']['float']} {symbol} (${val:.2f})" if val else f"  {sign}{t['quantity']['float']} {symbol}")
  ```

  ```bash cURL (EVM) theme={null}
  curl -g -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/transactions/?currency=usd&page[size]=20"
  ```

  ```bash cURL (Solana) theme={null}
  curl -g -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/6sEk1enayZBGFyNvvJMTP7qs5S3uC7KLrQWaEk38hSHH/transactions/?currency=usd&filter[chain_ids]=solana&page[size]=20"
  ```
</CodeGroup>

### Field mapping

| Moralis (`result[].…`)                                                                                                                                                                                                 | Zerion (`data[].attributes.…`)                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hash`                                                                                                                                                                                                                 | `hash`                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `block_timestamp` / `block_number`                                                                                                                                                                                     | `mined_at` (ISO 8601) / `mined_at_block`                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `chain` (query param)                                                                                                                                                                                                  | `relationships.chain.data.id` (per transaction)                                                                                                                                                                                                                                                                                                                                                                                                            |
| `category` (`send`, `receive`, `token send`, `token receive`, `nft send`, `nft receive`, `airdrop`, `token swap`, `nft purchase`, `nft sale`, `deposit`, `withdraw`, `mint`, `burn`, `borrow`, `contract interaction`) | `operation_type` (`send`, `receive`, `trade`, `approve`, `revoke`, `deposit`, `withdraw`, `mint`, `burn`, `claim`, `bid`, `delegate`, `revoke_delegation`, `execute`, `deploy`). The send/receive variants collapse into `send` / `receive`; `token swap`, `nft purchase`, and `nft sale` become `trade`; `contract interaction` becomes `execute`. Approvals, which Moralis files under `contract interaction`, get their own `approve` / `revoke` types. |
| `summary` (human-readable sentence)                                                                                                                                                                                    | No direct equivalent. Compose from `operation_type` + `transfers[]`.                                                                                                                                                                                                                                                                                                                                                                                       |
| `erc20_transfers[]` / `native_transfers[]` / `nft_transfers[]` (three arrays)                                                                                                                                          | One `transfers[]` array; each entry carries `fungible_info` or `nft_info`                                                                                                                                                                                                                                                                                                                                                                                  |
| transfer `value` / `value_formatted`                                                                                                                                                                                   | `transfers[].quantity.int` (raw) / `.float` (decimal). USD in `transfers[].value` (Moralis history has no USD values).                                                                                                                                                                                                                                                                                                                                     |
| transfer `direction` (`send` / `receive`)                                                                                                                                                                              | `transfers[].direction` (`out` / `in`)                                                                                                                                                                                                                                                                                                                                                                                                                     |
| transfer `from_address` / `to_address`                                                                                                                                                                                 | `transfers[].sender` / `transfers[].recipient`                                                                                                                                                                                                                                                                                                                                                                                                             |
| `from_address` / `to_address` (transaction-level)                                                                                                                                                                      | `sent_from` / `sent_to`                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `from_address_label` / `to_address_label`                                                                                                                                                                              | No direct equivalent. `relationships.dapp.data.id` names the dApp when Zerion recognizes the contract.                                                                                                                                                                                                                                                                                                                                                     |
| `method_label`                                                                                                                                                                                                         | `application_metadata.method.name`                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `transaction_fee` (native units)                                                                                                                                                                                       | `fee.quantity.float` (native) / `fee.value` (USD)                                                                                                                                                                                                                                                                                                                                                                                                          |
| `receipt_status`                                                                                                                                                                                                       | `status` (`confirmed` / `failed`, plus `pending` before mining)                                                                                                                                                                                                                                                                                                                                                                                            |
| `possible_spam`                                                                                                                                                                                                        | `flags.is_trash`                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `include_internal_transactions=true` + `internal_transaction` flag                                                                                                                                                     | Internal value movements already appear in `transfers[]`                                                                                                                                                                                                                                                                                                                                                                                                   |

### Filter mapping

| Moralis param                                           | Zerion equivalent                                                                          |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `chain=eth` (one chain per call)                        | `filter[chain_ids]=ethereum` (comma-separated for multiple; omit for all chains)           |
| `from_date` / `to_date`                                 | `filter[min_mined_at]` / `filter[max_mined_at]` (ms epoch)                                 |
| `limit=100`                                             | `page[size]=100`                                                                           |
| `cursor=<cursor>`                                       | Follow `links.next` from the response                                                      |
| `order=DESC`                                            | Default (newest first); ascending order is not supported                                   |
| `/wallets/{address}/swaps`, `transactionTypes=buy,sell` | `filter[operation_types]=trade`, then read `transfers[].direction` to tell buys from sells |

## Wallet PnL

Moralis's `profitability` endpoints return realized PnL only, per token (`/profitability`) or as totals (`/profitability/summary`), for one chain per call. Zerion's [`/pnl`](/api-reference/wallets/get-wallet-pnl) returns wallet-level PnL computed with FIFO across all chains: realized and unrealized gain, net invested, total fees, and external in/out flows.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/pnl?currency=usd`,
    { headers: { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` } }
  );
  const { data } = await res.json();
  const a = data.attributes;

  console.log(`Realized gain:   $${a.realized_gain?.toFixed(2)}`);
  console.log(`Unrealized gain: $${a.unrealized_gain?.toFixed(2)}`);
  console.log(`Net invested:    $${a.net_invested?.toFixed(2)}`);
  console.log(`Total fees:      $${a.total_fee?.toFixed(2)}`);
  ```

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

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

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/pnl",
      params={"currency": "usd"},
      auth=(api_key, ""),
  )
  a = res.json()["data"]["attributes"]
  print(f"Realized gain:   ${a['realized_gain']:.2f}")
  print(f"Unrealized gain: ${a['unrealized_gain']:.2f}")
  print(f"Net invested:    ${a['net_invested']:.2f}")
  print(f"Total fees:      ${a['total_fee']:.2f}")
  ```

  ```bash cURL theme={null}
  curl -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/pnl?currency=usd"
  ```
</CodeGroup>

### Field mapping

| Moralis (`profitability` / `summary`)                                                                           | Zerion (`data.attributes.…`)                                                                                                                                             |
| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `total_realized_profit_usd`                                                                                     | `realized_gain`                                                                                                                                                          |
| (not returned; realized only)                                                                                   | `unrealized_gain`                                                                                                                                                        |
| `total_realized_profit_percentage`                                                                              | `relative_realized_gain_percentage`                                                                                                                                      |
| `total_usd_invested` (per token)                                                                                | `net_invested` (wallet-level)                                                                                                                                            |
| `result[]` per-token rows (`avg_buy_price_usd`, `avg_sell_price_usd`, `realized_profit_usd`, `count_of_trades`) | Wallet-level totals. For a per-token breakdown, call `/pnl` with `filter[fungible_ids]` or `filter[fungible_implementations]`; average buy/sell prices are not returned. |
| `days=7/30/60/90/all`                                                                                           | `since` / `till` (ms epoch; pre-computed marks at 1 day, 1 week, 1 month, 1 year, year-to-date)                                                                          |
| `chain=eth` (one chain; summary only on Ethereum, Base, Polygon)                                                | All chains by default; narrow with `filter[chain_ids]`                                                                                                                   |
| (no equivalent)                                                                                                 | `total_fee`, `received_external`, `sent_external`                                                                                                                        |

<Tip>
  See the [wallet PnL tracker recipe](/recipes/wallet-pnl-tracker) for a worked example including per-token filtering.
</Tip>

## DeFi positions

Moralis serves DeFi positions from its newer Universal API (`api.moralis.com/v1`), where each position nests a `tokens[]` array typed as `supplied`, `borrowed`, `reward`, or `lp`. Zerion flattens this: each position is one row under `/positions/?filter[positions]=only_complex`, tagged with `protocol`, `protocol_module`, and `position_type` (including `loan` for borrowed assets).

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

  const res = await fetch(
    `https://api.zerion.io/v1/wallets/${address}/positions/?currency=usd&filter[positions]=only_complex&sort=value`,
    { headers: { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` } }
  );
  const { data } = await res.json();

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

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

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

  res = requests.get(
      f"https://api.zerion.io/v1/wallets/{address}/positions/",
      params={"currency": "usd", "filter[positions]": "only_complex", "sort": "-value"},
      auth=(api_key, ""),
  )
  for pos in res.json()["data"]:
      a = pos["attributes"]
      chain = pos["relationships"]["chain"]["data"]["id"]
      print(f"[{a.get('position_type')}] {a['name']} | {a.get('protocol')} ({a.get('protocol_module')}) on {chain}: {a['quantity']['float']} = ${a['value'] or 0:.2f}")
  ```

  ```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>

### Field mapping

| Moralis (Universal API, `result[].…`)                                                                          | Zerion (`data[].attributes.…`)                                                                                                                            |
| -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocolName` / `protocolId`                                                                                  | `protocol` / `relationships.dapp.data.id`                                                                                                                 |
| `protocolLogo`                                                                                                 | `application_metadata.icon.url`                                                                                                                           |
| `chainId`                                                                                                      | `relationships.chain.data.id`                                                                                                                             |
| `position.label` (`lending`, `liquidity`, `staking`, `farming`, `vault`, `yield`, `vesting`, `perps`, `other`) | `protocol_module` (`lending`, `liquidity_pool`, `staked`, `locked`, `rewards`, `vesting`, `deposit`, `investment`, `yield`)                               |
| `position.tokens[].tokenType` (`supplied` / `borrowed` / `reward` / `lp`)                                      | `position_type` (`deposit`, `loan`, `reward`, `staked`, `locked`)                                                                                         |
| `position.tokens[].balanceFormatted`, `.usdPrice`, `.usdValue`                                                 | `quantity.float`, `price`, `value` (one row per token leg)                                                                                                |
| `position.balanceUsd`                                                                                          | Sum `value` over the position's rows; wallet totals are in [`/portfolio`](/api-reference/wallets/get-wallet-portfolio) → `positions_distribution_by_type` |
| `position.unclaimedUsd`                                                                                        | Rows with `position_type: reward`                                                                                                                         |
| `tokenType: lp` legs                                                                                           | One row per pool token, grouped by `group_id`                                                                                                             |
| `positionDetails` (APY, health factor, shares)                                                                 | No direct equivalent. Zerion surfaces the underlying token, USD value, and protocol module; derive health from supplied vs borrowed `value`.              |
| `defi/summary` (`activeProtocols`, `totalUsd` per protocol)                                                    | Group rows by `relationships.dapp.data.id` and sum `value`                                                                                                |

<Note>
  Moralis returns positions labeled `perps`. Zerion's positions model is spot and DeFi-protocol oriented and does not return perpetuals as positions. If perps matter for your migration, [let us know](#get-in-touch).
</Note>

## Token prices

Moralis's `/erc20/{address}/price` returns a DEX-pair-derived price with exchange metadata, one token and one chain per call. Zerion resolves a token by its implementation and returns full asset metadata including `market_data.price` and recent changes.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const API_KEY = process.env.ZERION_API_KEY;
  const implementation = "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; // USDC

  const res = await fetch(
    `https://api.zerion.io/v1/fungibles/by-implementation?implementation=${implementation}&currency=usd`,
    { headers: { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` } }
  );
  const { data } = await res.json();
  const m = data.attributes.market_data;

  console.log(`${data.attributes.symbol}: $${m.price}`);
  console.log(`24h change: ${m.changes?.percent_1d?.toFixed(2)}%`);
  ```

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

  api_key = os.environ["ZERION_API_KEY"]
  implementation = "ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"  # USDC

  res = requests.get(
      "https://api.zerion.io/v1/fungibles/by-implementation",
      params={"implementation": implementation, "currency": "usd"},
      auth=(api_key, ""),
  )
  a = res.json()["data"]["attributes"]
  m = a["market_data"]
  print(f"{a['symbol']}: ${m['price']}")
  print(f"24h change: {m['changes']['percent_1d']}%")
  ```

  ```bash cURL theme={null}
  curl -u "YOUR_API_KEY:" \
    "https://api.zerion.io/v1/fungibles/by-implementation?implementation=ethereum:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&currency=usd"
  ```
</CodeGroup>

### Field mapping

| Moralis (price response)                                 | Zerion (`data.attributes.…`)                                                                                                                                           |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `usdPrice` / `usdPriceFormatted`                         | `market_data.price`                                                                                                                                                    |
| `usdPrice24hrPercentChange`                              | `market_data.changes.percent_1d`                                                                                                                                       |
| `nativePrice` (price in the chain's gas token)           | Request with `currency=eth` (the `currency` param accepts fiat and crypto units)                                                                                       |
| `tokenName`, `tokenSymbol`, `tokenLogo`, `tokenDecimals` | `name`, `symbol`, `icon.url`, `implementations[].decimals`                                                                                                             |
| `exchangeName`, `pairAddress`, `pairTotalLiquidityUsd`   | No direct equivalent. Zerion prices are aggregated, not tied to a single DEX pair.                                                                                     |
| `securityScore`, `possibleSpam`, `verifiedContract`      | `flags.verified`                                                                                                                                                       |
| `to_block` (price at a block)                            | No direct equivalent. Use the [fungible chart](/api-reference/fungibles/get-a-chart-for-a-fungible-asset) for a price timeseries.                                      |
| `POST /erc20/prices` (batch, max 30)                     | One implementation per call. For a batch, page [`/v1/fungibles/`](/api-reference/fungibles/get-list-of-fungible-assets) and read `market_data.price` from each result. |

<Note>
  If you also call `GET /erc20/metadata`, the same fungible objects carry the metadata: `market_data.total_supply`, `market_data.circulating_supply`, `market_data.market_cap`, and `market_data.fully_diluted_valuation` map to Moralis's `total_supply`, `circulating_supply`, `market_cap`, and `fully_diluted_valuation`.
</Note>

## NFTs

Moralis's `GET /{address}/nft` returns raw NFT holdings with tokenURI-based metadata for one chain per call. The Zerion equivalent is [`/v1/wallets/{address}/nft-positions/`](/api-reference/wallets/get-wallet-nft-positions), which returns each holding with collection metadata and floor-price-based valuation across chains.

```bash cURL theme={null}
curl -g -u "YOUR_API_KEY:" \
  "https://api.zerion.io/v1/wallets/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045/nft-positions/?currency=usd&page[size]=20"
```

| Moralis (`result[].…`)                                         | Zerion (`data[].attributes.…`)                                     |
| -------------------------------------------------------------- | ------------------------------------------------------------------ |
| `token_address`                                                | `nft_info.contract_address`                                        |
| `token_id`                                                     | `nft_info.token_id`                                                |
| `contract_type` (`ERC721` / `ERC1155`)                         | `nft_info.interface`                                               |
| `name` / `metadata` / `normalized_metadata`                    | `nft_info.name`, `nft_info.content`                                |
| `amount` (quantity held)                                       | `amount`                                                           |
| `floor_price` / `floor_price_usd` (with `include_prices=true`) | `price` (floor price) / `value` (floor value), returned by default |
| `possible_spam`                                                | `nft_info.flags.is_spam`                                           |
| `verified_collection`                                          | No direct equivalent                                               |
| `chain` (query param)                                          | `relationships.chain.data.id`                                      |

## Solana

Moralis serves Solana from a separate API (`https://solana-gateway.moralis.io`) with camelCase responses that don't match the EVM schema. On Zerion, Solana addresses go through the same wallet endpoints as EVM and return the same shape:

| Moralis Solana API                         | Zerion API                                                                                                                                                   |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `GET /account/mainnet/{address}/portfolio` | [`GET /v1/wallets/{address}/portfolio`](/api-reference/wallets/get-wallet-portfolio) + [`/positions/`](/api-reference/wallets/get-wallet-fungible-positions) |
| `GET /account/mainnet/{address}/balance`   | `/positions/` (native SOL comes back as a regular row)                                                                                                       |
| `GET /account/mainnet/{address}/tokens`    | [`GET /v1/wallets/{address}/positions/?filter[chain_ids]=solana`](/api-reference/wallets/get-wallet-fungible-positions)                                      |
| `GET /token/mainnet/{mint}/price`          | [`GET /v1/fungibles/by-implementation?implementation=solana:{mint}`](/api-reference/fungibles/get-fungible-asset-by-implementation)                          |
| `GET /account/mainnet/{address}/nft`       | [`GET /v1/wallets/{address}/nft-positions/?filter[chain_ids]=solana`](/api-reference/wallets/get-wallet-nft-positions)                                       |

Transaction history for Solana addresses works on the same [`/transactions/`](/api-reference/wallets/get-wallet-transactions) endpoint. DeFi positions are not yet supported for Solana.

## Pagination

Replace Moralis's `cursor` query parameter with Zerion's `links.next` URL. Each Zerion response includes a fully-formed next-page link you can fetch as-is.

```javascript theme={null}
async function getAll(url) {
  const all = [];
  const headers = { accept: "application/json", authorization: `Basic ${btoa(API_KEY + ":")}` };

  while (url) {
    const res = await fetch(url, { headers });
    const { data, links } = await res.json();
    all.push(...data);
    url = links?.next ?? null;
  }
  return all;
}
```

## Realtime updates

Moralis Streams pushes on-chain events to your backend via webhooks. Zerion's equivalent is [transaction webhooks](/webhooks): subscribe a callback URL to one or more wallets and receive a POST when any of them transact.

See the [wallet activity alerts recipe](/recipes/wallet-activity-alerts) for a working example.

## Differences from Moralis

Most Moralis wallet use cases have a direct Zerion equivalent. A few aren't covered, and others behave differently. Worth a scan before you cut over.

**Not supported today:**

* **Price at a block and DEX-pair detail:** Moralis prices accept `to_block` and return the source exchange, pair address, and pair liquidity. Zerion returns an aggregated live price plus a chart timeseries.
* **Per-token trade analytics:** Moralis's per-token PnL rows include average buy/sell prices and trade counts, and `/swaps` classifies each swap as a buy or sell. Zerion's PnL is wallet-level (scopeable per token with `filter[fungible_ids]`), and trades are `operation_type: trade` transactions you classify from transfer directions.
* **Address labels:** Moralis annotates transactions with `from_address_label` / `to_address_label` (exchanges, known entities). Zerion identifies dApps (`relationships.dapp`) but does not label counterparty addresses.
* **Token analytics signals:** `securityScore`, pair liquidity, and holder data have no Zerion equivalent. Use `filter[trash]` and `fungible_info.flags.verified` for curation.
* **Perpetuals:** Moralis DeFi positions include a `perps` label. Zerion does not return perpetuals as positions.
* **Bitcoin:** Moralis Streams can watch Bitcoin addresses. Zerion covers EVM chains and Solana; check the [supported chains list](/supported-blockchains) for the ones you rely on.

If any of these matter for your migration, [let us know](#get-in-touch). Your feedback helps shape our roadmap.

**Worth knowing:**

* **Authentication:** Moralis uses an `X-API-Key` header. Zerion uses [HTTP Basic Auth](/authentication). Get a key at [dashboard.zerion.io](https://dashboard.zerion.io).
* **Chains:** Moralis takes one `chain` per call (`eth`, `0x1`). Zerion returns all supported chains by default and filters with full names: `filter[chain_ids]=ethereum,base`. See [the note above](#a-note-on-chains).
* **One base URL:** Moralis splits EVM (`deep-index.moralis.io`), Solana (`solana-gateway.moralis.io`), and the Universal API (`api.moralis.com`) across different hosts and schemas. Zerion serves everything from `api.zerion.io/v1` with one response shape.
* **Consistent casing:** Moralis mixes snake\_case (wallet endpoints) and camelCase (prices, swaps, Solana, Universal API) across endpoint families. Zerion responses are uniformly snake\_case [JSON:API](https://jsonapi.org/): payloads live under `data[].attributes` with related entities under `data[].relationships`.
* **One endpoint for tokens and DeFi:** Zerion serves both wallet tokens and DeFi positions from `/positions/`. Switch via `filter[positions]=only_simple` (wallet only), `only_complex` (DeFi only), or `no_filter` (both).
* **Flattened DeFi:** Moralis nests typed `tokens[]` inside each position. Zerion returns one row per position leg tagged with `protocol_module` and `position_type` (including `loan` for debt). Group by `relationships.dapp.data.id` to reconstruct protocols, and by `group_id` to reconstruct LP pairs.
* **USD values in history:** Moralis history returns transfer amounts without USD values. Zerion returns `value` (USD at execution time) on each transfer and fee.
* **Wallet value over time:** Moralis has no net-worth-history endpoint. Zerion's [`/charts/{period}`](/api-reference/wallets/get-wallet-balance-chart) returns the wallet's value timeseries directly.
* **Spam filtering:** Moralis flags `possible_spam` and gates with `exclude_spam` / liquidity thresholds. Zerion uses `filter[trash]=only_non_trash`. See [spam filtering](/spam-filtering).
* **Pagination:** Moralis pages with a `cursor` parameter; Zerion returns a fully-formed `links.next` URL you can fetch as-is. See [pagination](/pagination-and-filtering).

## Get in touch

Have a use case we don't cover or need assistance with the migration? Our team is happy to help! Reach out via the chat widget on [dashboard.zerion.io](https://dashboard.zerion.io), or [email us](mailto:api@zerion.io).
