> ## 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 reads directly from the chains, so the data reflects real on-chain history.

**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`, `monad-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 to pull the latest numbers on demand.

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

## Syncing holders

Enrichment refreshes wallets you already have. **Sync** does the other half: it discovers the current holders of your configured contracts and **adds the new ones as contacts**, growing your audience from on-chain reality. Run it from the dashboard whenever you want to pull in holders you don't have yet. Owner, Admin, or Editor can start a sync.

A sync runs in two phases. First it reads the current holders of every contract you've configured and, for each holder that **isn't already a contact**, adds a wallet contact and records that contract balance, this is where new wallets enter your audience. Then it enriches, refreshing metrics for the wallets in the run, both the ones you already had and the ones just discovered.

While it runs you can watch two live counts: how many wallets are being processed, and how many brand-new contacts have been discovered and added. Discovery never changes existing contacts, and it stops adding once you reach your plan's contact limit.

### Sync vs enrich: the credit difference

Both pull from indexed on-chain data, but they cost very differently, and the difference is deliberate.

|               | **Sync** (discover)                                            | **Enrich** (refresh)                                 |
| ------------- | -------------------------------------------------------------- | ---------------------------------------------------- |
| What it does  | Finds contract holders and adds new wallet contacts            | Refreshes each wallet's on-chain metrics             |
| What it costs | Cheap: one holder-list lookup per contract, nothing per wallet | Metered: every wallet draws from your credit budget  |
| Use it for    | Growing the audience cheaply                                   | Keeping metrics current for scoring and segmentation |

<Note>
  Reach for **Sync** to pull in holders you don't have yet, it's cheap because it looks up the holder list once per contract. Reach for **Enrich** when you need each wallet's portfolio, activity, and scores refreshed, which is where the per-wallet credit cost lives. Discovery adds contacts without spending per-wallet credits; the enrich phase is what draws down your budget.
</Note>

## Cost and limits

Enrichment consumes **wallet-data credits**, up to 4 per wallet, covering balances, transactions, NFTs, and portfolio. The portfolio call is skipped when it isn't needed, so a wallet often costs fewer. Each paid plan includes a monthly allowance:

| Plan                            | Monthly credits          | Roughly this many wallets |
| ------------------------------- | ------------------------ | ------------------------- |
| **Pay as you go** (\$0 + usage) | Metered, \$10 per 10,000 | varies                    |
| **Launch** (\$39/mo)            | 1,000                    | \~250                     |
| **Growth** (\$349/mo)           | 10,000                   | \~2,500                   |
| **Pro** (\$1,622/mo)            | 25,000                   | \~6,000                   |

Once the allowance is exhausted, new enrichment is blocked, though the SQL editor and your already-enriched data keep working. On Pay as you go, enrichment draws from your balance at \$10 per 10,000 credits (about 4¢ a wallet) rather than a monthly allowance.

Recent results are held briefly, a few minutes for transactions, a little longer for balances, portfolio, and NFTs, so re-enriching the same wallet within those windows reuses the fresh data and doesn't re-spend credits, unless you explicitly force a refresh.

## 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={"theme":{"light":"light-plus","dark":"dark-plus"}}
-- 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>
