@@ -173,12 +173,54 @@ pub struct MonitoringData {
173173 pub system_status : MonitoringStatus ,
174174}
175175
176+ /// Critical contract transitions that indexers should track.
177+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
178+ #[ contracttype]
179+ pub enum TransitionDomain {
180+ Resolution ,
181+ Dispute ,
182+ Pause ,
183+ }
184+
185+ /// Generic transition hook payload for indexer consumption.
186+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
187+ #[ contracttype]
188+ pub struct TransitionHookEvent {
189+ /// Transition domain.
190+ pub domain : TransitionDomain ,
191+ /// Action label within the domain (e.g., "resolved", "created", "paused").
192+ pub action : String ,
193+ /// Related market when applicable.
194+ pub market_id : Option < Symbol > ,
195+ /// State before transition when applicable.
196+ pub old_state : Option < String > ,
197+ /// State after transition when applicable.
198+ pub new_state : Option < String > ,
199+ /// Actor that initiated the transition when known.
200+ pub actor : Option < Address > ,
201+ /// Human-readable transition details.
202+ pub details : String ,
203+ /// Event timestamp.
204+ pub timestamp : u64 ,
205+ }
206+
176207// ===== CONTRACT MONITOR STRUCT =====
177208
178209/// Main contract monitoring system
179210pub struct ContractMonitor ;
180211
181212impl ContractMonitor {
213+ fn market_state_label ( env : & Env , state : & MarketState ) -> String {
214+ match state {
215+ MarketState :: Active => String :: from_str ( env, "Active" ) ,
216+ MarketState :: Ended => String :: from_str ( env, "Ended" ) ,
217+ MarketState :: Disputed => String :: from_str ( env, "Disputed" ) ,
218+ MarketState :: Resolved => String :: from_str ( env, "Resolved" ) ,
219+ MarketState :: Closed => String :: from_str ( env, "Closed" ) ,
220+ MarketState :: Cancelled => String :: from_str ( env, "Cancelled" ) ,
221+ }
222+ }
223+
182224 /// Monitor market health for a specific market
183225 pub fn monitor_market_health (
184226 env : & Env ,
@@ -384,6 +426,91 @@ impl ContractMonitor {
384426 Ok ( ( ) )
385427 }
386428
429+ /// Emit an indexer-friendly hook for resolution state transitions.
430+ pub fn emit_resolution_transition_hook (
431+ env : & Env ,
432+ market_id : & Symbol ,
433+ old_state : & MarketState ,
434+ new_state : & MarketState ,
435+ details : & String ,
436+ ) {
437+ let event = TransitionHookEvent {
438+ domain : TransitionDomain :: Resolution ,
439+ action : String :: from_str ( env, "state_transition" ) ,
440+ market_id : Some ( market_id. clone ( ) ) ,
441+ old_state : Some ( Self :: market_state_label ( env, old_state) ) ,
442+ new_state : Some ( Self :: market_state_label ( env, new_state) ) ,
443+ actor : None ,
444+ details : details. clone ( ) ,
445+ timestamp : env. ledger ( ) . timestamp ( ) ,
446+ } ;
447+
448+ env. events ( ) . publish (
449+ (
450+ Symbol :: new ( env, "idx_transition" ) ,
451+ Symbol :: new ( env, "resolution" ) ,
452+ market_id. clone ( ) ,
453+ ) ,
454+ event,
455+ ) ;
456+ }
457+
458+ /// Emit an indexer-friendly hook for dispute lifecycle transitions.
459+ pub fn emit_dispute_transition_hook (
460+ env : & Env ,
461+ market_id : & Symbol ,
462+ action : & String ,
463+ actor : & Address ,
464+ details : & String ,
465+ ) {
466+ let event = TransitionHookEvent {
467+ domain : TransitionDomain :: Dispute ,
468+ action : action. clone ( ) ,
469+ market_id : Some ( market_id. clone ( ) ) ,
470+ old_state : None ,
471+ new_state : None ,
472+ actor : Some ( actor. clone ( ) ) ,
473+ details : details. clone ( ) ,
474+ timestamp : env. ledger ( ) . timestamp ( ) ,
475+ } ;
476+
477+ env. events ( ) . publish (
478+ (
479+ Symbol :: new ( env, "idx_transition" ) ,
480+ Symbol :: new ( env, "dispute" ) ,
481+ market_id. clone ( ) ,
482+ ) ,
483+ event,
484+ ) ;
485+ }
486+
487+ /// Emit an indexer-friendly hook for contract pause/unpause transitions.
488+ pub fn emit_pause_transition_hook (
489+ env : & Env ,
490+ action : & String ,
491+ actor : Option < Address > ,
492+ details : & String ,
493+ ) {
494+ let event = TransitionHookEvent {
495+ domain : TransitionDomain :: Pause ,
496+ action : action. clone ( ) ,
497+ market_id : None ,
498+ old_state : None ,
499+ new_state : None ,
500+ actor,
501+ details : details. clone ( ) ,
502+ timestamp : env. ledger ( ) . timestamp ( ) ,
503+ } ;
504+
505+ env. events ( ) . publish (
506+ (
507+ Symbol :: new ( env, "idx_transition" ) ,
508+ Symbol :: new ( env, "pause" ) ,
509+ ) ,
510+ event,
511+ ) ;
512+ }
513+
387514 /// Validate monitoring data integrity
388515 pub fn validate_monitoring_data ( env : & Env , data : & MonitoringData ) -> Result < bool , Error > {
389516 // Validate timestamp
@@ -1018,7 +1145,7 @@ impl MonitoringTestingUtils {
10181145#[ cfg( test) ]
10191146mod tests {
10201147 use super :: * ;
1021- use soroban_sdk:: testutils:: Address ;
1148+ use soroban_sdk:: testutils:: { Address as _ , Events } ;
10221149
10231150 #[ test]
10241151 fn test_market_health_monitoring ( ) {
@@ -1175,4 +1302,58 @@ mod tests {
11751302 let data = MonitoringTestingUtils :: create_test_monitoring_data ( & env) ;
11761303 assert_eq ! ( data. system_status, MonitoringStatus :: Healthy ) ;
11771304 }
1305+
1306+ #[ test]
1307+ fn test_resolution_transition_hook_emits_indexer_event ( ) {
1308+ let env = Env :: default ( ) ;
1309+ let contract_id = env. register ( crate :: PredictifyHybrid , ( ) ) ;
1310+ let market_id = Symbol :: new ( & env, "mkt_1" ) ;
1311+ env. as_contract ( & contract_id, || {
1312+ ContractMonitor :: emit_resolution_transition_hook (
1313+ & env,
1314+ & market_id,
1315+ & MarketState :: Ended ,
1316+ & MarketState :: Resolved ,
1317+ & String :: from_str ( & env, "oracle resolution" ) ,
1318+ ) ;
1319+ } ) ;
1320+
1321+ assert_eq ! ( env. events( ) . all( ) . len( ) , 1 ) ;
1322+ }
1323+
1324+ #[ test]
1325+ fn test_dispute_transition_hook_emits_indexer_event ( ) {
1326+ let env = Env :: default ( ) ;
1327+ let contract_id = env. register ( crate :: PredictifyHybrid , ( ) ) ;
1328+ let market_id = Symbol :: new ( & env, "mkt_2" ) ;
1329+ let actor = Address :: generate ( & env) ;
1330+ env. as_contract ( & contract_id, || {
1331+ ContractMonitor :: emit_dispute_transition_hook (
1332+ & env,
1333+ & market_id,
1334+ & String :: from_str ( & env, "created" ) ,
1335+ & actor,
1336+ & String :: from_str ( & env, "stake posted" ) ,
1337+ ) ;
1338+ } ) ;
1339+
1340+ assert_eq ! ( env. events( ) . all( ) . len( ) , 1 ) ;
1341+ }
1342+
1343+ #[ test]
1344+ fn test_pause_transition_hook_emits_indexer_event ( ) {
1345+ let env = Env :: default ( ) ;
1346+ let contract_id = env. register ( crate :: PredictifyHybrid , ( ) ) ;
1347+ let actor = Address :: generate ( & env) ;
1348+ env. as_contract ( & contract_id, || {
1349+ ContractMonitor :: emit_pause_transition_hook (
1350+ & env,
1351+ & String :: from_str ( & env, "paused" ) ,
1352+ Some ( actor) ,
1353+ & String :: from_str ( & env, "manual emergency pause" ) ,
1354+ ) ;
1355+ } ) ;
1356+
1357+ assert_eq ! ( env. events( ) . all( ) . len( ) , 1 ) ;
1358+ }
11781359}
0 commit comments