11import { Injectable , Logger , OnModuleInit } from '@nestjs/common' ;
22import { InjectRepository } from '@nestjs/typeorm' ;
3+ import { EventEmitter2 } from '@nestjs/event-emitter' ;
34import { Repository } from 'typeorm' ;
4- // import { ethers } from 'ethers';
55import {
66 GovernanceProposal ,
77 ProposalStatus ,
88} from './entities/governance-proposal.entity' ;
99import { Vote , VoteDirection } from './entities/vote.entity' ;
10+ import { Delegation } from './entities/delegation.entity' ;
1011
1112/**
1213 * Minimal ABI fragments for the DAO contract events we care about.
13- * ProposalCreated: emitted when a new proposal is submitted on-chain.
14- * VoteCast: emitted when a wallet casts a For (1) or Against (0) vote.
1514 */
1615const DAO_ABI_FRAGMENTS = [
1716 'event ProposalCreated(uint256 indexed proposalId, address indexed proposer, string description, uint256 startBlock, uint256 endBlock)' ,
1817 'event VoteCast(address indexed voter, uint256 indexed proposalId, uint8 support, uint256 weight)' ,
18+ 'event DelegationUpdated(address indexed delegator, address indexed delegate)' ,
19+ 'event ProposalStatusChanged(uint256 indexed proposalId, uint8 status)' ,
1920] ;
2021
2122@Injectable ( )
@@ -29,6 +30,9 @@ export class GovernanceIndexerService implements OnModuleInit {
2930 private readonly proposalRepo : Repository < GovernanceProposal > ,
3031 @InjectRepository ( Vote )
3132 private readonly voteRepo : Repository < Vote > ,
33+ @InjectRepository ( Delegation )
34+ private readonly delegationRepo : Repository < Delegation > ,
35+ private readonly eventEmitter : EventEmitter2 ,
3236 ) { }
3337
3438 onModuleInit ( ) {
@@ -47,26 +51,15 @@ export class GovernanceIndexerService implements OnModuleInit {
4751 }
4852
4953 // TODO: Implement ethers integration when ethers package is added
50- // this.provider = new ethers.JsonRpcProvider(rpcUrl);
51- // this.contract = new ethers.Contract(
52- // contractAddress,
53- // DAO_ABI_FRAGMENTS,
54- // this.provider,
55- // );
56- // this.contract.on('ProposalCreated', this.handleProposalCreated.bind(this));
57- // this.contract.on('VoteCast', this.handleVoteCast.bind(this));
58-
5954 this . logger . log (
6055 `Governance indexer listening on contract ${ contractAddress } ` ,
6156 ) ;
6257 }
6358
6459 /**
6560 * Handles the ProposalCreated event.
66- * Inserts a skeletal GovernanceProposal row with status=Active.
67- * Parses the on-chain ID and creates a local database entry.
6861 */
69- private async handleProposalCreated (
62+ async handleProposalCreated (
7063 proposalId : bigint ,
7164 proposer : string ,
7265 description : string ,
@@ -96,18 +89,21 @@ export class GovernanceIndexerService implements OnModuleInit {
9689 } ) ;
9790
9891 await this . proposalRepo . save ( proposal ) ;
99- this . logger . log (
100- `Indexed new proposal onChainId=${ onChainId } from proposer=${ proposer } ` ,
101- ) ;
92+ this . logger . log ( `Indexed new proposal onChainId=${ onChainId } ` ) ;
93+
94+ // Emit event for notifications
95+ this . eventEmitter . emit ( 'governance.proposal.created' , {
96+ proposalId : proposal . id ,
97+ onChainId,
98+ proposer,
99+ title : description . slice ( 0 , 50 ) , // Fallback title
100+ } ) ;
102101 }
103102
104103 /**
105104 * Handles the VoteCast event.
106- * Isolates the direction (For=1, Against=0) and maps it to the Vote database table
107- * linked to the walletAddress and the corresponding GovernanceProposal.
108- * Supports updating proposal status based on voting outcomes.
109105 */
110- private async handleVoteCast (
106+ async handleVoteCast (
111107 voter : string ,
112108 proposalId : bigint ,
113109 support : number ,
@@ -117,17 +113,14 @@ export class GovernanceIndexerService implements OnModuleInit {
117113
118114 const proposal = await this . proposalRepo . findOneBy ( { onChainId } ) ;
119115 if ( ! proposal ) {
120- this . logger . warn (
121- `VoteCast received for unknown proposal ${ onChainId } — skipping.` ,
122- ) ;
116+ this . logger . warn ( `VoteCast received for unknown proposal ${ onChainId } ` ) ;
123117 return ;
124118 }
125119
126120 // Map on-chain support value: 1 = FOR, 0 = AGAINST
127121 const direction : VoteDirection =
128122 support === 1 ? VoteDirection . FOR : VoteDirection . AGAINST ;
129123
130- // Upsert: one vote per wallet per proposal
131124 const existing = await this . voteRepo . findOneBy ( {
132125 walletAddress : voter ,
133126 proposalId : proposal . id ,
@@ -137,9 +130,6 @@ export class GovernanceIndexerService implements OnModuleInit {
137130 existing . direction = direction ;
138131 existing . weight = Number ( weight ) ;
139132 await this . voteRepo . save ( existing ) ;
140- this . logger . debug (
141- `Updated vote for wallet=${ voter } on proposal=${ onChainId } ` ,
142- ) ;
143133 } else {
144134 const vote = this . voteRepo . create ( {
145135 walletAddress : voter ,
@@ -149,9 +139,78 @@ export class GovernanceIndexerService implements OnModuleInit {
149139 proposalId : proposal . id ,
150140 } ) ;
151141 await this . voteRepo . save ( vote ) ;
152- this . logger . log (
153- `Indexed vote wallet=${ voter } direction=${ direction } proposal=${ onChainId } weight=${ weight } ` ,
154- ) ;
142+ }
143+
144+ // Emit event for notifications (to notify delegators)
145+ this . eventEmitter . emit ( 'governance.vote.cast' , {
146+ voter,
147+ onChainId,
148+ direction,
149+ weight : weight . toString ( ) ,
150+ } ) ;
151+ }
152+
153+ /**
154+ * Handles the DelegationUpdated event.
155+ */
156+ async handleDelegationUpdated (
157+ delegator : string ,
158+ delegate : string ,
159+ ) : Promise < void > {
160+ const existing = await this . delegationRepo . findOneBy ( {
161+ delegatorAddress : delegator ,
162+ } ) ;
163+
164+ if ( existing ) {
165+ existing . delegateAddress = delegate ;
166+ await this . delegationRepo . save ( existing ) ;
167+ } else {
168+ const newDelegation = this . delegationRepo . create ( {
169+ delegatorAddress : delegator ,
170+ delegateAddress : delegate ,
171+ } ) ;
172+ await this . delegationRepo . save ( newDelegation ) ;
173+ }
174+
175+ this . logger . log ( `Updated delegation: ${ delegator } -> ${ delegate } ` ) ;
176+ }
177+
178+ /**
179+ * Handles the ProposalStatusChanged event.
180+ */
181+ async handleProposalStatusChanged (
182+ proposalId : bigint ,
183+ status : number ,
184+ ) : Promise < void > {
185+ const onChainId = Number ( proposalId ) ;
186+ const proposal = await this . proposalRepo . findOneBy ( { onChainId } ) ;
187+ if ( ! proposal ) return ;
188+
189+ // Map on-chain status to enum
190+ let newStatus : ProposalStatus ;
191+ switch ( status ) {
192+ case 1 :
193+ newStatus = ProposalStatus . PASSED ;
194+ break ;
195+ case 2 :
196+ newStatus = ProposalStatus . FAILED ;
197+ break ;
198+ case 3 :
199+ newStatus = ProposalStatus . CANCELLED ;
200+ break ;
201+ default :
202+ newStatus = ProposalStatus . ACTIVE ;
203+ }
204+
205+ if ( proposal . status !== newStatus ) {
206+ proposal . status = newStatus ;
207+ await this . proposalRepo . save ( proposal ) ;
208+
209+ this . eventEmitter . emit ( 'governance.proposal.status_updated' , {
210+ proposalId : proposal . id ,
211+ onChainId,
212+ status : newStatus ,
213+ } ) ;
155214 }
156215 }
157216
0 commit comments