From 59441bd8a3be634a7d16ff459ff1eaae46e8a318 Mon Sep 17 00:00:00 2001 From: Pedro Binotto Date: Fri, 27 Mar 2026 19:14:44 -0300 Subject: [PATCH 1/2] fix: ffs --- apps/api/cmd/gnosis.ts | 189 +++++++ apps/api/package.json | 1 + apps/api/src/clients/gnosis/index.ts | 67 +++ apps/api/src/clients/index.ts | 1 + apps/api/src/lib/client.ts | 4 + apps/api/src/lib/constants.ts | 11 + apps/api/src/lib/enums.ts | 1 + apps/api/src/lib/eventRelevance.ts | 7 + apps/api/src/mappers/dao/index.ts | 2 +- apps/api/src/services/coingecko/types.ts | 1 + apps/api/tsup.config.ts | 1 + apps/dashboard/app/globals.css | 10 - .../governance-overview/GovernanceSection.tsx | 12 +- .../OffchainVotesContent.tsx | 2 +- .../proposal-overview/ProposalHeader.tsx | 33 +- .../proposal-overview/ProposalSection.tsx | 35 +- .../features/governance/hooks/useProposals.ts | 3 + .../design-system/drawer/Drawer.stories.tsx | 2 +- .../drawer/drawer-body/DrawerBody.stories.tsx | 2 +- .../drawer-header/DrawerHeader.stories.tsx | 2 +- .../design-system/form/fields/index.ts | 7 - .../design-system/modal/Modal.stories.tsx | 2 +- .../modal-footer/ModalFooter.stories.tsx | 2 +- .../modal-header/ModalHeader.stories.tsx | 2 +- .../dropdowns/HeaderDAOSidebarDropdown.tsx | 166 +++--- .../shared/components/icons/GnosisIcon.tsx | 35 ++ .../shared/components/icons/index.ts | 2 + apps/dashboard/shared/dao-config/aave.ts | 1 + apps/dashboard/shared/dao-config/comp.ts | 3 +- apps/dashboard/shared/dao-config/ens.ts | 4 +- apps/dashboard/shared/dao-config/fluid.ts | 1 + apps/dashboard/shared/dao-config/gno.ts | 36 ++ apps/dashboard/shared/dao-config/gtc.ts | 3 +- apps/dashboard/shared/dao-config/index.ts | 2 + apps/dashboard/shared/dao-config/lil-nouns.ts | 1 + apps/dashboard/shared/dao-config/nouns.ts | 3 +- apps/dashboard/shared/dao-config/obol.ts | 3 +- apps/dashboard/shared/dao-config/op.ts | 3 +- apps/dashboard/shared/dao-config/scr.ts | 3 +- apps/dashboard/shared/dao-config/shu.ts | 3 +- apps/dashboard/shared/dao-config/types.ts | 4 +- apps/dashboard/shared/dao-config/uni.ts | 3 +- apps/dashboard/shared/og/dao-og-icons.tsx | 23 + apps/dashboard/shared/types/daos.ts | 1 + apps/indexer/config/aave.config.ts | 2 +- apps/indexer/config/arbitrum.config.ts | 2 +- apps/indexer/config/compound.config.ts | 2 +- apps/indexer/config/ens.config.ts | 2 +- apps/indexer/config/fluid.config.ts | 2 +- apps/indexer/config/gitcoin.config.ts | 2 +- apps/indexer/config/gnosis.config.ts | 67 +++ apps/indexer/config/lil-nouns.config.ts | 2 +- apps/indexer/config/nouns.config.ts | 2 +- apps/indexer/config/obol.config.ts | 2 +- apps/indexer/config/optimism.config.ts | 2 +- apps/indexer/config/scroll.config.ts | 2 +- apps/indexer/config/shutter.config.ts | 2 +- apps/indexer/config/test.config.ts | 2 +- apps/indexer/config/uniswap.config.ts | 2 +- apps/indexer/config/zk.config.ts | 2 +- apps/indexer/ponder.config.ts | 3 + apps/indexer/src/env.ts | 5 +- apps/indexer/src/eventHandlers/transfer.ts | 82 +++ apps/indexer/src/index.ts | 21 + .../gnosis/abi/ethereum-mainnet/gno.ts | 126 +++++ .../gnosis/abi/ethereum-mainnet/index.ts | 2 + .../gnosis/abi/ethereum-mainnet/lgno.ts | 212 ++++++++ .../indexer/gnosis/abi/gnosis-chain/gno.ts | 480 ++++++++++++++++++ .../indexer/gnosis/abi/gnosis-chain/index.ts | 3 + .../indexer/gnosis/abi/gnosis-chain/lgno.ts | 115 +++++ .../indexer/gnosis/abi/gnosis-chain/sgno.ts | 428 ++++++++++++++++ apps/indexer/src/indexer/gnosis/gnosis-gno.ts | 194 +++++++ .../indexer/src/indexer/gnosis/gnosis-lgno.ts | 168 ++++++ .../indexer/src/indexer/gnosis/gnosis-sgno.ts | 167 ++++++ apps/indexer/src/indexer/gnosis/index.ts | 5 + .../indexer/src/indexer/gnosis/mainnet-gno.ts | 185 +++++++ .../src/indexer/gnosis/mainnet-lgno.ts | 158 ++++++ apps/indexer/src/lib/constants.ts | 44 ++ apps/indexer/src/lib/enums.ts | 1 + 79 files changed, 3013 insertions(+), 182 deletions(-) create mode 100644 apps/api/cmd/gnosis.ts create mode 100644 apps/api/src/clients/gnosis/index.ts create mode 100644 apps/dashboard/shared/components/icons/GnosisIcon.tsx create mode 100644 apps/dashboard/shared/dao-config/gno.ts create mode 100644 apps/indexer/config/gnosis.config.ts create mode 100644 apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/gno.ts create mode 100644 apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/index.ts create mode 100644 apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/lgno.ts create mode 100644 apps/indexer/src/indexer/gnosis/abi/gnosis-chain/gno.ts create mode 100644 apps/indexer/src/indexer/gnosis/abi/gnosis-chain/index.ts create mode 100644 apps/indexer/src/indexer/gnosis/abi/gnosis-chain/lgno.ts create mode 100644 apps/indexer/src/indexer/gnosis/abi/gnosis-chain/sgno.ts create mode 100644 apps/indexer/src/indexer/gnosis/gnosis-gno.ts create mode 100644 apps/indexer/src/indexer/gnosis/gnosis-lgno.ts create mode 100644 apps/indexer/src/indexer/gnosis/gnosis-sgno.ts create mode 100644 apps/indexer/src/indexer/gnosis/index.ts create mode 100644 apps/indexer/src/indexer/gnosis/mainnet-gno.ts create mode 100644 apps/indexer/src/indexer/gnosis/mainnet-lgno.ts diff --git a/apps/api/cmd/gnosis.ts b/apps/api/cmd/gnosis.ts new file mode 100644 index 000000000..d0936a070 --- /dev/null +++ b/apps/api/cmd/gnosis.ts @@ -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}`); + }, +); diff --git a/apps/api/package.json b/apps/api/package.json index 65cf7c531..4d3f34941 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -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", diff --git a/apps/api/src/clients/gnosis/index.ts b/apps/api/src/clients/gnosis/index.ts new file mode 100644 index 000000000..7a606ed8c --- /dev/null +++ b/apps/api/src/clients/gnosis/index.ts @@ -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) { + super(client); + this.address = zeroAddress; + this.abi = []; + } + + getDaoId(): string { + return "GNO"; + } + + async getProposalThreshold(): Promise { + return 0n; + } + + async getQuorum(): Promise { + return 0n; + } + + async getTimelockDelay(): Promise { + return 0n; + } + + async getVotingDelay(): Promise { + return 0n; + } + + async getVotingPeriod(): Promise { + return 0n; + } + + calculateQuorum(votes: { + forVotes: bigint; + againstVotes: bigint; + abstainVotes: bigint; + }): bigint { + return votes.forVotes; + } + + override supportOffchainData(): boolean { + return true; + } +} diff --git a/apps/api/src/clients/index.ts b/apps/api/src/clients/index.ts index f9ab39998..5d8620f16 100644 --- a/apps/api/src/clients/index.ts +++ b/apps/api/src/clients/index.ts @@ -10,6 +10,7 @@ export * from "./uni"; export * from "./shu"; export * from "./aave"; export * from "./fluid"; +export * from "./gnosis"; export interface DAOClient { getDaoId: () => string; diff --git a/apps/api/src/lib/client.ts b/apps/api/src/lib/client.ts index 3493aafd7..fa47ab4fc 100644 --- a/apps/api/src/lib/client.ts +++ b/apps/api/src/lib/client.ts @@ -14,6 +14,7 @@ import { SHUClient, AAVEClient, FLUIDClient, + GnosisClient, } from "@/clients"; import { CONTRACT_ADDRESSES } from "./constants"; @@ -84,6 +85,9 @@ export function getClient< case DaoIdEnum.AAVE: { return new AAVEClient(client); } + case DaoIdEnum.GNO: { + return new GnosisClient(client); + } default: return null; } diff --git a/apps/api/src/lib/constants.ts b/apps/api/src/lib/constants.ts index 79b268a8d..3aa4f9057 100644 --- a/apps/api/src/lib/constants.ts +++ b/apps/api/src/lib/constants.ts @@ -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> = { @@ -390,6 +400,7 @@ export const TreasuryAddresses: Record> = { "0x639f35C5E212D61Fe14Bd5CD8b66aAe4df11a50c", InstaTimelock: "0xC7Cb1dE2721BFC0E0DA1b9D526bCdC54eF1C0eFC", }, + [DaoIdEnum.GNO]: {}, }; export enum ProposalStatus { diff --git a/apps/api/src/lib/enums.ts b/apps/api/src/lib/enums.ts index fb99419a5..07bb256f0 100644 --- a/apps/api/src/lib/enums.ts +++ b/apps/api/src/lib/enums.ts @@ -13,6 +13,7 @@ export enum DaoIdEnum { OBOL = "OBOL", ZK = "ZK", FLUID = "FLUID", + GNO = "GNO", } export const SECONDS_IN_DAY = 24 * 60 * 60; diff --git a/apps/api/src/lib/eventRelevance.ts b/apps/api/src/lib/eventRelevance.ts index f0599ea20..657613ed8 100644 --- a/apps/api/src/lib/eventRelevance.ts +++ b/apps/api/src/lib/eventRelevance.ts @@ -232,6 +232,13 @@ const DAO_RELEVANCE_THRESHOLDS: Record = { [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 { diff --git a/apps/api/src/mappers/dao/index.ts b/apps/api/src/mappers/dao/index.ts index bbc33801c..dd2856bd4 100644 --- a/apps/api/src/mappers/dao/index.ts +++ b/apps/api/src/mappers/dao/index.ts @@ -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; diff --git a/apps/api/src/services/coingecko/types.ts b/apps/api/src/services/coingecko/types.ts index 8bb6f7667..2f77efa2c 100644 --- a/apps/api/src/services/coingecko/types.ts +++ b/apps/api/src/services/coingecko/types.ts @@ -26,6 +26,7 @@ export const CoingeckoTokenIdEnum: Record = { ZK: "zksync", SHU: "shutter", FLUID: "fluid", + GNO: "gnosis", } as const; export const CoingeckoIdToAssetPlatformId = { diff --git a/apps/api/tsup.config.ts b/apps/api/tsup.config.ts index ceb1fa804..62bc4e21e 100644 --- a/apps/api/tsup.config.ts +++ b/apps/api/tsup.config.ts @@ -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"], diff --git a/apps/dashboard/app/globals.css b/apps/dashboard/app/globals.css index d8162d5ee..c1dd2a0d9 100644 --- a/apps/dashboard/app/globals.css +++ b/apps/dashboard/app/globals.css @@ -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; @@ -255,15 +254,6 @@ } } - @keyframes fade-out { - from { - opacity: 1; - } - to { - opacity: 0; - } - } - /* Tooltip animation */ @keyframes tooltip-show { from { diff --git a/apps/dashboard/features/governance/components/governance-overview/GovernanceSection.tsx b/apps/dashboard/features/governance/components/governance-overview/GovernanceSection.tsx index 6b6756dea..59d589003 100644 --- a/apps/dashboard/features/governance/components/governance-overview/GovernanceSection.tsx +++ b/apps/dashboard/features/governance/components/governance-overview/GovernanceSection.tsx @@ -9,12 +9,12 @@ import { useCallback, useEffect, useRef, type RefObject } from "react"; import { ProposalItem } from "@/features/governance/components/proposal-overview/ProposalItem"; import { useOffchainProposals } from "@/features/governance/hooks/useOffchainProposals"; import { useProposals } from "@/features/governance/hooks/useProposals"; -import { TheSectionLayout } from "@/shared/components"; import { TabGroup } from "@/shared/components/design-system/tabs/tab-group/TabGroup"; import { EmptyState } from "@/shared/components/design-system/table/components/EmptyState"; import { SkeletonRow } from "@/shared/components/skeletons/SkeletonRow"; import daoConfig from "@/shared/dao-config"; import type { DaoIdEnum } from "@/shared/types/daos"; +import { TheSectionLayout } from "@/shared/components/containers/TheSectionLayout"; const PROPOSAL_TABS = [ { label: "Onchain", value: "onchain" }, @@ -24,13 +24,16 @@ const PROPOSAL_TABS = [ export const GovernanceSection = () => { const { daoId }: { daoId: string } = useParams(); const daoIdEnum = daoId.toUpperCase() as DaoIdEnum; - const hasOffchain = !!daoConfig[daoIdEnum]?.offchainProposals; + const proposalType = daoConfig[daoIdEnum]?.proposalTypeConfiguration; + const hasOffchain = + proposalType === "offchain" || proposalType === "composite"; + const hasOnchain = proposalType === "onchain" || proposalType === "composite"; const [activeTab, setActiveTab] = useQueryState( "tab", parseAsStringEnum<"onchain" | "offchain">([ "onchain", "offchain", - ]).withDefault("onchain"), + ]).withDefault(hasOnchain ? "onchain" : "offchain"), ); const { @@ -44,6 +47,7 @@ export const GovernanceSection = () => { itemsPerPage: 10, orderDirection: QueryInput_Proposals_OrderDirection.Desc, daoId: daoIdEnum, + skip: !hasOnchain, }); const { @@ -61,7 +65,7 @@ export const GovernanceSection = () => { const loadMoreOnchainRef = useRef(null); const loadMoreOffchainRef = useRef(null); - const isOnchain = activeTab === "onchain" || !hasOffchain; + const isOnchain = activeTab === "onchain" || (!hasOffchain && hasOnchain); const error = isOnchain ? onchainError : offchainError; const pagination = isOnchain ? onchainPagination : offchainPagination; const isPaginationLoading = isOnchain diff --git a/apps/dashboard/features/governance/components/proposal-overview/OffchainVotesContent.tsx b/apps/dashboard/features/governance/components/proposal-overview/OffchainVotesContent.tsx index f15ba6f03..a67112ffa 100644 --- a/apps/dashboard/features/governance/components/proposal-overview/OffchainVotesContent.tsx +++ b/apps/dashboard/features/governance/components/proposal-overview/OffchainVotesContent.tsx @@ -51,7 +51,7 @@ const getChoiceInfo = (choice: unknown, choices: string[]) => { if (Array.isArray(choice)) { const label = (choice as number[]) - .map((choice) => choices[choice - 1] ?? `Choice ${choice}`) + .map((choice) => choices[choice] ?? `Choice ${choice}`) .join(", "); return { label, icon: null as React.ReactNode }; } diff --git a/apps/dashboard/features/governance/components/proposal-overview/ProposalHeader.tsx b/apps/dashboard/features/governance/components/proposal-overview/ProposalHeader.tsx index 2658382c0..38e50daa8 100644 --- a/apps/dashboard/features/governance/components/proposal-overview/ProposalHeader.tsx +++ b/apps/dashboard/features/governance/components/proposal-overview/ProposalHeader.tsx @@ -1,7 +1,7 @@ "use client"; import type { GetAccountPowerQuery } from "@anticapture/graphql-client"; -import { ArrowLeft, ArrowRight } from "lucide-react"; +import { ArrowLeft, ArrowRight, ExternalLink } from "lucide-react"; import Link from "next/link"; import { Button, IconButton } from "@/shared/components"; @@ -104,24 +104,17 @@ export const ProposalHeader = ({
{snapshotLink ? ( - <> - {address && ( -
-

- Your voting power -

-

- {votingPower} -

-
- )} - - + + + ) : ( <>

@@ -141,7 +134,7 @@ export const ProposalHeader = ({ diff --git a/apps/dashboard/features/governance/components/proposal-overview/ProposalSection.tsx b/apps/dashboard/features/governance/components/proposal-overview/ProposalSection.tsx index 3029e67df..87d75caa3 100644 --- a/apps/dashboard/features/governance/components/proposal-overview/ProposalSection.tsx +++ b/apps/dashboard/features/governance/components/proposal-overview/ProposalSection.tsx @@ -1,12 +1,11 @@ "use client"; import type { Query_Proposals_Items_Items } from "@anticapture/graphql-client/hooks"; -import { ArrowRight } from "lucide-react"; +import { ArrowRight, ExternalLink } from "lucide-react"; import { useParams } from "next/navigation"; import { useState, useCallback, useMemo } from "react"; import { useAccount } from "wagmi"; -import { OffchainVotingModal } from "@/features/governance/components/modals/OffchainVotingModal"; import { VotingModal } from "@/features/governance/components/modals/VotingModal"; import { getVoteText, @@ -209,23 +208,15 @@ export const ProposalSection = ({ /> )} - - {isOffchain && rawOffchainProposal && ( - setIsVotingModalOpen(false)} - proposal={rawOffchainProposal} - /> - )}

{/* Fixed bottom bar for mobile */}
setIsVotingModalOpen(true)} />
@@ -235,25 +226,31 @@ export const ProposalSection = ({ const MobileBottomBar = ({ isOffchain, + snapshotLink, address, supportValue, - proposalStatus, onVoteClick, }: { isOffchain: boolean; + snapshotLink: string | null | undefined; address: string | undefined; supportValue: number | undefined; - proposalStatus: string; onVoteClick: () => void; }) => { if (isOffchain) { - const isOngoing = proposalStatus.toLowerCase() === "ongoing"; - if (address && isOngoing) { + if (snapshotLink) { return ( - + + + ); } return null; diff --git a/apps/dashboard/features/governance/hooks/useProposals.ts b/apps/dashboard/features/governance/hooks/useProposals.ts index cad039e39..d982741c5 100644 --- a/apps/dashboard/features/governance/hooks/useProposals.ts +++ b/apps/dashboard/features/governance/hooks/useProposals.ts @@ -40,6 +40,7 @@ export interface UseProposalsParams extends Omit< > { itemsPerPage?: number; daoId?: DaoIdEnum; + skip?: boolean; } export const useProposals = ({ @@ -48,6 +49,7 @@ export const useProposals = ({ status, itemsPerPage = 10, daoId, + skip = false, }: UseProposalsParams = {}): UseProposalsResult => { const [isPaginationLoading, setIsPaginationLoading] = useState(false); const [allProposals, setAllProposals] = useState([]); @@ -66,6 +68,7 @@ export const useProposals = ({ // Main proposals query const { data, loading, error, fetchMore } = useGetProposalsFromDaoQuery({ + skip, variables: queryVariables, notifyOnNetworkStatusChange: true, context: { diff --git a/apps/dashboard/shared/components/design-system/drawer/Drawer.stories.tsx b/apps/dashboard/shared/components/design-system/drawer/Drawer.stories.tsx index 2639310c3..08e430503 100644 --- a/apps/dashboard/shared/components/design-system/drawer/Drawer.stories.tsx +++ b/apps/dashboard/shared/components/design-system/drawer/Drawer.stories.tsx @@ -13,7 +13,7 @@ import { import { getFigmaDesignConfigByNodeId } from "@/shared/utils/figma-storybook"; const meta = { - title: "Data Display/Drawer/Drawer", + title: "Feedback/Drawer/Drawer", component: DrawerRoot, parameters: { layout: "centered", diff --git a/apps/dashboard/shared/components/design-system/drawer/drawer-body/DrawerBody.stories.tsx b/apps/dashboard/shared/components/design-system/drawer/drawer-body/DrawerBody.stories.tsx index 55ac41e45..3ba64af12 100644 --- a/apps/dashboard/shared/components/design-system/drawer/drawer-body/DrawerBody.stories.tsx +++ b/apps/dashboard/shared/components/design-system/drawer/drawer-body/DrawerBody.stories.tsx @@ -5,7 +5,7 @@ import type { DrawerBodyProps } from "@/shared/components/design-system/drawer/t import { getFigmaDesignConfigByNodeId } from "@/shared/utils/figma-storybook"; const meta: Meta = { - title: "Data Display/Drawer/DrawerBody", + title: "Feedback/Drawer/DrawerBody", component: DrawerBody, parameters: { layout: "padded", diff --git a/apps/dashboard/shared/components/design-system/drawer/drawer-header/DrawerHeader.stories.tsx b/apps/dashboard/shared/components/design-system/drawer/drawer-header/DrawerHeader.stories.tsx index a07468d83..2969501a7 100644 --- a/apps/dashboard/shared/components/design-system/drawer/drawer-header/DrawerHeader.stories.tsx +++ b/apps/dashboard/shared/components/design-system/drawer/drawer-header/DrawerHeader.stories.tsx @@ -8,7 +8,7 @@ import type { DrawerHeaderProps } from "@/shared/components/design-system/drawer import { getFigmaDesignConfigByNodeId } from "@/shared/utils/figma-storybook"; const meta: Meta = { - title: "Data Display/Drawer/DrawerHeader", + title: "Feedback/Drawer/DrawerHeader", component: DrawerHeader, parameters: { layout: "padded", diff --git a/apps/dashboard/shared/components/design-system/form/fields/index.ts b/apps/dashboard/shared/components/design-system/form/fields/index.ts index 38f733dfe..254af6211 100644 --- a/apps/dashboard/shared/components/design-system/form/fields/index.ts +++ b/apps/dashboard/shared/components/design-system/form/fields/index.ts @@ -12,10 +12,3 @@ export { Input } from "@/shared/components/design-system/form/fields/input/Input export type { InputProps } from "@/shared/components/design-system/form/fields/input/types"; export { Textarea } from "@/shared/components/design-system/form/fields/textarea/Textarea"; export type { TextareaProps } from "@/shared/components/design-system/form/fields/textarea/types"; -export { Select } from "@/shared/components/design-system/form/fields/select/Select"; -export type { - SelectProps, - SelectItem, -} from "@/shared/components/design-system/form/fields/select/types"; -export { RadioCard } from "@/shared/components/design-system/form/fields/radio-card/RadioCard"; -export type { RadioCardProps } from "@/shared/components/design-system/form/fields/radio-card/RadioCard"; diff --git a/apps/dashboard/shared/components/design-system/modal/Modal.stories.tsx b/apps/dashboard/shared/components/design-system/modal/Modal.stories.tsx index d23f2fa84..bd1338160 100644 --- a/apps/dashboard/shared/components/design-system/modal/Modal.stories.tsx +++ b/apps/dashboard/shared/components/design-system/modal/Modal.stories.tsx @@ -9,7 +9,7 @@ import type { ModalProps } from "@/shared/components/design-system/modal/types"; import { getFigmaDesignConfigByNodeId } from "@/shared/utils/figma-storybook"; const meta: Meta = { - title: "Data Display/Modal/Modal", + title: "Feedback/Modal/Modal", component: Modal, parameters: { layout: "centered", diff --git a/apps/dashboard/shared/components/design-system/modal/modal-footer/ModalFooter.stories.tsx b/apps/dashboard/shared/components/design-system/modal/modal-footer/ModalFooter.stories.tsx index 26954d5f5..142ff5f93 100644 --- a/apps/dashboard/shared/components/design-system/modal/modal-footer/ModalFooter.stories.tsx +++ b/apps/dashboard/shared/components/design-system/modal/modal-footer/ModalFooter.stories.tsx @@ -5,7 +5,7 @@ import type { ModalFooterProps } from "@/shared/components/design-system/modal/t import { getFigmaDesignConfigByNodeId } from "@/shared/utils/figma-storybook"; const meta: Meta = { - title: "Data Display/Modal/ModalFooter", + title: "Feedback/Modal/ModalFooter", component: ModalFooter, parameters: { layout: "centered", diff --git a/apps/dashboard/shared/components/design-system/modal/modal-header/ModalHeader.stories.tsx b/apps/dashboard/shared/components/design-system/modal/modal-header/ModalHeader.stories.tsx index 419a9225d..3854394e8 100644 --- a/apps/dashboard/shared/components/design-system/modal/modal-header/ModalHeader.stories.tsx +++ b/apps/dashboard/shared/components/design-system/modal/modal-header/ModalHeader.stories.tsx @@ -6,7 +6,7 @@ import type { ModalHeaderProps } from "@/shared/components/design-system/modal/t import { getFigmaDesignConfigByNodeId } from "@/shared/utils/figma-storybook"; const meta: Meta = { - title: "Data Display/Modal/ModalHeader", + title: "Feedback/Modal/ModalHeader", component: ModalHeader, parameters: { layout: "centered", diff --git a/apps/dashboard/shared/components/dropdowns/HeaderDAOSidebarDropdown.tsx b/apps/dashboard/shared/components/dropdowns/HeaderDAOSidebarDropdown.tsx index 7735849ff..7b4c42ba6 100644 --- a/apps/dashboard/shared/components/dropdowns/HeaderDAOSidebarDropdown.tsx +++ b/apps/dashboard/shared/components/dropdowns/HeaderDAOSidebarDropdown.tsx @@ -1,24 +1,24 @@ "use client"; -import { ChevronsLeft, ChevronsRight, ChevronsUpDown } from "lucide-react"; +import { ChevronsUpDown, ChevronsRight, ChevronsLeft } from "lucide-react"; import { useParams, usePathname, useRouter } from "next/navigation"; -import { useCallback, useEffect, useRef, useState } from "react"; +import React, { useState, useRef, useEffect, useCallback } from "react"; +import { Button } from "@/shared/components"; import { DaoAvatarIcon } from "@/shared/components/icons"; import daoConfigByDaoId from "@/shared/dao-config"; import { DaoIdEnum } from "@/shared/types/daos"; import { cn } from "@/shared/utils/"; import { getDaoNavigationPath } from "@/shared/utils/dao-navigation"; -const ANIMATION_DURATION = 200; - -type DropdownItem = { +type Item = { id: number; - daoId: DaoIdEnum; label: string; + icon: React.ReactNode; + name: string; }; -export interface HeaderDAOSidebarDropdownProps { +interface HeaderDAOSidebarDropdownProps { isCollapsed?: boolean; onToggleCollapse?: () => void; } @@ -27,71 +27,68 @@ export const HeaderDAOSidebarDropdown = ({ isCollapsed = false, onToggleCollapse, }: HeaderDAOSidebarDropdownProps) => { - const [isOpen, setIsOpen] = useState(false); - const [isClosing, setIsClosing] = useState(false); - const ref = useRef(null); - const closeTimerRef = useRef | null>(null); - + const [isOpen, setIsOpen] = useState(false); + const [selectedHeaderSidebarItem, setSelectedHeaderSidebarItem] = + useState(0); const router = useRouter(); const pathname = usePathname(); + const dropdownRef = useRef(null); const { daoId } = useParams<{ daoId: string }>(); - const close = useCallback(() => { - if (!isOpen) return; - if (closeTimerRef.current) clearTimeout(closeTimerRef.current); - setIsClosing(true); - closeTimerRef.current = setTimeout(() => { - setIsOpen(false); - setIsClosing(false); - }, ANIMATION_DURATION); - }, [isOpen]); - - const open = useCallback(() => { - if (closeTimerRef.current) clearTimeout(closeTimerRef.current); - setIsClosing(false); - setIsOpen(true); + // restore selected item on mount + useEffect(() => { + const savedItem = sessionStorage.getItem("selectedHeaderSidebarItem"); + if (savedItem) { + setSelectedHeaderSidebarItem(Number(savedItem)); + } }, []); - const toggle = useCallback(() => { - if (isOpen && !isClosing) { - close(); - } else { - open(); + const handleClickOutside = useCallback((event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsOpen(false); } - }, [isOpen, isClosing, close, open]); + }, []); useEffect(() => { - const handleClickOutside = (e: MouseEvent) => { - if (ref.current && !ref.current.contains(e.target as Node)) { - close(); - } - }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); - if (closeTimerRef.current) clearTimeout(closeTimerRef.current); }; - }, [close]); + }, [handleClickOutside]); - const dropdownItemsRef = useRef(null); + // stable, single-build dropdown items (imports are static) + const dropdownItemsRef = useRef(null); if (!dropdownItemsRef.current) { dropdownItemsRef.current = Object.values(DaoIdEnum).map( (daoIdValue, index) => ({ id: index, - daoId: daoIdValue, label: daoConfigByDaoId[daoIdValue].name, + icon: ( + + ), + name: daoIdValue, }), ); } - const dropdownItems = dropdownItemsRef.current; + const dropdownItems = dropdownItemsRef.current!; const currentDaoId = daoId?.toUpperCase() ?? pathname.split("/")[1]?.toUpperCase(); - const currentItem = dropdownItems.find((item) => item.daoId === currentDaoId); + const currentItem = dropdownItems.find((item) => item.name === currentDaoId); + + const toggleDropdown = () => setIsOpen((prev) => !prev); const handleSelectItem = (id: number, targetDaoId: DaoIdEnum) => { + setSelectedHeaderSidebarItem(id); sessionStorage.setItem("selectedHeaderSidebarItem", id.toString()); - close(); + setIsOpen(false); router.push( getDaoNavigationPath({ targetDaoId, @@ -103,35 +100,23 @@ export const HeaderDAOSidebarDropdown = ({ return (
isOpen && close()} + className="border-light-dark relative z-50 inline-block h-[57px] w-full shrink-0 border-b lg:h-[65px]" + ref={dropdownRef} + onMouseLeave={() => setIsOpen(false)} > -
- +
- {onToggleCollapse && ( )} - {(isOpen || isClosing) && ( + {isOpen && (
{dropdownItems.map((item) => ( - +
+ +

+ {item.label} +

+
+ ))}
)} diff --git a/apps/dashboard/shared/components/icons/GnosisIcon.tsx b/apps/dashboard/shared/components/icons/GnosisIcon.tsx new file mode 100644 index 000000000..0a46f17de --- /dev/null +++ b/apps/dashboard/shared/components/icons/GnosisIcon.tsx @@ -0,0 +1,35 @@ +import type { DaoIconProps } from "@/shared/components/icons/types"; + +export const GnosisIcon = ({ + showBackground = true, + ...props +}: DaoIconProps) => { + return ( + + {showBackground && } + + + + + + ); +}; diff --git a/apps/dashboard/shared/components/icons/index.ts b/apps/dashboard/shared/components/icons/index.ts index 3f71b11bc..a6b8a703f 100644 --- a/apps/dashboard/shared/components/icons/index.ts +++ b/apps/dashboard/shared/components/icons/index.ts @@ -15,6 +15,8 @@ export * from "@/shared/components/icons/ScrollIcon"; export * from "@/shared/components/icons/CompoundIcon"; export * from "@/shared/components/icons/ObolIcon"; export * from "@/shared/components/icons/ShutterIcon"; +export * from "@/shared/components/icons/GnosisIcon"; +// THE IMPORT OF DAO AVATAR ICON MUST BE LAST export * from "@/shared/components/icons/DaoAvatarIcon"; export * from "@/shared/components/icons/CookieBackground"; diff --git a/apps/dashboard/shared/dao-config/aave.ts b/apps/dashboard/shared/dao-config/aave.ts index 80ead7620..dc4aba39d 100644 --- a/apps/dashboard/shared/dao-config/aave.ts +++ b/apps/dashboard/shared/dao-config/aave.ts @@ -37,4 +37,5 @@ export const AAVE: DaoConfiguration = { dataTables: true, overviewPage: false, initialPage: "holders-and-delegates", + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/comp.ts b/apps/dashboard/shared/dao-config/comp.ts index e0bfb1af0..10ad19a0c 100644 --- a/apps/dashboard/shared/dao-config/comp.ts +++ b/apps/dashboard/shared/dao-config/comp.ts @@ -1,6 +1,6 @@ import { mainnet } from "viem/chains"; -import { CompoundIcon } from "@/shared/components/icons/CompoundIcon"; +import { CompoundIcon } from "@/shared/components/icons"; import { MainnetIcon } from "@/shared/components/icons/MainnetIcon"; import { GOVERNANCE_IMPLEMENTATION_CONSTANTS } from "@/shared/constants/governance-implementations"; import { QUORUM_CALCULATION_TYPES } from "@/shared/constants/labels"; @@ -352,4 +352,5 @@ export const COMP: DaoConfiguration = { dataTables: true, activityFeed: true, governancePage: true, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/ens.ts b/apps/dashboard/shared/dao-config/ens.ts index cc524d453..1f62393d1 100644 --- a/apps/dashboard/shared/dao-config/ens.ts +++ b/apps/dashboard/shared/dao-config/ens.ts @@ -1,6 +1,6 @@ import { mainnet } from "viem/chains"; -import { EnsIcon } from "@/shared/components/icons/EnsIcon"; +import { EnsIcon } from "@/shared/components/icons"; import { MainnetIcon } from "@/shared/components/icons/MainnetIcon"; import { GOVERNANCE_IMPLEMENTATION_CONSTANTS } from "@/shared/constants/governance-implementations"; import { QUORUM_CALCULATION_TYPES } from "@/shared/constants/labels"; @@ -369,5 +369,5 @@ export const ENS: DaoConfiguration = { activityFeed: true, governancePage: true, serviceProviders: true, - offchainProposals: true, + proposalTypeConfiguration: "composite", }; diff --git a/apps/dashboard/shared/dao-config/fluid.ts b/apps/dashboard/shared/dao-config/fluid.ts index 55defab9c..bd831b1bd 100644 --- a/apps/dashboard/shared/dao-config/fluid.ts +++ b/apps/dashboard/shared/dao-config/fluid.ts @@ -38,4 +38,5 @@ export const FLUID: DaoConfiguration = { dataTables: true, activityFeed: false, governancePage: true, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/gno.ts b/apps/dashboard/shared/dao-config/gno.ts new file mode 100644 index 000000000..3910a7ddc --- /dev/null +++ b/apps/dashboard/shared/dao-config/gno.ts @@ -0,0 +1,36 @@ +import { mainnet } from "viem/chains"; + +import { MainnetIcon } from "@/shared/components/icons/MainnetIcon"; +import type { DaoConfiguration } from "@/shared/dao-config/types"; +import { GnosisOgIcon } from "@/shared/og/dao-og-icons"; +import { GnosisIcon } from "@/shared/components/icons"; + +export const GNO: DaoConfiguration = { + name: "Gnosis", + decimals: 18, + color: { + svgColor: "#0080bc", + svgBgColor: "#fff", + }, + icon: GnosisIcon, + ogIcon: GnosisOgIcon, + daoOverview: { + token: "ERC20", + chain: { ...mainnet, icon: MainnetIcon, blockTime: 12 }, + // https://etherscan.io/address/0x6810e776880C02933D47DB1b9fc05908e5386b96 + contracts: { + token: "0x6810e776880C02933D47DB1b9fc05908e5386b96", + }, + snapshot: "https://snapshot.org/#/gnosis.eth", + govPlatform: { + name: "Snapshot", + url: "https://snapshot.org/#/gnosis.eth", + }, + }, + dataTables: true, + governancePage: true, + activityFeed: true, + overviewPage: false, + initialPage: "holders-and-delegates", + proposalTypeConfiguration: "offchain", +}; diff --git a/apps/dashboard/shared/dao-config/gtc.ts b/apps/dashboard/shared/dao-config/gtc.ts index e687d752c..0219e4a3d 100644 --- a/apps/dashboard/shared/dao-config/gtc.ts +++ b/apps/dashboard/shared/dao-config/gtc.ts @@ -1,6 +1,6 @@ import { mainnet } from "viem/chains"; -import { GitcoinIcon } from "@/shared/components/icons/GitcoinIcon"; +import { GitcoinIcon } from "@/shared/components/icons"; import { MainnetIcon } from "@/shared/components/icons/MainnetIcon"; import { GOVERNANCE_IMPLEMENTATION_CONSTANTS } from "@/shared/constants/governance-implementations"; import { QUORUM_CALCULATION_TYPES } from "@/shared/constants/labels"; @@ -324,4 +324,5 @@ export const GTC: DaoConfiguration = { }, }, governancePage: true, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/index.ts b/apps/dashboard/shared/dao-config/index.ts index 7c0621799..3a2967e69 100644 --- a/apps/dashboard/shared/dao-config/index.ts +++ b/apps/dashboard/shared/dao-config/index.ts @@ -10,9 +10,11 @@ import { SCR } from "@/shared/dao-config/scr"; import { SHU } from "@/shared/dao-config/shu"; import { UNI } from "@/shared/dao-config/uni"; import { AAVE } from "@/shared/dao-config/aave"; +import { GNO } from "@/shared/dao-config/gno"; export default { AAVE, + GNO, UNI, ENS, FLUID, diff --git a/apps/dashboard/shared/dao-config/lil-nouns.ts b/apps/dashboard/shared/dao-config/lil-nouns.ts index e964a519c..8eb0cd577 100644 --- a/apps/dashboard/shared/dao-config/lil-nouns.ts +++ b/apps/dashboard/shared/dao-config/lil-nouns.ts @@ -29,4 +29,5 @@ export const LIL_NOUNS: DaoConfiguration = { dataTables: true, activityFeed: true, tokenDistribution: true, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/nouns.ts b/apps/dashboard/shared/dao-config/nouns.ts index bbcdaf9b3..d0aa2d3d1 100644 --- a/apps/dashboard/shared/dao-config/nouns.ts +++ b/apps/dashboard/shared/dao-config/nouns.ts @@ -1,6 +1,6 @@ import { mainnet } from "viem/chains"; -import { NounsIcon } from "@/shared/components/icons/NounsIcon"; +import { NounsIcon } from "@/shared/components/icons"; import { MainnetIcon } from "@/shared/components/icons/MainnetIcon"; import { GOVERNANCE_IMPLEMENTATION_CONSTANTS } from "@/shared/constants/governance-implementations"; import { RECOMMENDED_SETTINGS } from "@/shared/constants/recommended-settings"; @@ -324,4 +324,5 @@ export const NOUNS: DaoConfiguration = { }, }, governancePage: true, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/obol.ts b/apps/dashboard/shared/dao-config/obol.ts index a9e25ed46..40a279831 100644 --- a/apps/dashboard/shared/dao-config/obol.ts +++ b/apps/dashboard/shared/dao-config/obol.ts @@ -1,6 +1,6 @@ import { mainnet } from "viem/chains"; -import { ObolIcon } from "@/shared/components/icons/ObolIcon"; +import { ObolIcon } from "@/shared/components/icons"; import { MainnetIcon } from "@/shared/components/icons/MainnetIcon"; import { GOVERNANCE_IMPLEMENTATION_CONSTANTS } from "@/shared/constants/governance-implementations"; import { QUORUM_CALCULATION_TYPES } from "@/shared/constants/labels"; @@ -336,4 +336,5 @@ export const OBOL: DaoConfiguration = { }, }, governancePage: true, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/op.ts b/apps/dashboard/shared/dao-config/op.ts index d331b0eb9..354cf78e5 100644 --- a/apps/dashboard/shared/dao-config/op.ts +++ b/apps/dashboard/shared/dao-config/op.ts @@ -1,6 +1,6 @@ import { optimism } from "viem/chains"; -import { OptimismIcon } from "@/shared/components/icons/OptimismIcon"; +import { OptimismIcon } from "@/shared/components/icons"; import { OptimismChainIcon } from "@/shared/components/icons/OptimismChainIcon"; import { GOVERNANCE_IMPLEMENTATION_CONSTANTS } from "@/shared/constants/governance-implementations"; import { QUORUM_CALCULATION_TYPES } from "@/shared/constants/labels"; @@ -345,4 +345,5 @@ export const OP: DaoConfiguration = { resilienceStages: true, tokenDistribution: true, dataTables: true, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/scr.ts b/apps/dashboard/shared/dao-config/scr.ts index 9938d15d6..bedc9e1b6 100644 --- a/apps/dashboard/shared/dao-config/scr.ts +++ b/apps/dashboard/shared/dao-config/scr.ts @@ -1,6 +1,6 @@ import { scroll } from "viem/chains"; -import { ScrollIcon } from "@/shared/components/icons/ScrollIcon"; +import { ScrollIcon } from "@/shared/components/icons"; import { GOVERNANCE_IMPLEMENTATION_CONSTANTS } from "@/shared/constants/governance-implementations"; import { QUORUM_CALCULATION_TYPES } from "@/shared/constants/labels"; import { RECOMMENDED_SETTINGS } from "@/shared/constants/recommended-settings"; @@ -318,4 +318,5 @@ export const SCR: DaoConfiguration = { }, }, }, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/shu.ts b/apps/dashboard/shared/dao-config/shu.ts index 6236f5c59..059376290 100644 --- a/apps/dashboard/shared/dao-config/shu.ts +++ b/apps/dashboard/shared/dao-config/shu.ts @@ -1,6 +1,6 @@ import { mainnet } from "viem/chains"; -import { ShutterIcon } from "@/shared/components/icons/ShutterIcon"; +import { ShutterIcon } from "@/shared/components/icons"; import { MainnetIcon } from "@/shared/components/icons/MainnetIcon"; import { GOVERNANCE_IMPLEMENTATION_CONSTANTS } from "@/shared/constants/governance-implementations"; import { QUORUM_CALCULATION_TYPES } from "@/shared/constants/labels"; @@ -350,4 +350,5 @@ export const SHU: DaoConfiguration = { tokenDistribution: true, dataTables: true, governancePage: true, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/dao-config/types.ts b/apps/dashboard/shared/dao-config/types.ts index 7c01e6f9b..8d2e31ef8 100644 --- a/apps/dashboard/shared/dao-config/types.ts +++ b/apps/dashboard/shared/dao-config/types.ts @@ -139,6 +139,8 @@ export type DaoFeaturePageSlug = | "risk-analysis" | "token-distribution"; +export type ProposalTypeConfiguration = "onchain" | "offchain" | "composite"; + // Complete DAO configuration structure export interface DaoConfiguration extends BaseInfo { daoOverview: DaoOverviewConfig; @@ -153,7 +155,7 @@ export interface DaoConfiguration extends BaseInfo { noStage?: boolean; governancePage?: boolean; serviceProviders?: boolean; - offchainProposals?: boolean; + proposalTypeConfiguration: ProposalTypeConfiguration; /** When false, hides the DAO Overview page from navigation. Defaults to true. */ overviewPage?: boolean; /** When set, visiting /{daoId}/ redirects to /{daoId}/{initialPage}. */ diff --git a/apps/dashboard/shared/dao-config/uni.ts b/apps/dashboard/shared/dao-config/uni.ts index 20060c3c8..9c9475709 100644 --- a/apps/dashboard/shared/dao-config/uni.ts +++ b/apps/dashboard/shared/dao-config/uni.ts @@ -1,6 +1,6 @@ import { mainnet } from "viem/chains"; -import { UniswapIcon } from "@/shared/components/icons/UniswapIcon"; +import { UniswapIcon } from "@/shared/components/icons"; import { MainnetIcon } from "@/shared/components/icons/MainnetIcon"; import { GOVERNANCE_IMPLEMENTATION_CONSTANTS } from "@/shared/constants/governance-implementations"; import { QUORUM_CALCULATION_TYPES } from "@/shared/constants/labels"; @@ -314,4 +314,5 @@ export const UNI: DaoConfiguration = { dataTables: true, activityFeed: true, governancePage: true, + proposalTypeConfiguration: "onchain", }; diff --git a/apps/dashboard/shared/og/dao-og-icons.tsx b/apps/dashboard/shared/og/dao-og-icons.tsx index 1e2bc1622..2f2a7b1b8 100644 --- a/apps/dashboard/shared/og/dao-og-icons.tsx +++ b/apps/dashboard/shared/og/dao-og-icons.tsx @@ -238,3 +238,26 @@ export function ShutterOgIcon({ size }: { size: number }) { ); } + +export function GnosisOgIcon({ size }: { size: number }) { + return ( + + + + + + + ); +} diff --git a/apps/dashboard/shared/types/daos.ts b/apps/dashboard/shared/types/daos.ts index d97193c60..f58d65a6e 100644 --- a/apps/dashboard/shared/types/daos.ts +++ b/apps/dashboard/shared/types/daos.ts @@ -11,6 +11,7 @@ export enum DaoIdEnum { // OPTIMISM = "OP", UNISWAP = "UNI", GITCOIN = "GTC", + GNO = "GNO", } export interface DAO { diff --git a/apps/indexer/config/aave.config.ts b/apps/indexer/config/aave.config.ts index 9a71d09e5..65cba28ea 100644 --- a/apps/indexer/config/aave.config.ts +++ b/apps/indexer/config/aave.config.ts @@ -12,7 +12,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/arbitrum.config.ts b/apps/indexer/config/arbitrum.config.ts index e9e63fc4a..8de7e39c5 100644 --- a/apps/indexer/config/arbitrum.config.ts +++ b/apps/indexer/config/arbitrum.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { arbitrum_mainnet: { id: 42161, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/compound.config.ts b/apps/indexer/config/compound.config.ts index 5ee51b5aa..a7fa5ba87 100644 --- a/apps/indexer/config/compound.config.ts +++ b/apps/indexer/config/compound.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/ens.config.ts b/apps/indexer/config/ens.config.ts index 72c4b4f26..9fdc897f2 100644 --- a/apps/indexer/config/ens.config.ts +++ b/apps/indexer/config/ens.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/fluid.config.ts b/apps/indexer/config/fluid.config.ts index a73809c7b..d60658084 100644 --- a/apps/indexer/config/fluid.config.ts +++ b/apps/indexer/config/fluid.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/gitcoin.config.ts b/apps/indexer/config/gitcoin.config.ts index 8d8ba2bc7..397c75d16 100644 --- a/apps/indexer/config/gitcoin.config.ts +++ b/apps/indexer/config/gitcoin.config.ts @@ -11,7 +11,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/gnosis.config.ts b/apps/indexer/config/gnosis.config.ts new file mode 100644 index 000000000..d8c5b950b --- /dev/null +++ b/apps/indexer/config/gnosis.config.ts @@ -0,0 +1,67 @@ +import { createConfig } from "ponder"; + +import { env } from "@/env"; +import { + GnosisChainGno, + GnosisChainLGno, + GnosisChainSGno, +} from "@/indexer/gnosis/abi/gnosis-chain"; +import { MainnetGno, MainnetLGno } from "@/indexer/gnosis/abi/ethereum-mainnet"; + +export default createConfig({ + database: { + kind: "postgres", + connectionString: env.DATABASE_URL, + }, + chains: { + ethereum_mainnet: { + id: 1, + rpc: env.RPC_URLS[0], + maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, + pollingInterval: env.POLLING_INTERVAL, + }, + gnosis_chain: { + id: 100, + rpc: env.RPC_URLS[1], + maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, + pollingInterval: env.POLLING_INTERVAL, + }, + }, + contracts: { + GnosisGNO: { + abi: GnosisChainGno, + chain: "gnosis_chain", + // https://gnosisscan.io/address/0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb + address: "0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb", + startBlock: 11629829, + }, + GnosisLGNO: { + abi: GnosisChainLGno, + chain: "gnosis_chain", + // https://gnosisscan.io/address/0xd4Ca39f78Bf14BfaB75226AC833b1858dB16f9a1 + address: "0xd4Ca39f78Bf14BfaB75226AC833b1858dB16f9a1", + startBlock: 20388099, + }, + GnosisSGNO: { + abi: GnosisChainSGno, + chain: "gnosis_chain", + // https://gnosisscan.io/address/0xA4eF9Da5BA71Cc0D2e5E877a910A37eC43420445 + address: "0xA4eF9Da5BA71Cc0D2e5E877a910A37eC43420445", + startBlock: 21275850, + }, + MainnetGNO: { + abi: MainnetGno, + chain: "ethereum_mainnet", + // https://etherscan.io/address/0x6810e776880C02933D47DB1b9fc05908e5386b96 + address: "0x6810e776880C02933D47DB1b9fc05908e5386b96", + startBlock: 3557596, + }, + MainnetLGNO: { + abi: MainnetLGno, + chain: "ethereum_mainnet", + // https://etherscan.io/address/0x6810e776880C02933D47DB1b9fc05908e5386b96 + address: "0x4f8AD938eBA0CD19155a835f617317a6E788c868", + startBlock: 14111111, + }, + }, +}); diff --git a/apps/indexer/config/lil-nouns.config.ts b/apps/indexer/config/lil-nouns.config.ts index 5b36557cf..f28be03e0 100644 --- a/apps/indexer/config/lil-nouns.config.ts +++ b/apps/indexer/config/lil-nouns.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/nouns.config.ts b/apps/indexer/config/nouns.config.ts index 8e8efcddb..4e042be4c 100644 --- a/apps/indexer/config/nouns.config.ts +++ b/apps/indexer/config/nouns.config.ts @@ -16,7 +16,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/obol.config.ts b/apps/indexer/config/obol.config.ts index 07adce010..b5a67b590 100644 --- a/apps/indexer/config/obol.config.ts +++ b/apps/indexer/config/obol.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/optimism.config.ts b/apps/indexer/config/optimism.config.ts index d941e9557..9c6a75bf7 100644 --- a/apps/indexer/config/optimism.config.ts +++ b/apps/indexer/config/optimism.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { optimism_mainnet: { id: 10, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/scroll.config.ts b/apps/indexer/config/scroll.config.ts index e75ff28b1..893945351 100644 --- a/apps/indexer/config/scroll.config.ts +++ b/apps/indexer/config/scroll.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { scroll_mainnet: { id: 534352, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/shutter.config.ts b/apps/indexer/config/shutter.config.ts index d719e1033..a9d25dfea 100644 --- a/apps/indexer/config/shutter.config.ts +++ b/apps/indexer/config/shutter.config.ts @@ -19,7 +19,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/test.config.ts b/apps/indexer/config/test.config.ts index 4718c9ec9..ef928f835 100644 --- a/apps/indexer/config/test.config.ts +++ b/apps/indexer/config/test.config.ts @@ -7,7 +7,7 @@ export default createConfig({ chains: { anvil: { id: 31337, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: 10, pollingInterval: 1000, disableCache: true, diff --git a/apps/indexer/config/uniswap.config.ts b/apps/indexer/config/uniswap.config.ts index b2a09f452..6da9a2625 100644 --- a/apps/indexer/config/uniswap.config.ts +++ b/apps/indexer/config/uniswap.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { ethereum_mainnet: { id: 1, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/config/zk.config.ts b/apps/indexer/config/zk.config.ts index 8cee8013f..5db4e1d1e 100644 --- a/apps/indexer/config/zk.config.ts +++ b/apps/indexer/config/zk.config.ts @@ -15,7 +15,7 @@ export default createConfig({ chains: { zksync_mainnet: { id: 324, - rpc: env.RPC_URL, + rpc: env.RPC_URLS[0], maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, pollingInterval: env.POLLING_INTERVAL, }, diff --git a/apps/indexer/ponder.config.ts b/apps/indexer/ponder.config.ts index 57cc70f94..c267cddc0 100644 --- a/apps/indexer/ponder.config.ts +++ b/apps/indexer/ponder.config.ts @@ -12,6 +12,7 @@ import scrollConfig from "./config/scroll.config"; import shutterConfig from "./config/shutter.config"; import uniswapConfig from "./config/uniswap.config"; import zkConfig from "./config/zk.config"; +import gnosisConfig from "./config/gnosis.config"; export default { chains: { @@ -29,6 +30,7 @@ export default { ...obolConfig.chains, ...zkConfig.chains, ...shutterConfig.chains, + ...gnosisConfig.chains, }, contracts: { ...aaveConfig.contracts, @@ -45,5 +47,6 @@ export default { ...obolConfig.contracts, ...zkConfig.contracts, ...shutterConfig.contracts, + ...gnosisConfig.contracts, }, }; diff --git a/apps/indexer/src/env.ts b/apps/indexer/src/env.ts index 21c6d81a7..042694799 100644 --- a/apps/indexer/src/env.ts +++ b/apps/indexer/src/env.ts @@ -7,7 +7,10 @@ dotenv.config(); export const env = z .object({ - RPC_URL: z.string(), + RPC_URLS: z + .string() + .transform((s) => s.split(",")) + .pipe(z.string().url().array().min(1)), DATABASE_URL: z.string().optional(), POLLING_INTERVAL: z.coerce.number().default(10000), // 10s MAX_REQUESTS_PER_SECOND: z.coerce.number().default(20), diff --git a/apps/indexer/src/eventHandlers/transfer.ts b/apps/indexer/src/eventHandlers/transfer.ts index d72ed1664..4b80e4d34 100644 --- a/apps/indexer/src/eventHandlers/transfer.ts +++ b/apps/indexer/src/eventHandlers/transfer.ts @@ -1,9 +1,11 @@ import { Context } from "ponder:registry"; import { accountBalance, + accountPower, balanceHistory, feedEvent, transfer, + votingPowerHistory, } from "ponder:schema"; import { Address, getAddress, Hex, zeroAddress } from "viem"; @@ -169,3 +171,83 @@ export const tokenTransfer = async ( }, }); }; + +/** + * Mirrors balance updates into voting power tables for GNO, where token + * balance equals voting power (no on-chain delegation events exist). + * + * Must be called after `tokenTransfer` so that `accountBalance` already + * reflects the new balance — the same updated value is written to + * `accountPower.votingPower` and `votingPowerHistory`. + */ +export const gnoVotingPowerTransfer = async ( + context: Context, + daoId: DaoIdEnum, + args: { + from: Address; + to: Address; + transactionHash: Hex; + value: bigint; + timestamp: bigint; + logIndex: number; + }, +) => { + const { from, to, transactionHash, value, timestamp, logIndex } = args; + + const normalizedFrom = getAddress(from); + const normalizedTo = getAddress(to); + + // --- receiver --- + const { votingPower: newReceiverVotingPower } = await context.db + .insert(accountPower) + .values({ + accountId: normalizedTo, + daoId, + votingPower: value, + }) + .onConflictDoUpdate((current) => ({ + votingPower: current.votingPower + value, + })); + + await context.db + .insert(votingPowerHistory) + .values({ + daoId, + transactionHash, + accountId: normalizedTo, + votingPower: newReceiverVotingPower, + delta: value, + deltaMod: value > 0n ? value : -value, + timestamp, + logIndex, + }) + .onConflictDoNothing(); + + // --- sender (skip mints from zero address) --- + if (from !== zeroAddress) { + const { votingPower: newSenderVotingPower } = await context.db + .insert(accountPower) + .values({ + accountId: normalizedFrom, + daoId, + votingPower: -value, + }) + .onConflictDoUpdate((current) => ({ + votingPower: current.votingPower - value, + })); + + await context.db + .insert(votingPowerHistory) + .values({ + daoId, + transactionHash, + accountId: normalizedFrom, + votingPower: newSenderVotingPower, + delta: -value, + deltaMod: value > 0n ? value : -value, + timestamp, + logIndex, + }) + .onConflictDoNothing(); + } +}; diff --git a/apps/indexer/src/index.ts b/apps/indexer/src/index.ts index bc28059c2..a6f147dde 100644 --- a/apps/indexer/src/index.ts +++ b/apps/indexer/src/index.ts @@ -44,6 +44,13 @@ import { ZKTokenIndexer, GovernorIndexer as ZKGovernorIndexer, } from "./indexer/zk"; +import { + GnosisGnoTokenIndexer, + GnosisLGnoTokenIndexer, + GnosisSGnoTokenIndexer, + MainnetGnoTokenIndexer, + MainnetLGnoTokenIndexer, +} from "./indexer/gnosis"; const { DAO_ID: daoId } = env; @@ -127,6 +134,20 @@ switch (daoId) { aAAVETokenIndexer(aAAVE.address, aAAVE.decimals); break; } + case DaoIdEnum.GNO: { + // Mainnet GNO address will be the only one referenced in the DB since we'll always just use the sum of all balances + const { + mainnet: { + gno: { address, decimals }, + }, + } = CONTRACT_ADDRESSES[DaoIdEnum.GNO]; + MainnetGnoTokenIndexer(address, decimals); + MainnetLGnoTokenIndexer(address); + GnosisGnoTokenIndexer(address); + GnosisLGnoTokenIndexer(address); + GnosisSGnoTokenIndexer(address); + break; + } default: throw new Error(`DAO ${daoId} not supported`); } diff --git a/apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/gno.ts b/apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/gno.ts new file mode 100644 index 000000000..4175a684a --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/gno.ts @@ -0,0 +1,126 @@ +export const MainnetGno = [ + { + constant: true, + inputs: [], + name: "name", + outputs: [{ name: "", type: "string" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_spender", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "approve", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "totalSupply", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_from", type: "address" }, + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "decimals", + outputs: [{ name: "", type: "uint8" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [{ name: "_owner", type: "address" }], + name: "balanceOf", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "symbol", + outputs: [{ name: "", type: "string" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "transfer", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [ + { name: "_owner", type: "address" }, + { name: "_spender", type: "address" }, + ], + name: "allowance", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { name: "dutchAuction", type: "address" }, + { name: "owners", type: "address[]" }, + { name: "tokens", type: "uint256[]" }, + ], + payable: false, + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "from", type: "address" }, + { indexed: true, name: "to", type: "address" }, + { indexed: false, name: "value", type: "uint256" }, + ], + name: "Transfer", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "owner", type: "address" }, + { indexed: true, name: "spender", type: "address" }, + { indexed: false, name: "value", type: "uint256" }, + ], + name: "Approval", + type: "event", + }, +] as const; diff --git a/apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/index.ts b/apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/index.ts new file mode 100644 index 000000000..46811546e --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/index.ts @@ -0,0 +1,2 @@ +export * from "./gno"; +export * from "./lgno"; diff --git a/apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/lgno.ts b/apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/lgno.ts new file mode 100644 index 000000000..bc8c93015 --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/abi/ethereum-mainnet/lgno.ts @@ -0,0 +1,212 @@ +export const MainnetLGno = [ + { inputs: [], name: "DepositPeriodOver", type: "error" }, + { inputs: [], name: "ExceedsBalance", type: "error" }, + { inputs: [], name: "LockPeriodOngoing", type: "error" }, + { inputs: [], name: "NotSupported", type: "error" }, + { inputs: [], name: "TransferFailed", type: "error" }, + { + 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: "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: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + ], + name: "allowance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "amount", type: "uint256" }], + name: "deposit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "depositDeadline", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_owner", type: "address" }, + { internalType: "address", name: "_token", type: "address" }, + { internalType: "uint256", name: "_depositDeadline", type: "uint256" }, + { internalType: "uint256", name: "_lockDuration", type: "uint256" }, + { internalType: "string", name: "_name", type: "string" }, + { internalType: "string", name: "_symbol", type: "string" }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "lockDuration", + 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: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "token", + outputs: [{ internalType: "contract ERC20", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "address", name: "", type: "address" }, + { internalType: "uint256", name: "", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "amount", type: "uint256" }], + name: "withdraw", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/gno.ts b/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/gno.ts new file mode 100644 index 000000000..fb054d40a --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/gno.ts @@ -0,0 +1,480 @@ +export const GnosisChainGno = [ + { + constant: true, + inputs: [], + name: "mintingFinished", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "name", + outputs: [{ name: "", type: "string" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "approve", + outputs: [{ name: "result", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [{ name: "_bridgeContract", type: "address" }], + name: "setBridgeContract", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "totalSupply", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_sender", type: "address" }, + { name: "_recipient", type: "address" }, + { name: "_amount", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "PERMIT_TYPEHASH", + outputs: [{ name: "", type: "bytes32" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "decimals", + outputs: [{ name: "", type: "uint8" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "DOMAIN_SEPARATOR", + outputs: [{ name: "", type: "bytes32" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_addedValue", type: "uint256" }, + ], + name: "increaseAllowance", + outputs: [{ name: "result", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + { name: "_data", type: "bytes" }, + ], + name: "transferAndCall", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_amount", type: "uint256" }, + ], + name: "mint", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [{ name: "_value", type: "uint256" }], + name: "burn", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "version", + outputs: [{ name: "", type: "string" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_spender", type: "address" }, + { name: "_subtractedValue", type: "uint256" }, + ], + name: "decreaseApproval", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_token", type: "address" }, + { name: "_to", type: "address" }, + ], + name: "claimTokens", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [{ name: "_owner", type: "address" }], + name: "balanceOf", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [], + name: "renounceOwnership", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [{ name: "_address", type: "address" }], + name: "isBridge", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [], + name: "finishMinting", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [{ name: "", type: "address" }], + name: "nonces", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "getTokenInterfacesVersion", + outputs: [ + { name: "major", type: "uint64" }, + { name: "minor", type: "uint64" }, + { name: "patch", type: "uint64" }, + ], + payable: false, + stateMutability: "pure", + type: "function", + }, + { + constant: true, + inputs: [], + name: "owner", + outputs: [{ name: "", type: "address" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_holder", type: "address" }, + { name: "_spender", type: "address" }, + { name: "_nonce", type: "uint256" }, + { name: "_expiry", type: "uint256" }, + { name: "_allowed", type: "bool" }, + { name: "_v", type: "uint8" }, + { name: "_r", type: "bytes32" }, + { name: "_s", type: "bytes32" }, + ], + name: "permit", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "symbol", + outputs: [{ name: "", type: "string" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "spender", type: "address" }, + { name: "subtractedValue", type: "uint256" }, + ], + name: "decreaseAllowance", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "transfer", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_amount", type: "uint256" }, + ], + name: "push", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_from", type: "address" }, + { name: "_to", type: "address" }, + { name: "_amount", type: "uint256" }, + ], + name: "move", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "PERMIT_TYPEHASH_LEGACY", + outputs: [{ name: "", type: "bytes32" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "bridgeContract", + outputs: [{ name: "", type: "address" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_holder", type: "address" }, + { name: "_spender", type: "address" }, + { name: "_value", type: "uint256" }, + { name: "_deadline", type: "uint256" }, + { name: "_v", type: "uint8" }, + { name: "_r", type: "bytes32" }, + { name: "_s", type: "bytes32" }, + ], + name: "permit", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_spender", type: "address" }, + { name: "_addedValue", type: "uint256" }, + ], + name: "increaseApproval", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [ + { name: "_owner", type: "address" }, + { name: "_spender", type: "address" }, + ], + name: "allowance", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_from", type: "address" }, + { name: "_amount", type: "uint256" }, + ], + name: "pull", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [{ name: "_newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [ + { name: "", type: "address" }, + { name: "", type: "address" }, + ], + name: "expirations", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { name: "_name", type: "string" }, + { name: "_symbol", type: "string" }, + { name: "_decimals", type: "uint8" }, + { name: "_chainId", type: "uint256" }, + ], + payable: false, + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "to", type: "address" }, + { indexed: false, name: "amount", type: "uint256" }, + ], + name: "Mint", + type: "event", + }, + { anonymous: false, inputs: [], name: "MintFinished", type: "event" }, + { + anonymous: false, + inputs: [{ indexed: true, name: "previousOwner", type: "address" }], + name: "OwnershipRenounced", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "previousOwner", type: "address" }, + { indexed: true, name: "newOwner", type: "address" }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "burner", type: "address" }, + { indexed: false, name: "value", type: "uint256" }, + ], + name: "Burn", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "from", type: "address" }, + { indexed: true, name: "to", type: "address" }, + { indexed: false, name: "value", type: "uint256" }, + { indexed: false, name: "data", type: "bytes" }, + ], + name: "Transfer", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "owner", type: "address" }, + { indexed: true, name: "spender", type: "address" }, + { indexed: false, name: "value", type: "uint256" }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "from", type: "address" }, + { indexed: true, name: "to", type: "address" }, + { indexed: false, name: "value", type: "uint256" }, + ], + name: "Transfer", + type: "event", + }, +] as const; diff --git a/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/index.ts b/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/index.ts new file mode 100644 index 000000000..b5f6486ec --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/index.ts @@ -0,0 +1,3 @@ +export * from "./gno"; +export * from "./lgno"; +export * from "./sgno"; diff --git a/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/lgno.ts b/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/lgno.ts new file mode 100644 index 000000000..7e06cb0a5 --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/lgno.ts @@ -0,0 +1,115 @@ +export const GnosisChainLGno = [ + { + inputs: [ + { internalType: "address", name: "_logic", type: "address" }, + { internalType: "address", name: "admin_", type: "address" }, + { internalType: "bytes", name: "_data", type: "bytes" }, + ], + stateMutability: "payable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "previousAdmin", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "newAdmin", + type: "address", + }, + ], + name: "AdminChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "beacon", + type: "address", + }, + ], + name: "BeaconUpgraded", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "implementation", + type: "address", + }, + ], + name: "Upgraded", + type: "event", + }, + { stateMutability: "payable", type: "fallback" }, + { + inputs: [], + name: "admin", + outputs: [{ internalType: "address", name: "admin_", type: "address" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newAdmin", type: "address" }], + name: "changeAdmin", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "implementation", + outputs: [ + { internalType: "address", name: "implementation_", type: "address" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "newImplementation", type: "address" }, + ], + name: "upgradeTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "newImplementation", type: "address" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "upgradeToAndCall", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { stateMutability: "payable", type: "receive" }, + { + 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", + }, +] as const; diff --git a/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/sgno.ts b/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/sgno.ts new file mode 100644 index 000000000..6fcb43149 --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/abi/gnosis-chain/sgno.ts @@ -0,0 +1,428 @@ +export const GnosisChainSGno = [ + { + 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: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Paused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "role", type: "bytes32" }, + { + indexed: true, + internalType: "bytes32", + name: "previousAdminRole", + type: "bytes32", + }, + { + indexed: true, + internalType: "bytes32", + name: "newAdminRole", + type: "bytes32", + }, + ], + name: "RoleAdminChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "role", type: "bytes32" }, + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "RoleGranted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "bytes32", name: "role", type: "bytes32" }, + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + ], + name: "RoleRevoked", + 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", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Unpaused", + type: "event", + }, + { + inputs: [], + name: "DEFAULT_ADMIN_ROLE", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "DOMAIN_SEPARATOR", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "PAUSER_ROLE", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_account", type: "address" }], + name: "addAdmin", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_account", type: "address" }], + name: "addPauser", + outputs: [], + stateMutability: "nonpayable", + 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: "uint256", name: "amount", type: "uint256" }, + ], + name: "burn", + 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: [], + name: "distributorPrincipal", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "role", type: "bytes32" }], + name: "getRoleAdmin", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "uint256", name: "index", type: "uint256" }, + ], + name: "getRoleMember", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes32", name: "role", type: "bytes32" }], + name: "getRoleMemberCount", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "grantRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "hasRole", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + 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: "address", name: "_account", type: "address" }], + name: "isAdmin", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_account", type: "address" }], + name: "isPauser", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "name", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "nonces", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "paused", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + 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: [{ internalType: "address", name: "_account", type: "address" }], + name: "removeAdmin", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_account", type: "address" }], + name: "removePauser", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "renounceRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes32", name: "role", type: "bytes32" }, + { internalType: "address", name: "account", type: "address" }, + ], + name: "revokeRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "bool", name: "isDisabled", type: "bool" }, + ], + name: "toggleRewards", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "totalDeposits", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + 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: [], + name: "unpause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/apps/indexer/src/indexer/gnosis/gnosis-gno.ts b/apps/indexer/src/indexer/gnosis/gnosis-gno.ts new file mode 100644 index 000000000..b3d68bb52 --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/gnosis-gno.ts @@ -0,0 +1,194 @@ +import { ponder } from "ponder:registry"; +import type { Context, Event } from "ponder:registry"; +import { DaoIdEnum } from "@/lib/enums"; +import { + BurningAddresses, + CEXAddresses, + DEXAddresses, + LendingAddresses, + MetricTypesEnum, + NonCirculatingAddresses, + TreasuryAddresses, +} from "@/lib/constants"; +import { createAddressSet, handleTransaction } from "@/eventHandlers/shared"; +import { Address } from "viem"; +import { gnoVotingPowerTransfer, tokenTransfer } from "@/eventHandlers"; +import { + updateCirculatingSupply, + updateSupplyMetric, + updateTotalSupply, +} from "@/eventHandlers/metrics"; + +export function GnosisGnoTokenIndexer(address: Address) { + const daoId = DaoIdEnum.GNO; + const addressSets = { + cex: createAddressSet(Object.values(CEXAddresses[daoId])), + dex: createAddressSet(Object.values(DEXAddresses[daoId])), + lending: createAddressSet(Object.values(LendingAddresses[daoId])), + treasury: createAddressSet(Object.values(TreasuryAddresses[daoId])), + nonCirculating: createAddressSet( + Object.values(NonCirculatingAddresses[daoId]), + ), + burning: createAddressSet(Object.values(BurningAddresses[daoId])), + }; + + const processTransfer = async ( + event: Event<`GnosisGNO:Transfer(address indexed from, address indexed to, uint256 value)`>, + context: Context, + ) => { + const { logIndex } = event.log; + const { hash } = event.transaction; + const { from, to, value } = event.args; + const { timestamp } = event.block; + + // Process the transfer + await tokenTransfer( + context, + daoId, + { + from: from, + to: to, + token: address, + transactionHash: hash, + value: value, + timestamp: timestamp, + logIndex: logIndex, + }, + { + cex: addressSets.cex, + dex: addressSets.dex, + burning: addressSets.burning, + }, + ); + + // GNO has no on-chain delegation; balance == voting power + await gnoVotingPowerTransfer(context, daoId, { + from, + to, + transactionHash: hash, + value, + timestamp, + logIndex, + }); + + const lendingChanged = await updateSupplyMetric( + context, + "lendingSupply", + addressSets.lending, + MetricTypesEnum.LENDING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const cexChanged = await updateSupplyMetric( + context, + "cexSupply", + addressSets.cex, + MetricTypesEnum.CEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const dexChanged = await updateSupplyMetric( + context, + "dexSupply", + addressSets.dex, + MetricTypesEnum.DEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const treasuryChanged = await updateSupplyMetric( + context, + "treasury", + addressSets.treasury, + MetricTypesEnum.TREASURY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const nonCirculatingChanged = await updateSupplyMetric( + context, + "nonCirculatingSupply", + addressSets.nonCirculating, + MetricTypesEnum.NON_CIRCULATING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const totalSupplyChanged = await updateTotalSupply( + context, + addressSets.burning, + MetricTypesEnum.TOTAL_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + if ( + lendingChanged || + cexChanged || + dexChanged || + treasuryChanged || + nonCirculatingChanged || + totalSupplyChanged + ) { + await updateCirculatingSupply(context, daoId, address, timestamp); + } + + if (!to) return; + + // Handle transaction creation/update with flag calculation + await handleTransaction( + context, + hash, + from, + to, + timestamp, + [from, to], // Addresses to check + { + cex: addressSets.cex, + dex: addressSets.dex, + lending: addressSets.lending, + burning: addressSets.burning, + }, + ); + }; + + ponder.on( + `GnosisGNO:Transfer(address indexed from, address indexed to, uint256 value)`, + async ({ event, context }) => { + await processTransfer(event, context); + }, + ); + + ponder.on( + `GnosisGNO:Transfer(address indexed from, address indexed to, uint256 value, bytes data)`, + async ({ event, context }) => { + await processTransfer(event, context); + }, + ); +} diff --git a/apps/indexer/src/indexer/gnosis/gnosis-lgno.ts b/apps/indexer/src/indexer/gnosis/gnosis-lgno.ts new file mode 100644 index 000000000..c7ad78e0e --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/gnosis-lgno.ts @@ -0,0 +1,168 @@ +import { ponder } from "ponder:registry"; +import type { Context, Event } from "ponder:registry"; +import { Address } from "viem"; + +import { DaoIdEnum } from "@/lib/enums"; +import { + BurningAddresses, + CEXAddresses, + DEXAddresses, + LendingAddresses, + MetricTypesEnum, + NonCirculatingAddresses, + TreasuryAddresses, +} from "@/lib/constants"; +import { createAddressSet } from "@/eventHandlers/shared"; +import { gnoVotingPowerTransfer, tokenTransfer } from "@/eventHandlers"; +import { + updateCirculatingSupply, + updateSupplyMetric, + updateTotalSupply, +} from "@/eventHandlers/metrics"; + +export function GnosisLGnoTokenIndexer(address: Address) { + const daoId = DaoIdEnum.GNO; + const addressSets = { + cex: createAddressSet(Object.values(CEXAddresses[daoId])), + dex: createAddressSet(Object.values(DEXAddresses[daoId])), + lending: createAddressSet(Object.values(LendingAddresses[daoId])), + treasury: createAddressSet(Object.values(TreasuryAddresses[daoId])), + nonCirculating: createAddressSet( + Object.values(NonCirculatingAddresses[daoId]), + ), + burning: createAddressSet(Object.values(BurningAddresses[daoId])), + }; + + ponder.on( + `GnosisLGNO:Transfer`, + async ({ + event, + context, + }: { + event: Event<`GnosisLGNO:Transfer`>; + context: Context; + }) => { + const { logIndex } = event.log; + const { hash } = event.transaction; + const { from, to, value } = event.args; + const { timestamp } = event.block; + + await tokenTransfer( + context, + daoId, + { + from, + to, + token: address, + transactionHash: hash, + value, + timestamp, + logIndex, + }, + { + cex: addressSets.cex, + dex: addressSets.dex, + burning: addressSets.burning, + }, + ); + + // GNO DAO has no on-chain delegation; balance == voting power + await gnoVotingPowerTransfer(context, daoId, { + from, + to, + transactionHash: hash, + value, + timestamp, + logIndex, + }); + + const lendingChanged = await updateSupplyMetric( + context, + "lendingSupply", + addressSets.lending, + MetricTypesEnum.LENDING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const cexChanged = await updateSupplyMetric( + context, + "cexSupply", + addressSets.cex, + MetricTypesEnum.CEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const dexChanged = await updateSupplyMetric( + context, + "dexSupply", + addressSets.dex, + MetricTypesEnum.DEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const treasuryChanged = await updateSupplyMetric( + context, + "treasury", + addressSets.treasury, + MetricTypesEnum.TREASURY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const nonCirculatingChanged = await updateSupplyMetric( + context, + "nonCirculatingSupply", + addressSets.nonCirculating, + MetricTypesEnum.NON_CIRCULATING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const totalSupplyChanged = await updateTotalSupply( + context, + addressSets.burning, + MetricTypesEnum.TOTAL_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + if ( + lendingChanged || + cexChanged || + dexChanged || + treasuryChanged || + nonCirculatingChanged || + totalSupplyChanged + ) { + await updateCirculatingSupply(context, daoId, address, timestamp); + } + }, + ); +} diff --git a/apps/indexer/src/indexer/gnosis/gnosis-sgno.ts b/apps/indexer/src/indexer/gnosis/gnosis-sgno.ts new file mode 100644 index 000000000..2c500b088 --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/gnosis-sgno.ts @@ -0,0 +1,167 @@ +import { ponder } from "ponder:registry"; +import { Address } from "viem"; + +import { DaoIdEnum } from "@/lib/enums"; +import { + BurningAddresses, + CEXAddresses, + DEXAddresses, + LendingAddresses, + MetricTypesEnum, + NonCirculatingAddresses, + TreasuryAddresses, +} from "@/lib/constants"; +import { createAddressSet, handleTransaction } from "@/eventHandlers/shared"; +import { gnoVotingPowerTransfer, tokenTransfer } from "@/eventHandlers"; +import { + updateCirculatingSupply, + updateSupplyMetric, + updateTotalSupply, +} from "@/eventHandlers/metrics"; + +export function GnosisSGnoTokenIndexer(address: Address) { + const daoId = DaoIdEnum.GNO; + const addressSets = { + cex: createAddressSet(Object.values(CEXAddresses[daoId])), + dex: createAddressSet(Object.values(DEXAddresses[daoId])), + lending: createAddressSet(Object.values(LendingAddresses[daoId])), + treasury: createAddressSet(Object.values(TreasuryAddresses[daoId])), + nonCirculating: createAddressSet( + Object.values(NonCirculatingAddresses[daoId]), + ), + burning: createAddressSet(Object.values(BurningAddresses[daoId])), + }; + + ponder.on(`GnosisSGNO:Transfer`, async ({ event, context }) => { + const { logIndex } = event.log; + const { hash } = event.transaction; + const { from, to, value } = event.args; + const { timestamp } = event.block; + + await tokenTransfer( + context, + daoId, + { + from, + to, + token: address, + transactionHash: hash, + value, + timestamp, + logIndex, + }, + { + cex: addressSets.cex, + dex: addressSets.dex, + burning: addressSets.burning, + }, + ); + + // GNO DAO has no on-chain delegation; balance == voting power + await gnoVotingPowerTransfer(context, daoId, { + from, + to, + transactionHash: hash, + value, + timestamp, + logIndex, + }); + + const lendingChanged = await updateSupplyMetric( + context, + "lendingSupply", + addressSets.lending, + MetricTypesEnum.LENDING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const cexChanged = await updateSupplyMetric( + context, + "cexSupply", + addressSets.cex, + MetricTypesEnum.CEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const dexChanged = await updateSupplyMetric( + context, + "dexSupply", + addressSets.dex, + MetricTypesEnum.DEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const treasuryChanged = await updateSupplyMetric( + context, + "treasury", + addressSets.treasury, + MetricTypesEnum.TREASURY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const nonCirculatingChanged = await updateSupplyMetric( + context, + "nonCirculatingSupply", + addressSets.nonCirculating, + MetricTypesEnum.NON_CIRCULATING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const totalSupplyChanged = await updateTotalSupply( + context, + addressSets.burning, + MetricTypesEnum.TOTAL_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + if ( + lendingChanged || + cexChanged || + dexChanged || + treasuryChanged || + nonCirculatingChanged || + totalSupplyChanged + ) { + await updateCirculatingSupply(context, daoId, address, timestamp); + } + + if (!to) return; + + await handleTransaction(context, hash, from, to, timestamp, [from, to], { + cex: addressSets.cex, + dex: addressSets.dex, + lending: addressSets.lending, + burning: addressSets.burning, + }); + }); +} diff --git a/apps/indexer/src/indexer/gnosis/index.ts b/apps/indexer/src/indexer/gnosis/index.ts new file mode 100644 index 000000000..1fece0a16 --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/index.ts @@ -0,0 +1,5 @@ +export * from "./gnosis-gno"; +export * from "./gnosis-lgno"; +export * from "./gnosis-sgno"; +export * from "./mainnet-gno"; +export * from "./mainnet-lgno"; diff --git a/apps/indexer/src/indexer/gnosis/mainnet-gno.ts b/apps/indexer/src/indexer/gnosis/mainnet-gno.ts new file mode 100644 index 000000000..348635f31 --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/mainnet-gno.ts @@ -0,0 +1,185 @@ +import { ponder } from "ponder:registry"; +import { token } from "ponder:schema"; +import { DaoIdEnum } from "@/lib/enums"; +import { + BurningAddresses, + CEXAddresses, + DEXAddresses, + LendingAddresses, + MetricTypesEnum, + NonCirculatingAddresses, + TreasuryAddresses, +} from "@/lib/constants"; +import { createAddressSet, handleTransaction } from "@/eventHandlers/shared"; +import { Address } from "viem"; +import { gnoVotingPowerTransfer, tokenTransfer } from "@/eventHandlers"; +import { + updateCirculatingSupply, + updateSupplyMetric, + updateTotalSupply, +} from "@/eventHandlers/metrics"; + +export function MainnetGnoTokenIndexer(address: Address, decimals: number) { + const daoId = DaoIdEnum.GNO; + const addressSets = { + cex: createAddressSet(Object.values(CEXAddresses[daoId])), + dex: createAddressSet(Object.values(DEXAddresses[daoId])), + lending: createAddressSet(Object.values(LendingAddresses[daoId])), + treasury: createAddressSet(Object.values(TreasuryAddresses[daoId])), + nonCirculating: createAddressSet( + Object.values(NonCirculatingAddresses[daoId]), + ), + burning: createAddressSet(Object.values(BurningAddresses[daoId])), + }; + + ponder.on(`MainnetGNO:setup`, async ({ context }) => { + await context.db.insert(token).values({ + id: address, + name: daoId, + decimals, + }); + }); + + ponder.on(`MainnetGNO:Transfer`, async ({ event, context }) => { + const { logIndex } = event.log; + const { hash } = event.transaction; + const { from, to, value } = event.args; + const { timestamp } = event.block; + + // Process the transfer + await tokenTransfer( + context, + daoId, + { + from: from, + to: to, + token: address, + transactionHash: hash, + value: value, + timestamp: timestamp, + logIndex: logIndex, + }, + { + cex: addressSets.cex, + dex: addressSets.dex, + burning: addressSets.burning, + }, + ); + + // GNO has no on-chain delegation; balance == voting power + await gnoVotingPowerTransfer(context, daoId, { + from, + to, + transactionHash: hash, + value, + timestamp, + logIndex, + }); + + const lendingChanged = await updateSupplyMetric( + context, + "lendingSupply", + addressSets.lending, + MetricTypesEnum.LENDING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const cexChanged = await updateSupplyMetric( + context, + "cexSupply", + addressSets.cex, + MetricTypesEnum.CEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const dexChanged = await updateSupplyMetric( + context, + "dexSupply", + addressSets.dex, + MetricTypesEnum.DEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const treasuryChanged = await updateSupplyMetric( + context, + "treasury", + addressSets.treasury, + MetricTypesEnum.TREASURY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const nonCirculatingChanged = await updateSupplyMetric( + context, + "nonCirculatingSupply", + addressSets.nonCirculating, + MetricTypesEnum.NON_CIRCULATING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const totalSupplyChanged = await updateTotalSupply( + context, + addressSets.burning, + MetricTypesEnum.TOTAL_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + if ( + lendingChanged || + cexChanged || + dexChanged || + treasuryChanged || + nonCirculatingChanged || + totalSupplyChanged + ) { + await updateCirculatingSupply(context, daoId, address, timestamp); + } + + if (!to) return; + + // Handle transaction creation/update with flag calculation + await handleTransaction( + context, + hash, + from, + to, + timestamp, + [from, to], // Addresses to check + { + cex: addressSets.cex, + dex: addressSets.dex, + lending: addressSets.lending, + burning: addressSets.burning, + }, + ); + }); +} diff --git a/apps/indexer/src/indexer/gnosis/mainnet-lgno.ts b/apps/indexer/src/indexer/gnosis/mainnet-lgno.ts new file mode 100644 index 000000000..5b9f36e1a --- /dev/null +++ b/apps/indexer/src/indexer/gnosis/mainnet-lgno.ts @@ -0,0 +1,158 @@ +import { ponder } from "ponder:registry"; +import { DaoIdEnum } from "@/lib/enums"; +import { + BurningAddresses, + CEXAddresses, + DEXAddresses, + LendingAddresses, + MetricTypesEnum, + NonCirculatingAddresses, + TreasuryAddresses, +} from "@/lib/constants"; +import { createAddressSet } from "@/eventHandlers/shared"; +import { Address } from "viem"; +import { gnoVotingPowerTransfer, tokenTransfer } from "@/eventHandlers"; +import { + updateCirculatingSupply, + updateSupplyMetric, + updateTotalSupply, +} from "@/eventHandlers/metrics"; + +export function MainnetLGnoTokenIndexer(address: Address) { + const daoId = DaoIdEnum.GNO; + const addressSets = { + cex: createAddressSet(Object.values(CEXAddresses[daoId])), + dex: createAddressSet(Object.values(DEXAddresses[daoId])), + lending: createAddressSet(Object.values(LendingAddresses[daoId])), + treasury: createAddressSet(Object.values(TreasuryAddresses[daoId])), + nonCirculating: createAddressSet( + Object.values(NonCirculatingAddresses[daoId]), + ), + burning: createAddressSet(Object.values(BurningAddresses[daoId])), + }; + + ponder.on(`MainnetLGNO:Transfer`, async ({ event, context }) => { + const { logIndex } = event.log; + const { hash } = event.transaction; + const { from, to, value } = event.args; + const { timestamp } = event.block; + + // Process the transfer + await tokenTransfer( + context, + daoId, + { + from: from, + to: to, + token: address, + transactionHash: hash, + value: value, + timestamp: timestamp, + logIndex: logIndex, + }, + { + cex: addressSets.cex, + dex: addressSets.dex, + burning: addressSets.burning, + }, + ); + + // GNO DAO has no on-chain delegation; balance == voting power + await gnoVotingPowerTransfer(context, daoId, { + from, + to, + transactionHash: hash, + value, + timestamp, + logIndex, + }); + + const lendingChanged = await updateSupplyMetric( + context, + "lendingSupply", + addressSets.lending, + MetricTypesEnum.LENDING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const cexChanged = await updateSupplyMetric( + context, + "cexSupply", + addressSets.cex, + MetricTypesEnum.CEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const dexChanged = await updateSupplyMetric( + context, + "dexSupply", + addressSets.dex, + MetricTypesEnum.DEX_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const treasuryChanged = await updateSupplyMetric( + context, + "treasury", + addressSets.treasury, + MetricTypesEnum.TREASURY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const nonCirculatingChanged = await updateSupplyMetric( + context, + "nonCirculatingSupply", + addressSets.nonCirculating, + MetricTypesEnum.NON_CIRCULATING_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + const totalSupplyChanged = await updateTotalSupply( + context, + addressSets.burning, + MetricTypesEnum.TOTAL_SUPPLY, + from, + to, + value, + daoId, + address, + timestamp, + ); + + if ( + lendingChanged || + cexChanged || + dexChanged || + treasuryChanged || + nonCirculatingChanged || + totalSupplyChanged + ) { + await updateCirculatingSupply(context, daoId, address, timestamp); + } + }); +} diff --git a/apps/indexer/src/lib/constants.ts b/apps/indexer/src/lib/constants.ts index 0562ff8fa..5897f310b 100644 --- a/apps/indexer/src/lib/constants.ts +++ b/apps/indexer/src/lib/constants.ts @@ -230,6 +230,40 @@ export const CONTRACT_ADDRESSES = { address: "0xA700b4eB416Be35b2911fd5Dee80678ff64fF6C9", }, }, + [DaoIdEnum.GNO]: { + gnosis: { + blockTime: 5, + gno: { + address: "0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb", + decimals: 18, + }, + lgno: { + address: "0xd4Ca39f78Bf14BfaB75226AC833b1858dB16f9a1", + decimals: 18, + }, + sgno: { + address: "0xA4eF9Da5BA71Cc0D2e5E877a910A37eC43420445", + decimals: 18, + }, + }, + mainnet: { + blockTime: 12, + gno: { + address: "0x6810e776880C02933D47DB1b9fc05908e5386b96", + decimals: 18, + }, + lgno: { + address: "0x6810e776880C02933D47DB1b9fc05908e5386b96", + decimals: 18, + }, + }, + // FIXME: quick n dirty fix + blockTime: 1, + token: { + decimals: 18, + address: zeroAddress, + }, + }, } as const; export const TreasuryAddresses: Record> = { @@ -403,6 +437,7 @@ export const TreasuryAddresses: Record> = { "0x639f35C5E212D61Fe14Bd5CD8b66aAe4df11a50c", InstaTimelock: "0xC7Cb1dE2721BFC0E0DA1b9D526bCdC54eF1C0eFC", }, + [DaoIdEnum.GNO]: {}, }; export const CEXAddresses: Record> = { @@ -622,6 +657,7 @@ export const CEXAddresses: Record> = { Gate: "0x0D0707963952f2fBA59dD06f2b425ace40b492Fe", Bitvavo: "0xaB782bc7D4a2b306825de5a7730034F8F63ee1bC", }, + [DaoIdEnum.GNO]: {}, }; export const DEXAddresses: Record> = { @@ -695,6 +731,7 @@ export const DEXAddresses: Record> = { [DaoIdEnum.FLUID]: { "Uniswap V3 INST/WETH": "0xc1cd3D0913f4633b43FcdDBCd7342bC9b71C676f", }, + [DaoIdEnum.GNO]: {}, }; export const LendingAddresses: Record> = { @@ -745,6 +782,7 @@ export const LendingAddresses: Record> = { }, [DaoIdEnum.SHU]: {}, [DaoIdEnum.FLUID]: {}, + [DaoIdEnum.GNO]: {}, }; export const BurningAddresses: Record< @@ -832,6 +870,11 @@ export const BurningAddresses: Record< Dead: "0x000000000000000000000000000000000000dEaD", TokenContract: "0x6f40d4A6237C257fff2dB00FA0510DeEECd303eb", }, + [DaoIdEnum.GNO]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0x000000000000000000000000000000000000dEaD", + }, }; export const NonCirculatingAddresses: Record< @@ -872,6 +915,7 @@ export const NonCirculatingAddresses: Record< }, [DaoIdEnum.LIL_NOUNS]: {}, [DaoIdEnum.SHU]: {}, + [DaoIdEnum.GNO]: {}, }; export enum ProposalStatus { diff --git a/apps/indexer/src/lib/enums.ts b/apps/indexer/src/lib/enums.ts index 3580fd6f3..8b94dfc06 100644 --- a/apps/indexer/src/lib/enums.ts +++ b/apps/indexer/src/lib/enums.ts @@ -14,6 +14,7 @@ export enum DaoIdEnum { SHU = "SHU", FLUID = "FLUID", LIL_NOUNS = "LIL_NOUNS", + GNO = "GNO", } export const SECONDS_IN_DAY = 24 * 60 * 60; From 66739a5d6f90836d520562bda1c5446c79fadd24 Mon Sep 17 00:00:00 2001 From: Pedro Binotto Date: Mon, 30 Mar 2026 15:43:11 -0300 Subject: [PATCH 2/2] fix: hide tabgroup for non-composite governance --- .../components/governance-overview/GovernanceSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/features/governance/components/governance-overview/GovernanceSection.tsx b/apps/dashboard/features/governance/components/governance-overview/GovernanceSection.tsx index 59d589003..977f9a770 100644 --- a/apps/dashboard/features/governance/components/governance-overview/GovernanceSection.tsx +++ b/apps/dashboard/features/governance/components/governance-overview/GovernanceSection.tsx @@ -126,7 +126,7 @@ export const GovernanceSection = () => { description="View and vote on executable proposals from this DAO." className="lg:bg-transparent" > - {hasOffchain && ( + {proposalType === "composite" && (