diff --git a/apps/indexer/config/shutter.config.ts b/apps/indexer/config/shutter.config.ts new file mode 100644 index 000000000..2427101d5 --- /dev/null +++ b/apps/indexer/config/shutter.config.ts @@ -0,0 +1,43 @@ +import { CONTRACT_ADDRESSES } from "@/lib/constants"; +import { DaoIdEnum } from "@/lib/enums"; +import { env } from "@/env"; +import { createConfig } from "ponder"; +import { TokenAbi, AzoriusAbi, LinearVotingStrategyAbi } from "@/indexer/shu/abi"; + +const SHUTTER_CONTRACTS = CONTRACT_ADDRESSES[DaoIdEnum.SHU]; + +export default createConfig({ + database: { + kind: "postgres", + connectionString: env.DATABASE_URL, + }, + chains: { + ethereum_mainnet: { + id: 1, + rpc: env.RPC_URL, + maxRequestsPerSecond: env.MAX_REQUESTS_PER_SECOND, + pollingInterval: env.POLLING_INTERVAL, + }, + }, + contracts: { + SHUToken: { + // ABI could also come from the contract address + abi: TokenAbi, + chain: "ethereum_mainnet", + address: SHUTTER_CONTRACTS.token.address, + startBlock: SHUTTER_CONTRACTS.token.startBlock, + }, + Azorius: { + abi: AzoriusAbi, + chain: "ethereum_mainnet", + address: SHUTTER_CONTRACTS.azorius.address, + startBlock: SHUTTER_CONTRACTS.azorius.startBlock, + }, + LinearVotingStrategy: { + abi: LinearVotingStrategyAbi, + chain: "ethereum_mainnet", + address: SHUTTER_CONTRACTS.linearVotingStrategy.address, + startBlock: SHUTTER_CONTRACTS.linearVotingStrategy.startBlock, + }, + }, +}); diff --git a/apps/indexer/ponder.config.ts b/apps/indexer/ponder.config.ts index 5a97d74d1..e9da62dab 100644 --- a/apps/indexer/ponder.config.ts +++ b/apps/indexer/ponder.config.ts @@ -8,6 +8,7 @@ import scrollConfig from "./config/scroll.config"; import compoundConfig from "./config/compound.config"; import obolConfig from "./config/obol.config"; import zkConfig from "./config/zk.config"; +import shutterConfig from "./config/shutter.config"; export default { chains: { @@ -21,6 +22,7 @@ export default { ...compoundConfig.chains, ...obolConfig.chains, ...zkConfig.chains, + ...shutterConfig.chains, }, contracts: { ...arbitrumConfig.contracts, @@ -33,5 +35,6 @@ export default { ...compoundConfig.contracts, ...obolConfig.contracts, ...zkConfig.contracts, + ...shutterConfig.contracts, }, }; diff --git a/apps/indexer/src/api/services/coingecko/types.ts b/apps/indexer/src/api/services/coingecko/types.ts index 11d3bc20a..09855ea00 100644 --- a/apps/indexer/src/api/services/coingecko/types.ts +++ b/apps/indexer/src/api/services/coingecko/types.ts @@ -23,6 +23,7 @@ export const CoingeckoTokenIdEnum: Record = { COMP: "compound-governance-token", OBOL: "obol-2", ZK: "zksync", + SHU: "shutter", } as const; export const CoingeckoIdToAssetPlatformId = { @@ -35,6 +36,7 @@ export const CoingeckoIdToAssetPlatformId = { [CoingeckoTokenIdEnum.COMP]: AssetPlatformEnum.ETHEREUM, [CoingeckoTokenIdEnum.OBOL]: AssetPlatformEnum.ETHEREUM, [CoingeckoTokenIdEnum.ZK]: AssetPlatformEnum.ZKSYNC, + [CoingeckoTokenIdEnum.SHU]: AssetPlatformEnum.ETHEREUM, } as const; export interface CoingeckoHistoricalMarketData { diff --git a/apps/indexer/src/index.ts b/apps/indexer/src/index.ts index c71e2266f..7dca7cc68 100644 --- a/apps/indexer/src/index.ts +++ b/apps/indexer/src/index.ts @@ -1,4 +1,5 @@ import { env } from "@/env"; + import { DaoIdEnum } from "@/lib/enums"; import { CONTRACT_ADDRESSES } from "@/lib/constants"; import { @@ -32,6 +33,7 @@ import { ZKTokenIndexer, GovernorIndexer as ZKGovernorIndexer, } from "./indexer/zk"; +import { SHUGovernorIndexer, SHUTokenIndexer } from "./indexer/shu"; const { DAO_ID: daoId } = env; @@ -103,6 +105,12 @@ switch (daoId) { ZKGovernorIndexer(blockTime); break; } + case DaoIdEnum.SHU: { + const { token } = CONTRACT_ADDRESSES[daoId]; + SHUTokenIndexer(token.address, token.decimals); + SHUGovernorIndexer(blockTime); + break; + } default: throw new Error(`DAO ${daoId} not supported`); } diff --git a/apps/indexer/src/indexer/shu/abi/azorius.ts b/apps/indexer/src/indexer/shu/abi/azorius.ts new file mode 100644 index 000000000..d909862de --- /dev/null +++ b/apps/indexer/src/indexer/shu/abi/azorius.ts @@ -0,0 +1,529 @@ +export const AzoriusAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { inputs: [], name: "InvalidArrayLengths", type: "error" }, + { inputs: [], name: "InvalidProposal", type: "error" }, + { inputs: [], name: "InvalidProposer", type: "error" }, + { inputs: [], name: "InvalidStrategy", type: "error" }, + { inputs: [], name: "InvalidTxHash", type: "error" }, + { inputs: [], name: "InvalidTxs", type: "error" }, + { + inputs: [{ internalType: "address", name: "guard_", type: "address" }], + name: "NotIERC165Compliant", + type: "error", + }, + { inputs: [], name: "ProposalNotExecutable", type: "error" }, + { inputs: [], name: "StrategyDisabled", type: "error" }, + { inputs: [], name: "StrategyEnabled", type: "error" }, + { inputs: [], name: "TxFailed", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousAvatar", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newAvatar", + type: "address", + }, + ], + name: "AvatarSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "creator", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "avatar", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "target", + type: "address", + }, + ], + name: "AzoriusSetUp", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "guard", + type: "address", + }, + ], + name: "ChangedGuard", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "strategy", + type: "address", + }, + ], + name: "DisabledStrategy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "strategy", + type: "address", + }, + ], + name: "EnabledStrategy", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint32", + name: "executionPeriod", + type: "uint32", + }, + ], + name: "ExecutionPeriodUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "uint8", name: "version", type: "uint8" }, + ], + name: "Initialized", + 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: false, + internalType: "address", + name: "strategy", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "proposalId", + type: "uint256", + }, + { + indexed: false, + internalType: "address", + name: "proposer", + type: "address", + }, + { + components: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + { + internalType: "enum Enum.Operation", + name: "operation", + type: "uint8", + }, + ], + indexed: false, + internalType: "struct IAzorius.Transaction[]", + name: "transactions", + type: "tuple[]", + }, + { + indexed: false, + internalType: "string", + name: "metadata", + type: "string", + }, + ], + name: "ProposalCreated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint32", + name: "proposalId", + type: "uint32", + }, + { + indexed: false, + internalType: "bytes32[]", + name: "txHashes", + type: "bytes32[]", + }, + ], + name: "ProposalExecuted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousTarget", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newTarget", + type: "address", + }, + ], + name: "TargetSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint32", + name: "timelockPeriod", + type: "uint32", + }, + ], + name: "TimelockPeriodUpdated", + type: "event", + }, + { + inputs: [], + name: "DOMAIN_SEPARATOR_TYPEHASH", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "TRANSACTION_TYPEHASH", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "avatar", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_prevStrategy", type: "address" }, + { internalType: "address", name: "_strategy", type: "address" }, + ], + name: "disableStrategy", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_strategy", type: "address" }], + name: "enableStrategy", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint32", name: "_proposalId", type: "uint32" }, + { internalType: "address[]", name: "_targets", type: "address[]" }, + { internalType: "uint256[]", name: "_values", type: "uint256[]" }, + { internalType: "bytes[]", name: "_data", type: "bytes[]" }, + { + internalType: "enum Enum.Operation[]", + name: "_operations", + type: "uint8[]", + }, + ], + name: "executeProposal", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "executionPeriod", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_to", type: "address" }, + { internalType: "uint256", name: "_value", type: "uint256" }, + { internalType: "bytes", name: "_data", type: "bytes" }, + { + internalType: "enum Enum.Operation", + name: "_operation", + type: "uint8", + }, + { internalType: "uint256", name: "_nonce", type: "uint256" }, + ], + name: "generateTxHashData", + outputs: [{ internalType: "bytes", name: "", type: "bytes" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getGuard", + outputs: [{ internalType: "address", name: "_guard", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint32", name: "_proposalId", type: "uint32" }], + name: "getProposal", + outputs: [ + { internalType: "address", name: "_strategy", type: "address" }, + { internalType: "bytes32[]", name: "_txHashes", type: "bytes32[]" }, + { internalType: "uint32", name: "_timelockPeriod", type: "uint32" }, + { internalType: "uint32", name: "_executionPeriod", type: "uint32" }, + { internalType: "uint32", name: "_executionCounter", type: "uint32" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint32", name: "_proposalId", type: "uint32" }, + { internalType: "uint32", name: "_txIndex", type: "uint32" }, + ], + name: "getProposalTxHash", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint32", name: "_proposalId", type: "uint32" }], + name: "getProposalTxHashes", + outputs: [{ internalType: "bytes32[]", name: "", type: "bytes32[]" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_startAddress", type: "address" }, + { internalType: "uint256", name: "_count", type: "uint256" }, + ], + name: "getStrategies", + outputs: [ + { internalType: "address[]", name: "_strategies", type: "address[]" }, + { internalType: "address", name: "_next", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_to", type: "address" }, + { internalType: "uint256", name: "_value", type: "uint256" }, + { internalType: "bytes", name: "_data", type: "bytes" }, + { + internalType: "enum Enum.Operation", + name: "_operation", + type: "uint8", + }, + ], + name: "getTxHash", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "guard", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_strategy", type: "address" }], + name: "isStrategyEnabled", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint32", name: "_proposalId", type: "uint32" }], + name: "proposalState", + outputs: [ + { internalType: "enum IAzorius.ProposalState", name: "", type: "uint8" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_avatar", type: "address" }], + name: "setAvatar", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_guard", type: "address" }], + name: "setGuard", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_target", type: "address" }], + name: "setTarget", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes", name: "initializeParams", type: "bytes" }, + ], + name: "setUp", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_strategy", type: "address" }, + { internalType: "bytes", name: "_data", type: "bytes" }, + { + components: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "bytes", name: "data", type: "bytes" }, + { + internalType: "enum Enum.Operation", + name: "operation", + type: "uint8", + }, + ], + internalType: "struct IAzorius.Transaction[]", + name: "_transactions", + type: "tuple[]", + }, + { internalType: "string", name: "_metadata", type: "string" }, + ], + name: "submitProposal", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "target", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "timelockPeriod", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalProposalCount", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint32", name: "_executionPeriod", type: "uint32" }, + ], + name: "updateExecutionPeriod", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint32", name: "_timelockPeriod", type: "uint32" }, + ], + name: "updateTimelockPeriod", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/apps/indexer/src/indexer/shu/abi/index.ts b/apps/indexer/src/indexer/shu/abi/index.ts new file mode 100644 index 000000000..f9eee785b --- /dev/null +++ b/apps/indexer/src/indexer/shu/abi/index.ts @@ -0,0 +1,3 @@ +export * from "./azorius"; +export * from "./linear-voting-strategy"; +export * from "./token"; diff --git a/apps/indexer/src/indexer/shu/abi/linear-voting-strategy.ts b/apps/indexer/src/indexer/shu/abi/linear-voting-strategy.ts new file mode 100644 index 000000000..39ad36771 --- /dev/null +++ b/apps/indexer/src/indexer/shu/abi/linear-voting-strategy.ts @@ -0,0 +1,411 @@ +export const LinearVotingStrategyAbi = [ + { inputs: [], name: "AlreadyVoted", type: "error" }, + { inputs: [], name: "InvalidBasisNumerator", type: "error" }, + { inputs: [], name: "InvalidProposal", type: "error" }, + { inputs: [], name: "InvalidQuorumNumerator", type: "error" }, + { inputs: [], name: "InvalidTokenAddress", type: "error" }, + { inputs: [], name: "InvalidVote", type: "error" }, + { inputs: [], name: "OnlyAzorius", type: "error" }, + { inputs: [], name: "VotingEnded", type: "error" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "azoriusModule", + type: "address", + }, + ], + name: "AzoriusSet", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "basisNumerator", + type: "uint256", + }, + ], + name: "BasisNumeratorUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: false, internalType: "uint8", name: "version", type: "uint8" }, + ], + name: "Initialized", + 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: false, + internalType: "uint32", + name: "proposalId", + type: "uint32", + }, + { + indexed: false, + internalType: "uint32", + name: "votingEndBlock", + type: "uint32", + }, + ], + name: "ProposalInitialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "quorumNumerator", + type: "uint256", + }, + ], + name: "QuorumNumeratorUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint256", + name: "requiredProposerWeight", + type: "uint256", + }, + ], + name: "RequiredProposerWeightUpdated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "azoriusModule", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + ], + name: "StrategySetUp", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "voter", + type: "address", + }, + { + indexed: false, + internalType: "uint32", + name: "proposalId", + type: "uint32", + }, + { + indexed: false, + internalType: "uint8", + name: "voteType", + type: "uint8", + }, + { + indexed: false, + internalType: "uint256", + name: "weight", + type: "uint256", + }, + ], + name: "Voted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint32", + name: "votingPeriod", + type: "uint32", + }, + ], + name: "VotingPeriodUpdated", + type: "event", + }, + { + inputs: [], + name: "BASIS_DENOMINATOR", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "QUORUM_DENOMINATOR", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "azoriusModule", + outputs: [{ internalType: "contract IAzorius", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "basisNumerator", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint32", name: "_proposalId", type: "uint32" }], + name: "getProposalVotes", + outputs: [ + { internalType: "uint256", name: "noVotes", type: "uint256" }, + { internalType: "uint256", name: "yesVotes", type: "uint256" }, + { internalType: "uint256", name: "abstainVotes", type: "uint256" }, + { internalType: "uint32", name: "startBlock", type: "uint32" }, + { internalType: "uint32", name: "endBlock", type: "uint32" }, + { internalType: "uint256", name: "votingSupply", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint32", name: "_proposalId", type: "uint32" }], + name: "getProposalVotingSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_voter", type: "address" }, + { internalType: "uint32", name: "_proposalId", type: "uint32" }, + ], + name: "getVotingWeight", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "governanceToken", + outputs: [{ internalType: "contract IVotes", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint32", name: "_proposalId", type: "uint32" }, + { internalType: "address", name: "_address", type: "address" }, + ], + name: "hasVoted", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes", name: "_data", type: "bytes" }], + name: "initializeProposal", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint32", name: "_proposalId", type: "uint32" }], + name: "isPassed", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "_address", type: "address" }], + name: "isProposer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_yesVotes", type: "uint256" }, + { internalType: "uint256", name: "_noVotes", type: "uint256" }, + ], + name: "meetsBasis", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_totalSupply", type: "uint256" }, + { internalType: "uint256", name: "_yesVotes", type: "uint256" }, + { internalType: "uint256", name: "_abstainVotes", type: "uint256" }, + ], + name: "meetsQuorum", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "quorumNumerator", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint32", name: "_proposalId", type: "uint32" }], + name: "quorumVotes", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "requiredProposerWeight", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "_azoriusModule", type: "address" }, + ], + name: "setAzorius", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes", name: "initializeParams", type: "bytes" }, + ], + name: "setUp", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_basisNumerator", type: "uint256" }, + ], + name: "updateBasisNumerator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint256", name: "_quorumNumerator", type: "uint256" }, + ], + name: "updateQuorumNumerator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_requiredProposerWeight", + type: "uint256", + }, + ], + name: "updateRequiredProposerWeight", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint32", name: "_votingPeriod", type: "uint32" }], + name: "updateVotingPeriod", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "uint32", name: "_proposalId", type: "uint32" }, + { internalType: "uint8", name: "_voteType", type: "uint8" }, + ], + name: "vote", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint32", name: "_proposalId", type: "uint32" }], + name: "votingEndBlock", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "votingPeriod", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/apps/indexer/src/indexer/shu/abi/token.ts b/apps/indexer/src/indexer/shu/abi/token.ts new file mode 100644 index 000000000..4b4ca24a9 --- /dev/null +++ b/apps/indexer/src/indexer/shu/abi/token.ts @@ -0,0 +1,495 @@ +export const TokenAbi = [ + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + stateMutability: "nonpayable", + type: "constructor", + }, + { inputs: [], name: "AlreadyInitialized", type: "error" }, + { inputs: [], name: "CheckpointUnorderedInsertion", type: "error" }, + { inputs: [], name: "ECDSAInvalidSignature", type: "error" }, + { + inputs: [{ internalType: "uint256", name: "length", type: "uint256" }], + name: "ECDSAInvalidSignatureLength", + type: "error", + }, + { + inputs: [{ internalType: "bytes32", name: "s", type: "bytes32" }], + name: "ECDSAInvalidSignatureS", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "increasedSupply", type: "uint256" }, + { internalType: "uint256", name: "cap", type: "uint256" }, + ], + name: "ERC20ExceededSafeSupply", + type: "error", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "allowance", type: "uint256" }, + { internalType: "uint256", name: "needed", type: "uint256" }, + ], + name: "ERC20InsufficientAllowance", + type: "error", + }, + { + inputs: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint256", name: "balance", type: "uint256" }, + { internalType: "uint256", name: "needed", type: "uint256" }, + ], + name: "ERC20InsufficientBalance", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "approver", type: "address" }], + name: "ERC20InvalidApprover", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "receiver", type: "address" }], + name: "ERC20InvalidReceiver", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "sender", type: "address" }], + name: "ERC20InvalidSender", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "spender", type: "address" }], + name: "ERC20InvalidSpender", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "timepoint", type: "uint256" }, + { internalType: "uint48", name: "clock", type: "uint48" }, + ], + name: "ERC5805FutureLookup", + type: "error", + }, + { inputs: [], name: "ERC6372InconsistentClock", type: "error" }, + { inputs: [], name: "EnforcedPause", type: "error" }, + { inputs: [], name: "ExpectedPause", type: "error" }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "currentNonce", type: "uint256" }, + ], + name: "InvalidAccountNonce", + type: "error", + }, + { inputs: [], name: "InvalidShortString", type: "error" }, + { inputs: [], name: "NotInitialized", type: "error" }, + { inputs: [], name: "NotPaused", type: "error" }, + { + inputs: [{ internalType: "address", name: "owner", type: "address" }], + name: "OwnableInvalidOwner", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "OwnableUnauthorizedAccount", + type: "error", + }, + { + inputs: [ + { internalType: "uint8", name: "bits", type: "uint8" }, + { internalType: "uint256", name: "value", type: "uint256" }, + ], + name: "SafeCastOverflowedUintDowncast", + type: "error", + }, + { + inputs: [{ internalType: "string", name: "str", type: "string" }], + name: "StringTooLong", + type: "error", + }, + { inputs: [], name: "TransferToTokenContract", type: "error" }, + { inputs: [], name: "TransferWhilePaused", type: "error" }, + { + inputs: [{ internalType: "uint256", name: "expiry", type: "uint256" }], + name: "VotesExpiredSignature", + 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: "delegator", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "fromDelegate", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "toDelegate", + type: "address", + }, + ], + name: "DelegateChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "delegate", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "previousVotes", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "newVotes", + type: "uint256", + }, + ], + name: "DelegateVotesChanged", + type: "event", + }, + { anonymous: false, inputs: [], name: "EIP712DomainChanged", 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: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Paused", + 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: "CLOCK_MODE", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "spender", type: "address" }, + ], + name: "allowance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint32", name: "pos", type: "uint32" }, + ], + name: "checkpoints", + outputs: [ + { + components: [ + { internalType: "uint48", name: "_key", type: "uint48" }, + { internalType: "uint208", name: "_value", type: "uint208" }, + ], + internalType: "struct Checkpoints.Checkpoint208", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "clock", + outputs: [{ internalType: "uint48", name: "", type: "uint48" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "delegatee", type: "address" }], + name: "delegate", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "delegatee", type: "address" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "uint256", name: "expiry", type: "uint256" }, + { internalType: "uint8", name: "v", type: "uint8" }, + { internalType: "bytes32", name: "r", type: "bytes32" }, + { internalType: "bytes32", name: "s", type: "bytes32" }, + ], + name: "delegateBySig", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "delegates", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "eip712Domain", + outputs: [ + { internalType: "bytes1", name: "fields", type: "bytes1" }, + { internalType: "string", name: "name", type: "string" }, + { internalType: "string", name: "version", type: "string" }, + { internalType: "uint256", name: "chainId", type: "uint256" }, + { internalType: "address", name: "verifyingContract", type: "address" }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "uint256[]", name: "extensions", type: "uint256[]" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "timepoint", type: "uint256" }], + name: "getPastTotalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "timepoint", type: "uint256" }, + ], + name: "getPastVotes", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "getVotes", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "newOwner", type: "address" }, + { internalType: "address", name: "airdropContract", type: "address" }, + { + internalType: "uint256", + name: "airdropContractBalance", + type: "uint256", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + 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: [{ internalType: "address", name: "account", type: "address" }], + name: "numCheckpoints", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "paused", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + 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: "totalSupply", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "from", type: "address" }, + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "newOwner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "unpause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/apps/indexer/src/indexer/shu/client.ts b/apps/indexer/src/indexer/shu/client.ts new file mode 100644 index 000000000..ec116a446 --- /dev/null +++ b/apps/indexer/src/indexer/shu/client.ts @@ -0,0 +1,127 @@ +import { + Account, + Address, + Chain, + Client, + Transport, + fromHex, + toHex, +} from "viem"; +import { readContract } from "viem/actions"; +import { DAOClient } from "@/interfaces/client"; +import { DaoIdEnum } from "@/lib/enums"; +import { GovernorBase } from "../governor.base"; +import { CONTRACT_ADDRESSES } from "@/lib/constants"; + +export class SHUClient< + TTransport extends Transport = Transport, + TChain extends Chain = Chain, + TAccount extends Account | undefined = Account | undefined, +> + extends GovernorBase + implements DAOClient +{ + private governorAddress: Address; + private votingStrategyAddress: Address; + + constructor(client: Client, address: Address) { + super(client); + this.governorAddress = address; + this.votingStrategyAddress = + CONTRACT_ADDRESSES[DaoIdEnum.SHU].linearVotingStrategy.address; + } + + getDaoId(): string { + return "SHU"; + } + + async getQuorum(): Promise { + return 30n * 1000000n * 10n ** 18n; + } + + async getProposalThreshold(): Promise { + return 0n; + // readContract(this.client, { + // abi: [ + // { + // anonymous: false, + // inputs: [ + // { + // indexed: false, + // internalType: "uint256", + // name: "requiredProposerWeight", + // type: "uint256", + // }, + // ], + // name: "RequiredProposerWeightUpdated", + // type: "event", + // }, + // ], + // address: this.votingStrategyAddress, + // functionName: "requiredProposerWeight", + // }); + } + + async getVotingDelay(): Promise { + return 0n; + } + + async getVotingPeriod(): Promise { + return BigInt( + await readContract(this.client, { + abi: [ + { + inputs: [], + name: "votingPeriod", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "view", + type: "function", + }, + ], + address: this.votingStrategyAddress, + functionName: "votingPeriod", + }), + ); + } + + async getTimelockDelay(): Promise { + return BigInt( + await readContract(this.client, { + abi: [ + { + inputs: [], + name: "timelockPeriod", + outputs: [{ internalType: "uint32", name: "", type: "uint32" }], + stateMutability: "view", + type: "function", + }, + ], + address: this.governorAddress, + functionName: "timelockPeriod", + }), + ); + } + + async getBlockTime(blockNumber: number): Promise { + const block = await this.client.request({ + method: "eth_getBlockByNumber", + params: [toHex(blockNumber), false], + }); + return block?.timestamp ? fromHex(block.timestamp, "number") : null; + } + + async getCurrentBlockNumber(): Promise { + const result = await this.client.request({ + method: "eth_blockNumber", + }); + return fromHex(result, "number"); + } + + calculateQuorum(votes: { + forVotes: bigint; + againstVotes: bigint; + abstainVotes: bigint; + }): bigint { + return votes.forVotes + votes.abstainVotes + votes.abstainVotes; + } +} diff --git a/apps/indexer/src/indexer/shu/erc20.ts b/apps/indexer/src/indexer/shu/erc20.ts new file mode 100644 index 000000000..2ae36398c --- /dev/null +++ b/apps/indexer/src/indexer/shu/erc20.ts @@ -0,0 +1,191 @@ +import { ponder } from "ponder:registry"; +import { token } from "ponder:schema"; +import { Address } from "viem"; + +import { DaoIdEnum } from "@/lib/enums"; + +import { + delegateChanged, + delegatedVotesChanged, + tokenTransfer, +} from "@/eventHandlers"; +import { + CEXAddresses, + DEXAddresses, + BurningAddresses, + TreasuryAddresses, + MetricTypesEnum, +} from "@/lib/constants"; +import { + updateCirculatingSupply, + updateDelegatedSupply, + updateSupplyMetric, + updateTotalSupply, +} from "@/eventHandlers/metrics"; +import { handleTransaction } from "@/eventHandlers/shared"; + +export function SHUTokenIndexer(address: Address, decimals: number) { + const daoId = DaoIdEnum.SHU; + + ponder.on("SHUToken:setup", async ({ context }) => { + await context.db.insert(token).values({ + id: address, + name: daoId, + decimals, + }); + }); + + ponder.on("SHUToken:Transfer", async ({ event, context }) => { + const cexAddressList = Object.values(CEXAddresses[daoId]); + const dexAddressList = Object.values(DEXAddresses[daoId]); + const burningAddressList = Object.values(BurningAddresses[daoId]); + const treasuryAddressList = Object.values(TreasuryAddresses[daoId]); + + await tokenTransfer( + context, + daoId, + { + from: event.args.from, + to: event.args.to, + token: address, + transactionHash: event.transaction.hash, + value: event.args.value, + timestamp: event.block.timestamp, + logIndex: event.log.logIndex, + }, + { + cex: cexAddressList, + dex: dexAddressList, + burning: burningAddressList, + }, + ); + + await updateSupplyMetric( + context, + "cexSupply", + cexAddressList, + MetricTypesEnum.CEX_SUPPLY, + event.args.from, + event.args.to, + event.args.value, + daoId, + address, + event.block.timestamp, + ); + + await updateSupplyMetric( + context, + "dexSupply", + dexAddressList, + MetricTypesEnum.DEX_SUPPLY, + event.args.from, + event.args.to, + event.args.value, + daoId, + address, + event.block.timestamp, + ); + + await updateSupplyMetric( + context, + "treasury", + treasuryAddressList, + MetricTypesEnum.TREASURY, + event.args.from, + event.args.to, + event.args.value, + daoId, + address, + event.block.timestamp, + ); + + await updateTotalSupply( + context, + burningAddressList, + MetricTypesEnum.TOTAL_SUPPLY, + event.args.from, + event.args.to, + event.args.value, + daoId, + address, + event.block.timestamp, + ); + + await updateCirculatingSupply( + context, + MetricTypesEnum.CIRCULATING_SUPPLY, + daoId, + address, + event.block.timestamp, + ); + + if (!event.transaction.to) return; + + await handleTransaction( + context, + event.transaction.hash, + event.transaction.from, + event.transaction.to, + event.block.timestamp, + [event.args.from, event.args.to], + { + cex: cexAddressList, + dex: dexAddressList, + burning: burningAddressList, + }, + ); + }); + + ponder.on(`SHUToken:DelegateChanged`, async ({ event, context }) => { + await delegateChanged(context, daoId, { + delegator: event.args.delegator, + delegate: event.args.toDelegate, + tokenId: event.log.address, + previousDelegate: event.args.fromDelegate, + txHash: event.transaction.hash, + timestamp: event.block.timestamp, + logIndex: event.log.logIndex, + }); + + if (!event.transaction.to) return; + + await handleTransaction( + context, + event.transaction.hash, + event.transaction.from, + event.transaction.to, + event.block.timestamp, + [event.args.delegator, event.args.toDelegate], // Addresses to check + ); + }); + + ponder.on(`SHUToken:DelegateVotesChanged`, async ({ event, context }) => { + await delegatedVotesChanged(context, daoId, { + delegate: event.args.delegate, + txHash: event.transaction.hash, + newBalance: event.args.newVotes, + oldBalance: event.args.previousVotes, + timestamp: event.block.timestamp, + logIndex: event.log.logIndex, + }); + + await updateDelegatedSupply( + context, + daoId, + event.log.address, + event.args.newVotes - event.args.previousVotes, + event.block.timestamp, + ); + + if (!event.transaction.to) return; + + await handleTransaction( + context, + event.transaction.hash, + event.transaction.from, + event.transaction.to, + event.block.timestamp, + [event.args.delegate], // Address to check + ); + }); +} diff --git a/apps/indexer/src/indexer/shu/governor.ts b/apps/indexer/src/indexer/shu/governor.ts new file mode 100644 index 000000000..cf71d6fc5 --- /dev/null +++ b/apps/indexer/src/indexer/shu/governor.ts @@ -0,0 +1,70 @@ +import { ponder } from "ponder:registry"; +import { ProposalStatus } from "@/lib/constants"; + +import { + proposalCreated, + voteCast, + updateProposalStatus, +} from "@/eventHandlers"; +import { DaoIdEnum } from "@/lib/enums"; + +export function SHUGovernorIndexer(blockTime: number) { + const daoId = DaoIdEnum.SHU; + + ponder.on(`LinearVotingStrategy:Voted`, async ({ event, context }) => { + await voteCast(context, daoId, { + proposalId: event.args.proposalId.toString(), + voter: event.args.voter, + reason: "", + support: event.args.voteType, + timestamp: event.block.timestamp, + txHash: event.transaction.hash, + votingPower: event.args.weight, + }); + }); + + // ponder.on( + // `LinearVotingStrategy:ProposalInitialized`, + // async ({ event, context }) => { + // await updateProposalStatus( + // context, + // event.args.proposalId.toString(), + // ProposalStatus.ACTIVE, + // ); + // }, + // ); + + ponder.on(`Azorius:ProposalCreated`, async ({ event, context }) => { + const targets: `0x${string}`[] = []; + const values: bigint[] = []; + const calldatas: `0x${string}`[] = []; + + event.args.transactions.forEach((transaction) => { + targets.push(transaction.to); + values.push(transaction.value); + calldatas.push(transaction.data); + }); + + await proposalCreated(context, daoId, blockTime, { + proposalId: event.args.proposalId.toString(), + txHash: event.transaction.hash, + proposer: event.args.proposer, + targets, + values, + signatures: [], + calldatas, + startBlock: event.block.number.toString(), + endBlock: (event.block.number + 10n).toString(), // Not yet possible; must query strategy contract + description: event.args.metadata, + timestamp: event.block.timestamp, + }); + }); + + ponder.on(`Azorius:ProposalExecuted`, async ({ event, context }) => { + await updateProposalStatus( + context, + event.args.proposalId.toString(), + ProposalStatus.EXECUTED, + ); + }); +} diff --git a/apps/indexer/src/indexer/shu/index.ts b/apps/indexer/src/indexer/shu/index.ts new file mode 100644 index 000000000..b9cbb4f18 --- /dev/null +++ b/apps/indexer/src/indexer/shu/index.ts @@ -0,0 +1,4 @@ +export * from "./abi"; +export * from "./client"; +export * from "./governor"; +export * from "./erc20"; diff --git a/apps/indexer/src/lib/client.ts b/apps/indexer/src/lib/client.ts index a2070d4bf..43ef16dd2 100644 --- a/apps/indexer/src/lib/client.ts +++ b/apps/indexer/src/lib/client.ts @@ -12,6 +12,7 @@ import { SCRClient } from "@/indexer/scr"; import { COMPClient } from "@/indexer/comp"; import { ObolClient } from "@/indexer/obol/client"; import { ZKClient } from "@/indexer/zk"; +import { SHUClient } from "@/indexer/shu"; export function getClient< TTransport extends Transport = Transport, @@ -62,6 +63,10 @@ export function getClient< const { governor } = CONTRACT_ADDRESSES[daoId]; return new ZKClient(client, governor.address); } + case DaoIdEnum.SHU: { + const { azorius: governor } = CONTRACT_ADDRESSES[daoId]; + return new SHUClient(client, governor.address); + } default: return null; } diff --git a/apps/indexer/src/lib/constants.ts b/apps/indexer/src/lib/constants.ts index c938001d2..8b8ee6706 100644 --- a/apps/indexer/src/lib/constants.ts +++ b/apps/indexer/src/lib/constants.ts @@ -176,6 +176,26 @@ export const CONTRACT_ADDRESSES = { startBlock: 55519658, }, }, + [DaoIdEnum.SHU]: { + blockTime: 12, + tokenType: "ERC20", + // https://etherscan.io/address/0xe485E2f1bab389C08721B291f6b59780feC83Fd7 + token: { + address: "0xe485E2f1bab389C08721B291f6b59780feC83Fd7", + decimals: 18, + startBlock: 19021394, + }, + // https://etherscan.io/address/0xAA6BfA174d2f803b517026E93DBBEc1eBa26258e + azorius: { + address: "0xAA6BfA174d2f803b517026E93DBBEc1eBa26258e", + startBlock: 19021698, + }, + // https://etherscan.io/address/0x4b29d8B250B8b442ECfCd3a4e3D91933d2db720F + linearVotingStrategy: { + address: "0x4b29d8B250B8b442ECfCd3a4e3D91933d2db720F", + startBlock: 19021698, + }, + }, } as const; export const TreasuryAddresses: Record> = { @@ -319,6 +339,9 @@ export const TreasuryAddresses: Record> = { [DaoIdEnum.ZK]: { timelock: "0xe5d21A9179CA2E1F0F327d598D464CcF60d89c3d", }, + [DaoIdEnum.SHU]: { + timelock: "0x36bD3044ab68f600f6d3e081056F34f2a58432c4", + }, }; export const CEXAddresses: Record> = { @@ -530,6 +553,7 @@ export const CEXAddresses: Record> = { OKX2: "0xf9b52be2426f06ab6d560f64a7b15e820f33cbdb", OKX3: "0xecf17c7f6a6090f1edd21e0beb2268197270fb44", }, + [DaoIdEnum.SHU]: {}, }; export const DEXAddresses: Record> = { @@ -595,6 +619,7 @@ export const DEXAddresses: Record> = { "Pancake Swap": "0xf92b0178bc932a59d45c1c4aac81712aac6a5b61", Uniswap: "0x3d7264539E6e3f596bb485E3091f3Ae02Ad01ef8", }, + [DaoIdEnum.SHU]: {}, }; export const LendingAddresses: Record> = { @@ -641,6 +666,7 @@ export const LendingAddresses: Record> = { Aave: "0xd6cd2c0fc55936498726cacc497832052a9b2d1b", Venus: "0x697a70779c1a03ba2bd28b7627a902bff831b616", }, + [DaoIdEnum.SHU]: {}, }; export const BurningAddresses: Record< @@ -708,6 +734,11 @@ export const BurningAddresses: Record< Dead: "0x0000000000000000000000000000000000000000", TokenContract: "0x5A7d6b2F92C77FAD6CCaBd7EE0624E64907Eaf3E", }, + [DaoIdEnum.SHU]: { + ZeroAddress: zeroAddress, + Dead: "0x000000000000000000000000000000000000dEaD", + TokenContract: "0xe485E2f1bab389C08721B291f6b59780feC83Fd7", + }, }; export enum ProposalStatus { diff --git a/apps/indexer/src/lib/enums.ts b/apps/indexer/src/lib/enums.ts index 64fd68c89..c0752e17c 100644 --- a/apps/indexer/src/lib/enums.ts +++ b/apps/indexer/src/lib/enums.ts @@ -10,6 +10,7 @@ export enum DaoIdEnum { COMP = "COMP", OBOL = "OBOL", ZK = "ZK", + SHU = "SHU", } export const SECONDS_IN_DAY = 24 * 60 * 60;