|
1 | | -# Indexer + API app |
2 | | -The project has 3 parts |
3 | | -- Postgres DB - schema defined in `supabase` hosted by supabase, uses some of their tools |
4 | | -- Indexer "ETL" go code in `go-indexer` - long-running server that |
5 | | - - connects to an ethereum node |
6 | | - - reads events for specific contracts (configured in `config`) |
7 | | - - write events to DB |
8 | | - - transforms events into data required for the app (accounts, positions, etc) |
9 | | -- Next JS API app in `app` - easy way to host the API. reads from the same schema. Deployed on vercel. |
10 | | - |
11 | | -## Database |
12 | | -### Prerequisites |
13 | | -- npm |
14 | | -- docker |
15 | | -- supabase installed globally (`npm install supabase --save-dev`) |
16 | | - |
17 | | -### Start locally |
18 | | -start docker |
| 1 | +# ERC4626 Ethereum Vault Indexer and API Gateway |
| 2 | +This project demonstrates the design and implementation of a production-grade Ethereum indexer and API gateway, written entirely in Go. |
| 3 | +It ingests on-chain events from an ERC-4626 vault, processes and normalizes them into a Supabase PostgreSQL database, and exposes a public REST API with automatically generated documentation. |
| 4 | +The system is built around a concurrent, multi-threaded architecture designed for data integrity, fault tolerance, and maintainable scaling — with live and historical event ingestion, chain reorganization handling, and finalized block tracking. |
| 5 | +Deployed on a Linux server and managed via systemd, the project demonstrates a full end-to-end data pipeline: from blockchain ingestion to queryable structured data. |
| 6 | + |
| 7 | +## Features |
| 8 | +- ***Ethereum Indexer in Go***: Listens to Deposit, WithdrawRequested, and Transfer events from an ERC-4626 vault contract |
| 9 | +- ***Supabase Integration***: Stores normalized event data in a hosted Postgres database |
| 10 | +- ***API Gateway***: Exposes REST endpoints (via Next.js) for querying indexed data |
| 11 | +- ***Auto-Generated Docs***: OpenAPI documentation generated automatically from Next.js endpoints, available at `<API_URL>/docs` |
| 12 | +- ***Resilient Architecture***: Includes queue-based event processing and reorg verification |
| 13 | +- ***Production-Ready Deployment***: Can run as a Linux systemd service for reliability and observability |
| 14 | + |
| 15 | +## Architecture |
| 16 | + |
| 17 | +The Vault Indexer is designed as a concurrent Go service that ingests, processes, and serves Ethereum vault events in real time. |
| 18 | + |
| 19 | +It operates through three coordinated threads — each responsible for a specific stage of the indexing pipeline — and exposes the processed data through an API gateway backed by Supabase Postgres. |
| 20 | + |
| 21 | +### Main Indexer Thread |
| 22 | +- Connects to an Ethereum node via JSON-RPC and subscribes to the vault’s on-chain events. |
| 23 | +- Handles both historical backfill and live streaming of events such as Deposit, WithdrawRequested, and Transfer. |
| 24 | +- Writes all captured events into an in-memory event queue, ensuring they’re processed in chronological order. |
| 25 | +- Acts as the entry point for all blockchain data entering the system. |
| 26 | + |
| 27 | +### Event Processing Queue |
| 28 | +- Serves as a buffer between event ingestion and downstream processing. |
| 29 | +- Maintains ordered delivery of events to prevent race conditions and ensure deterministic indexing. |
| 30 | +- Enables the system to handle bursts of incoming events without blocking network I/O. |
| 31 | + |
| 32 | +### Data Processor Thread |
| 33 | +- Continuously fetches newly queued events and transforms them into structured index records suitable for querying. |
| 34 | +- Normalizes Ethereum event data (e.g., addresses, amounts, block metadata) into relational database entries. |
| 35 | +- Persists these records into the Postgres database, providing a clean and queryable representation of vault state over time. |
| 36 | + |
| 37 | +### Finality Processor Thread |
| 38 | +- Monitors blockchain finality and safety levels to maintain data integrity. |
| 39 | +- Detects potential chain reorganizations (reorgs) by verifying block hashes and canonicality. |
| 40 | +- Triggers cleanup and rollback operations if non-finalized data becomes invalid. |
| 41 | +- Updates internal tables to reflect finalized blocks and ensures all indexed data corresponds to the canonical Ethereum chain. |
| 42 | + |
| 43 | +### Postgres Database (Supabase) |
| 44 | +- Acts as the central data store for both raw events and processed indices. |
| 45 | +- Enables efficient queries over user positions, vault balances, and event histories. |
| 46 | +- Managed through Supabase, which provides hosted PostgreSQL, authentication, and built-in REST access. |
| 47 | + |
| 48 | +### Next.js API Gateway |
| 49 | +- Provides a RESTful API layer for external clients, dashboards, or analytics tools. |
| 50 | +- Offers auto-generated documentation (Swagger / OpenAPI) for ease of exploration. |
| 51 | +- Fetches data directly from the Postgres database, exposing endpoints for querying vault events, user histories, and position summaries. |
| 52 | +- Enables fast, queryable access to indexed data by contract address or user wallet. |
| 53 | + |
| 54 | + |
| 55 | +## Getting Started |
| 56 | +### 1. Prerequisites |
| 57 | +- indexer |
| 58 | + - go |
| 59 | + - ethereum node (HTTP endpoint) |
| 60 | +- api gateway |
| 61 | + - npm |
| 62 | + - node.js |
| 63 | +- local postgres development |
| 64 | + - supabase installed globally (`npm install supabase --save-dev`) |
| 65 | + - docker |
| 66 | + |
| 67 | +### 2. Supply environment |
| 68 | +Copy `.env.example` and create `.env.local`. You can also create an `.env.prod` and run the process with an environment arg. The default environment is dev. |
| 69 | + |
| 70 | +### 3. Configure vault addresses to index at `go-indexer/config/config.yaml`. |
| 71 | + |
| 72 | +### 4. Start Database |
| 73 | +- start docker |
| 74 | +- start database |
19 | 75 | ```bash |
20 | | -npx supabase start |
21 | | -npx supabase status |
| 76 | +npx supabase start # will deploy existing schema |
| 77 | +npx supabase status # view the available endpoints |
22 | 78 | ``` |
23 | 79 |
|
| 80 | +### 5. Start indexer |
| 81 | +```bash |
| 82 | +go mod tidy # installs Go dependencies |
| 83 | +./start-indexer.sh # Start server in development (default) |
| 84 | +./start-indexer.sh prod # example with .env.prod file |
| 85 | +``` |
| 86 | + |
| 87 | +### 6. Start API gateway / docs |
| 88 | +```bash |
| 89 | +npm install |
| 90 | +npm run dev |
| 91 | +``` |
| 92 | +Swagger docs will be available at `/docs` |
| 93 | + |
| 94 | +## Working with Supabase |
| 95 | +Here are some useful commands for working with the database. |
| 96 | + |
24 | 97 | ### Deploy schema change |
25 | 98 | ```bash |
26 | 99 | npx suapbase migration new <name> |
@@ -55,94 +128,33 @@ docker volume ls |
55 | 128 | docker volume rm <volume name> |
56 | 129 | ``` |
57 | 130 |
|
58 | | -## Indexer |
| 131 | +## Deployment |
| 132 | +### Indexer |
| 133 | +The indexer is meant to run as a constant-uptime script on a remote server. It is safe to stop and restart, and will backfill the data on each restart. |
59 | 134 |
|
60 | | -### Prereqs |
61 | | -- go |
62 | | -- docker |
| 135 | +This process was previously deployed on Linux servers, run with systemd commands which were configured via Nix. |
63 | 136 |
|
64 | | -### Local development |
65 | | -1. install dependencies |
66 | | -```bash |
67 | | -go mod tidy |
68 | | -``` |
69 | | - |
70 | | -2. set config in `go-indexer/config/config.yaml` |
71 | | -- add contracts info + abis |
72 | | -- check supabase local studio (http://127.0.0.1:54323) for anon key and connection string |
| 137 | +Persistent errors will cause the process to stop running. If you plan to deploy this yourself, make sure to add a configuration which will restart the process with a cooldown if it stops. |
73 | 138 |
|
74 | | -3. run server |
75 | | -```bash |
76 | | -# For development (default) |
77 | | -./start-indexer.sh |
78 | | - |
79 | | -# For production |
80 | | -./start-indexer.sh prod |
81 | | -``` |
82 | | - |
83 | | -### |
84 | | -Run with nix |
| 139 | +## Running with nix |
85 | 140 | ```bash |
86 | 141 | nix run .#indexer |
87 | 142 | ``` |
88 | 143 |
|
89 | 144 | ## Testing |
90 | 145 | ```bash |
91 | 146 | go test ./... |
92 | | - |
93 | 147 | go test ./go-indexer/indexer -v |
94 | | - |
95 | 148 | ``` |
96 | 149 |
|
| 150 | + |
97 | 151 | ## Health checks |
98 | | -Health checks should be deployed for every process. Currently there are only 2: |
99 | | -1. historicla loading + event ingestion (main routine) |
| 152 | +The main thread spins up a lightweight HTTP server available for health checks. |
100 | 153 | ```bash |
101 | 154 | curl http://localhost:8080/health |
102 | 155 | ``` |
103 | | -2. event processing (transform.go) |
104 | | -```bash |
105 | | -curl http://localhost:8081/health |
106 | | -``` |
107 | | - |
108 | | -## Deploying |
109 | | -1. stop server + update |
110 | | -2. run database migration [doc](https://supabase.com/docs/guides/deployment/database-migrations#deploy-your-project) |
111 | | -3. if you don't want to refetch all historical events, update `go-indexer/config/config.yaml` to fetch events after a specific block |
112 | | -4. restart server |
113 | | -``` |
114 | | -
|
115 | | -``` |
116 | | - |
117 | | -# API server |
118 | | -Simple nextjs app to provide an API for the indexer database |
119 | | - |
120 | | -## Prereqs |
121 | | -- node.js |
122 | | -- npm |
123 | | - |
124 | | -## Start API server |
125 | | -1. install dependencies |
126 | | -```bash |
127 | | -npm install |
128 | | -``` |
129 | | - |
130 | | -2. copy `.env.example` |
131 | | -- SUPABASE_URL: http url for postgres db |
132 | | -- SUPABASE_ANON_KEY: read-only key |
133 | | -- API_KEY: random string to give access to API |
134 | | - |
135 | | -3. start server |
136 | | -```bash |
137 | | -npm run dev |
138 | | -``` |
139 | | -Visit the api at `localhost:3000/v1` |
140 | | - |
141 | | -## API docs and schema |
142 | | -Docs served at `/docs`. Generated automatically |
143 | | - |
144 | 156 |
|
145 | | -# Data Testing |
| 157 | +## Data Testing |
146 | 158 | Manaul check which can be run to load all historical data locally, and compare that production matches the historical load. |
147 | 159 |
|
148 | 160 | 1. Start with fresh local DB |
|
0 commit comments