diff --git a/Dockerfile.hypersync-indexer b/Dockerfile.hypersync-indexer new file mode 100644 index 000000000..8ccce2fe6 --- /dev/null +++ b/Dockerfile.hypersync-indexer @@ -0,0 +1,28 @@ +FROM node:20-slim AS base + +FROM base AS builder +WORKDIR /app +COPY . . +RUN npx turbo@2.3.1 prune @anticapture/hypersync-indexer --docker + +FROM base AS installer +RUN apt-get update && apt-get install -y curl && npm install -g pnpm ts-node +WORKDIR /app +COPY package.json package.json +COPY pnpm-lock.yaml pnpm-lock.yaml +COPY --from=builder /app/out/json/ . +RUN pnpm install +COPY --from=builder /app/out/full/ . +COPY --from=builder /app/turbo.json turbo.json +WORKDIR /app/apps/hypersync-indexer +RUN npm install --prefix generated --legacy-peer-deps +RUN pnpm envio codegen + +FROM base AS runner +ENV NODE_ENV=production +RUN apt-get update && apt-get install -y curl && npm install -g ts-node +WORKDIR /app +COPY --from=installer /app . +WORKDIR /app/apps/hypersync-indexer + +ENTRYPOINT ["npm", "run", "start"] diff --git a/apps/api/cmd/aave.ts b/apps/api/cmd/aave.ts index 99df3bd1d..dd7d5889d 100644 --- a/apps/api/cmd/aave.ts +++ b/apps/api/cmd/aave.ts @@ -105,7 +105,7 @@ if (!daoClient) { throw new Error(`Client not found for DAO ${env.DAO_ID}`); } -const pgClient = drizzle(env.DATABASE_URL, { schema, casing: "snake_case" }); +const pgClient = drizzle(env.DATABASE_URL, { schema }); health(app, pgClient); diff --git a/apps/api/cmd/index.ts b/apps/api/cmd/index.ts index 2593e4682..a07c4f272 100644 --- a/apps/api/cmd/index.ts +++ b/apps/api/cmd/index.ts @@ -174,7 +174,7 @@ if (!daoClient) { throw new Error(`Client not found for DAO ${env.DAO_ID}`); } -const pgClient = drizzle(env.DATABASE_URL, { schema, casing: "snake_case" }); +const pgClient = drizzle(env.DATABASE_URL, { schema }); health(app, pgClient); @@ -351,7 +351,6 @@ tokenMetrics(app, tokenMetricsService); if (daoClient.supportOffchainData()) { const pgUnifiedClient = drizzle(env.DATABASE_URL, { schema: { ...schema, ...offchainSchema }, - casing: "snake_case", }); const offchainProposalsRepo = wrapWithTracing( diff --git a/apps/api/src/controllers/account-balance/historical.integration.test.ts b/apps/api/src/controllers/account-balance/historical.integration.test.ts index c9673d562..74aee8e4d 100644 --- a/apps/api/src/controllers/account-balance/historical.integration.test.ts +++ b/apps/api/src/controllers/account-balance/historical.integration.test.ts @@ -24,6 +24,7 @@ const DAO_ID = "ENS"; const createHistoryRow = ( overrides: Partial = {}, ): BalanceHistoryInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: DAO_ID, accountId: VALID_ADDRESS, @@ -38,6 +39,7 @@ const createHistoryRow = ( const createTransferRow = ( overrides: Partial = {}, ): TransferInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: DAO_ID, tokenId: TOKEN_ID, diff --git a/apps/api/src/controllers/account-balance/interactions.integration.test.ts b/apps/api/src/controllers/account-balance/interactions.integration.test.ts index 7d0fb877f..0065b5677 100644 --- a/apps/api/src/controllers/account-balance/interactions.integration.test.ts +++ b/apps/api/src/controllers/account-balance/interactions.integration.test.ts @@ -25,6 +25,7 @@ const DAO_ID = "ENS"; const createAccountBalanceRow = ( overrides: Partial = {}, ): AccountBalanceInsert => ({ + id: "test-id", accountId: COUNTERPART, tokenId: TOKEN_ID, balance: 200000000000000000n, @@ -35,6 +36,7 @@ const createAccountBalanceRow = ( const createTransferRow = ( overrides: Partial = {}, ): TransferInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: DAO_ID, tokenId: TOKEN_ID, diff --git a/apps/api/src/controllers/account-balance/listing.integration.test.ts b/apps/api/src/controllers/account-balance/listing.integration.test.ts index d42b9500c..378a0266f 100644 --- a/apps/api/src/controllers/account-balance/listing.integration.test.ts +++ b/apps/api/src/controllers/account-balance/listing.integration.test.ts @@ -34,6 +34,7 @@ const TOKEN_ID = getAddress("0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72"); const createAccountBalanceRow = ( overrides: Partial = {}, ): AccountBalanceInsert => ({ + id: "test-id", accountId: VALID_ADDRESS, tokenId: TOKEN_ID, balance: 1200000000000000000n, @@ -44,6 +45,7 @@ const createAccountBalanceRow = ( const createTransferRow = ( overrides: Partial = {}, ): TransferInsert => ({ + id: "test-id", transactionHash: "0xabc1230000000000000000000000000000000000000000000000000000000001", daoId: "ENS", diff --git a/apps/api/src/controllers/account-balance/variations.integration.test.ts b/apps/api/src/controllers/account-balance/variations.integration.test.ts index 2a00f7a01..3c6710e24 100644 --- a/apps/api/src/controllers/account-balance/variations.integration.test.ts +++ b/apps/api/src/controllers/account-balance/variations.integration.test.ts @@ -29,6 +29,7 @@ const DAO_ID = "ENS"; const createAccountBalanceRow = ( overrides: Partial = {}, ): AccountBalanceInsert => ({ + id: "test-id", accountId: VALID_ADDRESS, tokenId: TOKEN_ID, balance: 1200000000000000000n, @@ -39,6 +40,7 @@ const createAccountBalanceRow = ( const createTransferRow = ( overrides: Partial = {}, ): TransferInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: DAO_ID, tokenId: TOKEN_ID, diff --git a/apps/api/src/controllers/delegation-percentage/index.integration.test.ts b/apps/api/src/controllers/delegation-percentage/index.integration.test.ts index d4083cbff..3cde45417 100644 --- a/apps/api/src/controllers/delegation-percentage/index.integration.test.ts +++ b/apps/api/src/controllers/delegation-percentage/index.integration.test.ts @@ -15,6 +15,7 @@ type MetricInsert = typeof daoMetricsDayBucket.$inferInsert; const TEST_DATE = 1699920000n; const createMetric = (overrides: Partial = {}): MetricInsert => ({ + id: "test-id", date: TEST_DATE, daoId: "UNI", tokenId: "uni", diff --git a/apps/api/src/controllers/delegations/delegations.integration.test.ts b/apps/api/src/controllers/delegations/delegations.integration.test.ts index ab39c1bd8..ab38145c9 100644 --- a/apps/api/src/controllers/delegations/delegations.integration.test.ts +++ b/apps/api/src/controllers/delegations/delegations.integration.test.ts @@ -27,6 +27,7 @@ const TX_HASH = const createAccountBalanceRow = ( overrides: Partial = {}, ): AccountBalanceInsert => ({ + id: "test-id", accountId: VALID_ADDRESS, tokenId: "uni", balance: 1000000000000000000n, @@ -37,6 +38,7 @@ const createAccountBalanceRow = ( const createDelegationRow = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: DAO_ID, delegateAccountId: VALID_ADDRESS, diff --git a/apps/api/src/controllers/delegations/delegators.integration.test.ts b/apps/api/src/controllers/delegations/delegators.integration.test.ts index 613ad3d43..eb1343f24 100644 --- a/apps/api/src/controllers/delegations/delegators.integration.test.ts +++ b/apps/api/src/controllers/delegations/delegators.integration.test.ts @@ -29,6 +29,7 @@ const DAO_ID = "uni"; const createDelegationRow = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: "test-id", transactionHash: "0xaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccddaabbccdd", daoId: DAO_ID, @@ -48,6 +49,7 @@ const createDelegationRow = ( const createAccountBalanceRow = ( overrides: Partial = {}, ): AccountBalanceInsert => ({ + id: "test-id", accountId: DELEGATOR_1, tokenId: "uni", balance: 1000000000000000000n, diff --git a/apps/api/src/controllers/delegations/historical.delegations.integration.test.ts b/apps/api/src/controllers/delegations/historical.delegations.integration.test.ts index 487f11482..e2190140c 100644 --- a/apps/api/src/controllers/delegations/historical.delegations.integration.test.ts +++ b/apps/api/src/controllers/delegations/historical.delegations.integration.test.ts @@ -32,6 +32,7 @@ const TX_HASH_2 = const createDelegationRow = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: DAO_ID, delegatorAccountId: VALID_ADDRESS, diff --git a/apps/api/src/controllers/feed/feed.integration.test.ts b/apps/api/src/controllers/feed/feed.integration.test.ts index 3ba0156ac..52eaef825 100644 --- a/apps/api/src/controllers/feed/feed.integration.test.ts +++ b/apps/api/src/controllers/feed/feed.integration.test.ts @@ -17,16 +17,18 @@ type FeedEventInsert = typeof feedEvent.$inferInsert; const nounsThresholds = getDaoRelevanceThreshold(DaoIdEnum.NOUNS); const createEvent = ( - overrides: Partial = {}, -): FeedEventInsert => ({ - txHash: "0xabc123def456abc1", - logIndex: 0, - type: "VOTE" as const, - value: nounsThresholds[FeedEventType.VOTE][FeedRelevance.MEDIUM], - timestamp: 1700000000, - metadata: null, - ...overrides, -}); + overrides: Partial & { type: string }> = {}, +): FeedEventInsert => + ({ + id: "test-id", + txHash: "0xabc123def456abc1", + logIndex: 0, + type: "VOTE", + value: nounsThresholds[FeedEventType.VOTE][FeedRelevance.MEDIUM], + timestamp: 1700000000, + metadata: null, + ...overrides, + }) as FeedEventInsert; let client: PGlite; let db: Drizzle; diff --git a/apps/api/src/controllers/governance-activity/controller.integration.test.ts b/apps/api/src/controllers/governance-activity/controller.integration.test.ts index b6167af8d..0993e375b 100644 --- a/apps/api/src/controllers/governance-activity/controller.integration.test.ts +++ b/apps/api/src/controllers/governance-activity/controller.integration.test.ts @@ -100,6 +100,7 @@ describe("GovernanceActivity Controller", () => { it("should return 200 with activeSupply when recent voters exist", async () => { await db.insert(accountPower).values({ + id: "test-id-1", accountId: "0x1111111111111111111111111111111111111111", daoId: "TEST", votingPower: 1000000000000000000n, @@ -121,6 +122,7 @@ describe("GovernanceActivity Controller", () => { it("should use default days=90d when not provided", async () => { await db.insert(accountPower).values({ + id: "test-id-2", accountId: "0x1111111111111111111111111111111111111111", daoId: "TEST", votingPower: 500n, @@ -203,6 +205,7 @@ describe("GovernanceActivity Controller", () => { it("should return 200 with data and calculated changeRate", async () => { await db.insert(votesOnchain).values([ { + id: "vote-1", txHash: "0xv1", daoId: "TEST", voterAccountId: "0x1111111111111111111111111111111111111111", @@ -212,6 +215,7 @@ describe("GovernanceActivity Controller", () => { timestamp: RECENT_TS, }, { + id: "vote-2", txHash: "0xv2", daoId: "TEST", voterAccountId: "0x2222222222222222222222222222222222222222", @@ -221,6 +225,7 @@ describe("GovernanceActivity Controller", () => { timestamp: RECENT_TS, }, { + id: "vote-3", txHash: "0xv3", daoId: "TEST", voterAccountId: "0x3333333333333333333333333333333333333333", diff --git a/apps/api/src/controllers/last-update/index.integration.test.ts b/apps/api/src/controllers/last-update/index.integration.test.ts index 11232a682..db5433184 100644 --- a/apps/api/src/controllers/last-update/index.integration.test.ts +++ b/apps/api/src/controllers/last-update/index.integration.test.ts @@ -13,6 +13,7 @@ import { lastUpdate } from "./index"; type MetricInsert = typeof daoMetricsDayBucket.$inferInsert; const createMetric = (overrides: Partial = {}): MetricInsert => ({ + id: "test-id", date: 1700000000n, daoId: "UNI", tokenId: "uni", diff --git a/apps/api/src/controllers/proposals/proposals-activity.integration.test.ts b/apps/api/src/controllers/proposals/proposals-activity.integration.test.ts index 4086f3aed..c64da0f7b 100644 --- a/apps/api/src/controllers/proposals/proposals-activity.integration.test.ts +++ b/apps/api/src/controllers/proposals/proposals-activity.integration.test.ts @@ -84,6 +84,7 @@ const createProposal = ( }); const createVote = (overrides: Partial = {}): VoteInsert => ({ + id: "0xvote123", txHash: "0xvote123", daoId: "ENS", voterAccountId: VALID_ADDRESS, diff --git a/apps/api/src/controllers/token-metrics/index.integration.test.ts b/apps/api/src/controllers/token-metrics/index.integration.test.ts index a4a305a05..37d0502d3 100644 --- a/apps/api/src/controllers/token-metrics/index.integration.test.ts +++ b/apps/api/src/controllers/token-metrics/index.integration.test.ts @@ -15,6 +15,7 @@ import { tokenMetrics } from "./index"; type MetricInsert = typeof daoMetricsDayBucket.$inferInsert; const createMetric = (overrides: Partial = {}): MetricInsert => ({ + id: "test-id", date: 1700000000n, daoId: "UNI", tokenId: "uni", diff --git a/apps/api/src/controllers/token/token-distribution.integration.test.ts b/apps/api/src/controllers/token/token-distribution.integration.test.ts index 784fb9a14..da92020b0 100644 --- a/apps/api/src/controllers/token/token-distribution.integration.test.ts +++ b/apps/api/src/controllers/token/token-distribution.integration.test.ts @@ -28,6 +28,7 @@ let repo: DrizzleRepository; let app: Hono; const createMetric = (overrides: Partial = {}): MetricInsert => ({ + id: "test-id", date: BigInt(NOW - 10), daoId: "ENS", tokenId: "ens", diff --git a/apps/api/src/controllers/transactions/index.integration.test.ts b/apps/api/src/controllers/transactions/index.integration.test.ts index 08f2ba534..a3b43cc5a 100644 --- a/apps/api/src/controllers/transactions/index.integration.test.ts +++ b/apps/api/src/controllers/transactions/index.integration.test.ts @@ -19,6 +19,7 @@ type DelegationInsert = typeof delegation.$inferInsert; const createTransaction = ( overrides: Partial = {}, ): TransactionInsert => ({ + id: "test-id", transactionHash: TX_HASH, fromAddress: getAddress("0x1111111111111111111111111111111111111111"), toAddress: getAddress("0x2222222222222222222222222222222222222222"), @@ -33,6 +34,7 @@ const createTransaction = ( const createTransfer = ( overrides: Partial = {}, ): TransferInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: "UNI", tokenId: "uni", @@ -51,6 +53,7 @@ const createTransfer = ( const createDelegation = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: "UNI", delegateAccountId: getAddress("0x3333333333333333333333333333333333333333"), diff --git a/apps/api/src/controllers/transfers/index.integration.test.ts b/apps/api/src/controllers/transfers/index.integration.test.ts index 41957f6cf..c338290cf 100644 --- a/apps/api/src/controllers/transfers/index.integration.test.ts +++ b/apps/api/src/controllers/transfers/index.integration.test.ts @@ -20,6 +20,7 @@ type TransferInsert = typeof transfer.$inferInsert; const createTransfer = ( overrides: Partial = {}, ): TransferInsert => ({ + id: "test-id", transactionHash: "0xabc", daoId: "UNI", tokenId: "uni", diff --git a/apps/api/src/controllers/votes/onchainVotes.integration.test.ts b/apps/api/src/controllers/votes/onchainVotes.integration.test.ts index e6dd83270..f1185287a 100644 --- a/apps/api/src/controllers/votes/onchainVotes.integration.test.ts +++ b/apps/api/src/controllers/votes/onchainVotes.integration.test.ts @@ -49,6 +49,7 @@ const createProposal = ( }); const createVote = (overrides: Partial = {}): VoteInsert => ({ + id: "test-id", txHash: "0xabc123", daoId: "ENS", voterAccountId: VOTER_ADDRESS, @@ -63,6 +64,7 @@ const createVote = (overrides: Partial = {}): VoteInsert => ({ const createAccountPower = ( overrides: Partial = {}, ): AccountPowerInsert => ({ + id: "test-id", accountId: VOTER_ADDRESS, daoId: "ENS", votingPower: 1000000000000000000n, diff --git a/apps/api/src/controllers/voting-power/historical.integration.test.ts b/apps/api/src/controllers/voting-power/historical.integration.test.ts index fb9fe157d..3e09600a4 100644 --- a/apps/api/src/controllers/voting-power/historical.integration.test.ts +++ b/apps/api/src/controllers/voting-power/historical.integration.test.ts @@ -29,6 +29,7 @@ const DAO_ID = "ENS"; const createHistoryRow = ( overrides: Partial = {}, ): VotingPowerHistoryInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: DAO_ID, accountId: VALID_ADDRESS, @@ -43,6 +44,7 @@ const createHistoryRow = ( const createDelegationRow = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: "test-id", transactionHash: TX_HASH, daoId: DAO_ID, delegateAccountId: VALID_ADDRESS, diff --git a/apps/api/src/controllers/voting-power/listing.integration.test.ts b/apps/api/src/controllers/voting-power/listing.integration.test.ts index c10d4f639..f4def71b1 100644 --- a/apps/api/src/controllers/voting-power/listing.integration.test.ts +++ b/apps/api/src/controllers/voting-power/listing.integration.test.ts @@ -25,6 +25,7 @@ const DAO_ID = "test-dao"; const createAccountPowerRow = ( overrides: Partial = {}, ): AccountPowerInsert => ({ + id: "test-id", accountId: TEST_ACCOUNT_1, daoId: DAO_ID, votingPower: 1000n, @@ -38,6 +39,7 @@ const createAccountPowerRow = ( const createHistoryRow = ( overrides: Partial = {}, ): VotingPowerHistoryInsert => ({ + id: "test-id", transactionHash: "0xabc1230000000000000000000000000000000000000000000000000000000000", daoId: DAO_ID, diff --git a/apps/api/src/controllers/voting-power/variations.integration.test.ts b/apps/api/src/controllers/voting-power/variations.integration.test.ts index 07a29af8e..2ce061b3c 100644 --- a/apps/api/src/controllers/voting-power/variations.integration.test.ts +++ b/apps/api/src/controllers/voting-power/variations.integration.test.ts @@ -30,6 +30,7 @@ const TX_4 = const createHistoryRow = ( overrides: Partial = {}, ): VotingPowerHistoryInsert => ({ + id: "test-id", transactionHash: "0xabc1230000000000000000000000000000000000000000000000000000000000", daoId: DAO_ID, @@ -45,6 +46,7 @@ const createHistoryRow = ( const createAccountPowerRow = ( overrides: Partial = {}, ): AccountPowerInsert => ({ + id: "test-id", accountId: VALID_ADDRESS, daoId: DAO_ID, votingPower: 1200000000000000000n, diff --git a/apps/api/src/database/schema.ts b/apps/api/src/database/schema.ts index 091f4491f..d2666f211 100644 --- a/apps/api/src/database/schema.ts +++ b/apps/api/src/database/schema.ts @@ -1,151 +1,116 @@ import { relations } from "drizzle-orm"; -import { - pgTable, - index, - bigint, - pgEnum, - primaryKey, -} from "drizzle-orm/pg-core"; +import { pgTable, index, bigint } from "drizzle-orm/pg-core"; import { Address, zeroAddress } from "viem"; -import { MetricTypesArray } from "@/lib/constants"; +import { FeedEventType } from "@/lib/constants"; -export const token = pgTable("token", (drizzle) => ({ +export const token = pgTable("Token", (drizzle) => ({ id: drizzle.text().primaryKey(), name: drizzle.text(), decimals: drizzle.integer().notNull(), - totalSupply: bigint("total_supply", { mode: "bigint" }).notNull().default(0n), - delegatedSupply: bigint("delegated_supply", { mode: "bigint" }) + totalSupply: bigint("totalSupply", { mode: "bigint" }).notNull().default(0n), + delegatedSupply: bigint("delegatedSupply", { mode: "bigint" }) .notNull() .default(0n), - cexSupply: bigint("cex_supply", { mode: "bigint" }).notNull().default(0n), - dexSupply: bigint("dex_supply", { mode: "bigint" }).notNull().default(0n), - lendingSupply: bigint("lending_supply", { mode: "bigint" }) + cexSupply: bigint("cexSupply", { mode: "bigint" }).notNull().default(0n), + dexSupply: bigint("dexSupply", { mode: "bigint" }).notNull().default(0n), + lendingSupply: bigint("lendingSupply", { mode: "bigint" }) .notNull() .default(0n), - circulatingSupply: bigint("circulating_supply", { mode: "bigint" }) + circulatingSupply: bigint("circulatingSupply", { mode: "bigint" }) .notNull() .default(0n), treasury: bigint({ mode: "bigint" }).notNull().default(0n), - nonCirculatingSupply: bigint("non_circulating_supply", { mode: "bigint" }) + nonCirculatingSupply: bigint("nonCirculatingSupply", { mode: "bigint" }) .notNull() .default(0n), })); -export const account = pgTable("account", (drizzle) => ({ +export const account = pgTable("Account", (drizzle) => ({ id: drizzle.text().primaryKey(), })); export const accountBalance = pgTable( - "account_balance", + "AccountBalance", (drizzle) => ({ - accountId: drizzle.text("account_id").$type
().notNull(), - tokenId: drizzle.text("token_id").notNull(), + id: drizzle.text().primaryKey(), + accountId: drizzle.text("accountId").$type
().notNull(), + tokenId: drizzle.text("tokenId").notNull(), balance: bigint({ mode: "bigint" }).notNull(), - // This field represents for who the account is delegating their voting power to delegate: drizzle.text().$type
().default(zeroAddress).notNull(), }), - (table) => [ - primaryKey({ - columns: [table.accountId, table.tokenId], - }), - index().on(table.delegate), - ], + (table) => [index().on(table.delegate)], ); export const accountPower = pgTable( - "account_power", + "AccountPower", (drizzle) => ({ - accountId: drizzle.text("account_id").$type
().notNull(), - daoId: drizzle.text("dao_id").notNull(), - votingPower: bigint("voting_power", { mode: "bigint" }) + id: drizzle.text().primaryKey(), + accountId: drizzle.text("accountId").$type
().notNull(), + daoId: drizzle.text("daoId").notNull(), + votingPower: bigint("votingPower", { mode: "bigint" }) .default(BigInt(0)) .notNull(), - votesCount: drizzle.integer("votes_count").default(0).notNull(), - proposalsCount: drizzle.integer("proposals_count").default(0).notNull(), - delegationsCount: drizzle.integer("delegations_count").default(0).notNull(), - lastVoteTimestamp: bigint("last_vote_timestamp", { mode: "bigint" }) + votesCount: drizzle.integer("votesCount").default(0).notNull(), + proposalsCount: drizzle.integer("proposalsCount").default(0).notNull(), + delegationsCount: drizzle.integer("delegationsCount").default(0).notNull(), + lastVoteTimestamp: bigint("lastVoteTimestamp", { mode: "bigint" }) .default(BigInt(0)) .notNull(), }), - (table) => [ - primaryKey({ - columns: [table.accountId], - }), - index().on(table.lastVoteTimestamp), - ], + (table) => [index().on(table.lastVoteTimestamp)], ); -export const votingPowerHistory = pgTable( - "voting_power_history", - (drizzle) => ({ - transactionHash: drizzle.text("transaction_hash").notNull(), - daoId: drizzle.text("dao_id").notNull(), - accountId: drizzle.text("account_id").$type
().notNull(), - votingPower: bigint("voting_power", { mode: "bigint" }).notNull(), - delta: bigint({ mode: "bigint" }).notNull(), - deltaMod: bigint("delta_mod", { mode: "bigint" }).notNull(), - timestamp: bigint({ mode: "bigint" }).notNull(), - logIndex: drizzle.integer("log_index").notNull(), - }), - (table) => [ - primaryKey({ - columns: [table.transactionHash, table.accountId, table.logIndex], - }), - ], -); +export const votingPowerHistory = pgTable("VotingPowerHistory", (drizzle) => ({ + id: drizzle.text().primaryKey(), + transactionHash: drizzle.text("transactionHash").notNull(), + daoId: drizzle.text("daoId").notNull(), + accountId: drizzle.text("accountId").$type
().notNull(), + votingPower: bigint("votingPower", { mode: "bigint" }).notNull(), + delta: bigint({ mode: "bigint" }).notNull(), + deltaMod: bigint("deltaMod", { mode: "bigint" }).notNull(), + timestamp: bigint({ mode: "bigint" }).notNull(), + logIndex: drizzle.integer("logIndex").notNull(), +})); -export const balanceHistory = pgTable( - "balance_history", - (drizzle) => ({ - transactionHash: drizzle.text("transaction_hash").notNull(), - daoId: drizzle.text("dao_id").notNull(), - accountId: drizzle.text("account_id").$type
().notNull(), - balance: bigint({ mode: "bigint" }).notNull(), - delta: bigint({ mode: "bigint" }).notNull(), - deltaMod: bigint("delta_mod", { mode: "bigint" }).notNull(), - timestamp: bigint({ mode: "bigint" }).notNull(), - logIndex: drizzle.integer("log_index").notNull(), - }), - (table) => [ - primaryKey({ - columns: [table.transactionHash, table.accountId, table.logIndex], - }), - ], -); +export const balanceHistory = pgTable("BalanceHistory", (drizzle) => ({ + id: drizzle.text().primaryKey(), + transactionHash: drizzle.text("transactionHash").notNull(), + daoId: drizzle.text("daoId").notNull(), + accountId: drizzle.text("accountId").$type
().notNull(), + balance: bigint({ mode: "bigint" }).notNull(), + delta: bigint({ mode: "bigint" }).notNull(), + deltaMod: bigint("deltaMod", { mode: "bigint" }).notNull(), + timestamp: bigint({ mode: "bigint" }).notNull(), + logIndex: drizzle.integer("logIndex").notNull(), +})); export const delegation = pgTable( - "delegations", + "Delegation", (drizzle) => ({ - transactionHash: drizzle.text("transaction_hash").notNull(), - daoId: drizzle.text("dao_id").notNull(), + id: drizzle.text().primaryKey(), + transactionHash: drizzle.text("transactionHash").notNull(), + daoId: drizzle.text("daoId").notNull(), delegateAccountId: drizzle - .text("delegate_account_id") + .text("delegateAccountId") .$type
() .notNull(), delegatorAccountId: drizzle - .text("delegator_account_id") + .text("delegatorAccountId") .$type
() .notNull(), - delegatedValue: bigint("delegated_value", { mode: "bigint" }) + delegatedValue: bigint("delegatedValue", { mode: "bigint" }) .notNull() .default(0n), - previousDelegate: drizzle.text("previous_delegate"), + previousDelegate: drizzle.text("previousDelegate"), timestamp: bigint({ mode: "bigint" }).notNull(), - logIndex: drizzle.integer("log_index").notNull(), - isCex: drizzle.boolean("is_cex").notNull().default(false), - isDex: drizzle.boolean("is_dex").notNull().default(false), - isLending: drizzle.boolean("is_lending").notNull().default(false), - isTotal: drizzle.boolean("is_total").notNull().default(false), + logIndex: drizzle.integer("logIndex").notNull(), + isCex: drizzle.boolean("isCex").notNull().default(false), + isDex: drizzle.boolean("isDex").notNull().default(false), + isLending: drizzle.boolean("isLending").notNull().default(false), + isTotal: drizzle.boolean("isTotal").notNull().default(false), }), (table) => [ - primaryKey({ - columns: [ - table.transactionHash, - table.delegatorAccountId, - table.delegateAccountId, - ], - }), index().on(table.transactionHash), index().on(table.timestamp), index().on(table.delegatorAccountId), @@ -155,25 +120,23 @@ export const delegation = pgTable( ); export const transfer = pgTable( - "transfers", + "Transfer", (drizzle) => ({ - transactionHash: drizzle.text("transaction_hash").notNull(), - daoId: drizzle.text("dao_id").notNull(), - tokenId: drizzle.text("token_id").notNull(), + id: drizzle.text().primaryKey(), + transactionHash: drizzle.text("transactionHash").notNull(), + daoId: drizzle.text("daoId").notNull(), + tokenId: drizzle.text("tokenId").notNull(), amount: bigint({ mode: "bigint" }).notNull(), - fromAccountId: drizzle.text("from_account_id").$type
().notNull(), - toAccountId: drizzle.text("to_account_id").$type
().notNull(), + fromAccountId: drizzle.text("fromAccountId").$type
().notNull(), + toAccountId: drizzle.text("toAccountId").$type
().notNull(), timestamp: bigint({ mode: "bigint" }).notNull(), - logIndex: drizzle.integer("log_index").notNull(), - isCex: drizzle.boolean("is_cex").notNull().default(false), - isDex: drizzle.boolean("is_dex").notNull().default(false), - isLending: drizzle.boolean("is_lending").notNull().default(false), - isTotal: drizzle.boolean("is_total").notNull().default(false), + logIndex: drizzle.integer("logIndex").notNull(), + isCex: drizzle.boolean("isCex").notNull().default(false), + isDex: drizzle.boolean("isDex").notNull().default(false), + isLending: drizzle.boolean("isLending").notNull().default(false), + isTotal: drizzle.boolean("isTotal").notNull().default(false), }), (table) => [ - primaryKey({ - columns: [table.transactionHash, table.fromAccountId, table.toAccountId], - }), index().on(table.transactionHash), index().on(table.timestamp), index().on(table.fromAccountId), @@ -182,54 +145,47 @@ export const transfer = pgTable( ], ); -export const votesOnchain = pgTable( - "votes_onchain", - (drizzle) => ({ - txHash: drizzle.text("tx_hash").notNull(), - daoId: drizzle.text("dao_id").notNull(), - voterAccountId: drizzle.text("voter_account_id").$type
().notNull(), - proposalId: drizzle.text("proposal_id").notNull(), - support: drizzle.text().notNull(), - votingPower: bigint("voting_power", { mode: "bigint" }).notNull(), - reason: drizzle.text(), - timestamp: bigint({ mode: "bigint" }).notNull(), - }), - (table) => [ - primaryKey({ - columns: [table.voterAccountId, table.proposalId], - }), - ], -); +export const votesOnchain = pgTable("VoteOnchain", (drizzle) => ({ + id: drizzle.text().primaryKey(), + txHash: drizzle.text("txHash").notNull(), + daoId: drizzle.text("daoId").notNull(), + voterAccountId: drizzle.text("voterAccountId").$type
().notNull(), + proposalId: drizzle.text("proposalId").notNull(), + support: drizzle.text().notNull(), + votingPower: bigint("votingPower", { mode: "bigint" }).notNull(), + reason: drizzle.text(), + timestamp: bigint({ mode: "bigint" }).notNull(), +})); export const proposalsOnchain = pgTable( - "proposals_onchain", + "ProposalOnchain", (drizzle) => ({ id: drizzle.text().primaryKey(), - txHash: drizzle.text("tx_hash").notNull(), - daoId: drizzle.text("dao_id").notNull(), + txHash: drizzle.text("txHash").notNull(), + daoId: drizzle.text("daoId").notNull(), proposerAccountId: drizzle - .text("proposer_account_id") + .text("proposerAccountId") .$type
() .notNull(), targets: drizzle.json().$type().notNull(), values: drizzle.json().$type().notNull(), signatures: drizzle.json().$type().notNull(), calldatas: drizzle.json().$type().notNull(), - startBlock: drizzle.integer("start_block").notNull(), - endBlock: drizzle.integer("end_block").notNull(), + startBlock: drizzle.integer("startBlock").notNull(), + endBlock: drizzle.integer("endBlock").notNull(), title: drizzle.text().notNull(), description: drizzle.text().notNull(), timestamp: bigint({ mode: "bigint" }).notNull(), - endTimestamp: bigint("end_timestamp", { mode: "bigint" }).notNull(), + endTimestamp: bigint("endTimestamp", { mode: "bigint" }).notNull(), status: drizzle.text().notNull(), - forVotes: bigint("for_votes", { mode: "bigint" }).default(0n).notNull(), - againstVotes: bigint("against_votes", { mode: "bigint" }) + forVotes: bigint("forVotes", { mode: "bigint" }).default(0n).notNull(), + againstVotes: bigint("againstVotes", { mode: "bigint" }) .default(0n) .notNull(), - abstainVotes: bigint("abstain_votes", { mode: "bigint" }) + abstainVotes: bigint("abstainVotes", { mode: "bigint" }) .default(0n) .notNull(), - proposalType: drizzle.integer("proposal_type"), + proposalType: drizzle.integer("proposalType"), }), (table) => [index().on(table.proposerAccountId)], ); @@ -241,15 +197,14 @@ export const votesOnchainRelations = relations(votesOnchain, ({ one }) => ({ }), })); -export const metricType = pgEnum("metricType", MetricTypesArray); - export const daoMetricsDayBucket = pgTable( - "dao_metrics_day_buckets", + "DaoMetricsDayBucket", (drizzle) => ({ + id: drizzle.text().primaryKey(), date: bigint({ mode: "bigint" }).notNull(), - daoId: drizzle.text("dao_id").notNull(), - tokenId: drizzle.text("token_id").notNull(), - metricType: metricType("metricType").notNull(), + daoId: drizzle.text("daoId").notNull(), + tokenId: drizzle.text("tokenId").notNull(), + metricType: drizzle.text("metricType").notNull(), open: bigint({ mode: "bigint" }).notNull(), close: bigint({ mode: "bigint" }).notNull(), low: bigint({ mode: "bigint" }).notNull(), @@ -257,54 +212,40 @@ export const daoMetricsDayBucket = pgTable( average: bigint({ mode: "bigint" }).notNull(), volume: bigint({ mode: "bigint" }).notNull(), count: drizzle.integer().notNull(), - lastUpdate: bigint("last_update", { mode: "bigint" }).notNull(), + lastUpdate: bigint("lastUpdate", { mode: "bigint" }).notNull(), }), - (table) => [ - primaryKey({ - columns: [table.date, table.tokenId, table.metricType], - }), - ], ); -export const transaction = pgTable("transaction", (drizzle) => ({ - transactionHash: drizzle.text("transaction_hash").primaryKey(), - fromAddress: drizzle.text("from_address"), - toAddress: drizzle.text("to_address"), - isCex: drizzle.boolean("is_cex").notNull().default(false), - isDex: drizzle.boolean("is_dex").notNull().default(false), - isLending: drizzle.boolean("is_lending").notNull().default(false), - isTotal: drizzle.boolean("is_total").notNull().default(false), +export const transaction = pgTable("Transaction", (drizzle) => ({ + id: drizzle.text().primaryKey(), + transactionHash: drizzle.text("transactionHash").notNull(), + fromAddress: drizzle.text("fromAddress"), + toAddress: drizzle.text("toAddress"), + isCex: drizzle.boolean("isCex").notNull().default(false), + isDex: drizzle.boolean("isDex").notNull().default(false), + isLending: drizzle.boolean("isLending").notNull().default(false), + isTotal: drizzle.boolean("isTotal").notNull().default(false), timestamp: bigint({ mode: "bigint" }).notNull(), })); -export const tokenPrice = pgTable("token_price", (_drizzle) => ({ - price: bigint({ mode: "bigint" }).notNull(), // price in ETH - timestamp: bigint({ mode: "bigint" }).primaryKey(), +export const tokenPrice = pgTable("TokenPrice", (_drizzle) => ({ + id: _drizzle.text().primaryKey(), + price: bigint({ mode: "bigint" }).notNull(), + timestamp: bigint({ mode: "bigint" }).notNull(), })); -export const evenTypeEnum = pgEnum("event_type", [ - "VOTE", - "PROPOSAL", - "DELEGATION", - "TRANSFER", - "DELEGATION_VOTES_CHANGED", - "PROPOSAL_EXTENDED", -]); - export const feedEvent = pgTable( - "feed_event", + "FeedEvent", (drizzle) => ({ - txHash: drizzle.text("tx_hash").notNull(), - logIndex: drizzle.integer("log_index").notNull(), - type: evenTypeEnum("type").notNull(), + id: drizzle.text().primaryKey(), + txHash: drizzle.text("txHash").notNull(), + logIndex: drizzle.integer("logIndex").notNull(), + type: drizzle.text("type").$type().notNull(), value: bigint({ mode: "bigint" }).notNull().default(0n), timestamp: bigint({ mode: "number" }).notNull(), metadata: drizzle.json().$type>(), }), (table) => [ - primaryKey({ - columns: [table.txHash, table.logIndex], - }), index().on(table.timestamp), index().on(table.type), index().on(table.value), diff --git a/apps/api/src/env.ts b/apps/api/src/env.ts index 547eb0959..3f4179673 100644 --- a/apps/api/src/env.ts +++ b/apps/api/src/env.ts @@ -7,9 +7,7 @@ dotenv.config(); const envSchema = z.object({ RPC_URL: z.string(), - DATABASE_URL: z - .string() - .transform((val) => `${val}?options=-c%20search_path%3Danticapture`), + DATABASE_URL: z.string(), DAO_ID: z.enum(DaoIdEnum), CHAIN_ID: z.coerce.number(), diff --git a/apps/api/src/repositories/account-balance/aave.unit.test.ts b/apps/api/src/repositories/account-balance/aave.unit.test.ts index b9a8a8354..7db98f656 100644 --- a/apps/api/src/repositories/account-balance/aave.unit.test.ts +++ b/apps/api/src/repositories/account-balance/aave.unit.test.ts @@ -27,26 +27,34 @@ let txCounter = 0; const createAccountBalance = ( overrides: Partial = {}, -): AccountBalanceInsert => ({ - accountId: ACCOUNT_A, - tokenId: `token-${txCounter++}`, - balance: 1000n, - ...overrides, -}); +): AccountBalanceInsert => { + const n = txCounter++; + return { + id: `ab-${n}`, + accountId: ACCOUNT_A, + tokenId: `token-${n}`, + balance: 1000n, + ...overrides, + }; +}; const createTransfer = ( overrides: Partial = {}, -): TransferInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: "test-dao", - tokenId: "token-1", - amount: 100n, - fromAccountId: ACCOUNT_A, - toAccountId: ACCOUNT_B, - timestamp: 1700000000n, - logIndex: 0, - ...overrides, -}); +): TransferInsert => { + const n = txCounter++; + return { + id: `tx-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: "test-dao", + tokenId: "token-1", + amount: 100n, + fromAccountId: ACCOUNT_A, + toAccountId: ACCOUNT_B, + timestamp: 1700000000n, + logIndex: 0, + ...overrides, + }; +}; describe("AAVEAccountBalanceRepository", () => { let client: PGlite; diff --git a/apps/api/src/repositories/account-balance/common.unit.test.ts b/apps/api/src/repositories/account-balance/common.unit.test.ts index e1103ff5a..f8a928d88 100644 --- a/apps/api/src/repositories/account-balance/common.unit.test.ts +++ b/apps/api/src/repositories/account-balance/common.unit.test.ts @@ -20,26 +20,34 @@ let txCounter = 0; const createAccountBalance = ( overrides: Partial = {}, -): AccountBalanceInsert => ({ - accountId: ACCOUNT_A, - tokenId: `token-${txCounter++}`, - balance: 1000n, - ...overrides, -}); +): AccountBalanceInsert => { + const n = txCounter++; + return { + id: `ab-${n}`, + accountId: ACCOUNT_A, + tokenId: `token-${n}`, + balance: 1000n, + ...overrides, + }; +}; const createTransfer = ( overrides: Partial = {}, -): TransferInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: "test-dao", - tokenId: "token-1", - amount: 100n, - fromAccountId: ACCOUNT_A, - toAccountId: ACCOUNT_B, - timestamp: 1700000000n, - logIndex: 0, - ...overrides, -}); +): TransferInsert => { + const n = txCounter++; + return { + id: `tx-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: "test-dao", + tokenId: "token-1", + amount: 100n, + fromAccountId: ACCOUNT_A, + toAccountId: ACCOUNT_B, + timestamp: 1700000000n, + logIndex: 0, + ...overrides, + }; +}; describe("AccountBalanceQueryFragments", () => { let client: PGlite; diff --git a/apps/api/src/repositories/account-balance/historical.ts b/apps/api/src/repositories/account-balance/historical.ts index 20355033f..1ceaa6a0c 100644 --- a/apps/api/src/repositories/account-balance/historical.ts +++ b/apps/api/src/repositories/account-balance/historical.ts @@ -53,8 +53,8 @@ export class HistoricalBalanceRepository { .offset(skip); return result.map((row) => ({ - ...row.balance_history, - transfer: row.transfers, + ...row.BalanceHistory, + transfer: row.Transfer, })); } diff --git a/apps/api/src/repositories/account-balance/historical.unit.test.ts b/apps/api/src/repositories/account-balance/historical.unit.test.ts index 1ad557d61..90a73e0e5 100644 --- a/apps/api/src/repositories/account-balance/historical.unit.test.ts +++ b/apps/api/src/repositories/account-balance/historical.unit.test.ts @@ -19,21 +19,26 @@ let txCounter = 0; const createBalanceHistory = ( overrides: Partial = {}, -): BalanceHistoryInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: "test-dao", - accountId: ACCOUNT_A, - balance: 1000n, - delta: 200n, - deltaMod: 200n, - timestamp: 1700000000n, - logIndex: 0, - ...overrides, -}); +): BalanceHistoryInsert => { + const n = txCounter++; + return { + id: `bh-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: "test-dao", + accountId: ACCOUNT_A, + balance: 1000n, + delta: 200n, + deltaMod: 200n, + timestamp: 1700000000n, + logIndex: 0, + ...overrides, + }; +}; const createTransfer = ( overrides: Partial = {}, ): TransferInsert => ({ + id: `tx-${txCounter}`, transactionHash: `0x${txCounter.toString(16).padStart(64, "0")}`, daoId: "test-dao", tokenId: "token-1", diff --git a/apps/api/src/repositories/account-balance/interactions.unit.test.ts b/apps/api/src/repositories/account-balance/interactions.unit.test.ts index ec779b028..7ae17d865 100644 --- a/apps/api/src/repositories/account-balance/interactions.unit.test.ts +++ b/apps/api/src/repositories/account-balance/interactions.unit.test.ts @@ -23,26 +23,34 @@ let txCounter = 0; const createAccountBalance = ( overrides: Partial = {}, -): AccountBalanceInsert => ({ - accountId: ACCOUNT_A, - tokenId: `token-${txCounter++}`, - balance: 1000n, - ...overrides, -}); +): AccountBalanceInsert => { + const n = txCounter++; + return { + id: `ab-${n}`, + accountId: ACCOUNT_A, + tokenId: `token-${n}`, + balance: 1000n, + ...overrides, + }; +}; const createTransfer = ( overrides: Partial = {}, -): TransferInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: "test-dao", - tokenId: "token-1", - amount: 100n, - fromAccountId: ACCOUNT_A, - toAccountId: ACCOUNT_B, - timestamp: 1700000000n, - logIndex: 0, - ...overrides, -}); +): TransferInsert => { + const n = txCounter++; + return { + id: `tx-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: "test-dao", + tokenId: "token-1", + amount: 100n, + fromAccountId: ACCOUNT_A, + toAccountId: ACCOUNT_B, + timestamp: 1700000000n, + logIndex: 0, + ...overrides, + }; +}; describe("AccountInteractionsRepository", () => { let client: PGlite; diff --git a/apps/api/src/repositories/account-balance/listing.unit.test.ts b/apps/api/src/repositories/account-balance/listing.unit.test.ts index f7cbbe474..8c21076e1 100644 --- a/apps/api/src/repositories/account-balance/listing.unit.test.ts +++ b/apps/api/src/repositories/account-balance/listing.unit.test.ts @@ -27,6 +27,7 @@ let txCounter = 0; const createAccountBalance = ( overrides: Partial = {}, ): AccountBalanceInsert => ({ + id: "test-id", accountId: ACCOUNT_A, tokenId: TOKEN_ID, balance: 1000n, @@ -35,17 +36,21 @@ const createAccountBalance = ( const createTransfer = ( overrides: Partial = {}, -): TransferInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: "test-dao", - tokenId: TOKEN_ID, - amount: 100n, - fromAccountId: ACCOUNT_A, - toAccountId: ACCOUNT_B, - timestamp: 1700000000n, - logIndex: 0, - ...overrides, -}); +): TransferInsert => { + const n = txCounter++; + return { + id: `tx-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: "test-dao", + tokenId: TOKEN_ID, + amount: 100n, + fromAccountId: ACCOUNT_A, + toAccountId: ACCOUNT_B, + timestamp: 1700000000n, + logIndex: 0, + ...overrides, + }; +}; describe("AccountBalanceRepository", () => { let client: PGlite; diff --git a/apps/api/src/repositories/account-balance/variations.unit.test.ts b/apps/api/src/repositories/account-balance/variations.unit.test.ts index a71beae30..04679befa 100644 --- a/apps/api/src/repositories/account-balance/variations.unit.test.ts +++ b/apps/api/src/repositories/account-balance/variations.unit.test.ts @@ -21,26 +21,34 @@ let txCounter = 0; const createAccountBalance = ( overrides: Partial = {}, -): AccountBalanceInsert => ({ - accountId: ACCOUNT_A, - tokenId: `token-${txCounter++}`, - balance: 1000n, - ...overrides, -}); +): AccountBalanceInsert => { + const n = txCounter++; + return { + id: `ab-${n}`, + accountId: ACCOUNT_A, + tokenId: `token-${n}`, + balance: 1000n, + ...overrides, + }; +}; const createTransfer = ( overrides: Partial = {}, -): TransferInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: "test-dao", - tokenId: "token-1", - amount: 100n, - fromAccountId: ACCOUNT_A, - toAccountId: ACCOUNT_B, - timestamp: 1700000000n, - logIndex: 0, - ...overrides, -}); +): TransferInsert => { + const n = txCounter++; + return { + id: `tx-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: "test-dao", + tokenId: "token-1", + amount: 100n, + fromAccountId: ACCOUNT_A, + toAccountId: ACCOUNT_B, + timestamp: 1700000000n, + logIndex: 0, + ...overrides, + }; +}; describe("BalanceVariationsRepository", () => { let client: PGlite; diff --git a/apps/api/src/repositories/daoMetricsDayBucket/index.unit.test.ts b/apps/api/src/repositories/daoMetricsDayBucket/index.unit.test.ts index 58723f15d..8c5ad6df9 100644 --- a/apps/api/src/repositories/daoMetricsDayBucket/index.unit.test.ts +++ b/apps/api/src/repositories/daoMetricsDayBucket/index.unit.test.ts @@ -10,6 +10,7 @@ import { DaoMetricsDayBucketRepository } from "./index"; type MetricInsert = typeof daoMetricsDayBucket.$inferInsert; const createMetric = (overrides: Partial = {}): MetricInsert => ({ + id: "test-id", date: 1700000000n, daoId: "UNI", tokenId: "uni", diff --git a/apps/api/src/repositories/delegations/delegators.unit.test.ts b/apps/api/src/repositories/delegations/delegators.unit.test.ts index 7407888e3..20b933214 100644 --- a/apps/api/src/repositories/delegations/delegators.unit.test.ts +++ b/apps/api/src/repositories/delegations/delegators.unit.test.ts @@ -23,26 +23,34 @@ let txCounter = 0; const createAccountBalance = ( overrides: Partial = {}, -): AccountBalanceInsert => ({ - accountId: DELEGATOR_A, - tokenId: `token-${txCounter++}`, - balance: 1000000000000000000n, - delegate: DELEGATE, - ...overrides, -}); +): AccountBalanceInsert => { + const n = txCounter++; + return { + id: `ab-${n}`, + accountId: DELEGATOR_A, + tokenId: `token-${n}`, + balance: 1000000000000000000n, + delegate: DELEGATE, + ...overrides, + }; +}; const createDelegation = ( overrides: Partial = {}, -): DelegationInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: "uni", - delegateAccountId: DELEGATE, - delegatorAccountId: DELEGATOR_A, - delegatedValue: 0n, - timestamp: 1700000000n, - logIndex: 0, - ...overrides, -}); +): DelegationInsert => { + const n = txCounter++; + return { + id: `d-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: "uni", + delegateAccountId: DELEGATE, + delegatorAccountId: DELEGATOR_A, + delegatedValue: 0n, + timestamp: 1700000000n, + logIndex: 0, + ...overrides, + }; +}; const defaultSort = ( overrides: Partial = {}, diff --git a/apps/api/src/repositories/delegations/general.unit.test.ts b/apps/api/src/repositories/delegations/general.unit.test.ts index 38c4e11d9..b78b02161 100644 --- a/apps/api/src/repositories/delegations/general.unit.test.ts +++ b/apps/api/src/repositories/delegations/general.unit.test.ts @@ -18,6 +18,7 @@ const delegatedAccount: Address = "0x2222222222222222222222222222222222222222"; const createDelegation = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: "test-id", transactionHash: "0xdefault", daoId: "uni", delegateAccountId: accountAddress, @@ -32,6 +33,7 @@ const createDelegation = ( const createAccountBalance = ( overrides: Partial = {}, ): AccountBalanceInsert => ({ + id: "test-id", accountId: accountAddress, tokenId: "uni", balance: 1000n, diff --git a/apps/api/src/repositories/delegations/historical.unit.test.ts b/apps/api/src/repositories/delegations/historical.unit.test.ts index 0f3422c0e..c68ad336b 100644 --- a/apps/api/src/repositories/delegations/historical.unit.test.ts +++ b/apps/api/src/repositories/delegations/historical.unit.test.ts @@ -18,6 +18,7 @@ const delegate2: Address = "0x9999999999999999999999999999999999999999"; const createDelegation = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: "test-id", transactionHash: "0xdefault", daoId: "uni", delegateAccountId: delegate1, diff --git a/apps/api/src/repositories/feed/feed.repository.unit.test.ts b/apps/api/src/repositories/feed/feed.repository.unit.test.ts index 6aa052e30..dd4d7eaeb 100644 --- a/apps/api/src/repositories/feed/feed.repository.unit.test.ts +++ b/apps/api/src/repositories/feed/feed.repository.unit.test.ts @@ -36,16 +36,18 @@ const defaultThresholds = ( }); const createFeedEvent = ( - overrides: Partial = {}, -): FeedEventInsert => ({ - txHash: "0xabc123", - logIndex: 0, - type: "VOTE", - value: 1000n, - timestamp: 1700000000, - metadata: null, - ...overrides, -}); + overrides: Partial & { type: string }> = {}, +): FeedEventInsert => + ({ + id: "test-id", + txHash: "0xabc123", + logIndex: 0, + type: "VOTE", + value: 1000n, + timestamp: 1700000000, + metadata: null, + ...overrides, + }) as FeedEventInsert; describe("FeedRepository", () => { let client: PGlite; diff --git a/apps/api/src/repositories/last-update/index.unit.test.ts b/apps/api/src/repositories/last-update/index.unit.test.ts index 447e3ea23..14510a1f1 100644 --- a/apps/api/src/repositories/last-update/index.unit.test.ts +++ b/apps/api/src/repositories/last-update/index.unit.test.ts @@ -11,6 +11,7 @@ import { LastUpdateRepositoryImpl } from "./index"; type MetricInsert = typeof daoMetricsDayBucket.$inferInsert; const createMetric = (overrides: Partial = {}): MetricInsert => ({ + id: "test-id", date: 1700000000n, daoId: "UNI", tokenId: "uni", diff --git a/apps/api/src/repositories/proposals-activity/index.unit.test.ts b/apps/api/src/repositories/proposals-activity/index.unit.test.ts index 392f0c69f..0f73e6bff 100644 --- a/apps/api/src/repositories/proposals-activity/index.unit.test.ts +++ b/apps/api/src/repositories/proposals-activity/index.unit.test.ts @@ -14,17 +14,21 @@ const OTHER_VOTER = getAddress("0x2222222222222222222222222222222222222222"); type VoteInsert = typeof votesOnchain.$inferInsert; type ProposalInsert = typeof proposalsOnchain.$inferInsert; -const createVote = (overrides: Partial = {}): VoteInsert => ({ - txHash: "0xvote1", - daoId: "UNI", - voterAccountId: VOTER, - proposalId: "proposal-1", - support: "1", - votingPower: 1000n, - reason: "", - timestamp: 1700000000n, - ...overrides, -}); +const createVote = (overrides: Partial = {}): VoteInsert => { + const txHash = overrides.txHash ?? "0xvote1"; + return { + id: txHash, + txHash, + daoId: "UNI", + voterAccountId: VOTER, + proposalId: "proposal-1", + support: "1", + votingPower: 1000n, + reason: "", + timestamp: 1700000000n, + ...overrides, + }; +}; const createProposal = ( overrides: Partial = {}, diff --git a/apps/api/src/repositories/token/nft.unit.test.ts b/apps/api/src/repositories/token/nft.unit.test.ts index 0745e9a7a..589474300 100644 --- a/apps/api/src/repositories/token/nft.unit.test.ts +++ b/apps/api/src/repositories/token/nft.unit.test.ts @@ -14,11 +14,15 @@ let counter = 0; const createTokenPrice = ( overrides: Partial = {}, -): TokenPriceInsert => ({ - price: 1000000000000000000n, - timestamp: BigInt(1700000000 + counter++), - ...overrides, -}); +): TokenPriceInsert => { + const n = counter++; + return { + id: `price-${n}`, + price: 1000000000000000000n, + timestamp: BigInt(1700000000 + n), + ...overrides, + }; +}; describe("NFTPriceRepository", () => { let client: PGlite; diff --git a/apps/api/src/repositories/transactions/index.unit.test.ts b/apps/api/src/repositories/transactions/index.unit.test.ts index adfb4f7f2..0cb4605db 100644 --- a/apps/api/src/repositories/transactions/index.unit.test.ts +++ b/apps/api/src/repositories/transactions/index.unit.test.ts @@ -22,17 +22,22 @@ let txCounter = 0; const createTransaction = ( overrides: Partial = {}, -): TransactionInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - fromAddress: ACCOUNT_A, - toAddress: ACCOUNT_B, - timestamp: 1700000000n, - ...overrides, -}); +): TransactionInsert => { + const n = txCounter++; + return { + id: `tx-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + fromAddress: ACCOUNT_A, + toAddress: ACCOUNT_B, + timestamp: 1700000000n, + ...overrides, + }; +}; const createTransfer = ( overrides: Partial = {}, ): TransferInsert => ({ + id: `tf-${txCounter}`, transactionHash: `0x${txCounter.toString(16).padStart(64, "0")}`, daoId: TEST_DAO, tokenId: "token-1", @@ -47,6 +52,7 @@ const createTransfer = ( const createDelegation = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: `d-${txCounter}`, transactionHash: `0x${txCounter.toString(16).padStart(64, "0")}`, daoId: TEST_DAO, delegateAccountId: ACCOUNT_B, diff --git a/apps/api/src/repositories/transfers/index.unit.test.ts b/apps/api/src/repositories/transfers/index.unit.test.ts index b593d979d..4c53f318f 100644 --- a/apps/api/src/repositories/transfers/index.unit.test.ts +++ b/apps/api/src/repositories/transfers/index.unit.test.ts @@ -17,6 +17,7 @@ type TransferInsert = typeof transfer.$inferInsert; const createTransfer = ( overrides: Partial = {}, ): TransferInsert => ({ + id: "test-id", transactionHash: "0xabc", daoId: "UNI", tokenId: "uni", diff --git a/apps/api/src/repositories/treasury/treasury.repository.integration.test.ts b/apps/api/src/repositories/treasury/treasury.repository.integration.test.ts index 706e7112b..1e6fb9f7c 100644 --- a/apps/api/src/repositories/treasury/treasury.repository.integration.test.ts +++ b/apps/api/src/repositories/treasury/treasury.repository.integration.test.ts @@ -14,6 +14,7 @@ type DaoMetricInsert = typeof daoMetricsDayBucket.$inferInsert; const createMetricRow = ( overrides: Partial = {}, ): DaoMetricInsert => ({ + id: "test-id", date: 1600041600n, daoId: "ENS", tokenId: "ENS-token", diff --git a/apps/api/src/repositories/votes/offchainNonVoters.unit.test.ts b/apps/api/src/repositories/votes/offchainNonVoters.unit.test.ts index 1f4752f7d..561018762 100644 --- a/apps/api/src/repositories/votes/offchainNonVoters.unit.test.ts +++ b/apps/api/src/repositories/votes/offchainNonVoters.unit.test.ts @@ -26,6 +26,7 @@ const SPACE_ID = "test.eth"; const createAccountPower = ( overrides: Partial = {}, ): AccountPowerInsert => ({ + id: "test-id", accountId: VOTER_A, daoId: DAO_ID, votingPower: 100n, diff --git a/apps/api/src/repositories/votes/onchainVotes.unit.test.ts b/apps/api/src/repositories/votes/onchainVotes.unit.test.ts index 6516af994..8077f9e40 100644 --- a/apps/api/src/repositories/votes/onchainVotes.unit.test.ts +++ b/apps/api/src/repositories/votes/onchainVotes.unit.test.ts @@ -50,21 +50,26 @@ const createProposal = ( const createVote = ( overrides: Partial = {}, -): VotesOnchainInsert => ({ - txHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: TEST_DAO, - voterAccountId: VOTER_A, - proposalId: "proposal-1", - support: "1", - votingPower: 1000n, - reason: null, - timestamp: 1700000000n, - ...overrides, -}); +): VotesOnchainInsert => { + const n = txCounter++; + return { + id: `vote-${n}`, + txHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: TEST_DAO, + voterAccountId: VOTER_A, + proposalId: "proposal-1", + support: "1", + votingPower: 1000n, + reason: null, + timestamp: 1700000000n, + ...overrides, + }; +}; const createAccountPowerRow = ( overrides: Partial = {}, ): AccountPowerInsert => ({ + id: "test-id", accountId: VOTER_A, daoId: TEST_DAO, votingPower: 1000n, @@ -77,17 +82,21 @@ const createAccountPowerRow = ( const createHistoryRow = ( overrides: Partial = {}, -): VotingPowerHistoryInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: TEST_DAO, - accountId: VOTER_A, - votingPower: 1000n, - delta: 200n, - deltaMod: 200n, - timestamp: 1700000000n, - logIndex: 0, - ...overrides, -}); +): VotingPowerHistoryInsert => { + const n = txCounter++; + return { + id: `h-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: TEST_DAO, + accountId: VOTER_A, + votingPower: 1000n, + delta: 200n, + deltaMod: 200n, + timestamp: 1700000000n, + logIndex: 0, + ...overrides, + }; +}; describe("VotesRepository", () => { let client: PGlite; diff --git a/apps/api/src/repositories/voting-power/aave.ts b/apps/api/src/repositories/voting-power/aave.ts index eba94ea7b..33b184472 100644 --- a/apps/api/src/repositories/voting-power/aave.ts +++ b/apps/api/src/repositories/voting-power/aave.ts @@ -124,17 +124,16 @@ export class AAVEVotingPowerRepository { .offset(skip); return result.map((row) => ({ - ...row.voting_power_history, + ...row.VotingPowerHistory, delegations: - row.transfers && - row.transfers?.logIndex > (row.delegations?.logIndex || 0) + row.Transfer && row.Transfer?.logIndex > (row.Delegation?.logIndex || 0) ? null - : row.delegations, + : row.Delegation, transfers: - row.delegations && - row.delegations?.logIndex > (row.transfers?.logIndex || 0) + row.Delegation && + row.Delegation?.logIndex > (row.Transfer?.logIndex || 0) ? null - : row.transfers, + : row.Transfer, })); } @@ -226,6 +225,7 @@ export class AAVEVotingPowerRepository { const items = await this.db .select({ + id: accountPower.id, accountId: allAccountIds.accountId, daoId: accountPower.daoId, votingPower: combinedPowerSql, @@ -287,6 +287,7 @@ export class AAVEVotingPowerRepository { return { items: items.map((row) => ({ ...row, + id: row.id ?? "", daoId: row.daoId ?? "", votesCount: row.votesCount ?? 0, proposalsCount: row.proposalsCount ?? 0, @@ -341,6 +342,7 @@ export class AAVEVotingPowerRepository { const [result] = await this.db .select({ + id: accountPower.id, accountId: accountPower.accountId, daoId: accountPower.daoId, votingPower: combinedPowerSql, @@ -378,6 +380,7 @@ export class AAVEVotingPowerRepository { percentageChange: result.percentageChange, } : { + id: "", accountId: accountId, votingPower: 0n, delegationsCount: 0, diff --git a/apps/api/src/repositories/voting-power/aave.unit.test.ts b/apps/api/src/repositories/voting-power/aave.unit.test.ts index 3fcc21578..fc619b4b6 100644 --- a/apps/api/src/repositories/voting-power/aave.unit.test.ts +++ b/apps/api/src/repositories/voting-power/aave.unit.test.ts @@ -33,6 +33,7 @@ let txCounter = 0; const createAccountPowerRow = ( overrides: Partial = {}, ): AccountPowerInsert => ({ + id: "test-id", accountId: TEST_ACCOUNT_1, daoId: TEST_DAO, votingPower: 1000n, @@ -45,21 +46,26 @@ const createAccountPowerRow = ( const createHistoryRow = ( overrides: Partial = {}, -): VotingPowerHistoryInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: TEST_DAO, - accountId: TEST_ACCOUNT_1, - votingPower: 1000n, - delta: 200n, - deltaMod: 200n, - timestamp: 1700000000n, - logIndex: 10, - ...overrides, -}); +): VotingPowerHistoryInsert => { + const n = txCounter++; + return { + id: `h-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: TEST_DAO, + accountId: TEST_ACCOUNT_1, + votingPower: 1000n, + delta: 200n, + deltaMod: 200n, + timestamp: 1700000000n, + logIndex: 10, + ...overrides, + }; +}; const createDelegation = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: `d-${txCounter}`, transactionHash: `0x${txCounter.toString(16).padStart(64, "0")}`, daoId: TEST_DAO, delegateAccountId: TEST_ACCOUNT_1, @@ -72,12 +78,16 @@ const createDelegation = ( const createAccountBalance = ( overrides: Partial = {}, -): AccountBalanceInsert => ({ - accountId: TEST_ACCOUNT_1, - tokenId: `token-${txCounter++}`, - balance: 500n, - ...overrides, -}); +): AccountBalanceInsert => { + const n = txCounter++; + return { + id: `ab-${n}`, + accountId: TEST_ACCOUNT_1, + tokenId: `token-${n}`, + balance: 500n, + ...overrides, + }; +}; describe("AAVEVotingPowerRepository", () => { let client: PGlite; diff --git a/apps/api/src/repositories/voting-power/general.ts b/apps/api/src/repositories/voting-power/general.ts index a87771c5e..42ea70f3b 100644 --- a/apps/api/src/repositories/voting-power/general.ts +++ b/apps/api/src/repositories/voting-power/general.ts @@ -124,17 +124,16 @@ export class VotingPowerRepository { .offset(skip); return result.map((row) => ({ - ...row.voting_power_history, + ...row.VotingPowerHistory, delegations: - row.transfers && - row.transfers?.logIndex > (row.delegations?.logIndex || 0) + row.Transfer && row.Transfer?.logIndex > (row.Delegation?.logIndex || 0) ? null - : row.delegations, + : row.Delegation, transfers: - row.delegations && - row.delegations?.logIndex > (row.transfers?.logIndex || 0) + row.Delegation && + row.Delegation?.logIndex > (row.Transfer?.logIndex || 0) ? null - : row.transfers, + : row.Transfer, })); } @@ -342,6 +341,7 @@ export class VotingPowerRepository { const items = await this.db .select({ + id: accountPower.id, accountId: accountPower.accountId, daoId: accountPower.daoId, votingPower: accountPower.votingPower, @@ -407,6 +407,7 @@ export class VotingPowerRepository { const [result] = await this.db .select({ + id: accountPower.id, accountId: accountPower.accountId, daoId: accountPower.daoId, votingPower: accountPower.votingPower, @@ -438,6 +439,7 @@ export class VotingPowerRepository { percentageChange: String(result.percentageChange ?? "0"), } : { + id: "", accountId: accountId, votingPower: 0n, delegationsCount: 0, diff --git a/apps/api/src/repositories/voting-power/nouns.ts b/apps/api/src/repositories/voting-power/nouns.ts index 10d946c7b..f1b97f8b9 100644 --- a/apps/api/src/repositories/voting-power/nouns.ts +++ b/apps/api/src/repositories/voting-power/nouns.ts @@ -100,17 +100,16 @@ export class NounsVotingPowerRepository { .offset(skip); return result.map((row) => ({ - ...row.voting_power_history, + ...row.VotingPowerHistory, delegations: - row.transfers && - row.transfers?.logIndex < (row.delegations?.logIndex || 0) + row.Transfer && row.Transfer?.logIndex < (row.Delegation?.logIndex || 0) ? null - : row.delegations, + : row.Delegation, transfers: - row.delegations && - row.delegations?.logIndex > (row.transfers?.logIndex || 0) + row.Delegation && + row.Delegation?.logIndex > (row.Transfer?.logIndex || 0) ? null - : row.transfers, + : row.Transfer, })); } } diff --git a/apps/api/src/repositories/voting-power/nouns.unit.test.ts b/apps/api/src/repositories/voting-power/nouns.unit.test.ts index 7de46e71d..88abca8fc 100644 --- a/apps/api/src/repositories/voting-power/nouns.unit.test.ts +++ b/apps/api/src/repositories/voting-power/nouns.unit.test.ts @@ -21,21 +21,26 @@ let txCounter = 0; const createHistoryRow = ( overrides: Partial = {}, -): VotingPowerHistoryInsert => ({ - transactionHash: `0x${(txCounter++).toString(16).padStart(64, "0")}`, - daoId: TEST_DAO, - accountId: TEST_ACCOUNT, - votingPower: 1000n, - delta: 200n, - deltaMod: 200n, - timestamp: 1700000000n, - logIndex: 10, - ...overrides, -}); +): VotingPowerHistoryInsert => { + const n = txCounter++; + return { + id: `h-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: TEST_DAO, + accountId: TEST_ACCOUNT, + votingPower: 1000n, + delta: 200n, + deltaMod: 200n, + timestamp: 1700000000n, + logIndex: 10, + ...overrides, + }; +}; const createDelegation = ( overrides: Partial = {}, ): DelegationInsert => ({ + id: `d-${txCounter}`, transactionHash: `0x${txCounter.toString(16).padStart(64, "0")}`, daoId: TEST_DAO, delegateAccountId: TEST_ACCOUNT, @@ -49,6 +54,7 @@ const createDelegation = ( const createTransfer = ( overrides: Partial = {}, ): TransferInsert => ({ + id: `tf-${txCounter}`, transactionHash: `0x${txCounter.toString(16).padStart(64, "0")}`, daoId: TEST_DAO, tokenId: "token-1", diff --git a/apps/api/src/repositories/voting-power/voting-power.repository.unit.test.ts b/apps/api/src/repositories/voting-power/voting-power.repository.unit.test.ts index 69e8214c5..eb66d9855 100644 --- a/apps/api/src/repositories/voting-power/voting-power.repository.unit.test.ts +++ b/apps/api/src/repositories/voting-power/voting-power.repository.unit.test.ts @@ -30,6 +30,7 @@ const TEST_DAO = "test-dao"; const createAccountPowerRow = ( overrides: Partial = {}, ): AccountPowerInsert => ({ + id: "test-id", accountId: TEST_ACCOUNT_1, daoId: TEST_DAO, votingPower: 1000n, @@ -43,6 +44,7 @@ const createAccountPowerRow = ( const createHistoryRow = ( overrides: Partial = {}, ): VotingPowerHistoryInsert => ({ + id: "test-id", transactionHash: "0xtx1", daoId: TEST_DAO, accountId: TEST_ACCOUNT_1, @@ -58,20 +60,25 @@ let vpTxCounter = 0; const createDelegationRow = ( overrides: Partial = {}, -): DelegationInsert => ({ - transactionHash: `0x${(vpTxCounter++).toString(16).padStart(64, "0")}`, - daoId: TEST_DAO, - delegateAccountId: TEST_ACCOUNT_1, - delegatorAccountId: TEST_ACCOUNT_2, - delegatedValue: 0n, - timestamp: 1700000000n, - logIndex: 5, - ...overrides, -}); +): DelegationInsert => { + const n = vpTxCounter++; + return { + id: `d-${n}`, + transactionHash: `0x${n.toString(16).padStart(64, "0")}`, + daoId: TEST_DAO, + delegateAccountId: TEST_ACCOUNT_1, + delegatorAccountId: TEST_ACCOUNT_2, + delegatedValue: 0n, + timestamp: 1700000000n, + logIndex: 5, + ...overrides, + }; +}; const createTransferRow = ( overrides: Partial = {}, ): TransferInsert => ({ + id: `tf-${vpTxCounter}`, transactionHash: `0x${vpTxCounter.toString(16).padStart(64, "0")}`, daoId: TEST_DAO, tokenId: "token-1", diff --git a/apps/api/src/services/account-balance/historical.unit.test.ts b/apps/api/src/services/account-balance/historical.unit.test.ts index 8e9354c94..234b87fe5 100644 --- a/apps/api/src/services/account-balance/historical.unit.test.ts +++ b/apps/api/src/services/account-balance/historical.unit.test.ts @@ -8,6 +8,7 @@ const MOCK_ACCOUNT = getAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); const createMockHistoricalBalance = ( overrides: Partial = {}, ): DBHistoricalBalanceWithRelations => ({ + id: "test-id", transactionHash: "0xabc", daoId: "UNI", accountId: MOCK_ACCOUNT, @@ -17,6 +18,7 @@ const createMockHistoricalBalance = ( timestamp: 1700000000n, logIndex: 0, transfer: { + id: "test-id", transactionHash: "0xabc", daoId: "UNI", tokenId: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", diff --git a/apps/api/src/services/account-balance/variations.unit.test.ts b/apps/api/src/services/account-balance/variations.unit.test.ts index 878d8c677..6bc9dfa30 100644 --- a/apps/api/src/services/account-balance/variations.unit.test.ts +++ b/apps/api/src/services/account-balance/variations.unit.test.ts @@ -21,6 +21,7 @@ const createMockVariation = ( const createMockBalance = ( overrides: Partial = {}, ): DBAccountBalance => ({ + id: "test-id", accountId: ADDR_A, tokenId: MOCK_TOKEN, balance: 500n, diff --git a/apps/api/src/services/delegation-percentage/delegation-percentage.unit.test.ts b/apps/api/src/services/delegation-percentage/delegation-percentage.unit.test.ts index 0eec8797d..239fca587 100644 --- a/apps/api/src/services/delegation-percentage/delegation-percentage.unit.test.ts +++ b/apps/api/src/services/delegation-percentage/delegation-percentage.unit.test.ts @@ -18,6 +18,7 @@ import { const createMockRow = ( overrides: Partial = {}, ): DBTokenMetric => ({ + id: "test-id", date: 0n, daoId: "uniswap", tokenId: "uni", diff --git a/apps/api/src/services/delegations/current.unit.test.ts b/apps/api/src/services/delegations/current.unit.test.ts index 56230594c..7597d8ac3 100644 --- a/apps/api/src/services/delegations/current.unit.test.ts +++ b/apps/api/src/services/delegations/current.unit.test.ts @@ -8,6 +8,7 @@ import { DelegationsService } from "./current"; const createMockDelegation = ( overrides: Partial = {}, ): DBDelegation => ({ + id: "test-id", transactionHash: "0xabc123", daoId: "uni", delegateAccountId: "0x1234567890123456789012345678901234567890", diff --git a/apps/api/src/services/delegations/historical.unit.test.ts b/apps/api/src/services/delegations/historical.unit.test.ts index 92124812b..ac3a3f3c1 100644 --- a/apps/api/src/services/delegations/historical.unit.test.ts +++ b/apps/api/src/services/delegations/historical.unit.test.ts @@ -11,6 +11,7 @@ import { const createMockDelegation = ( overrides: Partial = {}, ): DBDelegation => ({ + id: "test-id", transactionHash: "0xabc123", daoId: "uni", delegateAccountId: "0x1234567890123456789012345678901234567890" as Address, diff --git a/apps/api/src/services/feed/feed.unit.test.ts b/apps/api/src/services/feed/feed.unit.test.ts index 19c7ebbf5..25a8f88b4 100644 --- a/apps/api/src/services/feed/feed.unit.test.ts +++ b/apps/api/src/services/feed/feed.unit.test.ts @@ -9,16 +9,18 @@ import { DBFeedEvent, FeedRequest } from "@/mappers"; import { FeedService } from "."; const createFeedEvent = ( - overrides: Partial = {}, -): DBFeedEvent => ({ - txHash: "0xabc123", - logIndex: 0, - type: "VOTE", - value: parseEther("100000"), - timestamp: 1700000000, - metadata: null, - ...overrides, -}); + overrides: Partial & { type: string }> = {}, +): DBFeedEvent => + ({ + id: "test-id", + txHash: "0xabc123", + logIndex: 0, + type: "VOTE", + value: parseEther("100000"), + timestamp: 1700000000, + metadata: null, + ...overrides, + }) as DBFeedEvent; const createRequest = (overrides: Partial = {}): FeedRequest => ({ skip: 0, @@ -37,7 +39,7 @@ class SimpleFeedRepository { valueThresholds: Partial>, ) { const filtered = this.items.filter((e) => { - if (e.type === "DELEGATION_VOTES_CHANGED") return false; + if ((e.type as string) === "DELEGATION_VOTES_CHANGED") return false; const threshold = valueThresholds[e.type]; return threshold === undefined || e.value >= threshold; }); diff --git a/apps/api/src/services/transactions/index.unit.test.ts b/apps/api/src/services/transactions/index.unit.test.ts index 6e9237eae..91af6e88e 100644 --- a/apps/api/src/services/transactions/index.unit.test.ts +++ b/apps/api/src/services/transactions/index.unit.test.ts @@ -16,6 +16,7 @@ function createStubRepo(items: DBTransaction[] = [], count = 0) { } const makeDBTransaction = (overrides = {}): DBTransaction => ({ + id: "test-id", transactionHash: "0xabc123", fromAddress: "0x1234567890123456789012345678901234567890", toAddress: "0x0987654321098765432109876543210987654321", diff --git a/apps/api/src/services/transfers/index.unit.test.ts b/apps/api/src/services/transfers/index.unit.test.ts index f597fe315..2e6282aed 100644 --- a/apps/api/src/services/transfers/index.unit.test.ts +++ b/apps/api/src/services/transfers/index.unit.test.ts @@ -11,6 +11,7 @@ function createStubRepo(items: DBTransfer[] = [], count = 0) { } const makeDBTransfer = (overrides = {}): DBTransfer => ({ + id: "test-id", transactionHash: "0xdeadbeef", daoId: "UNI", tokenId: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", diff --git a/apps/api/src/services/votes/onchainVotes.unit.test.ts b/apps/api/src/services/votes/onchainVotes.unit.test.ts index 3f344d37c..dd7ef7b95 100644 --- a/apps/api/src/services/votes/onchainVotes.unit.test.ts +++ b/apps/api/src/services/votes/onchainVotes.unit.test.ts @@ -17,6 +17,7 @@ type VoteWithTitle = DBVote & { const createMockDBVote = ( overrides: Partial = {}, ): VoteWithTitle => ({ + id: "test-id", voterAccountId: VOTER_A, txHash: TX_HASH, transactionHash: TX_HASH, diff --git a/apps/api/src/services/voting-power/aave.unit.test.ts b/apps/api/src/services/voting-power/aave.unit.test.ts index 64a829e1a..e2658177b 100644 --- a/apps/api/src/services/voting-power/aave.unit.test.ts +++ b/apps/api/src/services/voting-power/aave.unit.test.ts @@ -12,6 +12,7 @@ const MOCK_ADDRESS = getAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"); const createMockHistoricalVP = ( overrides: Partial = {}, ): DBHistoricalVotingPowerWithRelations => ({ + id: "test-id", transactionHash: "0xabc", daoId: "AAVE", accountId: MOCK_ADDRESS, @@ -28,6 +29,7 @@ const createMockHistoricalVP = ( const createMockAccountPower = ( overrides: Partial = {}, ): DBAccountPowerWithVariation => ({ + id: "test-id", accountId: MOCK_ADDRESS, daoId: "AAVE", votingPower: 1000n, diff --git a/apps/api/src/services/voting-power/index.unit.test.ts b/apps/api/src/services/voting-power/index.unit.test.ts index 0ea5698d7..f6695adf2 100644 --- a/apps/api/src/services/voting-power/index.unit.test.ts +++ b/apps/api/src/services/voting-power/index.unit.test.ts @@ -38,6 +38,7 @@ function createStubVotingPowersRepo( const makeDBHistoricalVotingPower = ( overrides = {}, ): DBHistoricalVotingPowerWithRelations => ({ + id: "test-id", transactionHash: "0xabc", daoId: "UNI", accountId: "0x1111111111111111111111111111111111111111" as Address, @@ -252,6 +253,7 @@ describe("VotingPowerService (index)", () => { describe("getVotingPowersByAccountId", () => { it("returns the voting power for the given account", async () => { const repoResult: DBAccountPowerWithVariation = { + id: "test-id", accountId: addr1, daoId: "UNI", votingPower: 1000n, diff --git a/apps/api/src/services/voting-power/voting-power.unit.test.ts b/apps/api/src/services/voting-power/voting-power.unit.test.ts index 4a7a7c94d..0a12beafd 100644 --- a/apps/api/src/services/voting-power/voting-power.unit.test.ts +++ b/apps/api/src/services/voting-power/voting-power.unit.test.ts @@ -16,6 +16,7 @@ function createStubVariationRepo(variations: DBVotingPowerVariation[] = []) { } const makeDBAccountPower = (overrides = {}): DBAccountPower => ({ + id: "test-id", accountId: "0x1111111111111111111111111111111111111111" as Address, daoId: "UNI", votingPower: 1000n, diff --git a/apps/hypersync-indexer/.gitignore b/apps/hypersync-indexer/.gitignore new file mode 100644 index 000000000..867e74673 --- /dev/null +++ b/apps/hypersync-indexer/.gitignore @@ -0,0 +1,22 @@ +# Dependencies +/node_modules + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# Misc +.DS_Store + +# Env files +.env + +# HyperIndex codegen output (regenerated by `envio codegen`) +/generated/* +!/generated/package.json + +# HyperIndex runtime state +persisted_state.envio.json +logs/ diff --git a/apps/hypersync-indexer/abis/ens-governor.json b/apps/hypersync-indexer/abis/ens-governor.json new file mode 100644 index 000000000..b9ef26c6f --- /dev/null +++ b/apps/hypersync-indexer/abis/ens-governor.json @@ -0,0 +1,787 @@ +[ + { + "inputs": [ + { + "internalType": "contract ERC20Votes", + "name": "_token", + "type": "address" + }, + { + "internalType": "contract TimelockController", + "name": "_timelock", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "ProposalCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "string[]", + "name": "signatures", + "type": "string[]" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "startBlock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "endBlock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "name": "ProposalCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "ProposalExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "eta", + "type": "uint256" + } + ], + "name": "ProposalQueued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldQuorumNumerator", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newQuorumNumerator", + "type": "uint256" + } + ], + "name": "QuorumNumeratorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldTimelock", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newTimelock", + "type": "address" + } + ], + "name": "TimelockChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "weight", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "VoteCast", + "type": "event" + }, + { + "inputs": [], + "name": "BALLOT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "COUNTING_MODE", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + } + ], + "name": "castVote", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "castVoteBySig", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "castVoteWithReason", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "execute", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasVoted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "hashProposal", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalDeadline", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalEta", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalSnapshot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proposalThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "againstVotes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forVotes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "abstainVotes", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "name": "propose", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "queue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "quorum", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumDenominator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "quorumNumerator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "state", + "outputs": [ + { + "internalType": "enum IGovernor.ProposalState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "timelock", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract ERC20Votes", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newQuorumNumerator", + "type": "uint256" + } + ], + "name": "updateQuorumNumerator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TimelockController", + "name": "newTimelock", + "type": "address" + } + ], + "name": "updateTimelock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "votingDelay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "votingPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/apps/hypersync-indexer/abis/ens-token.json b/apps/hypersync-indexer/abis/ens-token.json new file mode 100644 index 000000000..0b7ab9802 --- /dev/null +++ b/apps/hypersync-indexer/abis/ens-token.json @@ -0,0 +1,840 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "freeSupply", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "airdropSupply", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_claimPeriodEnds", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "claimant", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Claim", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "fromDelegate", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "toDelegate", + "type": "address" + } + ], + "name": "DelegateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "previousBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newBalance", + "type": "uint256" + } + ], + "name": "DelegateVotesChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + } + ], + "name": "MerkleRootChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint32", + "name": "pos", + "type": "uint32" + } + ], + "name": "checkpoints", + "outputs": [ + { + "components": [ + { + "internalType": "uint32", + "name": "fromBlock", + "type": "uint32" + }, + { + "internalType": "uint224", + "name": "votes", + "type": "uint224" + } + ], + "internalType": "struct ERC20Votes.Checkpoint", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimPeriodEnds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "internalType": "bytes32[]", + "name": "merkleProof", + "type": "bytes32[]" + } + ], + "name": "claimTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatee", + "type": "address" + } + ], + "name": "delegate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "delegatee", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "delegateBySig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "delegates", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getPastTotalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getPastVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "isClaimed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "merkleRoot", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minimumMintInterval", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dest", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "mintCap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextMint", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "numCheckpoints", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_merkleRoot", + "type": "bytes32" + } + ], + "name": "setMerkleRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dest", + "type": "address" + } + ], + "name": "sweep", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/apps/hypersync-indexer/config.yaml b/apps/hypersync-indexer/config.yaml new file mode 100644 index 000000000..a007ff9d4 --- /dev/null +++ b/apps/hypersync-indexer/config.yaml @@ -0,0 +1,37 @@ +name: anticapture-indexer +description: Anticapture DAO governance indexer +field_selection: + transaction_fields: + - hash + - to + - from + +contracts: + - name: ENSToken + abi_file_path: abis/ens-token.json + handler: src/eventHandlers/ENSToken.ts + events: + - event: "Transfer(address indexed from, address indexed to, uint256 value)" + - event: "DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate)" + - event: "DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance)" + - name: ENSGovernor + abi_file_path: abis/ens-governor.json + handler: src/eventHandlers/ENSGovernor.ts + events: + - event: "ProposalCreated(uint256 proposalId, address proposer, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 startBlock, uint256 endBlock, string description)" + - event: "VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason)" + - event: "ProposalCanceled(uint256 proposalId)" + - event: "ProposalExecuted(uint256 proposalId)" + - event: "ProposalQueued(uint256 proposalId, uint256 eta)" + +networks: + - id: 1 + hypersync_config: + url: https://eth.hypersync.xyz + start_block: 9380410 + contracts: + - name: ENSToken + address: "0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72" + - name: ENSGovernor + address: "0x323a76393544d5ecca80cd6ef2a560c6a395b7e3" + start_block: 13533772 diff --git a/apps/hypersync-indexer/generated/package.json b/apps/hypersync-indexer/generated/package.json new file mode 100644 index 000000000..c6d1606a3 --- /dev/null +++ b/apps/hypersync-indexer/generated/package.json @@ -0,0 +1,49 @@ +{ + "name": "generated", + "version": "0.1.0", + "main": "index.js", + "types": "index.d.ts", + "private": true, + "scripts": { + "clean": "rescript clean", + "build": "rescript", + "watch": "rescript -w", + "format": "rescript format -all", + "db-up": "node -e 'require(`./src/db/Migrations.res.js`).runUpMigrations(true)'", + "db-down": "node -e 'require(`./src/db/Migrations.res.js`).runDownMigrations(true)'", + "db-setup": "node -e 'require(`./src/db/Migrations.res.js`).runUpMigrations(true, true)'", + "print-benchmark-summary": "node -e 'require(`./src/Benchmark.res.js`).Summary.printSummary()'", + "start": "node_modules/.bin/ts-node src/Index.res.js" + }, + "keywords": [ + "ReScript" + ], + "engines": { + "node": ">=18.0.0" + }, + "author": "", + "license": "MIT", + "dependencies": { + "ts-node": "10.9.1", + "@rescript/react": "0.12.1", + "bignumber.js": "9.1.2", + "date-fns": "3.3.1", + "dotenv": "16.4.5", + "ethers": "6.8.0", + "express": "4.19.2", + "ink": "3.2.0", + "ink-big-text": "1.2.0", + "ink-spinner": "4.0.3", + "js-sdsl": "4.4.2", + "pino": "8.16.1", + "postgres": "3.4.1", + "react": "18.2.0", + "rescript": "11.1.3", + "rescript-envsafe": "5.0.0", + "rescript-schema": "9.3.0", + "envio": "2.32.12", + "viem": "2.21.0", + "yargs": "17.7.2", + "prom-client": "15.0.0" + } +} diff --git a/apps/hypersync-indexer/package.json b/apps/hypersync-indexer/package.json new file mode 100644 index 000000000..305bc05b5 --- /dev/null +++ b/apps/hypersync-indexer/package.json @@ -0,0 +1,30 @@ +{ + "name": "@anticapture/hypersync-indexer", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "envio dev", + "start": "envio start", + "codegen": "envio codegen", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "typecheck": "[ -d generated ] && tsc --noEmit || echo 'Skipping typecheck: run envio codegen first'", + "clean": "rm -rf node_modules generated *.tsbuildinfo" + }, + "dependencies": { + "viem": "^2.37.11" + }, + "devDependencies": { + "@types/node": "^20.16.5", + "dotenv": "^16.5.0", + "envio": "2.31.1", + "eslint": "^9", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "prettier": "^3.5.3", + "typescript": "^5.8.3" + }, + "engines": { + "node": ">=18.14" + } +} diff --git a/apps/hypersync-indexer/schema.graphql b/apps/hypersync-indexer/schema.graphql new file mode 100644 index 000000000..c2f10829b --- /dev/null +++ b/apps/hypersync-indexer/schema.graphql @@ -0,0 +1,202 @@ +enum MetricType { + total + delegated + cex + dex + lending + circulating + treasury + non_circulating +} + +enum EventType { + VOTE + PROPOSAL + PROPOSAL_EXTENDED + DELEGATION + DELEGATION_VOTES_CHANGED + TRANSFER +} + +type Token { + id: ID! + name: String + decimals: Int! + totalSupply: BigInt! + delegatedSupply: BigInt! + cexSupply: BigInt! + dexSupply: BigInt! + lendingSupply: BigInt! + circulatingSupply: BigInt! + treasury: BigInt! + nonCirculatingSupply: BigInt! +} + +type Account { + id: ID! +} + +# Composite PK: "{accountId}-{tokenId}" +type AccountBalance { + id: ID! + accountId: String! @index + tokenId: String! @index + balance: BigInt! + delegate: String! +} + +# Composite PK: "{accountId}" +type AccountPower { + id: ID! + accountId: String! @index + daoId: String! + votingPower: BigInt! + votesCount: Int! + proposalsCount: Int! + delegationsCount: Int! + lastVoteTimestamp: BigInt! +} + +# Composite PK: "{txHash}-{accountId}-{logIndex}" +type VotingPowerHistory { + id: ID! + transactionHash: String! @index + daoId: String! + accountId: String! @index + votingPower: BigInt! + delta: BigInt! + deltaMod: BigInt! + timestamp: BigInt! + logIndex: Int! +} + +# Composite PK: "{txHash}-{accountId}-{logIndex}" +type BalanceHistory { + id: ID! + transactionHash: String! @index + daoId: String! + accountId: String! @index + balance: BigInt! + delta: BigInt! + deltaMod: BigInt! + timestamp: BigInt! + logIndex: Int! +} + +# Composite PK: "{txHash}-{delegatorId}-{delegateId}" +type Delegation { + id: ID! + transactionHash: String! @index + daoId: String! + delegateAccountId: String! @index + delegatorAccountId: String! @index + delegatedValue: BigInt! + previousDelegate: String + timestamp: BigInt! @index + logIndex: Int! + isCex: Boolean! + isDex: Boolean! + isLending: Boolean! + isTotal: Boolean! + delegationType: Int +} + +# Composite PK: "{txHash}-{fromId}-{toId}" +type Transfer { + id: ID! + transactionHash: String! @index + daoId: String! + tokenId: String! @index + amount: BigInt! @index + fromAccountId: String! @index + toAccountId: String! @index + timestamp: BigInt! @index + logIndex: Int! + isCex: Boolean! + isDex: Boolean! + isLending: Boolean! + isTotal: Boolean! +} + +# Composite PK: "{voterId}-{proposalId}" +type VoteOnchain { + id: ID! + txHash: String! + daoId: String! + voterAccountId: String! @index + proposalId: String! @index + support: String! + votingPower: BigInt! + reason: String + timestamp: BigInt! +} + +type ProposalOnchain { + id: ID! + txHash: String! + daoId: String! + proposerAccountId: String! @index + targets: Json! + values: Json! + signatures: Json! + calldatas: Json! + startBlock: Int! + endBlock: Int! + title: String! + description: String! + timestamp: BigInt! + logIndex: Int! + endTimestamp: BigInt! + status: String! + forVotes: BigInt! + againstVotes: BigInt! + abstainVotes: BigInt! + proposalType: Int +} + +# Composite PK: "{date}-{tokenId}-{metricType}" +type DaoMetricsDayBucket { + id: ID! + date: BigInt! + daoId: String! + tokenId: String! @index + metricType: MetricType! + open: BigInt! + close: BigInt! + low: BigInt! + high: BigInt! + average: BigInt! + volume: BigInt! + count: Int! + lastUpdate: BigInt! +} + +type Transaction { + id: ID! + transactionHash: String! + fromAddress: String + toAddress: String + isCex: Boolean! + isDex: Boolean! + isLending: Boolean! + isTotal: Boolean! + timestamp: BigInt! +} + + +type TokenPrice { + id: ID! + price: BigInt! + timestamp: BigInt! +} + +# Composite PK: "{txHash}-{logIndex}" +type FeedEvent { + id: ID! + txHash: String! @index + logIndex: Int! + type: EventType! + value: BigInt! @index + timestamp: BigInt! @index + metadata: Json +} diff --git a/apps/hypersync-indexer/src/eventHandlers/ENSGovernor.ts b/apps/hypersync-indexer/src/eventHandlers/ENSGovernor.ts new file mode 100644 index 000000000..3ecdec77f --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/ENSGovernor.ts @@ -0,0 +1,65 @@ +import { ENSGovernor } from "../../generated/index.js"; +import type { Hex, Address } from "viem"; + +import { ProposalStatus, CONTRACT_ADDRESSES } from "../lib/constants.ts"; +import { DaoIdEnum } from "../lib/enums.ts"; + +import { proposalCreated, updateProposalStatus, voteCast } from "./voting.ts"; + +const DAO_ID = DaoIdEnum.ENS; +const BLOCK_TIME = CONTRACT_ADDRESSES[DAO_ID].blockTime; + +ENSGovernor.VoteCast.handler(async ({ event, context }) => { + await voteCast(context, DAO_ID, { + proposalId: event.params.proposalId.toString(), + voter: event.params.voter as Address, + reason: event.params.reason, + support: Number(event.params.support), + timestamp: BigInt(event.block.timestamp), + txHash: event.transaction.hash as Hex, + votingPower: event.params.weight, + logIndex: event.logIndex, + }); +}); + +ENSGovernor.ProposalCreated.handler(async ({ event, context }) => { + await proposalCreated(context, DAO_ID, BLOCK_TIME, { + proposalId: event.params.proposalId.toString(), + txHash: event.transaction.hash as Hex, + proposer: event.params.proposer as Address, + targets: [...event.params.targets] as Address[], + values: [...event.params.values], + signatures: [...event.params.signatures], + calldatas: [...event.params.calldatas] as Hex[], + startBlock: event.params.startBlock.toString(), + endBlock: event.params.endBlock.toString(), + description: event.params.description, + timestamp: BigInt(event.block.timestamp), + blockNumber: BigInt(event.block.number as number), + logIndex: event.logIndex, + }); +}); + +ENSGovernor.ProposalCanceled.handler(async ({ event, context }) => { + await updateProposalStatus( + context, + event.params.proposalId.toString(), + ProposalStatus.CANCELED, + ); +}); + +ENSGovernor.ProposalExecuted.handler(async ({ event, context }) => { + await updateProposalStatus( + context, + event.params.proposalId.toString(), + ProposalStatus.EXECUTED, + ); +}); + +ENSGovernor.ProposalQueued.handler(async ({ event, context }) => { + await updateProposalStatus( + context, + event.params.proposalId.toString(), + ProposalStatus.QUEUED, + ); +}); diff --git a/apps/hypersync-indexer/src/eventHandlers/ENSToken.ts b/apps/hypersync-indexer/src/eventHandlers/ENSToken.ts new file mode 100644 index 000000000..674fbebc8 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/ENSToken.ts @@ -0,0 +1,259 @@ +import { ENSToken } from "../../generated/index.js"; +import type { handlerContext } from "../../generated/index.js"; +import type { Address, Hex } from "viem"; +import { getAddress } from "viem"; + +import { + CONTRACT_ADDRESSES, + MetricTypesEnum, + BurningAddresses, + CEXAddresses, + DEXAddresses, + LendingAddresses, + TreasuryAddresses, + NonCirculatingAddresses, +} from "../lib/constants.ts"; +import { DaoIdEnum } from "../lib/enums.ts"; + +import { delegateChanged, delegatedVotesChanged } from "./delegation.ts"; +import { tokenTransfer } from "./transfer.ts"; +import { createAddressSet, handleTransaction } from "./shared.ts"; +import { + updateDelegatedSupply, + updateCirculatingSupply, + updateSupplyMetric, + updateTotalSupply, +} from "./metrics/index.ts"; + +const DAO_ID = DaoIdEnum.ENS; +const ENS_CONTRACTS = CONTRACT_ADDRESSES[DAO_ID]; +const TOKEN_ADDRESS = getAddress(ENS_CONTRACTS.token.address); +const TOKEN_DECIMALS = ENS_CONTRACTS.token.decimals; + +const cexAddressSet = createAddressSet(Object.values(CEXAddresses[DAO_ID])); +const dexAddressSet = createAddressSet(Object.values(DEXAddresses[DAO_ID])); +const lendingAddressSet = createAddressSet( + Object.values(LendingAddresses[DAO_ID]), +); +const burningAddressSet = createAddressSet( + Object.values(BurningAddresses[DAO_ID]), +); +const treasuryAddressSet = createAddressSet( + Object.values(TreasuryAddresses[DAO_ID]), +); +const nonCirculatingAddressSet = createAddressSet( + Object.values(NonCirculatingAddresses[DAO_ID]), +); +const delegationAddressSets = { + cex: cexAddressSet, + dex: dexAddressSet, + lending: lendingAddressSet, + burning: burningAddressSet, +}; + +// Lazy token initialization — replaces Ponder's setup event +const ensureTokenExists = async (context: handlerContext) => { + await context.Token.getOrCreate({ + id: TOKEN_ADDRESS, + name: DAO_ID, + decimals: TOKEN_DECIMALS, + totalSupply: 0n, + delegatedSupply: 0n, + cexSupply: 0n, + dexSupply: 0n, + lendingSupply: 0n, + circulatingSupply: 0n, + treasury: 0n, + nonCirculatingSupply: 0n, + }); +}; + +ENSToken.Transfer.handler(async ({ event, context }) => { + const from = event.params.from as Address; + const to = event.params.to as Address; + const { value } = event.params; + const timestamp = BigInt(event.block.timestamp); + + await ensureTokenExists(context); + + await tokenTransfer( + context, + DAO_ID, + { + from, + to, + value, + token: TOKEN_ADDRESS, + transactionHash: event.transaction.hash as Hex, + timestamp, + logIndex: event.logIndex, + }, + { + cex: cexAddressSet, + dex: dexAddressSet, + lending: lendingAddressSet, + burning: burningAddressSet, + }, + ); + + await updateSupplyMetric( + context, + "lendingSupply", + lendingAddressSet, + MetricTypesEnum.LENDING_SUPPLY, + from, + to, + value, + DAO_ID, + TOKEN_ADDRESS, + timestamp, + ); + await updateSupplyMetric( + context, + "cexSupply", + cexAddressSet, + MetricTypesEnum.CEX_SUPPLY, + from, + to, + value, + DAO_ID, + TOKEN_ADDRESS, + timestamp, + ); + await updateSupplyMetric( + context, + "dexSupply", + dexAddressSet, + MetricTypesEnum.DEX_SUPPLY, + from, + to, + value, + DAO_ID, + TOKEN_ADDRESS, + timestamp, + ); + await updateSupplyMetric( + context, + "treasury", + treasuryAddressSet, + MetricTypesEnum.TREASURY, + from, + to, + value, + DAO_ID, + TOKEN_ADDRESS, + timestamp, + ); + await updateSupplyMetric( + context, + "nonCirculatingSupply", + nonCirculatingAddressSet, + MetricTypesEnum.NON_CIRCULATING_SUPPLY, + from, + to, + value, + DAO_ID, + TOKEN_ADDRESS, + timestamp, + ); + await updateTotalSupply( + context, + burningAddressSet, + MetricTypesEnum.TOTAL_SUPPLY, + from, + to, + value, + DAO_ID, + TOKEN_ADDRESS, + timestamp, + ); + await updateCirculatingSupply(context, DAO_ID, TOKEN_ADDRESS, timestamp); + + if (!event.transaction.to) return; + + await handleTransaction( + context, + event.transaction.hash as Hex, + from, + event.transaction.to as Address, + timestamp, + [from, to], + { + cex: cexAddressSet, + dex: dexAddressSet, + lending: lendingAddressSet, + burning: burningAddressSet, + }, + ); +}); + +ENSToken.DelegateChanged.handler(async ({ event, context }) => { + const delegator = event.params.delegator as Address; + const fromDelegate = event.params.fromDelegate as Address; + const toDelegate = event.params.toDelegate as Address; + const timestamp = BigInt(event.block.timestamp); + + await ensureTokenExists(context); + + await delegateChanged( + context, + DAO_ID, + { + delegator, + delegate: toDelegate, + tokenId: TOKEN_ADDRESS, + previousDelegate: fromDelegate, + txHash: event.transaction.hash as Hex, + timestamp, + logIndex: event.logIndex, + }, + delegationAddressSets, + ); + + if (!event.transaction.to) return; + + await handleTransaction( + context, + event.transaction.hash as Hex, + delegator, + event.transaction.to as Address, + timestamp, + [delegator, toDelegate], + ); +}); + +ENSToken.DelegateVotesChanged.handler(async ({ event, context }) => { + const delegate = event.params.delegate as Address; + const { previousBalance, newBalance } = event.params; + const timestamp = BigInt(event.block.timestamp); + + await ensureTokenExists(context); + + await delegatedVotesChanged(context, DAO_ID, { + delegate, + txHash: event.transaction.hash as Hex, + newBalance, + oldBalance: previousBalance, + timestamp, + logIndex: event.logIndex, + }); + + await updateDelegatedSupply( + context, + DAO_ID, + TOKEN_ADDRESS, + newBalance - previousBalance, + timestamp, + ); + + if (!event.transaction.to) return; + + await handleTransaction( + context, + event.transaction.hash as Hex, + delegate, + event.transaction.to as Address, + timestamp, + [delegate], + ); +}); diff --git a/apps/hypersync-indexer/src/eventHandlers/delegation.ts b/apps/hypersync-indexer/src/eventHandlers/delegation.ts new file mode 100644 index 000000000..142c1b215 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/delegation.ts @@ -0,0 +1,212 @@ +import type { handlerContext } from "../../generated/index.js"; +import type { EventType_t } from "../../generated/src/db/Enums.gen.ts"; +import type { Address, Hex } from "viem"; +import { getAddress, zeroAddress } from "viem"; + +import { + BurningAddresses, + CEXAddresses, + DEXAddresses, + LendingAddresses, +} from "../lib/constants.ts"; +import { DaoIdEnum } from "../lib/enums.ts"; + +import { + createAddressSet, + ensureAccountExists, + ensureAccountsExist, +} from "./shared.ts"; + +type DelegationAddressSets = { + cex: ReadonlySet
; + dex: ReadonlySet
; + lending: ReadonlySet
; + burning: ReadonlySet
; +}; + +export const delegateChanged = async ( + context: handlerContext, + daoId: DaoIdEnum, + args: { + delegator: Address; + delegate: Address; + tokenId: Address; + previousDelegate: Address; + txHash: Hex; + timestamp: bigint; + logIndex: number; + delegatorBalance?: bigint; + }, + addressSets?: DelegationAddressSets, +) => { + const { + delegator, + delegate, + tokenId, + txHash, + previousDelegate, + timestamp, + logIndex, + delegatorBalance: _delegatorBalance, + } = args; + + const normalizedDelegator = getAddress(delegator); + const normalizedDelegate = getAddress(delegate); + + await ensureAccountsExist(context, [delegator, delegate]); + + const delegatorBalanceId = `${normalizedDelegator}-${getAddress(tokenId)}`; + const storedBalance = _delegatorBalance + ? { balance: _delegatorBalance } + : await context.AccountBalance.get(delegatorBalanceId); + const delegatedValue = storedBalance?.balance ?? 0n; + + const { cex, dex, lending, burning } = addressSets ?? { + cex: createAddressSet(Object.values(CEXAddresses[daoId] || {})), + dex: createAddressSet(Object.values(DEXAddresses[daoId] || {})), + lending: createAddressSet(Object.values(LendingAddresses[daoId] || {})), + burning: createAddressSet(Object.values(BurningAddresses[daoId] || {})), + }; + + const isCex = cex.has(normalizedDelegator) || cex.has(normalizedDelegate); + const isDex = dex.has(normalizedDelegator) || dex.has(normalizedDelegate); + const isLending = + lending.has(normalizedDelegator) || lending.has(normalizedDelegate); + const isTotal = + burning.has(normalizedDelegator) || burning.has(normalizedDelegate); + + const delegationId = `${txHash}-${normalizedDelegator}-${normalizedDelegate}`; + const existingDelegation = await context.Delegation.get(delegationId); + context.Delegation.set({ + id: delegationId, + transactionHash: txHash, + daoId, + delegateAccountId: normalizedDelegate, + delegatorAccountId: normalizedDelegator, + delegatedValue: (existingDelegation?.delegatedValue ?? 0n) + delegatedValue, + previousDelegate: getAddress(previousDelegate), + timestamp, + logIndex, + isCex, + isDex, + isLending, + isTotal, + delegationType: undefined, + }); + + // Update delegator's balance record to point to new delegate + const existingBalance = await context.AccountBalance.get(delegatorBalanceId); + context.AccountBalance.set({ + id: delegatorBalanceId, + accountId: normalizedDelegator, + tokenId: getAddress(tokenId), + balance: existingBalance?.balance ?? 0n, + delegate: normalizedDelegate, + }); + + // Decrement previous delegate's count + if (previousDelegate !== zeroAddress) { + const prevPowerId = getAddress(previousDelegate); + const prevPower = await context.AccountPower.get(prevPowerId); + context.AccountPower.set({ + id: prevPowerId, + accountId: prevPowerId, + daoId, + votingPower: prevPower?.votingPower ?? 0n, + votesCount: prevPower?.votesCount ?? 0, + proposalsCount: prevPower?.proposalsCount ?? 0, + delegationsCount: Math.max(0, (prevPower?.delegationsCount ?? 0) - 1), + lastVoteTimestamp: prevPower?.lastVoteTimestamp ?? 0n, + }); + } + + // Increment new delegate's count + const delegatePowerId = normalizedDelegate; + const delegatePower = await context.AccountPower.get(delegatePowerId); + context.AccountPower.set({ + id: delegatePowerId, + accountId: normalizedDelegate, + daoId, + votingPower: delegatePower?.votingPower ?? 0n, + votesCount: delegatePower?.votesCount ?? 0, + proposalsCount: delegatePower?.proposalsCount ?? 0, + delegationsCount: (delegatePower?.delegationsCount ?? 0) + 1, + lastVoteTimestamp: delegatePower?.lastVoteTimestamp ?? 0n, + }); + + context.FeedEvent.set({ + id: `${txHash}-${logIndex}`, + txHash, + logIndex, + eventType: "DELEGATION" as EventType_t, + value: delegatedValue, + timestamp, + metadata: { + delegator: normalizedDelegator, + delegate: normalizedDelegate, + previousDelegate: getAddress(previousDelegate), + amount: delegatedValue.toString(), + }, + }); +}; + +export const delegatedVotesChanged = async ( + context: handlerContext, + daoId: DaoIdEnum, + args: { + delegate: Address; + txHash: Hex; + newBalance: bigint; + oldBalance: bigint; + timestamp: bigint; + logIndex: number; + }, +) => { + const { delegate, txHash, newBalance, oldBalance, timestamp, logIndex } = + args; + + const normalizedDelegate = getAddress(delegate); + + await ensureAccountExists(context, delegate); + + const diff = newBalance - oldBalance; + const deltaMod = diff > 0n ? diff : -diff; + + context.VotingPowerHistory.set({ + id: `${txHash}-${normalizedDelegate}-${logIndex}`, + daoId, + transactionHash: txHash, + accountId: normalizedDelegate, + votingPower: newBalance, + delta: diff, + deltaMod, + timestamp, + logIndex, + }); + + const existingPower = await context.AccountPower.get(normalizedDelegate); + context.AccountPower.set({ + id: normalizedDelegate, + accountId: normalizedDelegate, + daoId, + votingPower: newBalance, + votesCount: existingPower?.votesCount ?? 0, + proposalsCount: existingPower?.proposalsCount ?? 0, + delegationsCount: existingPower?.delegationsCount ?? 0, + lastVoteTimestamp: existingPower?.lastVoteTimestamp ?? 0n, + }); + + context.FeedEvent.set({ + id: `${txHash}-${logIndex}`, + txHash, + logIndex, + eventType: "DELEGATION_VOTES_CHANGED" as EventType_t, + value: deltaMod, + timestamp, + metadata: { + delta: diff.toString(), + deltaMod: deltaMod.toString(), + delegate: normalizedDelegate, + }, + }); +}; diff --git a/apps/hypersync-indexer/src/eventHandlers/index.ts b/apps/hypersync-indexer/src/eventHandlers/index.ts new file mode 100644 index 000000000..a5e8d7c14 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/index.ts @@ -0,0 +1,3 @@ +export * from "./transfer.ts"; +export * from "./delegation.ts"; +export * from "./voting.ts"; diff --git a/apps/hypersync-indexer/src/eventHandlers/metrics/circulating.ts b/apps/hypersync-indexer/src/eventHandlers/metrics/circulating.ts new file mode 100644 index 000000000..24c7b2479 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/metrics/circulating.ts @@ -0,0 +1,37 @@ +import type { Address } from "viem"; +import { getAddress } from "viem"; +import type { handlerContext } from "../../../generated/index.js"; + +import { storeDailyBucket } from "../shared.ts"; +import { MetricTypesEnum } from "../../lib/constants.ts"; + +export const updateCirculatingSupply = async ( + context: handlerContext, + daoId: string, + tokenAddress: Address, + timestamp: bigint, +) => { + const tokenId = getAddress(tokenAddress); + const token = await context.Token.get(tokenId); + if (!token) return false; + + const currentCirculatingSupply = token.circulatingSupply; + const newCirculatingSupply = + token.totalSupply - token.treasury - token.nonCirculatingSupply; + + if (currentCirculatingSupply === newCirculatingSupply) return false; + + context.Token.set({ ...token, circulatingSupply: newCirculatingSupply }); + + await storeDailyBucket( + context, + MetricTypesEnum.CIRCULATING_SUPPLY, + currentCirculatingSupply, + newCirculatingSupply, + daoId, + timestamp, + tokenAddress, + ); + + return true; +}; diff --git a/apps/hypersync-indexer/src/eventHandlers/metrics/delegated.ts b/apps/hypersync-indexer/src/eventHandlers/metrics/delegated.ts new file mode 100644 index 000000000..675d39025 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/metrics/delegated.ts @@ -0,0 +1,34 @@ +import type { Address } from "viem"; +import { getAddress } from "viem"; +import type { handlerContext } from "../../../generated/index.js"; + +import { DaoIdEnum } from "../../lib/enums.ts"; +import { MetricTypesEnum } from "../../lib/constants.ts"; +import { storeDailyBucket } from "../shared.ts"; + +export const updateDelegatedSupply = async ( + context: handlerContext, + daoId: DaoIdEnum, + tokenId: Address, + amount: bigint, + timestamp: bigint, +) => { + const normalizedId = getAddress(tokenId); + const token = await context.Token.get(normalizedId); + if (!token) return; + + const currentDelegatedSupply = token.delegatedSupply; + const newDelegatedSupply = currentDelegatedSupply + amount; + + context.Token.set({ ...token, delegatedSupply: newDelegatedSupply }); + + await storeDailyBucket( + context, + MetricTypesEnum.DELEGATED_SUPPLY, + currentDelegatedSupply, + newDelegatedSupply, + daoId, + timestamp, + tokenId, + ); +}; diff --git a/apps/hypersync-indexer/src/eventHandlers/metrics/index.ts b/apps/hypersync-indexer/src/eventHandlers/metrics/index.ts new file mode 100644 index 000000000..a4126ee00 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/metrics/index.ts @@ -0,0 +1,4 @@ +export * from "./delegated.ts"; +export * from "./total.ts"; +export * from "./supply.ts"; +export * from "./circulating.ts"; diff --git a/apps/hypersync-indexer/src/eventHandlers/metrics/supply.ts b/apps/hypersync-indexer/src/eventHandlers/metrics/supply.ts new file mode 100644 index 000000000..ecac65839 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/metrics/supply.ts @@ -0,0 +1,59 @@ +import type { Address } from "viem"; +import { getAddress } from "viem"; +import type { handlerContext } from "../../../generated/index.js"; + +import { + AddressCollection, + storeDailyBucket, + toAddressSet, +} from "../shared.ts"; +import { MetricTypesEnum } from "../../lib/constants.ts"; + +export const updateSupplyMetric = async ( + context: handlerContext, + supplyField: + | "lendingSupply" + | "cexSupply" + | "dexSupply" + | "treasury" + | "nonCirculatingSupply", + addressList: AddressCollection, + metricType: MetricTypesEnum, + from: Address, + to: Address, + value: bigint, + daoId: string, + tokenAddress: Address, + timestamp: bigint, +) => { + const normalizedAddressList = toAddressSet(addressList); + const isToRelevant = normalizedAddressList.has(getAddress(to)); + const isFromRelevant = normalizedAddressList.has(getAddress(from)); + + if ((isToRelevant || isFromRelevant) && !(isToRelevant && isFromRelevant)) { + const tokenId = getAddress(tokenAddress); + const token = await context.Token.get(tokenId); + if (!token) return false; + + const currentSupply = token[supplyField]; + const newSupply = isToRelevant + ? currentSupply + value + : currentSupply - value; + + context.Token.set({ ...token, [supplyField]: newSupply }); + + await storeDailyBucket( + context, + metricType, + currentSupply, + newSupply, + daoId, + timestamp, + tokenAddress, + ); + + return true; + } + + return false; +}; diff --git a/apps/hypersync-indexer/src/eventHandlers/metrics/total.ts b/apps/hypersync-indexer/src/eventHandlers/metrics/total.ts new file mode 100644 index 000000000..d80f01a61 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/metrics/total.ts @@ -0,0 +1,58 @@ +import type { Address } from "viem"; +import { getAddress } from "viem"; +import type { handlerContext } from "../../../generated/index.js"; + +import { DaoIdEnum } from "../../lib/enums.ts"; +import { MetricTypesEnum } from "../../lib/constants.ts"; +import { + AddressCollection, + storeDailyBucket, + toAddressSet, +} from "../shared.ts"; + +export const updateTotalSupply = async ( + context: handlerContext, + addressList: AddressCollection, + metricType: MetricTypesEnum, + from: Address, + to: Address, + value: bigint, + daoId: DaoIdEnum, + tokenAddress: Address, + timestamp: bigint, +) => { + const normalizedAddressList = toAddressSet(addressList); + const isToBurningAddress = normalizedAddressList.has(getAddress(to)); + const isFromBurningAddress = normalizedAddressList.has(getAddress(from)); + const isTotalSupplyTransaction = + (isToBurningAddress || isFromBurningAddress) && + !(isToBurningAddress && isFromBurningAddress); + + if (isTotalSupplyTransaction) { + const isBurningTokens = normalizedAddressList.has(getAddress(to)); + const tokenId = getAddress(tokenAddress); + const token = await context.Token.get(tokenId); + if (!token) return false; + + const currentTotalSupply = token.totalSupply; + const newTotalSupply = isBurningTokens + ? currentTotalSupply - value + : currentTotalSupply + value; + + context.Token.set({ ...token, totalSupply: newTotalSupply }); + + await storeDailyBucket( + context, + metricType, + currentTotalSupply, + newTotalSupply, + daoId, + timestamp, + tokenAddress, + ); + + return true; + } + + return false; +}; diff --git a/apps/hypersync-indexer/src/eventHandlers/shared.ts b/apps/hypersync-indexer/src/eventHandlers/shared.ts new file mode 100644 index 000000000..5771e329b --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/shared.ts @@ -0,0 +1,166 @@ +import type { Address } from "viem"; +import { getAddress } from "viem"; +import type { handlerContext } from "../../generated/index.js"; +import type { MetricType_t } from "../../generated/src/db/Enums.gen.ts"; + +import { MetricTypesEnum } from "../lib/constants.ts"; +import { delta, max, min } from "../lib/utils.ts"; +import { truncateTimestampToMidnight } from "../lib/date-helpers.ts"; + +const METRIC_TYPE_MAP: Record = { + [MetricTypesEnum.TOTAL_SUPPLY]: "total", + [MetricTypesEnum.DELEGATED_SUPPLY]: "delegated", + [MetricTypesEnum.CEX_SUPPLY]: "cex", + [MetricTypesEnum.DEX_SUPPLY]: "dex", + [MetricTypesEnum.LENDING_SUPPLY]: "lending", + [MetricTypesEnum.CIRCULATING_SUPPLY]: "circulating", + [MetricTypesEnum.TREASURY]: "treasury", + [MetricTypesEnum.NON_CIRCULATING_SUPPLY]: "non_circulating", +}; + +export type AddressCollection = readonly Address[] | ReadonlySet
; + +const normalizeAddressCollection = ( + addresses: AddressCollection, +): Address[] => { + if (Array.isArray(addresses)) { + return [...new Set(addresses.map((address) => getAddress(address)))]; + } + + return [...(addresses as ReadonlySet
)]; +}; + +export const createAddressSet = ( + addresses: readonly Address[], +): ReadonlySet
=> + new Set(addresses.map((address) => getAddress(address))); + +export const toAddressSet = ( + addresses: AddressCollection, +): ReadonlySet
=> { + if (Array.isArray(addresses)) { + return new Set(addresses.map((address) => getAddress(address))); + } + + return addresses as ReadonlySet
; +}; + +export const ensureAccountExists = async ( + context: handlerContext, + address: Address, +): Promise => { + await context.Account.getOrCreate({ id: getAddress(address) }); +}; + +/** + * Helper function to ensure multiple accounts exist + */ +export const ensureAccountsExist = async ( + context: handlerContext, + addresses: Address[], +): Promise => { + const normalized = normalizeAddressCollection(addresses); + if (normalized.length === 0) return; + await Promise.all( + normalized.map((id) => context.Account.getOrCreate({ id })), + ); +}; + +export const storeDailyBucket = async ( + context: handlerContext, + metricType: MetricTypesEnum, + currentValue: bigint, + newValue: bigint, + daoId: string, + timestamp: bigint, + tokenAddress: Address, +) => { + const vol = delta(newValue, currentValue); + const date = BigInt(truncateTimestampToMidnight(Number(timestamp))); + const tokenId = getAddress(tokenAddress); + const id = `${date}-${tokenId}-${metricType}`; + + const existing = await context.DaoMetricsDayBucket.get(id); + if (existing) { + context.DaoMetricsDayBucket.set({ + ...existing, + average: + (existing.average * BigInt(existing.count) + newValue) / + BigInt(existing.count + 1), + high: max(newValue, existing.high), + low: min(newValue, existing.low), + closeValue: newValue, + volume: existing.volume + vol, + count: existing.count + 1, + lastUpdate: timestamp, + }); + } else { + context.DaoMetricsDayBucket.set({ + id, + date, + tokenId, + metricType: METRIC_TYPE_MAP[metricType], + daoId, + average: newValue, + openValue: newValue, + high: newValue, + low: newValue, + closeValue: newValue, + volume: vol, + count: 1, + lastUpdate: timestamp, + }); + } +}; + +export const handleTransaction = async ( + context: handlerContext, + transactionHash: string, + from: Address, + to: Address, + timestamp: bigint, + addresses: AddressCollection, + { + cex = [], + dex = [], + lending = [], + burning = [], + }: { + cex?: AddressCollection; + dex?: AddressCollection; + lending?: AddressCollection; + burning?: AddressCollection; + } = {}, +) => { + const normalizedAddresses = normalizeAddressCollection(addresses); + const normalizedCex = toAddressSet(cex); + const normalizedDex = toAddressSet(dex); + const normalizedLending = toAddressSet(lending); + const normalizedBurning = toAddressSet(burning); + + const isCex = normalizedAddresses.some((addr) => normalizedCex.has(addr)); + const isDex = normalizedAddresses.some((addr) => normalizedDex.has(addr)); + const isLending = normalizedAddresses.some((addr) => + normalizedLending.has(addr), + ); + const isTotal = normalizedAddresses.some((addr) => + normalizedBurning.has(addr), + ); + + if (!(isCex || isDex || isLending || isTotal)) { + return; + } + + const existing = await context.Transaction.get(transactionHash); + context.Transaction.set({ + id: transactionHash, + transactionHash, + fromAddress: getAddress(from), + toAddress: getAddress(to), + timestamp, + isCex: (existing?.isCex ?? false) || isCex, + isDex: (existing?.isDex ?? false) || isDex, + isLending: (existing?.isLending ?? false) || isLending, + isTotal: (existing?.isTotal ?? false) || isTotal, + }); +}; diff --git a/apps/hypersync-indexer/src/eventHandlers/transfer.ts b/apps/hypersync-indexer/src/eventHandlers/transfer.ts new file mode 100644 index 000000000..f25566823 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/transfer.ts @@ -0,0 +1,149 @@ +import type { handlerContext } from "../../generated/index.js"; +import type { EventType_t } from "../../generated/src/db/Enums.gen.ts"; +import type { Address, Hex } from "viem"; +import { getAddress, zeroAddress } from "viem"; + +import { DaoIdEnum } from "../lib/enums.ts"; + +import { + AddressCollection, + ensureAccountsExist, + toAddressSet, +} from "./shared.ts"; + +export const tokenTransfer = async ( + context: handlerContext, + daoId: DaoIdEnum, + args: { + from: Address; + to: Address; + token: Address; + transactionHash: Hex; + value: bigint; + timestamp: bigint; + logIndex: number; + }, + { + cex = [], + dex = [], + lending = [], + burning = [], + }: { + cex?: AddressCollection; + dex?: AddressCollection; + lending?: AddressCollection; + burning?: AddressCollection; + }, +) => { + const { + from, + to, + token: tokenId, + transactionHash, + value, + timestamp, + logIndex, + } = args; + + const normalizedFrom = getAddress(from); + const normalizedTo = getAddress(to); + const normalizedTokenId = getAddress(tokenId); + + await ensureAccountsExist(context, [from, to]); + + // Upsert receiver balance and track current balance for history + const receiverBalanceId = `${normalizedTo}-${normalizedTokenId}`; + const existingReceiverBalance = + await context.AccountBalance.get(receiverBalanceId); + const currentReceiverBalance = existingReceiverBalance + ? existingReceiverBalance.balance + value + : value; + context.AccountBalance.set({ + id: receiverBalanceId, + accountId: normalizedTo, + tokenId: normalizedTokenId, + balance: currentReceiverBalance, + delegate: existingReceiverBalance?.delegate ?? zeroAddress, + }); + + context.BalanceHistory.set({ + id: `${transactionHash}-${normalizedTo}-${logIndex}`, + daoId, + transactionHash, + accountId: normalizedTo, + balance: currentReceiverBalance, + delta: value, + deltaMod: value > 0n ? value : -value, + timestamp, + logIndex, + }); + + if (from !== zeroAddress) { + const senderBalanceId = `${normalizedFrom}-${normalizedTokenId}`; + const existingSenderBalance = + await context.AccountBalance.get(senderBalanceId); + const currentSenderBalance = existingSenderBalance + ? existingSenderBalance.balance - value + : -value; + context.AccountBalance.set({ + id: senderBalanceId, + accountId: normalizedFrom, + tokenId: normalizedTokenId, + balance: currentSenderBalance, + delegate: existingSenderBalance?.delegate ?? zeroAddress, + }); + + context.BalanceHistory.set({ + id: `${transactionHash}-${normalizedFrom}-${logIndex}`, + daoId, + transactionHash, + accountId: normalizedFrom, + balance: currentSenderBalance, + delta: -value, + deltaMod: value > 0n ? value : -value, + timestamp, + logIndex, + }); + } + + const normalizedCex = toAddressSet(cex); + const normalizedDex = toAddressSet(dex); + const normalizedLending = toAddressSet(lending); + const normalizedBurning = toAddressSet(burning); + + const transferId = `${transactionHash}-${normalizedFrom}-${normalizedTo}`; + const existingTransfer = await context.Transfer.get(transferId); + context.Transfer.set({ + id: transferId, + transactionHash, + daoId, + tokenId: normalizedTokenId, + amount: (existingTransfer?.amount ?? 0n) + value, + fromAccountId: normalizedFrom, + toAccountId: normalizedTo, + timestamp, + logIndex, + isCex: normalizedCex.has(normalizedFrom) || normalizedCex.has(normalizedTo), + isDex: normalizedDex.has(normalizedFrom) || normalizedDex.has(normalizedTo), + isLending: + normalizedLending.has(normalizedFrom) || + normalizedLending.has(normalizedTo), + isTotal: + normalizedBurning.has(normalizedFrom) || + normalizedBurning.has(normalizedTo), + }); + + context.FeedEvent.set({ + id: `${transactionHash}-${logIndex}`, + txHash: transactionHash, + logIndex, + eventType: "TRANSFER" as EventType_t, + value, + timestamp, + metadata: { + from: normalizedFrom, + to: normalizedTo, + amount: value.toString(), + }, + }); +}; diff --git a/apps/hypersync-indexer/src/eventHandlers/voting.ts b/apps/hypersync-indexer/src/eventHandlers/voting.ts new file mode 100644 index 000000000..d3b82ad26 --- /dev/null +++ b/apps/hypersync-indexer/src/eventHandlers/voting.ts @@ -0,0 +1,261 @@ +import type { handlerContext } from "../../generated/index.js"; +import type { EventType_t } from "../../generated/src/db/Enums.gen.ts"; +import type { Address, Hex } from "viem"; +import { getAddress } from "viem"; + +import { ProposalStatus } from "../lib/constants.ts"; + +import { ensureAccountExists } from "./shared.ts"; + +export const voteCast = async ( + context: handlerContext, + daoId: string, + args: { + proposalId: string; + voter: Address; + reason: string; + support: number; + timestamp: bigint; + txHash: Hex; + votingPower: bigint; + logIndex: number; + }, +) => { + const { + voter, + timestamp, + txHash, + proposalId, + support, + votingPower, + reason, + logIndex, + } = args; + + await ensureAccountExists(context, voter); + + const normalizedVoter = getAddress(voter); + const powerId = normalizedVoter; + const existingPower = await context.AccountPower.get(powerId); + context.AccountPower.set({ + id: powerId, + accountId: normalizedVoter, + daoId, + votingPower: existingPower?.votingPower ?? 0n, + votesCount: (existingPower?.votesCount ?? 0) + 1, + proposalsCount: existingPower?.proposalsCount ?? 0, + delegationsCount: existingPower?.delegationsCount ?? 0, + lastVoteTimestamp: timestamp, + }); + + context.VoteOnchain.set({ + id: `${normalizedVoter}-${proposalId}`, + txHash, + daoId, + proposalId, + voterAccountId: normalizedVoter, + support: support.toString(), + votingPower, + reason, + timestamp, + }); + + // Update proposal vote totals + const proposal = await context.ProposalOnchain.get(proposalId); + if (proposal) { + context.ProposalOnchain.set({ + ...proposal, + againstVotes: proposal.againstVotes + (support === 0 ? votingPower : 0n), + forVotes: proposal.forVotes + (support === 1 ? votingPower : 0n), + abstainVotes: proposal.abstainVotes + (support === 2 ? votingPower : 0n), + }); + } + + context.FeedEvent.set({ + id: `${txHash}-${logIndex}`, + txHash, + logIndex, + eventType: "VOTE" as EventType_t, + value: votingPower, + timestamp, + metadata: { + voter: normalizedVoter, + reason, + support, + votingPower: votingPower.toString(), + proposalId, + title: proposal?.title ?? null, + }, + }); +}; + +const MAX_TITLE_LENGTH = 200; + +function parseProposalTitle(description: string): string { + const normalized = description.replace(/\\n/g, "\n"); + const lines = normalized.split("\n"); + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + if (/^# /.test(trimmed)) { + return trimmed.replace(/^# +/, ""); + } + break; + } + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || /^#{1,6}\s/.test(trimmed)) continue; + return trimmed.length > MAX_TITLE_LENGTH + ? trimmed.substring(0, MAX_TITLE_LENGTH) + "..." + : trimmed; + } + + return ""; +} + +export const proposalCreated = async ( + context: handlerContext, + daoId: string, + blockTime: number, + args: { + proposalId: string; + txHash: Hex; + proposer: Address; + targets: Address[]; + values: bigint[]; + signatures: string[]; + calldatas: Hex[]; + startBlock: string; + endBlock: string; + description: string; + blockNumber: bigint; + timestamp: bigint; + proposalType?: number; + logIndex: number; + }, +) => { + const { + proposer, + proposalId, + txHash, + targets, + values, + signatures, + calldatas, + startBlock, + endBlock, + description, + blockNumber, + timestamp, + logIndex, + } = args; + + await ensureAccountExists(context, proposer); + + const title = parseProposalTitle(description); + const blockDelta = parseInt(endBlock) - Number(blockNumber); + + context.ProposalOnchain.set({ + id: proposalId, + txHash, + daoId, + proposerAccountId: getAddress(proposer), + targets: targets.map((a) => getAddress(a)), + values: values.map((v) => v.toString()), + signatures, + calldatas, + startBlock: parseInt(startBlock), + endBlock: parseInt(endBlock), + title, + description, + timestamp, + logIndex, + status: ProposalStatus.PENDING, + endTimestamp: timestamp + BigInt(blockDelta * blockTime), + proposalType: args.proposalType, + forVotes: 0n, + againstVotes: 0n, + abstainVotes: 0n, + }); + + const powerId = getAddress(proposer); + const existingPower = await context.AccountPower.get(powerId); + const proposerVotingPower = existingPower?.votingPower ?? 0n; + context.AccountPower.set({ + id: powerId, + accountId: powerId, + daoId, + votingPower: proposerVotingPower, + votesCount: existingPower?.votesCount ?? 0, + proposalsCount: (existingPower?.proposalsCount ?? 0) + 1, + delegationsCount: existingPower?.delegationsCount ?? 0, + lastVoteTimestamp: existingPower?.lastVoteTimestamp ?? 0n, + }); + + context.FeedEvent.set({ + id: `${txHash}-${logIndex}`, + txHash, + logIndex, + eventType: "PROPOSAL" as EventType_t, + value: 0n, + timestamp, + metadata: { + id: proposalId, + proposer: getAddress(proposer), + votingPower: proposerVotingPower.toString(), + title, + }, + }); +}; + +export const updateProposalStatus = async ( + context: handlerContext, + proposalId: string, + status: string, +) => { + const proposal = await context.ProposalOnchain.get(proposalId); + if (proposal) { + context.ProposalOnchain.set({ ...proposal, status }); + } +}; + +export const proposalExtended = async ( + context: handlerContext, + proposalId: string, + blockTime: number, + extendedDeadline: bigint, + txHash: Hex, + logIndex: number, + timestamp: bigint, +) => { + const proposal = await context.ProposalOnchain.get(proposalId); + if (!proposal) return; + + const endTimestamp = + proposal.endTimestamp + + BigInt((Number(extendedDeadline) - proposal.endBlock) * blockTime); + + context.ProposalOnchain.set({ + ...proposal, + endBlock: Number(extendedDeadline), + endTimestamp, + }); + + context.FeedEvent.set({ + id: `${txHash}-${logIndex}`, + txHash, + logIndex, + eventType: "PROPOSAL_EXTENDED" as EventType_t, + value: 0n, + timestamp, + metadata: { + id: proposalId, + title: proposal.title, + endBlock: Number(extendedDeadline), + endTimestamp: endTimestamp.toString(), + proposer: getAddress(proposal.proposerAccountId), + }, + }); +}; diff --git a/apps/hypersync-indexer/src/lib/constants.ts b/apps/hypersync-indexer/src/lib/constants.ts new file mode 100644 index 000000000..a01a4773a --- /dev/null +++ b/apps/hypersync-indexer/src/lib/constants.ts @@ -0,0 +1,908 @@ +import type { Address } from "viem"; +import { zeroAddress } from "viem"; + +import { DaoIdEnum } from "./enums.ts"; + +export const CONTRACT_ADDRESSES = { + [DaoIdEnum.UNI]: { + blockTime: 12, + // https://etherscan.io/address/0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 + token: { + address: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", + decimals: 18, + startBlock: 10861674, + }, + // https://etherscan.io/address/0x408ED6354d4973f66138C91495F2f2FCbd8724C3 + governor: { + address: "0x408ED6354d4973f66138C91495F2f2FCbd8724C3", + startBlock: 13059157, + }, + }, + [DaoIdEnum.ENS]: { + blockTime: 12, + // https://etherscan.io/address/0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72 + token: { + address: "0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72", + decimals: 18, + startBlock: 9380410, + }, + // https://etherscan.io/address/0x323a76393544d5ecca80cd6ef2a560c6a395b7e3 + governor: { + address: "0x323a76393544d5ecca80cd6ef2a560c6a395b7e3", + startBlock: 13533772, + }, + }, + [DaoIdEnum.ARB]: { + blockTime: 0.25, + // https://arbiscan.io/address/0x912CE59144191C1204E64559FE8253a0e49E6548 + token: { + address: "0x912CE59144191C1204E64559FE8253a0e49E6548", + decimals: 18, + startBlock: 70398200, + }, + }, + [DaoIdEnum.OP]: { + blockTime: 2, + optimisticProposalType: 2, + // https://optimistic.etherscan.io/token/0x4200000000000000000000000000000000000042 + token: { + address: "0x4200000000000000000000000000000000000042", + decimals: 18, + startBlock: 6490467, + }, + // https://optimistic.etherscan.io/address/0xcDF27F107725988f2261Ce2256bDfCdE8B382B10 + governor: { + address: "0xcDF27F107725988f2261Ce2256bDfCdE8B382B10", + startBlock: 71801427, + }, + }, + [DaoIdEnum.TEST]: { + blockTime: 12, + token: { + address: "0x244dE6b06E7087110b94Cde88A42d9aBA17efa52", + decimals: 18, + startBlock: 22635098, + }, + governor: { + address: "0x7c28FC9709650D49c8d0aED2f6ece6b191F192a9", + startBlock: 22635098, + }, + }, + [DaoIdEnum.GTC]: { + blockTime: 12, + // https://etherscan.io/address/0xDe30da39c46104798bB5aA3fe8B9e0e1F348163F + token: { + address: "0xDe30da39c46104798bB5aA3fe8B9e0e1F348163F", + decimals: 18, + startBlock: 12422079, + }, + // https://etherscan.io/address/0x9D4C63565D5618310271bF3F3c01b2954C1D1639 + governor: { + address: "0x9D4C63565D5618310271bF3F3c01b2954C1D1639", + startBlock: 17813942, + }, + // https://etherscan.io/address/0xDbD27635A534A3d3169Ef0498beB56Fb9c937489 + governorAlpha: { + address: "0xDbD27635A534A3d3169Ef0498beB56Fb9c937489", + startBlock: 12497481, + }, + }, + [DaoIdEnum.NOUNS]: { + blockTime: 12, + token: { + // https://etherscan.io/token/0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03 + address: "0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03", + decimals: 0, + startBlock: 12985438, + }, + governor: { + // https://etherscan.io/address/0x6f3e6272a167e8accb32072d08e0957f9c79223d + address: "0x6f3e6272a167e8accb32072d08e0957f9c79223d", + startBlock: 12985453, + }, + auction: { + // https://etherscan.io/address/0x830BD73E4184ceF73443C15111a1DF14e495C706 + address: "0x830BD73E4184ceF73443C15111a1DF14e495C706", + startBlock: 12985451, + }, + }, + [DaoIdEnum.SCR]: { + blockTime: 1.5, + // https://scrollscan.com/address/0xd29687c813D741E2F938F4aC377128810E217b1b + token: { + address: "0xd29687c813D741E2F938F4aC377128810E217b1b", + decimals: 18, + startBlock: 8949006, + }, + // https://scrollscan.com/address/0x2f3f2054776bd3c2fc30d750734a8f539bb214f0 + governor: { + address: "0x2f3f2054776bd3c2fc30d750734a8f539bb214f0", + startBlock: 8963441, + }, + }, + [DaoIdEnum.COMP]: { + blockTime: 12, + // https://etherscan.io/address/0xc00e94Cb662C3520282E6f5717214004A7f26888 + token: { + address: "0xc00e94Cb662C3520282E6f5717214004A7f26888", + decimals: 18, + startBlock: 9601359, + }, + // https://etherscan.io/address/0x309a862bbC1A00e45506cB8A802D1ff10004c8C0 + governor: { + address: "0x309a862bbC1A00e45506cB8A802D1ff10004c8C0", + startBlock: 21688680, + }, + }, + [DaoIdEnum.OBOL]: { + blockTime: 12, + // https://etherscan.io/address/0x0B010000b7624eb9B3DfBC279673C76E9D29D5F7 + // Token created: Sep-19-2022 11:12:47 PM UTC + token: { + address: "0x0B010000b7624eb9B3DfBC279673C76E9D29D5F7", + decimals: 18, + startBlock: 15570746, + }, + // https://etherscan.io/address/0xcB1622185A0c62A80494bEde05Ba95ef29Fbf85c + // Governor created: Feb-19-2025 10:34:47 PM UTC + governor: { + address: "0xcB1622185A0c62A80494bEde05Ba95ef29Fbf85c", + startBlock: 21883431, + }, + }, + [DaoIdEnum.ZK]: { + blockTime: 1, + // https://explorer.zksync.io/address/0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E + token: { + address: "0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E", + decimals: 18, + startBlock: 34572100, + }, + // https://explorer.zksync.io/address/0xb83FF6501214ddF40C91C9565d095400f3F45746 + governor: { + address: "0xb83FF6501214ddF40C91C9565d095400f3F45746", + startBlock: 55519658, + }, + }, + [DaoIdEnum.SHU]: { + blockTime: 12, + tokenType: "ERC20", + // https://etherscan.io/address/0xe485E2f1bab389C08721B291f6b59780feC83Fd7 + token: { + address: "0xe485E2f1bab389C08721B291f6b59780feC83Fd7", + decimals: 18, + startBlock: 19021394, + }, + // https://etherscan.io/address/0xAA6BfA174d2f803b517026E93DBBEc1eBa26258e + azorius: { + address: "0xAA6BfA174d2f803b517026E93DBBEc1eBa26258e", + startBlock: 19021698, + }, + // https://etherscan.io/address/0x4b29d8B250B8b442ECfCd3a4e3D91933d2db720F + linearVotingStrategy: { + address: "0x4b29d8B250B8b442ECfCd3a4e3D91933d2db720F", + startBlock: 19021698, + }, + }, + [DaoIdEnum.FLUID]: { + blockTime: 12, + // https://etherscan.io/address/0x6f40d4A6237C257fff2dB00FA0510DeEECd303eb + token: { + address: "0x6f40d4A6237C257fff2dB00FA0510DeEECd303eb", + decimals: 18, + startBlock: 12183236, + }, + // https://etherscan.io/address/0x0204Cd037B2ec03605CFdFe482D8e257C765fA1B + governor: { + address: "0x0204Cd037B2ec03605CFdFe482D8e257C765fA1B", + startBlock: 12183245, + }, + }, + [DaoIdEnum.LIL_NOUNS]: { + blockTime: 12, + token: { + // https://etherscan.io/address/0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B + address: "0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B", + decimals: 0, + startBlock: 14736710, + }, + governor: { + // https://etherscan.io/address/0x5d2C31ce16924C2a71D317e5BbFd5ce387854039 + address: "0x5d2C31ce16924C2a71D317e5BbFd5ce387854039", + startBlock: 14736719, + }, + }, + [DaoIdEnum.AAVE]: { + blockTime: 1, + token: { + decimals: 18, + address: zeroAddress, + }, + aave: { + decimals: 18, + address: "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", + }, + stkAAVE: { + decimals: 18, + address: "0x4da27a545c0c5B758a6BA100e3a049001de870f5", + }, + aAAVE: { + decimals: 18, + address: "0xA700b4eB416Be35b2911fd5Dee80678ff64fF6C9", + }, + }, +} as const; + +export const TreasuryAddresses: Record> = { + [DaoIdEnum.UNI]: { + timelock: "0x1a9C8182C09F50C8318d769245beA52c32BE35BC", + treasuryVester1: "0x4750c43867EF5F89869132ecCF19B9b6C4286E1a", + treasuryVester2: "0xe3953D9d317B834592aB58AB2c7A6aD22b54075D", + treasuryVester3: "0x4b4e140D1f131fdaD6fb59C13AF796fD194e4135", + treasuryVester4: "0x3D30B1aB88D487B0F3061F40De76845Bec3F1e94", + }, + [DaoIdEnum.ENS]: { + timelock: "0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7", + endaoment: "0x4F2083f5fBede34C2714aFfb3105539775f7FE64", + oldEthRegistrarController: "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5", + ethRegistrarController: "0x253553366Da8546fC250F225fe3d25d0C782303b", + }, + [DaoIdEnum.ARB]: { + // https://docs.arbitrum.foundation/deployment-addresses + "DAO Treasury": "0xF3FC178157fb3c87548bAA86F9d24BA38E649B58", + "L2 Treasury Timelock": "0xbFc1FECa8B09A5c5D3EFfE7429eBE24b9c09EF58", + "L2 Core Timelock": "0x34d45e99f7D8c45ed05B5cA72D54bbD1fb3F98f0", + "Foundation Vesting Wallet": "0x15533b77981cDa0F85c4F9a485237DF4285D6844", + }, + [DaoIdEnum.AAVE]: { + // https://github.com/bgd-labs/aave-address-book + Collector: "0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c", + "Ecosystem Reserve": "0x25F2226B597E8F9514B3F68F00f494cF4f286491", + }, + [DaoIdEnum.OP]: { + // https://gov.optimism.io/t/where-are-the-optimisms-main-treasury-addresses/8880 + "Unallocated Treasury": "0x2A82Ae142b2e62Cb7D10b55E323ACB1Cab663a26", + "Foundation Budget": "0x2501c477D0A35545a387Aa4A3EEe4292A9a8B3F0", + "Foundation Grants": "0x19793c7824Be70ec58BB673CA42D2779d12581BE", + "Foundation Locked Grants": "0xE4553b743E74dA3424Ac51f8C1E586fd43aE226F", + }, + [DaoIdEnum.NOUNS]: { + timelock: "0xb1a32fc9f9d8b2cf86c068cae13108809547ef71", + auction: "0x830BD73E4184ceF73443C15111a1DF14e495C706", + }, + [DaoIdEnum.LIL_NOUNS]: { + timelock: "0xd5f279ff9EB21c6D40C8f345a66f2751C4eeA1fB", + }, + [DaoIdEnum.TEST]: {}, + [DaoIdEnum.GTC]: { + "Gitcoin Timelock": "0x57a8865cfB1eCEf7253c27da6B4BC3dAEE5Be518", + "Gitcoin CSDO": "0x931896A8A9313F622a2AFCA76d1471B97955e551", + "Gitcoin Fraud Detection & Defense": + "0xD4567069C5a1c1fc8261d8Ff5C0B1d98f069Cf47", + "Gitcoin Grants Matching Pool": + "0xde21F729137C5Af1b01d73aF1dC21eFfa2B8a0d6", + "Gitcoin Merch, Memes and Marketing": + "0xC23DA3Ca9300571B9CF43298228353cbb3E1b4c0", + "Gitcoin Timelock Transfer 1": "0x6EEdE31a2A15340342B4BCb3039447d457aC7C4b", + "Gitcoin Timelock Transfer 2": "0xeD95D629c4Db80060C59432e81254D256AEc97E2", + "Vesting Address GTC 1": "0x2AA5d15Eb36E5960d056e8FeA6E7BB3e2a06A351", + "Staking contract GTC": "0x0E3efD5BE54CC0f4C64e0D186b0af4b7F2A0e95F", + "OKX Ventures": "0xe527BbDE3654E9ba824f9B72DFF495eEe60fD366", + "Protocol Labs 1": "0x154855f5522f6B04ce654175368F428852DCd55D", + "Matt Solomon": "0x7aD3d9819B06E800F8A65f3440D599A23D6A0BDf", + "Arbitrum Bridge": "0xa3A7B6F88361F48403514059F1F16C8E78d60EeC", + "Optimism Bridge": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1", + "Radicle Timelock": "0x8dA8f82d2BbDd896822de723F55D6EdF416130ba", + "Vesting Address GTC 3": "0x2CDE9919e81b20B4B33DD562a48a84b54C48F00C", + "deltajuliet.eth 1": "0x5b1ddBEC956Ed39e1aC92AE3c3D99295ddD59865", + "deltajuliet.eth 2": "0x407466C56B8488c4d99558633Ff1AC5D84400B46", + "deltajuliet.eth 3": "0x14b9F70C3d4B367D496F3771EdA7EFA65282e55D", + "deltajuliet.eth 4": "0x0dcFc9323539A6eC47f9BC0A96882070540bf950", + "deltajuliet.eth 5": "0x08f3FB287AEc4E06EFF8de37410eaF52B05c7f56", + "Gitcoin Timelock Transfer 5": "0x9E75c3BFb82cf701AC0A74d6C1607461Ec65EfF9", + "Old Address, Large GTC Transfers 1": + "0xF5A7bA226CB94D87C29aDD2b59aC960904a163F3", + "Old Address, Large GTC Transfers 2": + "0xeD865C87c3509e3A908655777B13f7313b2fc196", + "Old Address, Large GTC Transfers 3": + "0xDD6a165B9e05549640149dF108AC0aF8579B7005", + "Old Address, Large GTC Transfers 4": + "0xaD467E6039F0Ca383b5FFd60F1C7a890acaB4bE3", + "Old Address, Large GTC Transfers 5": + "0x44d4d830788cc6D4d72C78203F5918a3E2761691", + "Old Address, Large GTC Transfers 6": + "0x38661187CfD779bEa00e14Bc5b986CF0C717A39B", + "Old Address, Large GTC Transfers 7": + "0x34237F91D2Ce322f3572376b82472C7FA56D7595", + "Old Address, Large GTC Transfers 8": + "0x2083e7B107347AE4F5Cb6Ff35EC5DAcf03391c57", + "Old Address, Large GTC Transfers 9": + "0x183a1CaF6750CF88E45812FCE0110D59d71833e4", + "Old Address, Large GTC Transfers 10": + "0x11e06eF6e42306dc40D2754Ef2629fB011d80aE9", + }, + [DaoIdEnum.SCR]: { + "DAO Treasury": "0x4cb06982dD097633426cf32038D9f1182a9aDA0c", + "Foundation Treasury": "0xfF120e015777E9AA9F1417a4009a65d2EdA78C13", + "Ecosystem Treasury": "0xeE198F4a91E5b05022dc90535729B2545D3b03DF", + }, + [DaoIdEnum.COMP]: { + Timelock: "0x6d903f6003cca6255D85CcA4D3B5E5146dC33925", + Comptroller: "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B", + /// v2 markets + v2WBTC: "0xccF4429DB6322D5C611ee964527D42E5d685DD6a", + v2USDC: "0x39AA39c021dfbaE8faC545936693aC917d5E7563", + v2DAI: "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643", + v2USDT: "0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9", + v2ETH: "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5", + v2UNI: "0x35A18000230DA775CAc24873d00Ff85BccdeD550", + v2BAT: "0x6C8c6b02E7b2BE14d4fA6022Dfd6d75921D90E4E", + v2LINK: "0xFAce851a4921ce59e912d19329929CE6da6EB0c7", + v2TUSD: "0x12392F67bdf24faE0AF363c24aC620a2f67DAd86", + v2AAVE: "0xe65cdB6479BaC1e22340E4E755fAE7E509EcD06c", + v2COMP: "0x70e36f6BF80a52b3B46b3aF8e106CC0ed743E8e4", + ///v3 markets + //Ethereum markets + mainnetETH: "0xA17581A9E3356d9A858b789D68B4d866e593aE94", + mainnetstETH: "0x3D0bb1ccaB520A66e607822fC55BC921738fAFE3", + mainnetUSDT: "0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840", + mainnetUSDS: "0x5D409e56D886231aDAf00c8775665AD0f9897b56", + mainnetUSDC: "0xc3d688B66703497DAA19211EEdff47f25384cdc3", + mainnetWBTC: "0xe85Dc543813B8c2CFEaAc371517b925a166a9293", + // Optimism markets + opETH: "0xE36A30D249f7761327fd973001A32010b521b6Fd", + opUSDT: "0x995E394b8B2437aC8Ce61Ee0bC610D617962B214", + opUSDC: "0x2e44e174f7D53F0212823acC11C01A11d58c5bCB", + // Unichain markets + uniUSDC: "0x2c7118c4C88B9841FCF839074c26Ae8f035f2921", + uniETH: "0x6C987dDE50dB1dcDd32Cd4175778C2a291978E2a", + // Polygon markets + polyUSDT0: "0xaeB318360f27748Acb200CE616E389A6C9409a07", + polyUSDC: "0xF25212E676D1F7F89Cd72fFEe66158f541246445", + // Ronin markets + ronWETH: "0x4006ed4097ee51c09a04c3b0951d28ccf19e6dfe", + ronRON: "0xc0Afdbd1cEB621Ef576BA969ce9D4ceF78Dbc0c0", + // Mantle markets + manUSDe: "0x606174f62cd968d8e684c645080fa694c1D7786E", + // Base markets + manUSDbC: "0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf", + manUSDC: "0xb125E6687d4313864e53df431d5425969c15Eb2F", + manAERO: "0x784efeB622244d2348d4F2522f8860B96fbEcE89", + manUSDS: "0x2c776041CCFe903071AF44aa147368a9c8EEA518", + manETH: "0x46e6b214b524310239732D51387075E0e70970bf", + // Arbitrum marketsVOTE + arbUSDT0: "0xd98Be00b5D27fc98112BdE293e487f8D4cA57d07", + arbUSDC: "0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf", + "arbUSDC.e": "0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA", + arbETH: "0x6f7D514bbD4aFf3BcD1140B7344b32f063dEe486", + // Linea markets + linUSDC: "0x8D38A3d6B3c3B7d96D6536DA7Eef94A9d7dbC991", + linETH: "0x60F2058379716A64a7A5d29219397e79bC552194", + // Scroll markets + scrUSDC: "0xB2f97c1Bd3bf02f5e74d13f02E3e26F93D77CE44", + }, + [DaoIdEnum.OBOL]: { + timelock: "0xCdBf527842Ab04Da548d33EB09d03DB831381Fb0", + "Ecosystem Treasury 1": "0x42D201CC4d9C1e31c032397F54caCE2f48C1FA72", + "Ecosystem Treasury 2": "0x54076088bE86943e27B99120c5905AAD8A1bD166", + "Staking Rewards Reserve": "0x33f3D61415784A5899b733976b0c1F9176051569", + "OBOL Incentives Reserve": "0xdc8A309111aB0574CA51cA9C7Dd0152738e4c374", + "Protocol Revenue": "0xDe5aE4De36c966747Ea7DF13BD9589642e2B1D0d", + "Grant Program": "0xa59f60A7684A69E63c07bEC087cEC3D0607cd5cE", + "DV Labs Treasury 2": "0x6BeFB6484AA10187947Dda81fC01e495f7168dB4", + }, + [DaoIdEnum.ZK]: { + timelock: "0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d", + }, + [DaoIdEnum.SHU]: { + timelock: "0x36bD3044ab68f600f6d3e081056F34f2a58432c4", + }, + [DaoIdEnum.FLUID]: { + "InstaDApp Treasury": "0x28849D2b63fA8D361e5fc15cB8aBB13019884d09", + "Fluid Liquidity": "0x52Aa899454998Be5b000Ad077a46Bbe360F4e497", + "Chainlink CCIP LockReleaseTokenPool": + "0x639f35C5E212D61Fe14Bd5CD8b66aAe4df11a50c", + InstaTimelock: "0xC7Cb1dE2721BFC0E0DA1b9D526bCdC54eF1C0eFC", + }, +}; + +export const CEXAddresses: Record> = { + [DaoIdEnum.UNI]: { + BinanceHotWallet: "0x5a52E96BAcdaBb82fd05763E25335261B270Efcb", + BinanceHotWallet2: "0x28C6c06298d514Db089934071355E5743bf21d60", + BinanceHotWallet3: "0x8894E0a0c962CB723c1976a4421c95949bE2D4E3", + BinanceHotWallet4: "0x43684D03D81d3a4C70da68feBDd61029d426F042", + BinanceHotWallet5: "0x21a31Ee1afC51d94C2eFcCAa2092aD1028285549", + BinanceHotWallet6: "0xDFd5293D8e347dFe59E90eFd55b2956a1343963d", + BinanceUSHotWallet: "0x21d45650db732cE5dF77685d6021d7D5d1da807f", + BinanceColdWallet: "0xF977814e90dA44bFA03b6295A0616a897441aceC", + BinancePegTokenFunds: "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503", + Robinhood: "0x73AF3bcf944a6559933396c1577B257e2054D935", + AnchorageDigital1: "0x985DE23260743c2c2f09BFdeC50b048C7a18c461", + AnchorageDigital2: "0xfad67fBdb7d4D8569671b8aa4A09F6a90d692Ed7", + BybitColdWallet1: "0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3", + UpbitDeposit: "0xacCFeA7d9e618f60CE1347C52AE206262412AA4a", + UpbitColdWallet: "0x245445940B317E509002eb682E03f4429184059d", + KrakenColdWallet: "0xDA9dfA130Df4dE4673b89022EE50ff26f6EA73Cf", + KrakenHotWallet: "0x4C6007e38Ce164Ed80FF8Ff94192225FcdAC68CD", + KrakenHotWallet2: "0x0A332d03367366dd5fD3a554EF8f8B47ED36e591", + Robinhood2: "0x2eFB50e952580f4ff32D8d2122853432bbF2E204", + GeminiColdWallet: "0xAFCD96e580138CFa2332C632E66308eACD45C5dA", + KrakenColdWallet2: "0xC06f25517E906b7F9B4deC3C7889503Bb00b3370", + CoinbaseColdWallet: "0x6cc8FfF60A60AB0373fB3072f0B846450a8FA443", + NobitexIrHotWallet: "0xF639d88a89384A4D97f2bA9159567Ddb3890Ea07", + MEXCHotWallet: "0x4982085C9e2F89F2eCb8131Eca71aFAD896e89CB", + MEXCHotWallet2: "0x2e8F79aD740de90dC5F5A9F0D8D9661a60725e64", + OKXHotWallet: "0x6cC5F688a315f3dC28A7781717a9A798a59fDA7b", + StakeComHotWallet: "0xFa500178de024BF43CFA69B7e636A28AB68F2741", + BinanceWithdrawalHotWallet: "0xe2fc31F816A9b94326492132018C3aEcC4a93aE1", + NobitexIrHotWallet2: "0xd582C78a04E7379DfC9EE991A25f549576962eE1", + }, + [DaoIdEnum.ENS]: { + BinanceHotWallet: "0x5a52E96BAcdaBb82fd05763E25335261B270Efcb", + BinanceHotWallet2: "0x28C6c06298d514Db089934071355E5743bf21d60", + BinanceHotWallet3: "0x8894E0a0c962CB723c1976a4421c95949bE2D4E3", + BinanceHotWallet4: "0x43684D03D81d3a4C70da68feBDd61029d426F042", + BinanceHotWallet5: "0x21a31Ee1afC51d94C2eFcCAa2092aD1028285549", + BinanceHotWallet6: "0xDFd5293D8e347dFe59E90eFd55b2956a1343963d", + BinanceUSHotWallet: "0x21d45650db732cE5dF77685d6021d7D5d1da807f", + BitThumbHotWallet: "0x498697892fd0e5e3a16bd40D7bF2644F33CBbBd4", + BybitColdWallet1: "0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3", + ByBitHotWallet: "0xf89d7b9c864f589bbF53a82105107622B35EaA40", + BtcTurkColdWallet: "0x76eC5A0D3632b2133d9f1980903305B62678Fbd3", + BitGetHotWallet: "0x5bdf85216ec1e38D6458C870992A69e38e03F7Ef", + CryptoComHotWallet: "0xA023f08c70A23aBc7EdFc5B6b5E171d78dFc947e", + CryptoComHotWallet2: "0xCFFAd3200574698b78f32232aa9D63eABD290703", + BitThumbHotWallet2: "0x10522336d85Cb52628C84e06CB05f79011FEf585", + ParibuColdWallet: "0xa23cbCdFAfd09De2ce793D0A08f51865885Be3f5", + CoinOneHotWallet: "0x167A9333BF582556f35Bd4d16A7E80E191aa6476", + BitvavoColdWallet: "0xc419733Ba8F13d8605141Cac8f681F5A0aBC0122", + KuCoinHotWallet: "0xD6216fC19DB775Df9774a6E33526131dA7D19a2c", + BitvavoColdWallet2: "0xedC6BacdC1e29D7c5FA6f6ECA6FDD447B9C487c9", + CoinbaseHotWallet: "0xA9D1e08C7793af67e9d92fe308d5697FB81d3E43", + MEXCHotWallet3: "0x3CC936b795A188F0e246cBB2D74C5Bd190aeCF18", + KuCoinColdWallet: "0x2933782B5A8d72f2754103D1489614F29bfA4625", + UpbitColdWallet: "0x245445940B317E509002eb682E03f4429184059d", + }, + [DaoIdEnum.ARB]: {}, + [DaoIdEnum.AAVE]: {}, + [DaoIdEnum.NOUNS]: {}, + [DaoIdEnum.LIL_NOUNS]: {}, + [DaoIdEnum.OP]: { + "Binance 1": "0xF977814e90dA44bFA03b6295A0616a897441aceC", + "Binance 2": "0x5a52E96BAcdaBb82fd05763E25335261B270Efcb", + OKX: "0x611f7bF868a6212f871e89F7e44684045DdFB09d", + Bybit: "0xf89d7b9c864f589bbF53a82105107622B35EaA40", + "Bybit 2": "0x88a1493366D48225fc3cEFbdae9eBb23E323Ade3", + Bithumb: "0xB18fe4B95b7d633c83689B5Ed3ac4ad0a857A2a7", + MEXC: "0xDF90C9B995a3b10A5b8570a47101e6c6a29eb945", + Gate: "0xC882b111A75C0c657fC507C04FbFcD2cC984F071", + "Kraken 1": "0x2a62C4aCcA1A166Ee582877112682cAe8Cc0ffe7", + "Kraken 2": "0xC06f25517E906b7F9B4deC3C7889503Bb00b3370", + "Bitkub 1": "0xda4231EF1768176536EEE3ec187315E60572BBD4", + "Bitkub 2": "0x7A1CF8CE543F4838c964FB14D403Cc6ED0bDbaCC", + Bitget: "0x5bdf85216ec1e38D6458C870992A69e38e03F7Ef", + "Kucoin 1": "0x2933782B5A8d72f2754103D1489614F29bfA4625", + "Kucoin 2": "0xC1274c580C5653cDF8246695c2E0112492a99D6F", + "Kucoin 3": "0xa3f45e619cE3AAe2Fa5f8244439a66B203b78bCc", + "Coinbase 1": "0xC8373EDFaD6d5C5f600b6b2507F78431C5271fF5", + "Coinbase 2": "0xD839C179a4606F46abD7A757f7Bb77D7593aE249", + "Crypto.com 1": "0x8a161a996617f130d0F37478483AfC8c1914DB6d", + "Crypto.com 2": "0x92BD687953Da50855AeE2Df0Cff282cC2d5F226b", + "Btcturk 1": "0xdE2fACa4BBC0aca08fF04D387c39B6f6325bf82A", + "Btcturk 2": "0xB5A46bC8b76FD2825AEB43db9C9e89e89158ECdE", + "Bitpanda 1": "0xb1A63489469868dD1d0004922C36D5079d6331c6", + "Bitpanda 2": "0x5E8c4499fDD78A5EFe998b3ABF78658E02BB7702", + "Bitpanda 3": "0x0529ea5885702715e83923c59746ae8734c553B7", + "BingX 1": "0xC3dcd744db3f114f0edF03682b807b78A227Bf74", + "Bingx 2": "0x0b07f64ABc342B68AEc57c0936E4B6fD4452967E", + "HTX 1": "0xe0B7A39Fef902c21bAd124b144c62E7F85f5f5fA", + "HTX 2": "0xd3Cc0C7d40366A061397274Eae7C387D840e6ff8", + Bitbank: "0x3727cfCBD85390Bb11B3fF421878123AdB866be8", + Revolut: "0x9b0c45d46D386cEdD98873168C36efd0DcBa8d46", + "Paribu 1": "0xc80Afd311c9626528De66D86814770361Fe92416", + Coinspot: "0xf35A6bD6E0459A4B53A27862c51A2A7292b383d1", + "Bitvavo 1": "0x48EcA43dB3a3Ca192a5fB9b20F4fc4d96017AF0F", + SwissBorg: "0x28cC933fecf280E720299b1258e8680355D8841F", + "Coinbase Prime": "0xDfD76BbFEB9Eb8322F3696d3567e03f894C40d6c", + "Binance US": "0x43c5b1C2bE8EF194a509cF93Eb1Ab3Dbd07B97eD", + "Bitstamp 1": "0x7C43E0270c868D0341c636a38C07e5Ae93908a04", + "Bitstamp 2": "0x4c2eEb203DDC70291e33796527dE4272Ac9fafc1", + "Coinhako 1": "0xE66BAa0B612003AF308D78f066Bbdb9a5e00fF6c", + "Coinhako 2": "0xE66BAa0B612003AF308D78f066Bbdb9a5e00fF6c", + Bitfinex: "0x77134cbC06cB00b66F4c7e623D5fdBF6777635EC", + "Woo Network": "0x63DFE4e34A3bFC00eB0220786238a7C6cEF8Ffc4", + Koribit: "0xf0bc8FdDB1F358cEf470D63F96aE65B1D7914953", + "Indodax 1": "0x3C02290922a3618A4646E3BbCa65853eA45FE7C6", + "Indodax 2": "0x91Dca37856240E5e1906222ec79278b16420Dc92", + }, + [DaoIdEnum.TEST]: { + // Major centralized exchanges (CEX) - Alice and Bob for comprehensive coverage + Alice_CEX: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Alice as CEX + Bob_CEX: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", // Bob as CEX + // ENS contract addresses for completeness + ENSToken: "0x244dE6b06E7087110b94Cde88A42d9aBA17efa52", + ENSGovernor: "0x7c28FC9709650D49c8d0aED2f6ece6b191F192a9", + ENSTimelock: "0xa7E99C1df635d13d61F7c81eCe571cc952E64526", + }, + [DaoIdEnum.GTC]: { + "Binance 1": "0xF977814e90dA44bFA03b6295A0616a897441aceC", + "Binance 2": "0x28C6c06298d514Db089934071355E5743bf21d60", + "Binance 3": "0x5a52E96BAcdaBb82fd05763E25335261B270Efcb", + "Binance 4": "0xDFd5293D8e347dFe59E90eFd55b2956a1343963d", + "Binance 5": "0x21a31Ee1afC51d94C2eFcCAa2092aD1028285549", + Bithumb: "0x74be0CF1c9972C00ed4EF290e0E5BCFd18873f13", + Upbit: "0x74be0CF1c9972C00ed4EF290e0E5BCFd18873f13", + "Upbit 2": "0xeDAe8A6cBA6867a0B7e565C21eaBAEe3D550fd9d", + "Coinbase 1": "0x237eF9564D74A1056c1A276B03C66055Fa61A700", + "Coinbase 2": "0x31Bc777E72A0A7F90cC7b1ec52eACeC806B27563", + "Coinbase 3": "0x11aC4fE470Cf8B5b3de59B31261030BD8514892d", + "Coinbase 4": "0x271Ac4A385F689f00D01716877e827702231447e", + "Coinbase 5": "0x4a630c042B2b07a0641d487b0Ccf5af36800415e", + "Coinbase 6": "0xA9D1e08C7793af67e9d92fe308d5697FB81d3E43", + Kraken: "0x310E035d176ccB589511eD16af7aE7BAc4fc7f83", + "Kraken 2": "0xC06f25517E906b7F9B4deC3C7889503Bb00b3370", + "Kraken 3": "0x22af984f13DFB5C80145E3F9eE1050Ae5a5FB651", + "Crypto.com": "0xCFFAd3200574698b78f32232aa9D63eABD290703", + "Crypto.com 2": "0xA023f08c70A23aBc7EdFc5B6b5E171d78dFc947e", + "Crypto.com 3": "0x46340b20830761efd32832A74d7169B29FEB9758", + Kucoin: "0x58edF78281334335EfFa23101bBe3371b6a36A51", + "Kucoin 2": "0xD6216fC19DB775Df9774a6E33526131dA7D19a2c", + Bittavo: "0xaB782bc7D4a2b306825de5a7730034F8F63ee1bC", + MEXC: "0x9642b23Ed1E01Df1092B92641051881a322F5D4E", + "MEXC 2": "0x75e89d5979E4f6Fba9F97c104c2F0AFB3F1dcB88", + Gate: "0x0D0707963952f2fBA59dD06f2b425ace40b492Fe", + BingX: "0xC3dcd744db3f114f0edF03682b807b78A227Bf74", + Bitget: "0x5bdf85216ec1e38D6458C870992A69e38e03F7Ef", + CoinEx: "0x38f6d5fb32f970Fe60924B282704899411126336", + Bitpanda: "0x0529ea5885702715e83923c59746ae8734c553B7", + }, + [DaoIdEnum.SCR]: { + "Binance 2": "0x98ADeF6F2ac8572ec48965509d69A8Dd5E8BbA9D", + "Binance 3": "0x687B50A70D33D71f9a82dD330b8C091e4D772508", + "Gate 2": "0xC882b111A75C0c657fC507C04FbFcD2cC984F071", + "OKX 2": "0xB0A27099582833c0Cb8C7A0565759fF145113d64", + Binance: "0xF977814e90dA44bFA03b6295A0616a897441aceC", + BingX: "0x2b3bf74B29f59fb8dDA41Cf3d6A8DA28CF8e7921", + Bitget: "0x1AB4973a48dc892Cd9971ECE8e01DcC7688f8F23", + Bitpanda: "0x0529ea5885702715e83923c59746ae8734c553B7", + Bybit: "0xf89d7b9c864f589bbF53a82105107622B35EaA40", + Gate: "0x0D0707963952f2fBA59dD06f2b425ace40b492Fe", + Kucoin: "0x2933782B5A8d72f2754103D1489614F29bfA4625", + OKX: "0x611f7bF868a6212f871e89F7e44684045DdFB09d", + }, + [DaoIdEnum.COMP]: { + Robinhood: "0x73AF3bcf944a6559933396c1577B257e2054D935", + "Robinhood 2": "0x841ed663F2636863D40be4EE76243377dff13a34", + "Binance 1": "0xF977814e90dA44bFA03b6295A0616a897441aceC", + "Binance 2": "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503", + "Binance 3": "0x28C6c06298d514Db089934071355E5743bf21d60", + "Binance 4": "0x21a31Ee1afC51d94C2eFcCAa2092aD1028285549", + "Binance 5": "0xDFd5293D8e347dFe59E90eFd55b2956a1343963d", + ByBit: "0x6522B7F9d481eCEB96557F44753a4b893F837E90", + OKX: "0x073F564419b625A45D8aEa3bb0dE4d5647113AD7", + Upbit: "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503", + BtcTurk: "0x76eC5A0D3632b2133d9f1980903305B62678Fbd3", + Bithumb: "0x75252a69676C2472EdF9974476e9c636ca7a8AF1", + Kraken: "0x7DAFbA1d69F6C01AE7567Ffd7b046Ca03B706f83", + "Kraken 2": "0xd2DD7b597Fd2435b6dB61ddf48544fd931e6869F", + "Kucoin 1": "0x2933782B5A8d72f2754103D1489614F29bfA4625", + "Kucoin 2": "0x58edF78281334335EfFa23101bBe3371b6a36A51", + }, + [DaoIdEnum.OBOL]: { + "Bybit Hot Wallet": "0xA31231E727Ca53Ff95f0D00a06C645110c4aB647", + "Binance Wallet": "0x93dEb693b170d56BdDe1B0a5222B14c0F885d976", + "Gate Cold Wallet": "0xC882b111A75C0c657fC507C04FbFcD2cC984F071", + "Gate Hot Wallet": "0x0D0707963952f2fBA59dD06f2b425ace40b492Fe", + "MEXC Hot Wallet": "0x9642b23Ed1E01Df1092B92641051881a322F5D4E", + "Binance Wallet Proxy": "0x73D8bD54F7Cf5FAb43fE4Ef40A62D390644946Db", + }, + [DaoIdEnum.ZK]: { + "Binance 1": "0xf977814e90da44bfa03b6295a0616a897441acec", + "Binance 2": "0x7aed074ca56f5050d5a2e512ecc5bf7103937d76", + "Binance 3": "0xa84fd90d8640fa63d194601e0b2d1c9094297083", + "Binance 4": "0x43684d03d81d3a4c70da68febdd61029d426f042", + "Binance 5": "0x98adef6f2ac8572ec48965509d69a8dd5e8bba9d", + Bybit: "0xacf9a5610cb9e6ec9c84ca7429815e95b6607e9f", + OKX1: "0x611f7bf868a6212f871e89f7e44684045ddfb09d", + BtcTurk: "0x7aed074ca56f5050d5a2e512ecc5bf7103937d76", + MEXC: "0xfe4931fb4deabc515f1a48b94b6b17653eeaa34f", + Bitget: "0x97b9d2102a9a65a26e1ee82d59e42d1b73b68689", + Kraken: "0xd2dd7b597fd2435b6db61ddf48544fd931e6869f", + Kucoin: "0xd6216fc19db775df9774a6e33526131da7d19a2c", + "Kucoin 2": "0x2933782b5a8d72f2754103d1489614f29bfa4625", + Gate: "0x0d0707963952f2fba59dd06f2b425ace40b492fe", + "Gate 2": "0xc882b111a75c0c657fc507c04fbfcd2cc984f071", + "Crypto.com": "0x2a584c02de672425729af2f174fb19fe734dde5d", + OKX2: "0xf9b52be2426f06ab6d560f64a7b15e820f33cbdb", + OKX3: "0xecf17c7f6a6090f1edd21e0beb2268197270fb44", + }, + [DaoIdEnum.SHU]: {}, + [DaoIdEnum.FLUID]: { + MEXC: "0x9642b23Ed1E01Df1092B92641051881a322F5D4E", + Gate: "0x0D0707963952f2fBA59dD06f2b425ace40b492Fe", + Bitvavo: "0xaB782bc7D4a2b306825de5a7730034F8F63ee1bC", + }, +}; + +export const DEXAddresses: Record> = { + [DaoIdEnum.UNI]: { + // ArbitrumL1ERC20Gateway: "0xa3a7b6f88361f48403514059f1f16c8e78d60eec", + Uniswap_UNI_ETH_V3_03: "0x1d42064Fc4Beb5F8aAF85F4617AE8b3b5B8Bd801", + Uniswap_UNI_ETH_V3_1: "0x360b9726186C0F62cc719450685ce70280774Dc8", + Uniswap_UNI_ETH_V2_03: "0xd3d2E2692501A5c9Ca623199D38826e513033a17", + Uniswap_UNI_USDT_V3_03: "0x3470447f3CecfFAc709D3e783A307790b0208d60", + Uniswap_UNI_AAVE_V3_03: "0x59c38b6775Ded821f010DbD30eCabdCF84E04756", + Uniswap_UNI_USDC_V3_03: "0xD0fC8bA7E267f2bc56044A7715A489d851dC6D78", + Uniswap_UNI_WBTC_V3_03: "0x8F0CB37cdFF37E004E0088f563E5fe39E05CCC5B", + Uniswap_UNI_LINK_V3_1: "0xA6B9a13B34db2A00284299c47DACF49FB62C1755", + Uniswap_UNI_1INCH_V3_1: "0x0619062B988576FE2d39b33fF23Fb1a0330c0ac7", + Uniswap_UNI_ETH_V3_005: "0xfaA318479b7755b2dBfDD34dC306cb28B420Ad12", + Sushi_UNI_ETH_V2_03: "0xDafd66636E2561b0284EDdE37e42d192F2844D40", + BalancerCow_UNI_ETH: "0xa81b22966f1841e383e69393175e2cc65f0a8854", + }, + [DaoIdEnum.ENS]: { + Uniswap_ENS_5: "0x92560C178cE069CC014138eD3C2F5221Ba71f58a", + SushiSwapEthENSV2: "0xa1181481beb2dc5de0daf2c85392d81c704bf75d", + }, + [DaoIdEnum.ARB]: {}, + [DaoIdEnum.AAVE]: {}, + [DaoIdEnum.NOUNS]: {}, + [DaoIdEnum.LIL_NOUNS]: {}, + [DaoIdEnum.OP]: { + "Velodrome Finance": "0x47029bc8f5CBe3b464004E87eF9c9419a48018cd", + "Uniswap 1": "0x9a13F98Cb987694C9F086b1F5eB990EeA8264Ec3", + "Uniswap 2": "0xFC1f3296458F9b2a27a0B91dd7681C4020E09D05", + "Uniswap 3": "0xA39fe8F7A00CE28B572617d3a0bC1c2B44110e79", + "WooFi 1": "0x5520385bFcf07Ec87C4c53A7d8d65595Dff69FA4", + Curve: "0xd8dD9a8b2AcA88E68c46aF9008259d0EC04b7751", + Balancer: "0xBA12222222228d8Ba445958a75a0704d566BF2C8", + Mux: "0xc6BD76FA1E9e789345e003B361e4A0037DFb7260", + }, + [DaoIdEnum.TEST]: { + // DEX pools - Charlie and David for comprehensive coverage + Charlie_DEX_Pool: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", // Charlie as DEX + David_DEX_Pool: "0x90F79bf6EB2c4f870365E785982E1f101E93b906", // David as DEX + // ENS contract addresses involved in DEX-like operations + ENSToken: "0x244dE6b06E7087110b94Cde88A42d9aBA17efa52", + ENSTimelock: "0xa7E99C1df635d13d61F7c81eCe571cc952E64526", + }, + [DaoIdEnum.GTC]: { + Uniswap: "0xD017617f6F0fD22796E137a8240cc38F52a147B2", + }, + [DaoIdEnum.SCR]: { + Honeypop: "0x7761786afAB6E496e6Bf3EBe56fc2ea71cd02d7D", + DEX: "0x7761786afAB6E496e6Bf3EBe56fc2ea71cd02d7D", + "Ambient Finance": "0xaaaaAAAACB71BF2C8CaE522EA5fa455571A74106", + SyncSwap: "0x7160570BB153Edd0Ea1775EC2b2Ac9b65F1aB61B", + Nuri: "0x76c662b1e25CB67D7365191B55813D8CD3Fdac02", + }, + [DaoIdEnum.COMP]: { + Uniswap: "0x5598931BfBb43EEC686fa4b5b92B5152ebADC2f6", + "Uniswap 2": "0xea4Ba4CE14fdd287f380b55419B1C5b6c3f22ab6", + "Pancake Swap": "0x0392957571F28037607C14832D16f8B653eDd472", + }, + [DaoIdEnum.OBOL]: { + "Uniswap V3 Pool": "0x57F52C9faa6D40c5163D76b8D7dD81ddB7c95434", + "Uniswap PoolManager": "0x000000000004444c5dc75cB358380D2e3dE08A90", + }, + [DaoIdEnum.ZK]: { + "Pancake Swap": "0xf92b0178bc932a59d45c1c4aac81712aac6a5b61", + Uniswap: "0x3d7264539E6e3f596bb485E3091f3Ae02Ad01ef8", + }, + [DaoIdEnum.SHU]: { + "Uniswap V3": "0x7A922aea89288d8c91777BeECc68DF4A17151df1", + }, + [DaoIdEnum.FLUID]: { + "Uniswap V3 INST/WETH": "0xc1cd3D0913f4633b43FcdDBCd7342bC9b71C676f", + }, +}; + +export const LendingAddresses: Record> = { + [DaoIdEnum.UNI]: { + AaveEthUni: "0xF6D2224916DDFbbab6e6bd0D1B7034f4Ae0CaB18", + MorphoBlue: "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb", + CompoundCUNI: "0x35A18000230DA775CAc24873d00Ff85BccdeD550", + }, + [DaoIdEnum.ENS]: { + //After research using intel.arkm and defi llama token-usage page, I only found this lending address so far + AaveEthENS: "0x545bD6c032eFdde65A377A6719DEF2796C8E0f2e", + }, + [DaoIdEnum.ARB]: {}, + [DaoIdEnum.AAVE]: {}, + [DaoIdEnum.NOUNS]: {}, + [DaoIdEnum.LIL_NOUNS]: {}, + [DaoIdEnum.OP]: { + Aave: "0x513c7E3a9c69cA3e22550eF58AC1C0088e918FFf", + Superfluid: "0x1828Bff08BD244F7990edDCd9B19cc654b33cDB4", + Moonwell: "0x9fc345a20541Bf8773988515c5950eD69aF01847", + "Silo Finance": "0x8ED1609D796345661d36291B411992e85DE7B224", + "Compound 1": "0x2e44e174f7D53F0212823acC11C01A11d58c5bCB", + "Compound 2": "0x995E394b8B2437aC8Ce61Ee0bC610D617962B214", + "Exactly Protocol": "0xa430A427bd00210506589906a71B54d6C256CEdb", + Morpho: "0xF057afeEc22E220f47AD4220871364e9E828b2e9", + dForce: "0x7702dC73e8f8D9aE95CF50933aDbEE68e9F1D725", + }, + [DaoIdEnum.TEST]: { + // Lending protocols - different addresses for comprehensive flag coverage + Alice_Lending_Protocol: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Alice as lending + Charlie_Lending_Pool: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", // Charlie as lending + // ENS contract addresses involved in lending-like operations + ENSGovernor: "0x7c28FC9709650D49c8d0aED2f6ece6b191F192a9", + ENSTimelock: "0xa7E99C1df635d13d61F7c81eCe571cc952E64526", + }, + [DaoIdEnum.GTC]: {}, + [DaoIdEnum.SCR]: { + Aave: "0x25718130C2a8eb94e2e1FAFB5f1cDd4b459aCf64", + }, + [DaoIdEnum.COMP]: { + Compound: "0xc3d688B66703497DAA19211EEdff47f25384cdc3", + "Compound 2": "0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840", + }, + [DaoIdEnum.OBOL]: {}, + [DaoIdEnum.ZK]: { + Aave: "0xd6cd2c0fc55936498726cacc497832052a9b2d1b", + Venus: "0x697a70779c1a03ba2bd28b7627a902bff831b616", + }, + [DaoIdEnum.SHU]: {}, + [DaoIdEnum.FLUID]: {}, +}; + +export const BurningAddresses: Record< + DaoIdEnum, + { + ZeroAddress: Address; + Dead: Address; + TokenContract: Address; + Airdrop?: Address; + } +> = { + [DaoIdEnum.UNI]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + Airdrop: "0x090D4613473dEE047c3f2706764f49E0821D256e", + }, + [DaoIdEnum.ENS]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72", + }, + [DaoIdEnum.ARB]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1", + }, + [DaoIdEnum.AAVE]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0x000000000000000000000000000000000000dEaD", + }, + [DaoIdEnum.OP]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0x4200000000000000000000000000000000000042", + }, + [DaoIdEnum.TEST]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0x244dE6b06E7087110b94Cde88A42d9aBA17efa52", + }, + [DaoIdEnum.GTC]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0xDe30da39c46104798bB5aA3fe8B9e0e1F348163F", + }, + [DaoIdEnum.NOUNS]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0x9C8fF314C9Bc7F6e59A9d9225Fb22946427eDC03", + }, + [DaoIdEnum.LIL_NOUNS]: { + ZeroAddress: zeroAddress, + Dead: "0x0000000000000000000000000000000000000000", + TokenContract: "0x4b10701Bfd7BFEdc47d50562b76b436fbB5BdB3B", + }, + [DaoIdEnum.SCR]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0xd29687c813D741E2F938F4aC377128810E217b1b", + }, + [DaoIdEnum.COMP]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0xc00e94Cb662C3520282E6f5717214004A7f26888", + }, + [DaoIdEnum.OBOL]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0x0B010000b7624eb9B3DfBC279673C76E9D29D5F7", + }, + [DaoIdEnum.ZK]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E", + }, + [DaoIdEnum.SHU]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0xe485E2f1bab389C08721B291f6b59780feC83Fd7", + }, + [DaoIdEnum.FLUID]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0x6f40d4A6237C257fff2dB00FA0510DeEECd303eb", + }, +}; + +export const NonCirculatingAddresses: Record< + DaoIdEnum, + Record +> = { + [DaoIdEnum.UNI]: {}, + [DaoIdEnum.ENS]: { + // https://etherscan.io/address/0xd7a029db2585553978190db5e85ec724aa4df23f + // Linear vesting for contributors, unlock end Dec 2025 + "Token Timelock": "0xd7a029db2585553978190db5e85ec724aa4df23f", + }, + [DaoIdEnum.ARB]: {}, + [DaoIdEnum.AAVE]: { + // https://etherscan.io/address/0x317625234562B1526Ea2FaC4030Ea499C5291de4 + // Permanently locked - LEND migration discontinued + "LEND to AAVE Migrator": "0x317625234562B1526Ea2FaC4030Ea499C5291de4", + }, + [DaoIdEnum.OP]: {}, + [DaoIdEnum.NOUNS]: {}, + [DaoIdEnum.TEST]: {}, + [DaoIdEnum.GTC]: {}, + [DaoIdEnum.SCR]: {}, + [DaoIdEnum.FLUID]: {}, + [DaoIdEnum.COMP]: {}, + [DaoIdEnum.OBOL]: {}, + [DaoIdEnum.ZK]: { + // https://docs.zknation.io/zk-nation/zksync-governance-contract-addresses + "Initial Merkle Distributor": "0x66fd4fc8fa52c9bec2aba368047a0b27e24ecfe4", + "Second ZK Distributor": "0xb294F411cB52c7C6B6c0B0b61DBDf398a8b0725d", + "Third ZK Distributor": "0xf29d698e74ef1904bcfdb20ed38f9f3ef0a89e5b", + "Matter Labs Allocation": "0xa97fbc75ccbc7d4353c4d2676ed18cd0c5aaf7e6", + "Foundation Allocation": "0xd78dc27d4db8f428c67f542216a2b23663838405", + "Guardians Allocation": "0x21b27952f8621f54f3cb652630e122ec81dd2dc1", + "Security Council Allocation": "0x0ad50686c159040e57ddce137db0b63c67473450", + "ZKsync Association Allocation": + "0x0681e3808a0aa12004fb815ebb4515dc823cfbb4", + }, + [DaoIdEnum.LIL_NOUNS]: {}, + [DaoIdEnum.SHU]: {}, +}; + +export const ProposalStatus = { + PENDING: "PENDING", + ACTIVE: "ACTIVE", + CANCELED: "CANCELED", + DEFEATED: "DEFEATED", + SUCCEEDED: "SUCCEEDED", + QUEUED: "QUEUED", + EXPIRED: "EXPIRED", + EXECUTED: "EXECUTED", + VETOED: "VETOED", + NO_QUORUM: "NO_QUORUM", +} as const; + +export type ProposalStatus = + (typeof ProposalStatus)[keyof typeof ProposalStatus]; + +export const MetricTypesEnum = { + TOTAL_SUPPLY: "TOTAL_SUPPLY", + DELEGATED_SUPPLY: "DELEGATED_SUPPLY", + CEX_SUPPLY: "CEX_SUPPLY", + DEX_SUPPLY: "DEX_SUPPLY", + LENDING_SUPPLY: "LENDING_SUPPLY", + CIRCULATING_SUPPLY: "CIRCULATING_SUPPLY", + TREASURY: "TREASURY", + NON_CIRCULATING_SUPPLY: "NON_CIRCULATING_SUPPLY", +} as const; + +export type MetricTypesEnum = + (typeof MetricTypesEnum)[keyof typeof MetricTypesEnum]; + +export const metricTypeArray = Object.values(MetricTypesEnum); diff --git a/apps/hypersync-indexer/src/lib/date-helpers.ts b/apps/hypersync-indexer/src/lib/date-helpers.ts new file mode 100644 index 000000000..4ef8104d5 --- /dev/null +++ b/apps/hypersync-indexer/src/lib/date-helpers.ts @@ -0,0 +1,62 @@ +/** + * Date and timestamp utilities for time-series data processing. + */ + +import { SECONDS_IN_DAY } from "./enums.ts"; + +/** + * Truncate timestamp (seconds) to midnight UTC + */ +export const truncateTimestampToMidnight = (timestampSec: number): number => { + return Math.floor(timestampSec / SECONDS_IN_DAY) * SECONDS_IN_DAY; +}; + +/** + * Calculate cutoff timestamp for filtering data by days + */ +export const calculateCutoffTimestamp = (days: number): number => { + return Math.floor(Date.now() / 1000) - days * SECONDS_IN_DAY; +}; + +/** + * Normalize all timestamps in a Map to midnight UTC (seconds) + */ +export const normalizeMapTimestamps = ( + map: Map, +): Map => { + const normalized = new Map(); + map.forEach((value, ts) => { + normalized.set(truncateTimestampToMidnight(ts), value); + }); + return normalized; +}; + +/** + * Get effective start date, adjusting if no initial value exists. + * + * When querying time-series data with forward-fill, if there's no initial value + * before the requested start date, we should start from the first real data point + * to avoid returning zeros/nulls. + * + * @param params.referenceDate - Requested start date (after ?? startDate) + * @param params.datesFromDb - Array of timestamps from database + * @param params.hasInitialValue - Whether an initial value exists before referenceDate + * @returns Effective start date to use + */ +export function getEffectiveStartDate(params: { + referenceDate?: number; + datesFromDb: number[]; + hasInitialValue: boolean; +}): number | undefined { + const { referenceDate, datesFromDb, hasInitialValue } = params; + + if (!referenceDate) return undefined; + if (hasInitialValue || datesFromDb.length === 0) return referenceDate; + + const sortedDates = [...datesFromDb].sort((a, b) => a - b); + const firstRealDate = sortedDates[0]; + + return firstRealDate && referenceDate < firstRealDate + ? firstRealDate + : referenceDate; +} diff --git a/apps/hypersync-indexer/src/lib/enums.ts b/apps/hypersync-indexer/src/lib/enums.ts new file mode 100644 index 000000000..84f040ae6 --- /dev/null +++ b/apps/hypersync-indexer/src/lib/enums.ts @@ -0,0 +1,21 @@ +export const DaoIdEnum = { + AAVE: "AAVE", + UNI: "UNI", + ENS: "ENS", + ARB: "ARB", + OP: "OP", + GTC: "GTC", + NOUNS: "NOUNS", + TEST: "TEST", + SCR: "SCR", + COMP: "COMP", + OBOL: "OBOL", + ZK: "ZK", + SHU: "SHU", + FLUID: "FLUID", + LIL_NOUNS: "LIL_NOUNS", +} as const; + +export type DaoIdEnum = (typeof DaoIdEnum)[keyof typeof DaoIdEnum]; + +export const SECONDS_IN_DAY = 24 * 60 * 60; diff --git a/apps/hypersync-indexer/src/lib/utils.ts b/apps/hypersync-indexer/src/lib/utils.ts new file mode 100644 index 000000000..907649e51 --- /dev/null +++ b/apps/hypersync-indexer/src/lib/utils.ts @@ -0,0 +1,32 @@ +import * as chains from "viem/chains"; + +/** + * Calculates the absolute difference between two numbers + */ +export function delta(a: bigint, b: bigint): bigint { + return a > b ? a - b : b - a; +} + +/** + * Returns the minimum of two or more numbers + */ +export function min(...values: bigint[]): bigint { + if (values.length === 0) { + throw new Error("At least one value must be provided"); + } + return values.reduce((min, value) => (value < min ? value : min)); +} + +/** + * Returns the maximum of two or more numbers + */ +export function max(...values: bigint[]): bigint { + if (values.length === 0) { + throw new Error("At least one value must be provided"); + } + return values.reduce((max, value) => (value > max ? value : max)); +} + +export function getChain(chainId: number): chains.Chain | undefined { + return Object.values(chains).find((chain) => chain.id === chainId); +} diff --git a/apps/hypersync-indexer/tsconfig.json b/apps/hypersync-indexer/tsconfig.json new file mode 100644 index 000000000..d17640ae8 --- /dev/null +++ b/apps/hypersync-indexer/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + // Type checking + "strict": true, + "noUncheckedIndexedAccess": true, + // Interop constraints + "verbatimModuleSyntax": false, + "esModuleInterop": true, + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + // Language and environment + "moduleResolution": "bundler", + "module": "ESNext", + "noEmit": true, + "lib": ["ES2022"], + "target": "ES2022", + "skipLibCheck": true, + "baseUrl": "." + }, + "ts-node": { + "compilerOptions": { + "module": "CommonJS", + "moduleResolution": "node" + } + }, + "include": [ + "src/eventHandlers/**/*.ts", + "src/lib/**/*.ts", + "generated/**/*.ts" + ], + "exclude": ["node_modules", "test"] +} diff --git a/eslint.config.mjs b/eslint.config.mjs index a0f56dca2..8a78324c7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -31,6 +31,7 @@ export default [ "apps/api-gateway/schema.graphql", "**/storybook-static/**", "**/.storybook/**", + "apps/hypersync-indexer/generated/**", ], }, @@ -134,6 +135,14 @@ export default [ }, }, + // HyperIndex indexer lib - allow const + type with same name pattern + { + files: ["apps/hypersync-indexer/src/lib/**/*.{js,ts}"], + rules: { + "@typescript-eslint/no-redeclare": "off", + }, + }, + // Dashboard — Storybook rules for story files ...storybookPlugin.configs["flat/recommended"], diff --git a/package.json b/package.json index 26bf3f80e..d3d094781 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "dashboard": "dotenv -- turbo run --filter=@anticapture/dashboard", "indexer": "dotenv -- turbo run --filter=@anticapture/indexer", + "hypersync-indexer": "dotenv -- turbo run --filter=@anticapture/hypersync-indexer", "gateway": "dotenv -- turbo run --filter=@anticapture/api-gateway", "gateful": "dotenv -- turbo run --filter=@anticapture/gateful", "api": "dotenv -- turbo run --filter=@anticapture/api", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3cc4a33d..68958c40d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,7 +102,7 @@ importers: version: 0.31.9 drizzle-orm: specifier: ~0.41.0 - version: 0.41.0(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0) + version: 0.41.0(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0)(postgres@3.4.1) hono: specifier: ^4.7.10 version: 4.12.7 @@ -163,7 +163,7 @@ importers: version: 0.31.9 drizzle-orm: specifier: ^0.45.1 - version: 0.45.1(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0) + version: 0.45.1(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0)(postgres@3.4.1) hono: specifier: ^4.7.10 version: 4.12.7 @@ -553,6 +553,37 @@ importers: specifier: ^4.0.18 version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.4.0)(jsdom@24.1.3(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.12.10(@types/node@25.4.0)(typescript@5.9.3))(vite@7.0.5(@types/node@25.4.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + apps/hypersync-indexer: + dependencies: + viem: + specifier: ^2.37.11 + version: 2.47.2(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + devDependencies: + "@types/node": + specifier: ^20.16.5 + version: 20.19.37 + dotenv: + specifier: ^16.5.0 + version: 16.6.1 + envio: + specifier: ^2.32.12 + version: 2.32.12(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + eslint: + specifier: ^9 + version: 9.39.4(jiti@2.6.1) + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.2(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-prettier: + specifier: ^5.2.1 + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@9.1.2(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.1) + prettier: + specifier: ^3.5.3 + version: 3.8.1 + typescript: + specifier: ^5.8.3 + version: 5.9.3 + apps/indexer: dependencies: "@anticapture/observability": @@ -566,7 +597,7 @@ importers: version: 4.12.7 ponder: specifier: ^0.16.2 - version: 0.16.3(@opentelemetry/api@1.9.0)(@types/node@20.19.37)(@types/pg@8.18.0)(bufferutil@4.0.9)(hono@4.12.7)(lightningcss@1.31.1)(terser@5.46.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.47.2(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(zod@3.25.76) + version: 0.16.3(@opentelemetry/api@1.9.0)(@types/node@20.19.37)(@types/pg@8.18.0)(bufferutil@4.0.9)(hono@4.12.7)(lightningcss@1.31.1)(postgres@3.4.1)(terser@5.46.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.47.2(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(zod@3.25.76) viem: specifier: ^2.37.11 version: 2.47.2(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) @@ -615,7 +646,7 @@ importers: version: 1.13.6 drizzle-orm: specifier: ^0.45.1 - version: 0.45.1(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0) + version: 0.45.1(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0)(postgres@3.4.1) pg: specifier: ^8.17.2 version: 8.20.0 @@ -698,7 +729,7 @@ importers: version: 4.9.6 forge-std: specifier: github:foundry-rs/forge-std - version: https://codeload.github.com/foundry-rs/forge-std/tar.gz/50fbb2ca69a24b8991771787dfa023c0429584e5 + version: https://codeload.github.com/foundry-rs/forge-std/tar.gz/f494b0c2c045dda3df3d761bc82209b9a015c4e7 packages/observability: dependencies: @@ -765,6 +796,12 @@ packages: integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==, } + "@adraffy/ens-normalize@1.10.0": + resolution: + { + integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==, + } + "@adraffy/ens-normalize@1.10.1": resolution: { @@ -2208,6 +2245,20 @@ packages: peerDependencies: "@noble/ciphers": ^1.0.0 + "@elastic/ecs-helpers@1.1.0": + resolution: + { + integrity: sha512-MDLb2aFeGjg46O5mLpdCzT5yOUDnXToJSrco2ShqGIXxNJaM8uJjX+4nd+hRYV4Vex8YJyDtOFEVBldQct6ndg==, + } + engines: { node: ">=10" } + + "@elastic/ecs-pino-format@1.4.0": + resolution: + { + integrity: sha512-eCSBUTgl8KbPyxky8cecDRLCYu2C1oFV4AZ72bEsI+TxXEvaljaL2kgttfzfu7gW+M89eCz55s49uF2t+YMTWA==, + } + engines: { node: ">=10" } + "@electric-sql/pglite@0.2.13": resolution: { @@ -2298,6 +2349,119 @@ packages: } engines: { node: ">=18.0.0" } + "@envio-dev/hyperfuel-client-darwin-arm64@1.2.2": + resolution: + { + integrity: sha512-eQyd9kJCIz/4WCTjkjpQg80DA3pdneHP7qhJIVQ2ZG+Jew9o5XDG+uI0Y16AgGzZ6KGmJSJF6wyUaaAjJfbO1Q==, + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [darwin] + + "@envio-dev/hyperfuel-client-darwin-x64@1.2.2": + resolution: + { + integrity: sha512-l7lRMSoyIiIvKZgQPfgqg7H1xnrQ37A8yUp4S2ys47R8f/wSCSrmMaY1u7n6CxVYCpR9fajwy0/356UgwwhVKw==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [darwin] + + "@envio-dev/hyperfuel-client-linux-arm64-gnu@1.2.2": + resolution: + { + integrity: sha512-kNiC/1fKuXnoSxp8yEsloDw4Ot/mIcNoYYGLl2CipSIpBtSuiBH5nb6eBcxnRZdKOwf5dKZtZ7MVPL9qJocNJw==, + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [linux] + + "@envio-dev/hyperfuel-client-linux-x64-gnu@1.2.2": + resolution: + { + integrity: sha512-XDkvkBG/frS+xiZkJdY4KqOaoAwyxPdi2MysDQgF8NmZdssi32SWch0r4LTqKWLLlCBg9/R55POeXL5UAjg2wQ==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [linux] + + "@envio-dev/hyperfuel-client-linux-x64-musl@1.2.2": + resolution: + { + integrity: sha512-DKnKJJSwsYtA7YT0EFGhFB5Eqoo42X0l0vZBv4lDuxngEXiiNjeLemXoKQVDzhcbILD7eyXNa5jWUc+2hpmkEg==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [linux] + + "@envio-dev/hyperfuel-client-win32-x64-msvc@1.2.2": + resolution: + { + integrity: sha512-SwIgTAVM9QhCFPyHwL+e1yQ6o3paV6q25klESkXw+r/KW9QPhOOyA6Yr8nfnur3uqMTLJHAKHTLUnkyi/Nh7Aw==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [win32] + + "@envio-dev/hyperfuel-client@1.2.2": + resolution: + { + integrity: sha512-raKA6DshYSle0sAOHBV1OkSRFMN+Mkz8sFiMmS3k+m5nP6pP56E17CRRePBL5qmR6ZgSEvGOz/44QUiKNkK9Pg==, + } + engines: { node: ">= 10" } + + "@envio-dev/hypersync-client-darwin-arm64@1.3.0": + resolution: + { + integrity: sha512-JZwiVRbMSuJnKsVUpfjTHc3YgAMvGlyuqWQxVc7Eok4Xp/sZLUCXRQUykbCh6fOUWRmoa2JG/ykP/NotoTRCBg==, + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [darwin] + + "@envio-dev/hypersync-client-darwin-x64@1.3.0": + resolution: + { + integrity: sha512-2eSzQqqqFBMK2enVucYGcny5Ep4DEKYxf3Xme7z9qp2d3c6fMcbVvM4Gt8KOzb7ySjwJ2gU+qY2h545T2NiJXQ==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [darwin] + + "@envio-dev/hypersync-client-linux-arm64-gnu@1.3.0": + resolution: + { + integrity: sha512-gsjMp3WKekwnA89HvJXvcTM3BE5wVFG/qTF4rmk3rGiXhZ+MGaZQKrYRAhnzQZblueFtF/xnnBYpO35Z3ZFThg==, + } + engines: { node: ">= 10" } + cpu: [arm64] + os: [linux] + + "@envio-dev/hypersync-client-linux-x64-gnu@1.3.0": + resolution: + { + integrity: sha512-Lkvi4lRVwCyFOXf9LYH2X91zmW2l1vbfojKhTwKgqFWv6PMN5atlYjt+/NcUCAAhk5EUavWGjoikwnvLp870cg==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [linux] + + "@envio-dev/hypersync-client-linux-x64-musl@1.3.0": + resolution: + { + integrity: sha512-UIjB/gUX2sl23EMXLBxqtkgMnOjNSiaHK+CSU5vXMXkzL3fOGbz24bvyaPsSv82cxCFEE0yTwlSKkCX6/L8o6Q==, + } + engines: { node: ">= 10" } + cpu: [x64] + os: [linux] + + "@envio-dev/hypersync-client@1.3.0": + resolution: + { + integrity: sha512-wUdfZzbsFPbGq6n/1mmUMsWuiAil+m+fL/GBX5LGUyMJV86TXy2SBtAqYYNyDxWLO6gvGr6PYKrP8pLVAUZDZg==, + } + engines: { node: ">= 10" } + "@esbuild-kit/core-utils@3.3.2": resolution: { @@ -5457,6 +5621,12 @@ packages: integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==, } + "@noble/curves@1.4.0": + resolution: + { + integrity: sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==, + } + "@noble/curves@1.4.2": resolution: { @@ -9244,6 +9414,20 @@ packages: zod: optional: true + abitype@1.0.5: + resolution: + { + integrity: sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw==, + } + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + abitype@1.0.6: resolution: { @@ -9325,14 +9509,6 @@ packages: } engines: { node: ">=0.4.0" } - acorn@8.15.0: - resolution: - { - integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, - } - engines: { node: ">=0.4.0" } - hasBin: true - acorn@8.16.0: resolution: { @@ -9897,6 +10073,12 @@ packages: integrity: sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==, } + bignumber.js@9.1.2: + resolution: + { + integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==, + } + bintrees@1.0.2: resolution: { @@ -11843,6 +12025,45 @@ packages: } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + envio-darwin-arm64@2.32.12: + resolution: + { + integrity: sha512-TLs9jjXUHVqKcBReMHgD7C06lbfWfnMkit3uT55XmgiJYc8zS85T0XmDCnCX4BRbZN7uzMNORqnUc2J3/LR9sQ==, + } + cpu: [arm64] + os: [darwin] + + envio-darwin-x64@2.32.12: + resolution: + { + integrity: sha512-JfKU3LaqxO/aabEAIvpHGKhDGNEiVGvcmmi98cZfG1/vP4S5lO+8KDEp563CaB986N6KtGJRKnDWivvCsseZMw==, + } + cpu: [x64] + os: [darwin] + + envio-linux-arm64@2.32.12: + resolution: + { + integrity: sha512-3sBfuR6JLcAkrFcoEfw2WiaPU3VyXGy4kf26HB5BJE/iJUqha+wHoDbv46MfFGuaC0QyM34QvlG0yGRES0ohPw==, + } + cpu: [arm64] + os: [linux] + + envio-linux-x64@2.32.12: + resolution: + { + integrity: sha512-886q+yztKVrhgkwOfoFKARDStbjk1032YBtA6tqrCN8uWjqgzAf30ZDPurJGlq26hQqYNKRp2LhgxChpivsvFw==, + } + cpu: [x64] + os: [linux] + + envio@2.32.12: + resolution: + { + integrity: sha512-bk9y/AjU+kYxO1a9c/jg8RFDrKKKWU0wCffnwtoXo7KGKmPDKq1WyNzVw6sTeboSfGB0i82hJ97WgSAwRAnR1Q==, + } + hasBin: true + environment@1.1.0: resolution: { @@ -12358,6 +12579,20 @@ packages: } engines: { node: ">=0.8.x" } + eventsource-parser@3.0.6: + resolution: + { + integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==, + } + engines: { node: ">=18.0.0" } + + eventsource@4.1.0: + resolution: + { + integrity: sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==, + } + engines: { node: ">=20.0.0" } + evp_bytestokey@1.0.3: resolution: { @@ -12420,6 +12655,12 @@ packages: } engines: { node: "> 0.1.90" } + fast-copy@3.0.2: + resolution: + { + integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==, + } + fast-copy@4.0.2: resolution: { @@ -12477,6 +12718,13 @@ packages: integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, } + fast-json-stringify@2.7.13: + resolution: + { + integrity: sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA==, + } + engines: { node: ">= 10.0.0" } + fast-json-stringify@5.16.1: resolution: { @@ -12692,12 +12940,6 @@ packages: } engines: { node: ">=16" } - flatted@3.3.3: - resolution: - { - integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, - } - flatted@3.4.1: resolution: { @@ -12736,10 +12978,10 @@ packages: } engines: { node: ">=14" } - forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/50fbb2ca69a24b8991771787dfa023c0429584e5: + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/f494b0c2c045dda3df3d761bc82209b9a015c4e7: resolution: { - tarball: https://codeload.github.com/foundry-rs/forge-std/tar.gz/50fbb2ca69a24b8991771787dfa023c0429584e5, + tarball: https://codeload.github.com/foundry-rs/forge-std/tar.gz/f494b0c2c045dda3df3d761bc82209b9a015c4e7, } version: 1.15.0 @@ -12975,6 +13217,14 @@ packages: } deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + glob@8.1.0: + resolution: + { + integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==, + } + engines: { node: ">=12" } + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + global-directory@4.0.1: resolution: { @@ -13278,6 +13528,12 @@ packages: integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==, } + help-me@4.2.0: + resolution: + { + integrity: sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==, + } + help-me@5.0.0: resolution: { @@ -14014,6 +14270,14 @@ packages: peerDependencies: ws: "*" + isows@1.0.4: + resolution: + { + integrity: sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ==, + } + peerDependencies: + ws: "*" + isows@1.0.6: resolution: { @@ -15327,6 +15591,13 @@ packages: integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==, } + minimatch@5.1.9: + resolution: + { + integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==, + } + engines: { node: ">=10" } + minimatch@9.0.9: resolution: { @@ -16325,6 +16596,12 @@ packages: integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==, } + pino-abstract-transport@1.1.0: + resolution: + { + integrity: sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==, + } + pino-abstract-transport@1.2.0: resolution: { @@ -16357,6 +16634,13 @@ packages: peerDependencies: pino: ^8.21.0 || ^9.0.0 + pino-pretty@10.2.3: + resolution: + { + integrity: sha512-4jfIUc8TC1GPUfDyMSlW1STeORqkoxec71yhxIpLDQapUu8WOuoz2TTCoidrIssyz78LZC69whBMPIKCMbi3cw==, + } + hasBin: true + pino-pretty@13.1.3: resolution: { @@ -16389,6 +16673,13 @@ packages: } hasBin: true + pino@8.16.1: + resolution: + { + integrity: sha512-3bKsVhBmgPjGV9pyn4fO/8RtoVDR8ssW1ev819FsRXlRNgW8gR/9Kx+gCK4UPWd4JjrRDLWpzd/pb1AyWm3MGA==, + } + hasBin: true + pino@8.21.0: resolution: { @@ -16658,6 +16949,13 @@ packages: } engines: { node: ">=0.10.0" } + postgres@3.4.1: + resolution: + { + integrity: sha512-Wasjv6WEzrZXbwKByR2RGD7MBfj7VBqco3hYWz8ifzSAp6tb2L6MlmcKFzkmgV1jT7/vKlcSa+lxXZeTdeVMzQ==, + } + engines: { node: ">=12" } + preact@10.24.2: resolution: { @@ -16796,6 +17094,12 @@ packages: integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==, } + process-warning@2.3.2: + resolution: + { + integrity: sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==, + } + process-warning@3.0.0: resolution: { @@ -16815,6 +17119,13 @@ packages: } engines: { node: ">= 0.6.0" } + prom-client@15.0.0: + resolution: + { + integrity: sha512-UocpgIrKyA2TKLVZDSfm8rGkL13C19YrQBAiG3xo3aDFWcHedxRxI3z+cIcucoxpSO0h5lff5iv/SXoxyeopeA==, + } + engines: { node: ^16 || ^18 || >=20 } + prom-client@15.1.3: resolution: { @@ -17424,6 +17735,25 @@ packages: integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==, } + rescript-schema@9.3.0: + resolution: + { + integrity: sha512-NiHAjlhFKZCmNhx/Ij40YltCEJJgVNhBWTN/ZfagTg5hdWWuvCiUacxZv+Q/QQolrAhTnHnCrL7RDvZBogHl5A==, + } + peerDependencies: + rescript: 11.x + peerDependenciesMeta: + rescript: + optional: true + + rescript@11.1.3: + resolution: + { + integrity: sha512-bI+yxDcwsv7qE34zLuXeO8Qkc2+1ng5ErlSjnUIZdrAWKoGzHXpJ6ZxiiRBUoYnoMsgRwhqvrugIFyNgWasmsw==, + } + engines: { node: ">=10" } + hasBin: true + resend@6.9.3: resolution: { @@ -17721,6 +18051,12 @@ packages: integrity: sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==, } + secure-json-parse@2.7.0: + resolution: + { + integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==, + } + secure-json-parse@4.1.0: resolution: { @@ -18185,6 +18521,13 @@ packages: } engines: { node: ">=10" } + string-similarity@4.0.4: + resolution: + { + integrity: sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==, + } + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + string-width@4.2.3: resolution: { @@ -19614,6 +19957,17 @@ packages: integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==, } + viem@2.21.0: + resolution: + { + integrity: sha512-9g3Gw2nOU6t4bNuoDI5vwVExzIxseU0J7Jjx10gA2RNQVrytIrLxggW++tWEe3w4mnnm/pS1WgZFjQ/QKf/nHw==, + } + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + viem@2.23.2: resolution: { @@ -19862,6 +20216,12 @@ packages: } engines: { node: ">= 8" } + webauthn-p256@0.0.5: + resolution: + { + integrity: sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg==, + } + webextension-polyfill@0.10.0: resolution: { @@ -20417,6 +20777,8 @@ packages: snapshots: "@adobe/css-tools@4.4.3": {} + "@adraffy/ens-normalize@1.10.0": {} + "@adraffy/ens-normalize@1.10.1": {} "@adraffy/ens-normalize@1.11.1": {} @@ -21694,7 +22056,7 @@ snapshots: "@commitlint/config-validator@19.8.1": dependencies: "@commitlint/types": 19.8.1 - ajv: 8.17.1 + ajv: 8.18.0 "@commitlint/ensure@19.8.1": dependencies: @@ -21813,6 +22175,14 @@ snapshots: dependencies: "@noble/ciphers": 1.3.0 + "@elastic/ecs-helpers@1.1.0": + dependencies: + fast-json-stringify: 2.7.13 + + "@elastic/ecs-pino-format@1.4.0": + dependencies: + "@elastic/ecs-helpers": 1.1.0 + "@electric-sql/pglite@0.2.13": {} "@electric-sql/pglite@0.3.16": {} @@ -21876,6 +22246,56 @@ snapshots: "@whatwg-node/promise-helpers": 1.3.2 tslib: 2.8.1 + "@envio-dev/hyperfuel-client-darwin-arm64@1.2.2": + optional: true + + "@envio-dev/hyperfuel-client-darwin-x64@1.2.2": + optional: true + + "@envio-dev/hyperfuel-client-linux-arm64-gnu@1.2.2": + optional: true + + "@envio-dev/hyperfuel-client-linux-x64-gnu@1.2.2": + optional: true + + "@envio-dev/hyperfuel-client-linux-x64-musl@1.2.2": + optional: true + + "@envio-dev/hyperfuel-client-win32-x64-msvc@1.2.2": + optional: true + + "@envio-dev/hyperfuel-client@1.2.2": + optionalDependencies: + "@envio-dev/hyperfuel-client-darwin-arm64": 1.2.2 + "@envio-dev/hyperfuel-client-darwin-x64": 1.2.2 + "@envio-dev/hyperfuel-client-linux-arm64-gnu": 1.2.2 + "@envio-dev/hyperfuel-client-linux-x64-gnu": 1.2.2 + "@envio-dev/hyperfuel-client-linux-x64-musl": 1.2.2 + "@envio-dev/hyperfuel-client-win32-x64-msvc": 1.2.2 + + "@envio-dev/hypersync-client-darwin-arm64@1.3.0": + optional: true + + "@envio-dev/hypersync-client-darwin-x64@1.3.0": + optional: true + + "@envio-dev/hypersync-client-linux-arm64-gnu@1.3.0": + optional: true + + "@envio-dev/hypersync-client-linux-x64-gnu@1.3.0": + optional: true + + "@envio-dev/hypersync-client-linux-x64-musl@1.3.0": + optional: true + + "@envio-dev/hypersync-client@1.3.0": + optionalDependencies: + "@envio-dev/hypersync-client-darwin-arm64": 1.3.0 + "@envio-dev/hypersync-client-darwin-x64": 1.3.0 + "@envio-dev/hypersync-client-linux-arm64-gnu": 1.3.0 + "@envio-dev/hypersync-client-linux-x64-gnu": 1.3.0 + "@envio-dev/hypersync-client-linux-x64-musl": 1.3.0 + "@esbuild-kit/core-utils@3.3.2": dependencies: esbuild: 0.18.20 @@ -24682,6 +25102,10 @@ snapshots: dependencies: "@noble/hashes": 1.3.2 + "@noble/curves@1.4.0": + dependencies: + "@noble/hashes": 1.4.0 + "@noble/curves@1.4.2": dependencies: "@noble/hashes": 1.4.0 @@ -28180,6 +28604,11 @@ snapshots: typescript: 5.9.3 zod: 3.25.76 + abitype@1.0.5(typescript@5.9.3)(zod@3.25.76): + optionalDependencies: + typescript: 5.9.3 + zod: 3.25.76 + abitype@1.0.6(typescript@5.9.3)(zod@3.25.76): optionalDependencies: typescript: 5.9.3 @@ -28217,16 +28646,14 @@ snapshots: dependencies: acorn: 8.16.0 - acorn-jsx@5.3.2(acorn@8.15.0): + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: - acorn: 8.15.0 + acorn: 8.16.0 acorn-walk@8.3.4: dependencies: acorn: 8.16.0 - acorn@8.15.0: {} - acorn@8.16.0: {} adjust-sourcemap-loader@4.0.0: @@ -28257,9 +28684,9 @@ snapshots: optionalDependencies: ajv: 8.17.1 - ajv-formats@3.0.1(ajv@8.17.1): + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: @@ -28269,9 +28696,9 @@ snapshots: dependencies: ajv: 6.14.0 - ajv-keywords@5.1.0(ajv@8.17.1): + ajv-keywords@5.1.0(ajv@8.18.0): dependencies: - ajv: 8.17.1 + ajv: 8.18.0 fast-deep-equal: 3.1.3 ajv@6.14.0: @@ -28638,6 +29065,8 @@ snapshots: big.js@6.2.2: {} + bignumber.js@9.1.2: {} + bintrees@1.0.2: {} bl@4.1.0: @@ -29025,8 +29454,8 @@ snapshots: conf@12.0.0: dependencies: - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) atomically: 2.0.3 debounce-fn: 5.1.2 dot-prop: 8.0.2 @@ -29544,29 +29973,32 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.41.0(@electric-sql/pglite@0.2.13)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0): + drizzle-orm@0.41.0(@electric-sql/pglite@0.2.13)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0)(postgres@3.4.1): optionalDependencies: "@electric-sql/pglite": 0.2.13 "@opentelemetry/api": 1.9.0 "@types/pg": 8.18.0 kysely: 0.26.3 pg: 8.20.0 + postgres: 3.4.1 - drizzle-orm@0.41.0(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0): + drizzle-orm@0.41.0(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0)(postgres@3.4.1): optionalDependencies: "@electric-sql/pglite": 0.3.16 "@opentelemetry/api": 1.9.0 "@types/pg": 8.18.0 kysely: 0.26.3 pg: 8.20.0 + postgres: 3.4.1 - drizzle-orm@0.45.1(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0): + drizzle-orm@0.45.1(@electric-sql/pglite@0.3.16)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0)(postgres@3.4.1): optionalDependencies: "@electric-sql/pglite": 0.3.16 "@opentelemetry/api": 1.9.0 "@types/pg": 8.18.0 kysely: 0.26.3 pg: 8.20.0 + postgres: 3.4.1 dset@3.1.4: {} @@ -29658,6 +30090,42 @@ snapshots: env-paths@3.0.0: {} + envio-darwin-arm64@2.32.12: + optional: true + + envio-darwin-x64@2.32.12: + optional: true + + envio-linux-arm64@2.32.12: + optional: true + + envio-linux-x64@2.32.12: + optional: true + + envio@2.32.12(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76): + dependencies: + "@elastic/ecs-pino-format": 1.4.0 + "@envio-dev/hyperfuel-client": 1.2.2 + "@envio-dev/hypersync-client": 1.3.0 + bignumber.js: 9.1.2 + eventsource: 4.1.0 + pino: 8.16.1 + pino-pretty: 10.2.3 + prom-client: 15.0.0 + rescript: 11.1.3 + rescript-schema: 9.3.0(rescript@11.1.3) + viem: 2.21.0(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76) + optionalDependencies: + envio-darwin-arm64: 2.32.12 + envio-darwin-x64: 2.32.12 + envio-linux-arm64: 2.32.12 + envio-linux-x64: 2.32.12 + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + environment@1.1.0: {} error-ex@1.3.2: @@ -30184,8 +30652,8 @@ snapshots: espree@10.4.0: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 4.2.1 esprima@4.0.1: {} @@ -30267,6 +30735,12 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.6: {} + + eventsource@4.1.0: + dependencies: + eventsource-parser: 3.0.6 + evp_bytestokey@1.0.3: dependencies: md5.js: 1.3.5 @@ -30311,6 +30785,8 @@ snapshots: eyes@0.1.8: {} + fast-copy@3.0.2: {} + fast-copy@4.0.2: {} fast-deep-equal@3.1.3: {} @@ -30343,11 +30819,18 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-json-stringify@2.7.13: + dependencies: + ajv: 6.14.0 + deepmerge: 4.3.1 + rfdc: 1.4.1 + string-similarity: 4.0.4 + fast-json-stringify@5.16.1: dependencies: "@fastify/merge-json-schemas": 0.1.1 - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) fast-deep-equal: 3.1.3 fast-uri: 2.4.0 json-schema-ref-resolver: 1.0.1 @@ -30471,11 +30954,9 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.3 + flatted: 3.4.1 keyv: 4.5.4 - flatted@3.3.3: {} - flatted@3.4.1: {} follow-redirects@1.15.11: {} @@ -30491,7 +30972,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/50fbb2ca69a24b8991771787dfa023c0429584e5: + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/f494b0c2c045dda3df3d761bc82209b9a015c4e7: {} fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.105.4): @@ -30653,6 +31134,14 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + global-directory@4.0.1: dependencies: ini: 4.1.1 @@ -30875,6 +31364,11 @@ snapshots: headers-polyfill@4.0.3: {} + help-me@4.2.0: + dependencies: + glob: 8.1.0 + readable-stream: 3.6.2 + help-me@5.0.0: {} hermes-estree@0.25.1: {} @@ -31287,6 +31781,10 @@ snapshots: dependencies: ws: 8.19.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) + isows@1.0.4(ws@8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + dependencies: + ws: 8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + isows@1.0.6(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: ws: 8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -32210,6 +32708,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.9: dependencies: brace-expansion: 2.0.2 @@ -32241,7 +32743,7 @@ snapshots: mlly@1.8.0: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 pathe: 2.0.3 pkg-types: 1.3.1 ufo: 1.6.1 @@ -32882,6 +33384,11 @@ snapshots: duplexify: 4.1.3 split2: 4.2.0 + pino-abstract-transport@1.1.0: + dependencies: + readable-stream: 4.7.0 + split2: 4.2.0 + pino-abstract-transport@1.2.0: dependencies: readable-stream: 4.7.0 @@ -32910,6 +33417,23 @@ snapshots: transitivePeerDependencies: - "@opentelemetry/api" + pino-pretty@10.2.3: + dependencies: + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 3.0.2 + fast-safe-stringify: 2.1.1 + help-me: 4.2.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.2.0 + pump: 3.0.3 + readable-stream: 4.7.0 + secure-json-parse: 2.7.0 + sonic-boom: 3.8.1 + strip-json-comments: 3.1.1 + pino-pretty@13.1.3: dependencies: colorette: 2.0.20 @@ -32946,6 +33470,20 @@ snapshots: sonic-boom: 2.8.0 thread-stream: 0.15.2 + pino@8.16.1: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.1.0 + pino-std-serializers: 6.2.2 + process-warning: 2.3.2 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 3.8.1 + thread-stream: 2.7.0 + pino@8.21.0: dependencies: atomic-sleep: 1.0.0 @@ -33002,7 +33540,7 @@ snapshots: pngjs@5.0.0: {} - ponder@0.16.3(@opentelemetry/api@1.9.0)(@types/node@20.19.37)(@types/pg@8.18.0)(bufferutil@4.0.9)(hono@4.12.7)(lightningcss@1.31.1)(terser@5.46.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.47.2(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(zod@3.25.76): + ponder@0.16.3(@opentelemetry/api@1.9.0)(@types/node@20.19.37)(@types/pg@8.18.0)(bufferutil@4.0.9)(hono@4.12.7)(lightningcss@1.31.1)(postgres@3.4.1)(terser@5.46.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(viem@2.47.2(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76))(zod@3.25.76): dependencies: "@babel/code-frame": 7.27.1 "@commander-js/extra-typings": 12.1.0(commander@12.1.0) @@ -33019,7 +33557,7 @@ snapshots: dataloader: 2.2.3 detect-package-manager: 3.0.2 dotenv: 16.6.1 - drizzle-orm: 0.41.0(@electric-sql/pglite@0.2.13)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0) + drizzle-orm: 0.41.0(@electric-sql/pglite@0.2.13)(@opentelemetry/api@1.9.0)(@types/pg@8.18.0)(kysely@0.26.3)(pg@8.20.0)(postgres@3.4.1) glob: 10.5.0 graphql: 16.8.2 graphql-yoga: 5.17.1(graphql@16.8.2) @@ -33180,6 +33718,9 @@ snapshots: dependencies: xtend: 4.0.2 + postgres@3.4.1: + optional: true + preact@10.24.2: {} preact@10.29.0: {} @@ -33219,12 +33760,19 @@ snapshots: process-warning@1.0.0: {} + process-warning@2.3.2: {} + process-warning@3.0.0: {} process-warning@5.0.0: {} process@0.11.10: {} + prom-client@15.0.0: + dependencies: + "@opentelemetry/api": 1.9.0 + tdigest: 0.1.2 + prom-client@15.1.3: dependencies: "@opentelemetry/api": 1.9.0 @@ -33664,6 +34212,12 @@ snapshots: requires-port@1.0.0: {} + rescript-schema@9.3.0(rescript@11.1.3): + optionalDependencies: + rescript: 11.1.3 + + rescript@11.1.3: {} + resend@6.9.3: dependencies: postal-mime: 2.7.3 @@ -33845,14 +34399,16 @@ snapshots: schema-utils@4.3.3: dependencies: "@types/json-schema": 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) scrypt-js@3.0.1: {} scuid@1.1.0: {} + secure-json-parse@2.7.0: {} + secure-json-parse@4.1.0: {} semver-compare@1.0.0: {} @@ -34170,6 +34726,8 @@ snapshots: char-regex: 1.0.2 strip-ansi: 6.0.1 + string-similarity@4.0.4: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -35008,6 +35566,24 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + viem@2.21.0(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76): + dependencies: + "@adraffy/ens-normalize": 1.10.0 + "@noble/curves": 1.4.0 + "@noble/hashes": 1.4.0 + "@scure/bip32": 1.4.0 + "@scure/bip39": 1.3.0 + abitype: 1.0.5(typescript@5.9.3)(zod@3.25.76) + isows: 1.0.4(ws@8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + webauthn-p256: 0.0.5 + ws: 8.17.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + - zod + viem@2.23.2(bufferutil@4.0.9)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@3.25.76): dependencies: "@noble/curves": 1.8.1 @@ -35310,6 +35886,11 @@ snapshots: web-streams-polyfill@3.3.3: {} + webauthn-p256@0.0.5: + dependencies: + "@noble/curves": 1.9.7 + "@noble/hashes": 1.8.0 + webextension-polyfill@0.10.0: {} webidl-conversions@3.0.1: {} diff --git a/ts-node b/ts-node new file mode 100644 index 000000000..e69de29bb