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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions apps/api/cmd/gnosis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import {
PROMETHEUS_MIME_TYPE,
PrometheusSerializer,
} from "@anticapture/observability";
import { serve } from "@hono/node-server";
import { OpenAPIHono as Hono } from "@hono/zod-openapi";
import { drizzle } from "drizzle-orm/node-postgres";
import { logger } from "hono/logger";
import { createPublicClient, http } from "viem";
import { fromZodError } from "zod-validation-error";

import { DaoCache } from "@/cache/dao-cache";
import {
accountBalances,
dao,
historicalBalances,
historicalVotingPower,
transfers,
votingPowers,
delegations,
delegators,
historicalDelegations,
token,
accountInteractions,
offchainProposals,
offchainVotes,
feed,
} from "@/controllers";
import * as offchainSchema from "@/database/offchain-schema";
import * as schema from "@/database/schema";
import { docs } from "@/docs";
import { env } from "@/env";
import { getClient } from "@/lib/client";
import { getChain } from "@/lib/utils";
import { exporter } from "@/metrics";
import { errorHandler, metricsMiddleware } from "@/middlewares";
import {
HistoricalBalanceRepository,
TransfersRepository,
DelegationsRepository,
DelegatorsRepository,
HistoricalDelegationsRepository,
AccountBalanceQueryFragments,
AccountBalanceRepository,
AccountInteractionsRepository,
TokenRepository,
VotingPowerRepository,
OffchainProposalRepository,
OffchainVoteRepository,
FeedRepository,
} from "@/repositories";
import {
AccountBalanceService,
DaoService,
HistoricalBalancesService,
TransfersService,
HistoricalDelegationsService,
DelegationsService,
DelegatorsService,
CoingeckoService,
TokenService,
VotingPowerService,
OffchainProposalsService,
OffchainVotesService,
FeedService,
} from "@/services";
import { AccountInteractionsService } from "@/services/account-balance/interactions";

const app = new Hono({
defaultHook: (result, c) => {
if (!result.success) {
const validationError = fromZodError(result.error);
return c.json(
{
error: "Validation Error",
message: validationError.message,
details: validationError.details,
},
400,
);
}
},
});

app.use(logger());
app.onError(errorHandler);

app.get("/metrics", async (c) => {
const result = await exporter.collect();
const serialized = new PrometheusSerializer().serialize(
result.resourceMetrics,
);
return c.text(serialized, 200, {
"Content-Type": PROMETHEUS_MIME_TYPE,
});
});

app.use(metricsMiddleware);

const chain = getChain(env.CHAIN_ID);
if (!chain) {
throw new Error(`Chain not found for chainId ${env.CHAIN_ID}`);
}
console.log("Connected to chain", chain.name);

const client = createPublicClient({
chain,
transport: http(env.RPC_URL),
});

const daoClient = getClient(env.DAO_ID, client);

if (!daoClient) {
throw new Error(`Client not found for DAO ${env.DAO_ID}`);
}

const pgClient = drizzle(env.DATABASE_URL, { schema, casing: "snake_case" });
const pgOffchainClient = drizzle(env.DATABASE_URL, {
schema: offchainSchema,
casing: "snake_case",
});

const daoCache = new DaoCache();

const balanceQueryFragments = new AccountBalanceQueryFragments(pgClient);
const accountBalanceRepo = new AccountBalanceRepository(
pgClient,
balanceQueryFragments,
);
const votingPowerRepo = new VotingPowerRepository(pgClient);
const votingPowerService = new VotingPowerService(
votingPowerRepo,
votingPowerRepo,
);

const accountInteractionRepo = new AccountInteractionsRepository(pgClient);
const daoService = new DaoService(daoClient, daoCache, env.CHAIN_ID);
const accountBalanceService = new AccountBalanceService(accountBalanceRepo);

historicalDelegations(
app,
new HistoricalDelegationsService(
new HistoricalDelegationsRepository(pgClient),
),
);

token(
app,
new CoingeckoService(
env.COINGECKO_API_URL,
env.COINGECKO_API_KEY,
env.DAO_ID,
),
new TokenService(new TokenRepository(pgClient)),
env.DAO_ID,
);
delegations(app, new DelegationsService(new DelegationsRepository(pgClient)));
delegators(app, new DelegatorsService(new DelegatorsRepository(pgClient)));
historicalBalances(
app,
new HistoricalBalancesService(new HistoricalBalanceRepository(pgClient)),
);
historicalVotingPower(app, votingPowerService);
votingPowers(app, votingPowerService);
accountBalances(app, env.DAO_ID, accountBalanceService);
accountInteractions(
app,
new AccountInteractionsService(accountInteractionRepo),
);
transfers(app, new TransfersService(new TransfersRepository(pgClient)));
dao(app, daoService);

const offchainProposalsRepo = new OffchainProposalRepository(pgOffchainClient);
const offchainVotesRepo = new OffchainVoteRepository(pgOffchainClient);
offchainProposals(app, new OffchainProposalsService(offchainProposalsRepo));
offchainVotes(app, new OffchainVotesService(offchainVotesRepo));
feed(app, new FeedService(env.DAO_ID, new FeedRepository(pgClient)));

docs(app);

serve(
{
fetch: app.fetch,
port: env.PORT,
},
(info) => {
console.log(`Server running at http://localhost:${info.port}`);
},
);
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"clean": "rm -rf node_modules *.tsbuildinfo dist",
"start": "node --import ./dist/instrumentation.js dist/index.js",
"start:aave": "node --import ./dist/instrumentation.js dist/aave.js",
"start:gnosis": "node --import ./dist/instrumentation.js dist/gnosis.js",
"build": "tsup",
"build:watch": "tsc --watch",
"lint": "eslint src",
Expand Down
67 changes: 67 additions & 0 deletions apps/api/src/clients/gnosis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
Abi,
Account,
Address,
Chain,
Client,
Transport,
zeroAddress,
} from "viem";

import { DAOClient } from "@/clients";

import { GovernorBase } from "../governor.base";

export class GnosisClient<
TTransport extends Transport = Transport,
TChain extends Chain = Chain,
TAccount extends Account | undefined = Account | undefined,
>
extends GovernorBase
implements DAOClient
{
protected address: Address;
protected abi: Abi;

constructor(client: Client<TTransport, TChain, TAccount>) {
super(client);
this.address = zeroAddress;
this.abi = [];
}

getDaoId(): string {
return "GNO";
}

async getProposalThreshold(): Promise<bigint> {
return 0n;
}

async getQuorum(): Promise<bigint> {
return 0n;
}

async getTimelockDelay(): Promise<bigint> {
return 0n;
}

async getVotingDelay(): Promise<bigint> {
return 0n;
}

async getVotingPeriod(): Promise<bigint> {
return 0n;
}

calculateQuorum(votes: {
forVotes: bigint;
againstVotes: bigint;
abstainVotes: bigint;
}): bigint {
return votes.forVotes;
}

override supportOffchainData(): boolean {
return true;
}
}
1 change: 1 addition & 0 deletions apps/api/src/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from "./uni";
export * from "./shu";
export * from "./aave";
export * from "./fluid";
export * from "./gnosis";

export interface DAOClient {
getDaoId: () => string;
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
SHUClient,
AAVEClient,
FLUIDClient,
GnosisClient,
} from "@/clients";

import { CONTRACT_ADDRESSES } from "./constants";
Expand Down Expand Up @@ -84,6 +85,9 @@ export function getClient<
case DaoIdEnum.AAVE: {
return new AAVEClient(client);
}
case DaoIdEnum.GNO: {
return new GnosisClient(client);
}
default:
return null;
}
Expand Down
11 changes: 11 additions & 0 deletions apps/api/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,16 @@ export const CONTRACT_ADDRESSES = {
startBlock: 12422079,
},
},
[DaoIdEnum.GNO]: {
blockTime: 12,
tokenType: "ERC20",
// https://etherscan.io/address/0x6810e776880C02933D47DB1b9fc05908e5386b96
token: {
address: "0x6810e776880C02933D47DB1b9fc05908e5386b96",
decimals: 18,
startBlock: 6481670,
},
},
} as const;

export const TreasuryAddresses: Record<DaoIdEnum, Record<string, Address>> = {
Expand Down Expand Up @@ -390,6 +400,7 @@ export const TreasuryAddresses: Record<DaoIdEnum, Record<string, Address>> = {
"0x639f35C5E212D61Fe14Bd5CD8b66aAe4df11a50c",
InstaTimelock: "0xC7Cb1dE2721BFC0E0DA1b9D526bCdC54eF1C0eFC",
},
[DaoIdEnum.GNO]: {},
};

export enum ProposalStatus {
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/lib/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export enum DaoIdEnum {
OBOL = "OBOL",
ZK = "ZK",
FLUID = "FLUID",
GNO = "GNO",
}

export const SECONDS_IN_DAY = 24 * 60 * 60;
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/lib/eventRelevance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,13 @@ const DAO_RELEVANCE_THRESHOLDS: Record<DaoIdEnum, EventRelevanceMap> = {
[FeedEventType.PROPOSAL]: EMPTY_THRESHOLDS,
[FeedEventType.PROPOSAL_EXTENDED]: EMPTY_THRESHOLDS,
},
[DaoIdEnum.GNO]: {
[FeedEventType.TRANSFER]: EMPTY_THRESHOLDS,
[FeedEventType.DELEGATION]: EMPTY_THRESHOLDS,
[FeedEventType.VOTE]: EMPTY_THRESHOLDS,
[FeedEventType.PROPOSAL]: EMPTY_THRESHOLDS,
[FeedEventType.PROPOSAL_EXTENDED]: EMPTY_THRESHOLDS,
},
};

export function getDaoRelevanceThreshold(daoId: DaoIdEnum): EventRelevanceMap {
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/mappers/dao/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const DaoResponseSchema = z.object({
votingPeriod: z.string(),
timelockDelay: z.string(),
alreadySupportCalldataReview: z.boolean(),
supportOffchainData: z.boolean(),
supportOffchainData: z.boolean().optional().default(false),
});

export type DaoResponse = z.infer<typeof DaoResponseSchema>;
1 change: 1 addition & 0 deletions apps/api/src/services/coingecko/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const CoingeckoTokenIdEnum: Record<DaoIdEnum, string> = {
ZK: "zksync",
SHU: "shutter",
FLUID: "fluid",
GNO: "gnosis",
} as const;

export const CoingeckoIdToAssetPlatformId = {
Expand Down
1 change: 1 addition & 0 deletions apps/api/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default defineConfig({
entry: {
index: "cmd/index.ts",
aave: "cmd/aave.ts",
gnosis: "cmd/gnosis.ts",
instrumentation: "src/instrumentation.ts",
},
format: ["esm"],
Expand Down
10 changes: 0 additions & 10 deletions apps/dashboard/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-scroll-left: scroll-left 20s linear infinite;
--animate-fade-in: fade-in 0.5s ease-in;
--animate-fade-out: fade-out 0.5s ease-in;
--animate-toast-slide-in: toast-slide-in 0.3s ease-out forwards;
--animate-toast-slide-out: toast-slide-out 0.2s ease-in forwards;
--animate-pulse-ring: pulse-ring 1.5s ease-out infinite;
Expand Down Expand Up @@ -255,15 +254,6 @@
}
}

@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}

/* Tooltip animation */
@keyframes tooltip-show {
from {
Expand Down
Loading
Loading