Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/indexer/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ node_modules
dist
*.log
.DS_Store
/data
*.db
7 changes: 7 additions & 0 deletions apps/indexer/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from "drizzle-kit";

export default defineConfig({
schema: "./src/schema.ts",
out: "./drizzle",
dialect: "postgresql",
});
29 changes: 29 additions & 0 deletions apps/indexer/drizzle/0000_dashing_leo.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
CREATE TABLE "deposit" (
"tx_id" text NOT NULL,
"chain_id" bigint NOT NULL,
"event_index" integer NOT NULL,
"contract_id" text NOT NULL,
"stacker" text NOT NULL,
"stx_amount" bigint,
"ststx_amount" bigint NOT NULL,
"referrer" text,
"pool" text,
"block_height" bigint NOT NULL,
"block_time" bigint NOT NULL,
CONSTRAINT "deposit_tx_id_chain_id_event_index_pk" PRIMARY KEY("tx_id","chain_id","event_index")
);
--> statement-breakpoint
CREATE TABLE "withdraw" (
"tx_id" text NOT NULL,
"chain_id" bigint NOT NULL,
"event_index" integer NOT NULL,
"contract_id" text NOT NULL,
"action" text NOT NULL,
"stacker" text NOT NULL,
"nft_id" bigint,
"ststx_amount" bigint NOT NULL,
"stx_amount" bigint,
"block_height" bigint NOT NULL,
"block_time" bigint NOT NULL,
CONSTRAINT "withdraw_tx_id_chain_id_event_index_pk" PRIMARY KEY("tx_id","chain_id","event_index")
);
187 changes: 187 additions & 0 deletions apps/indexer/drizzle/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
{
"id": "60debf12-6fea-4308-be94-1058663d1e4c",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.deposit": {
"name": "deposit",
"schema": "",
"columns": {
"tx_id": {
"name": "tx_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"chain_id": {
"name": "chain_id",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"event_index": {
"name": "event_index",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"contract_id": {
"name": "contract_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"stacker": {
"name": "stacker",
"type": "text",
"primaryKey": false,
"notNull": true
},
"stx_amount": {
"name": "stx_amount",
"type": "bigint",
"primaryKey": false,
"notNull": false
},
"ststx_amount": {
"name": "ststx_amount",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"referrer": {
"name": "referrer",
"type": "text",
"primaryKey": false,
"notNull": false
},
"pool": {
"name": "pool",
"type": "text",
"primaryKey": false,
"notNull": false
},
"block_height": {
"name": "block_height",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"block_time": {
"name": "block_time",
"type": "bigint",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"deposit_tx_id_chain_id_event_index_pk": {
"name": "deposit_tx_id_chain_id_event_index_pk",
"columns": ["tx_id", "chain_id", "event_index"]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.withdraw": {
"name": "withdraw",
"schema": "",
"columns": {
"tx_id": {
"name": "tx_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"chain_id": {
"name": "chain_id",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"event_index": {
"name": "event_index",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"contract_id": {
"name": "contract_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"action": {
"name": "action",
"type": "text",
"primaryKey": false,
"notNull": true
},
"stacker": {
"name": "stacker",
"type": "text",
"primaryKey": false,
"notNull": true
},
"nft_id": {
"name": "nft_id",
"type": "bigint",
"primaryKey": false,
"notNull": false
},
"ststx_amount": {
"name": "ststx_amount",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"stx_amount": {
"name": "stx_amount",
"type": "bigint",
"primaryKey": false,
"notNull": false
},
"block_height": {
"name": "block_height",
"type": "bigint",
"primaryKey": false,
"notNull": true
},
"block_time": {
"name": "block_time",
"type": "bigint",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"withdraw_tx_id_chain_id_event_index_pk": {
"name": "withdraw_tx_id_chain_id_event_index_pk",
"columns": ["tx_id", "chain_id", "event_index"]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
13 changes: 13 additions & 0 deletions apps/indexer/drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1789128098101,
"tag": "0000_dashing_leo",
"breakpoints": true
}
]
}
15 changes: 13 additions & 2 deletions apps/indexer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@
"scripts": {
"dev": "pnpm build && node --env-file-if-exists=.env dist/index.mjs",
"build": "vp pack",
"test": "vp test"
"test": "vp test",
"generate": "drizzle-kit generate",
"studio": "drizzle-kit studio"
},
"dependencies": {
"@electric-sql/pglite": "0.5.8",
"drizzle-orm": "0.45.2",
"pg": "8.23.0",
"stacksindex": "0.0.3",
"zod": "4.6.2"
},
"devDependencies": {
"@types/node": "22.15.17",
"@types/node": "22.20.2",
"@types/pg": "8.23.1",
"drizzle-kit": "0.31.10",
"typescript": "7.0.2",
"vite-plus": "catalog:"
}
Expand Down
115 changes: 112 additions & 3 deletions apps/indexer/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,114 @@
export function fn() {
return "Hello, tsdown!";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";

import { PGlite } from "@electric-sql/pglite";
import { drizzle } from "drizzle-orm/pglite";
import { migrate } from "drizzle-orm/pglite/migrator";
import { createDatabase, createHistoricalRuntime, createLogger } from "stacksindex";

import {
createStackingDaoHandler,
STACKINGDAO_CONTRACTS,
STACKINGDAO_START_BLOCKS,
} from "./protocols/stackingdao/index.ts";

export * from "./schema.ts";
export * from "./protocols/stackingdao/index.ts";

const apiKey = process.env.HIRO_API_KEY;
const dataDir = process.env.DATA_DIR ?? "./data";

fs.mkdirSync(dataDir, { recursive: true });

const appClient = new PGlite(path.join(dataDir, "app.db"));
await appClient.waitReady;
const appDb = drizzle({ client: appClient });

const migrationsFolder = fileURLToPath(new URL("../drizzle", import.meta.url));
await migrate(appDb, { migrationsFolder });

const indexerDatabase = await createDatabase({
kind: "pglite",
directory: path.join(dataDir, "indexer.db"),
});

const logger = createLogger({
level: 2,
});

let isShuttingDown = false;
async function shutdown(code: number) {
if (isShuttingDown) {
return;
}
isShuttingDown = true;
try {
await appClient.close();
} catch {
// Ignore error on close
}
try {
await indexerDatabase.close();
} catch {
// Ignore error on close
}
process.exit(code);
}

fn();
process.on("SIGINT", () => {
// oxlint-disable-next-line eslint/no-void
void shutdown(0);
});
process.on("SIGTERM", () => {
// oxlint-disable-next-line eslint/no-void
void shutdown(0);
});

const runtime = createHistoricalRuntime({
logger,
db: indexerDatabase.db,
network: "mainnet",
api: { apiKey },
});

const stackingDaoHandler = createStackingDaoHandler({ db: appDb, logger });

const contracts = [
{
contractId: STACKINGDAO_CONTRACTS.CORE_V1,
startBlock: STACKINGDAO_START_BLOCKS[STACKINGDAO_CONTRACTS.CORE_V1],
handler: stackingDaoHandler,
},
{
contractId: STACKINGDAO_CONTRACTS.CORE_V2,
startBlock: STACKINGDAO_START_BLOCKS[STACKINGDAO_CONTRACTS.CORE_V2],
handler: stackingDaoHandler,
},
{
contractId: STACKINGDAO_CONTRACTS.CORE_V3,
startBlock: STACKINGDAO_START_BLOCKS[STACKINGDAO_CONTRACTS.CORE_V3],
handler: stackingDaoHandler,
},
{
contractId: STACKINGDAO_CONTRACTS.CORE_V4,
startBlock: STACKINGDAO_START_BLOCKS[STACKINGDAO_CONTRACTS.CORE_V4],
handler: stackingDaoHandler,
},
];

logger.info({
msg: "Starting StackingDAO historical indexer",
contracts: contracts.map((c) => ({ contractId: c.contractId, startBlock: c.startBlock })),
});

const result = await runtime.run(contracts);

if (result.isErr()) {
logger.error({ msg: "Error running historical sync", error: result.error });
await shutdown(1);
} else {
logger.info({ msg: "Historical sync finished successfully" });
await shutdown(0);
}
Loading
Loading