MorScan indexes the Morpheus AI network from Base mainnet. A fresh deploy needs history before its dashboards are useful.
Quickstart: skip the full historical sync. Import the latest published data snapshot, then catch up only the small delta from the snapshot watermark to chain head. That turns a multi-hour backfill from the contract deploy blocks into about ten minutes of sync. This is Path B below, and it is the recommended way to stand up a node.
The snapshot is its own project, not part of MorScan:
DRM3Labs-OSS/morpheus-ai-base-data - a signed, CC0 SQL snapshot of Morpheus network activity on Base, verifiable against a key committed to that repo. Snapshots are published up to a high watermark; the most recent is at block 48,249,693 (July 5 2026).
MorScan is one consumer of it.
A fresh clone with no seed starts near chain head and backfills history from the
contract deploy blocks via RPC. The MOR token deployed at Base block 15,002,375
(2024-05-27) and the Morpheus Diamond at block 39,593,197, so a full holder and
transfer backfill replays close to two years of Base blocks (about 33 million
blocks). On a rate-limited public RPC that is slow: hours to days, and the
historical getLogs sweep wants an archive-capable RPC (see
docs/DEPENDENCIES.md). This is the zero-dependency path,
and the numbers come straight from chain, but you wait for the backfill.
-
Create the D1 database and KV caches, then apply the schema. (Full setup, including the KV ids and
wrangler.tomledits, is in docs/GETTING_STARTED.md; the minimum is:)npx wrangler d1 create morscan # -> copy the printed database_id into wrangler.toml ([[d1_databases]]) npx wrangler kv namespace create NONCE_CACHE npx wrangler kv namespace create MORSCAN_CACHE # -> copy each printed id into its [[kv_namespaces]] entry npx wrangler d1 execute morscan --remote --file=./schema.sql
-
Set the one required secret and deploy:
openssl rand -hex 32 | npx wrangler secret put MORSCAN_JWT_SECRET npx wrangler deploy -
Create an admin API key (the
/sync/*routes are admin-gated) and start the live sync loop. Live sync begins near chain head, so the site is fresh right away; history fills in behind it in step 4.KEY="mor_admin_$(openssl rand -hex 16)" echo "Save this admin key: $KEY" npx wrangler d1 execute morscan --remote \ --command="INSERT INTO api_keys (id, key, name, rate_limit, created_at) VALUES ('admin','$KEY','Admin',1000000,$(date +%s));" BASE="https://<your-worker-origin>" # e.g. https://morscan.example.workers.dev curl "$BASE/sync/coordinator/start" -H "X-Morscan-Key: $KEY"
-
Backfill history from the deploy blocks. Both calls are throttled and resumable: each returns
{"nextFrom": <block>, "done": false}while there is more to do, or{"done": true}when the range is covered. Re-call withfrom=<nextFrom>until done.<HEAD>is the current synced block, shown at$BASE/healthassyncedBlock.# Sessions, bids and MOR transfers, from the Diamond deploy block forward: curl "$BASE/sync/backfill?from=39593197&to=<HEAD>" -H "X-Morscan-Key: $KEY" # -> repeat with ?from=<nextFrom>&to=<HEAD> until {"done":true} # Holder history, from the MOR token deploy block forward (?from=15002375 # restarts the campaign from the token deploy; to defaults to head): curl "$BASE/sync/backfill-holders?from=15002375" -H "X-Morscan-Key: $KEY" # -> repeat with ?from=<nextFrom> until {"done":true}
Import the signed snapshot from morpheus-ai-base-data, then let live sync fill only the delta from the snapshot watermark (block 48,249,693) to head. Done promptly after a snapshot is published, that delta is small: roughly ten minutes of sync, not a full-history replay.
-
Create a fresh, empty D1 database and the KV caches (do NOT apply this repo's
schema.sql- the importer applies the snapshot's own schema in step 4). An authed wrangler with write access to that D1 is required.npx wrangler d1 create morscan # -> copy the printed database_id into wrangler.toml ([[d1_databases]]) npx wrangler kv namespace create NONCE_CACHE npx wrangler kv namespace create MORSCAN_CACHE # -> copy each printed id into its [[kv_namespaces]] entry
wrangler.toml(the committed template you configured in docs/GETTING_STARTED.md) is the config used throughout below. If you keep a separate private deploy config, pointWRANGLER_CONFIG(and--config) at that file instead. -
Clone the dataset and install its deps (its verifier needs them):
git clone https://github.com/DRM3Labs-OSS/morpheus-ai-base-data cd morpheus-ai-base-data && npm install && cd -
-
Download the snapshot Release asset (
morpheus-ai-base-data-<block>.sql.gz) from that repo's Releases. -
Run the importer. It verifies the snapshot with the dataset's own verifier, applies the shipped schema, loads the data, and sets the sync watermark (
TARGET_DBis the D1 name from step 1):DATASET_DIR=/path/to/morpheus-ai-base-data \ BLOB=/path/to/morpheus-ai-base-data-<block>.sql.gz \ TARGET_DB=morscan WRANGLER_CONFIG=wrangler.toml \ node scripts/import-seed.mjs
The importer refuses to run against a populated D1, so it is safe to re-run. To rehearse the whole thing against a local database first, add
LOCAL=1. -
Deploy the worker and start the sync loop. It resumes from
watermark_block + 1(48,249,694), so it only fetches the delta from the watermark forward, not a full-history replay.openssl rand -hex 32 | npx wrangler secret put MORSCAN_JWT_SECRET npx wrangler deploy # Admin key for the gated /sync routes, then kick the loop: KEY="mor_admin_$(openssl rand -hex 16)" echo "Save this admin key: $KEY" npx wrangler d1 execute morscan --remote \ --command="INSERT INTO api_keys (id, key, name, rate_limit, created_at) VALUES ('admin','$KEY','Admin',1000000,$(date +%s));" curl "https://<your-worker-origin>/sync/coordinator/start" -H "X-Morscan-Key: $KEY"
Notes:
- The snapshot ships its own
schema.sql(the exact schema the data was dumped under), so the import is self-consistent even if this repo'sschema.sqlhas drifted. Use the dataset's schema for a seeded deploy. - Only Morpheus network data is imported. Operator tables (API keys, config, the indexer's own signing keys and receipts) are not in the snapshot; set those up as normal for your deployment.
- Per-provider session offsets and the holder-balance backfill start from defaults after a seed; the first sync re-establishes them idempotently.
If you only need the data (holders, sessions, providers, and the full MOR/USD price history), you do not need MorScan at all. Grab the snapshot and query it directly.
# from a clone of morpheus-ai-base-data, with the Release asset downloaded
gunzip -k morpheus-ai-base-data-<block>.sql.gz
sqlite3 morpheus.db < schema.sql
sqlite3 morpheus.db < morpheus-ai-base-data-<block>.sql-- Top providers by compute sessions served
SELECT provider, COUNT(*) AS sessions
FROM sessions GROUP BY provider ORDER BY sessions DESC LIMIT 5;
-- Holders and total MOR held
SELECT COUNT(*) AS holders, ROUND(SUM(CAST(mor_balance_wei AS REAL))/1e18) AS total_mor
FROM mor_holders WHERE CAST(mor_balance_wei AS REAL) > 0;
-- MOR/USD range over the full price history (pool origin 2024-09-24 to watermark)
SELECT ROUND(MIN(usd),4) AS low, ROUND(MAX(usd),4) AS high, COUNT(*) AS points
FROM price_history;DuckDB works too: duckdb -c "INSTALL sqlite; LOAD sqlite; ATTACH 'morpheus.db' AS m (TYPE sqlite); SELECT * FROM m.network_economics;". See that repo's queries/examples.sql and its data dictionary for every table.