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

# Wallet Enrichment

> Turn a bare wallet address into a rich profile — portfolio value, activity, NFT holdings, and derived engagement and value scores.

Give Onchain Suite a wallet address and it fetches that wallet's on-chain history, normalizes it, and derives the metrics you actually segment and message on: portfolio value, recent activity, NFT holdings, an engagement score, and an estimated lifetime value.

Enrichment is what makes a wallet-first audience useful. A raw address tells you nothing; an enriched profile tells you whether this is a dormant whale, an active minter, or a wallet worth a win-back.

## What you get

Enrichment writes three datasets, all queryable in the [SQL editor](/api/sql-schema) and joined together in [`contact_360`](/api/sql-schema#contact_360).

<Tabs>
  <Tab title="Wallet metrics">
    One row per wallet in `user_onchain_metrics`:

    | Field                   | Meaning                                |
    | ----------------------- | -------------------------------------- |
    | `portfolio_value_usd`   | Total portfolio value in USD           |
    | `volume_last_90d`       | Traded volume in the last 90 days      |
    | `transaction_count_90d` | Transactions in the last 90 days       |
    | `total_token_count`     | Distinct token balances held           |
    | `nft_collection_count`  | Distinct NFT collections               |
    | `nft_item_count`        | Total NFT items                        |
    | `engagement_score`      | Derived 0–100 activity score           |
    | `est_ltv`               | Derived estimated lifetime value (USD) |
    | `primary_chain`         | Chain this wallet was enriched on      |
    | `last_enriched_at`      | When enrichment last ran               |
  </Tab>

  <Tab title="Activity summary">
    One row per wallet **per chain** in `wallet_activity_summary`:

    | Field                                    | Meaning                            |
    | ---------------------------------------- | ---------------------------------- |
    | `transaction_count`                      | Total transactions                 |
    | `volume_usd`                             | Total traded volume                |
    | `active_days_30d` / `active_days_90d`    | Distinct active days in the window |
    | `first_activity_at` / `last_activity_at` | Activity bounds                    |
  </Tab>

  <Tab title="NFT holdings">
    One row per token in `nft_holdings` (capped at 250 per wallet):

    | Field                                  | Meaning            |
    | -------------------------------------- | ------------------ |
    | `collection_name` / `contract_address` | The collection     |
    | `item_name` / `token_id`               | The specific token |
    | `floor_price_quote`                    | Floor price in USD |
    | `image_url`                            | Token image        |
    | `last_transferred_at`                  | Last transfer time |
  </Tab>
</Tabs>

## Derived scores

Two fields are computed rather than fetched. Both are heuristics — useful for ranking and segmentation, not accounting.

### Engagement score (0–100)

A weighted blend of recent activity, capped so no single signal dominates:

| Component          | Contribution | Cap |
| ------------------ | ------------ | --- |
| Transactions (90d) | `× 2`        | 40  |
| Active days (30d)  | `× 2`        | 30  |
| NFT collections    | `× 3`        | 15  |
| Portfolio value    | `÷ 1000`     | 15  |

A wallet with 20+ transactions, activity most days, a few collections, and a five-figure portfolio approaches 100. A dormant wallet trends toward zero.

### Estimated LTV

A weighted blend biased toward realized value:

```
est_ltv = portfolio_value_usd × 0.08
        + volume_last_90d     × 0.03
        + engagement_score    × 5
        + nft_collection_count × 20
```

<Note>
  `churn_risk` is **not** an enrichment field — it's derived at query time in `contact_360` from `last_activity_at`: `low` within 30 days, `medium` at 30–60, `high` beyond 60. Enrichment supplies the activity timestamps that drive it.
</Note>

## Supported chains

Enrichment pulls from [GoldRush](https://goldrush.dev) (Covalent).

**EVM mainnets:** `eth-mainnet`, `base-mainnet`, `matic-mainnet`, `bsc-mainnet`, `arbitrum-mainnet`, `optimism-mainnet`, `sei-mainnet`. **Solana:** `solana-mainnet`. Testnets including `eth-sepolia`, `base-sepolia-testnet`, and `solana-devnet` are also supported.

A wallet is enriched per chain, so metrics reflect the chain you enriched it on. Enrich the same wallet on multiple chains to build a cross-chain picture — activity summaries are stored per chain.

## When enrichment runs

<Steps>
  <Step title="On profile view (automatic)">
    Opening a contact profile that has a wallet triggers enrichment if it's never been enriched or is **more than 7 days stale**. This keeps active profiles fresh without any work from you.
  </Step>

  <Step title="On project-settings save (automatic)">
    Saving project settings that include contract addresses enriches the holders of those contracts in the background.
  </Step>

  <Step title="On demand">
    Refresh a specific wallet, your contacts, or a contract's holders whenever you need current numbers.
  </Step>
</Steps>

There is no fixed re-enrichment schedule — freshness is driven by the 7-day staleness check on view. You can force a refresh at any time, which bypasses the response cache.

## Enriching on demand

Three ways to enrich beyond the automatic paths:

| Scope                    | What it does                                                                                                                         |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| **One wallet**           | Refreshes a single wallet on a chosen chain. Runs in the background, or returns the metrics directly when you need them immediately. |
| **Your contacts**        | Enriches the wallets of your most recently updated contacts, up to 200 at a time.                                                    |
| **A contract's holders** | Discovers holders of a configured contract and enriches each one.                                                                    |

Enrichment is asynchronous — the request returns as soon as the work is queued. Progress is visible as a live queue count, and the workspace reports itself idle once everything has drained. Each wallet's `last_enriched_at` advances when its refresh completes, so you can also tell per wallet.

<Note>
  Enrichment jobs are deduplicated per wallet and chain, so enqueuing the same wallet twice in quick succession won't double-spend credits.
</Note>

## Cost and limits

Enrichment consumes **GoldRush credits** — roughly 4 per wallet (it makes four data calls: balances, transactions, NFTs, and portfolio). Credits are a monthly allowance that scales with your plan:

| Plan                  | Monthly GoldRush credits     | Roughly this many wallets |
| --------------------- | ---------------------------- | ------------------------- |
| **Pay as you go**     | Metered against your balance | —                         |
| **Launch** — \$29/mo  | 10,000                       | \~2,500                   |
| **Growth** — \$199/mo | 75,000                       | \~18,000                  |
| **Pro** — \$499/mo    | 200,000                      | \~50,000                  |

Once the allowance is exhausted, new enrichment is blocked — the SQL editor and your already-enriched data keep working. On Pay as you go, enrichment draws from your balance rather than a monthly allowance.

Responses are cached per organization — 5 minutes for transactions, 10 for balances and portfolio, 15 for NFTs — so repeated enrichment of the same wallet within those windows is served from cache and doesn't re-spend credits (unless you pass `forceRefresh`).

## Using enriched data

<CardGroup cols={2}>
  <Card title="Query it in SQL" icon="table" href="/api/sql-schema">
    `user_onchain_metrics`, `wallet_activity_summary`, `nft_holdings`, and the joined `contact_360` view.
  </Card>

  <Card title="Build audiences" icon="filter" href="/intelligence/sql-query-editor">
    Whales, dormant holders, high-LTV wallets — save any query as a segment.
  </Card>

  <Card title="Score and prioritize" icon="chart-line" href="/audience/health-and-churn">
    Engagement score feeds contact health and churn signals.
  </Card>

  <Card title="Personalize messages" icon="pen-to-square" href="/campaigns/email-editor">
    Reference wallet traits as merge variables in campaigns.
  </Card>
</CardGroup>

The most common starting point is a query against `contact_360`, where enrichment is already joined to identity and email engagement:

```sql theme={"dark"}
-- Dormant whales worth a win-back
SELECT email, wallet_address, portfolio_value_usd, days_since_last_activity
FROM contact_360
WHERE portfolio_value_usd > 10000
  AND churn_risk IN ('medium', 'high')
ORDER BY est_ltv DESC NULLS LAST
LIMIT 100
```

<Note>
  Campaign merge variables for on-chain fields (like `portfolioValueUsd`) render from contact metadata, not directly from the enrichment tables. The enrichment tables feed **analytics and segmentation** through `contact_360`; for per-recipient personalization, the value needs to be present in the contact's metadata.
</Note>
