Skip to content

Commit 1487dd4

Browse files
committed
Updated Supabase setup instructions to match current UI, added --env-file=.env to npm scripts, and clarified that RLS policies are the only manual database step required. Restructured implementation sections under a new 'Build the Bot' heading with numbered Steps for better flow. Fixed rate limiting timeouts to 250ms, updated ngrok documentation to note account requirement, and added it to prerequisites.
1 parent e931b0d commit 1487dd4

File tree

1 file changed

+41
-57
lines changed

1 file changed

+41
-57
lines changed

evm/build-a-top-holders-tracker-bot.mdx

Lines changed: 41 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Before you begin, ensure you have:
2929
- **Sim API Key** - [Get your API key](https://sim.dune.com)
3030
- **Telegram Bot Token** - Create one via [@BotFather](https://t.me/botfather)
3131
- **Supabase Account** - [Create a free account](https://supabase.com)
32+
- **ngrok Account** - [Create a free account](https://dashboard.ngrok.com) for local development
3233

3334
## Features
3435

@@ -61,10 +62,12 @@ Let's initialize the project.
6162
</Step>
6263

6364
<Step title="Set Up Supabase">
64-
1. Go to [supabase.com/dashboard](https://supabase.com/dashboard) and create a new project
65-
2. Once created, go to **Project Settings****Database**
66-
3. Scroll to **Connection string** and select the **URI** tab
67-
4. Copy the **Transaction pooler** connection string (uses port `6543`)
65+
1. Go to [supabase.com/dashboard](https://supabase.com/dashboard) and click **Create a new project**
66+
2. Fill in your project details (project name, database password, region) and click **Create new project**
67+
3. Once created, click **Connect** in the top navigation bar
68+
4. Select the **Connection string** tab, then choose type **URI**
69+
5. Select **Transaction pooler** and copy the connection string
70+
6. Replace `[YOUR-PASSWORD]` with your [URL-encoded](https://www.urlencoder.org/) database password
6871

6972
<Note>
7073
The Transaction pooler connection is recommended for serverless deployments like Vercel.
@@ -93,8 +96,8 @@ Let's initialize the project.
9396
{
9497
"type": "module",
9598
"scripts": {
96-
"dev": "node index.js",
97-
"start": "node index.js"
99+
"dev": "node --env-file=.env index.js",
100+
"start": "node --env-file=.env index.js"
98101
}
99102
}
100103
```
@@ -185,46 +188,21 @@ Let's initialize the project.
185188
```
186189
</Step>
187190
188-
<Step title="Create Tables and Enable Row Level Security">
189-
In your Supabase dashboard, go to **SQL Editor** and run:
191+
<Step title="Enable Row Level Security">
192+
The application creates tables automatically on startup, but you need to enable Row Level Security (RLS) policies in the Supabase dashboard. Go to **SQL Editor** and run:
190193
191194
```sql expandable
192-
-- ============================================
193-
-- 1. CREATE TABLES (Optional - app creates these automatically)
194-
-- ============================================
195-
196-
CREATE TABLE IF NOT EXISTS top_holders (
197-
id SERIAL PRIMARY KEY,
198-
token_address TEXT,
199-
chain_id INTEGER,
200-
symbol TEXT,
201-
blockchain TEXT,
202-
holders_json TEXT,
203-
UNIQUE(token_address, chain_id)
204-
);
205-
206-
CREATE TABLE IF NOT EXISTS subscribers (
207-
chat_id TEXT PRIMARY KEY,
208-
subscribed_at TEXT
209-
);
210-
211-
CREATE TABLE IF NOT EXISTS webhooks (
212-
id TEXT PRIMARY KEY,
213-
token_address TEXT,
214-
chain_id INTEGER,
215-
active INTEGER DEFAULT 1
216-
);
217195

218196
-- ============================================
219-
-- 2. ENABLE ROW LEVEL SECURITY (Required)
197+
-- 1. ENABLE ROW LEVEL SECURITY (Required)
220198
-- ============================================
221199

222200
ALTER TABLE top_holders ENABLE ROW LEVEL SECURITY;
223201
ALTER TABLE subscribers ENABLE ROW LEVEL SECURITY;
224202
ALTER TABLE webhooks ENABLE ROW LEVEL SECURITY;
225203

226204
-- ============================================
227-
-- 3. CREATE PERMISSIVE POLICIES (Required)
205+
-- 2. CREATE PERMISSIVE POLICIES (Required)
228206
-- ============================================
229207

230208
-- Drop existing policies if they exist (prevents errors on re-run)
@@ -243,7 +221,7 @@ CREATE POLICY "Allow all operations on webhooks" ON webhooks
243221
FOR ALL USING (true) WITH CHECK (true);
244222

245223
-- ============================================
246-
-- 4. VERIFY SETUP (Run to check everything)
224+
-- 3. VERIFY SETUP (Run to check everything)
247225
-- ============================================
248226

249227
SELECT
@@ -254,13 +232,17 @@ WHERE schemaname = 'public'
254232
AND tablename IN ('top_holders', 'subscribers', 'webhooks');
255233
```
256234
257-
This creates three tables: `top_holders` for storing token holder data, `subscribers` for Telegram chat IDs, and `webhooks` for tracking active subscriptions. The RLS policies allow your server full access while keeping the database secure.
235+
These RLS policies allow your server full access while keeping the database secure.
258236
</Step>
259237
</Steps>
260238
261-
## Get Top ERC20 Tokens
239+
## Build the Bot
262240
263-
We need a list of popular tokens to monitor. Create a file called `tokens.csv` in your project root with the following content:
241+
With the project set up, we'll now implement the core functionality: loading token data, fetching top holders from Sim's Token Holders API, setting up webhook subscriptions, and wiring everything to Telegram.
242+
243+
<Steps>
244+
<Step title="Get Top ERC20 Tokens">
245+
We need a list of popular tokens to monitor. Create a file called `tokens.csv` in your project root with the following content:
264246
265247
```csv tokens.csv expandable
266248
blockchain,symbol,contract_address,rank,volume_24h
@@ -354,10 +336,10 @@ function loadTokensFromCSV() {
354336
}
355337
}
356338
```
339+
</Step>
357340
358-
## Get Top Token Holders
359-
360-
Now we'll identify the top holder addresses for each token using Sim's Token Holders API and store them in our database.
341+
<Step title="Get Top Token Holders">
342+
Now we'll identify the top holder addresses for each token using Sim's Token Holders API and store them in our database.
361343
362344
### Fetch Holders for a Token
363345
@@ -429,10 +411,10 @@ app.post("/setup/fetch-holders", async (req, res) => {
429411
}
430412
});
431413
```
414+
</Step>
432415
433-
## Set Up Webhooks
434-
435-
We'll use Sim's Subscriptions API (`/beta/`) to register webhooks. We use the `balances` subscription type.
416+
<Step title="Set Up Webhooks">
417+
We'll use the Sim Subscriptions API (`/beta/subscriptions`) to register webhooks. We use the `balances` subscription type.
436418
437419
### Create a Webhook
438420
@@ -508,10 +490,10 @@ app.post("/setup/create-webhooks", async (req, res) => {
508490
}
509491
});
510492
```
493+
</Step>
511494
512-
## Handle Balance Change Events
513-
514-
When a top holder moves funds, Sim sends a POST request to your webhook URL.
495+
<Step title="Handle Balance Change Events">
496+
When a top holder moves funds, Sim sends a POST request to your webhook URL.
515497
516498
### Process Incoming Webhooks
517499
@@ -541,10 +523,10 @@ app.post("/balances", async (req, res) => {
541523
res.json({ ok: true, processed: processedTxs.size });
542524
});
543525
```
526+
</Step>
544527
545-
## Send Telegram Notifications
546-
547-
### Manage Subscribers
528+
<Step title="Send Telegram Notifications">
529+
### Manage Subscribers
548530
549531
We use PostgreSQL to persist chat IDs.
550532
@@ -684,10 +666,10 @@ app.post("/telegram/webhook", async (req, res) => {
684666
res.json({ ok: true });
685667
});
686668
```
669+
</Step>
687670
688-
## Manage Webhooks
689-
690-
The Subscriptions API provides endpoints to list and update your webhooks.
671+
<Step title="Manage Webhooks">
672+
The Subscriptions API provides endpoints to list and update your webhooks.
691673
692674
### Pause and Resume
693675
@@ -730,7 +712,7 @@ app.post("/setup/pause-webhooks", async (req, res) => {
730712
if (webhook.active) {
731713
await updateWebhookStatus(webhook.id, false);
732714
paused++;
733-
await setTimeout(100);
715+
await setTimeout(250);
734716
}
735717
}
736718
res.json({ ok: true, paused });
@@ -743,12 +725,14 @@ app.post("/setup/resume-webhooks", async (req, res) => {
743725
if (!webhook.active) {
744726
await updateWebhookStatus(webhook.id, true);
745727
resumed++;
746-
await setTimeout(100);
728+
await setTimeout(250);
747729
}
748730
}
749731
res.json({ ok: true, resumed });
750732
});
751733
```
734+
</Step>
735+
</Steps>
752736
753737
## Deploy and Configure
754738
@@ -762,7 +746,7 @@ app.post("/setup/resume-webhooks", async (req, res) => {
762746
</Step>
763747
764748
<Step title="Expose to Public Internet">
765-
If developing locally, use ngrok to make your localhost accessible:
749+
If developing locally, you can expose a port to the public internet so that Sim Subscriptions can call it. [ngrok](https://ngrok.com) is a utility that helps with that. You need an account on [dashboard.ngrok.com](https://dashboard.ngrok.com) to use it.
766750
767751
```bash
768752
ngrok http 3001

0 commit comments

Comments
 (0)