@@ -37,6 +37,10 @@ type OnChainMonitor struct {
3737 cancel context.CancelFunc
3838}
3939
40+ func (m * OnChainMonitor ) IsEnabled () bool {
41+ return m .enabled
42+ }
43+
4044// RouterMonitor tracks one router and its destinations
4145type RouterMonitor struct {
4246 RouterID string
@@ -70,7 +74,7 @@ func DefaultMonitorConfig() MonitorConfig {
7074 return MonitorConfig {
7175 Enabled : false ,
7276 TimeThresholdOffset : 1 * time .Minute ,
73- PriceDeviationOffset : big .NewFloat (0.10 ), // 10%
77+ PriceDeviationOffset : big .NewFloat (0.50 ), // 10%
7478 CheckInterval : 10 * time .Second ,
7579 }
7680}
@@ -218,7 +222,7 @@ func (m *OnChainMonitor) checkRouter(routerMonitor *RouterMonitor) {
218222 percentageChange .Mul (percentageChange , big .NewFloat (100 ))
219223 dest .lastPercentageChange = percentageChange
220224
221- priceDevThreshold := m .GetPriceDeviationThreshold (dest .ChainID , dest .ContractAddress , symbol )
225+ priceDevThreshold := m .getPriceDeviationThresholdWithOffset (dest .ChainID , dest .ContractAddress , symbol )
222226 if priceDevThreshold != nil {
223227 thresholdPercent := new (big.Float ).Mul (priceDevThreshold , big .NewFloat (100 ))
224228 absChange := new (big.Float ).Abs (percentageChange )
@@ -259,6 +263,11 @@ func (m *OnChainMonitor) checkRouter(routerMonitor *RouterMonitor) {
259263 }
260264}
261265
266+ // FindDestination
267+ func (m * OnChainMonitor ) FindDestination (key string ) * DestinationMonitor {
268+ return m .findDestination (key )
269+ }
270+
262271func (m * OnChainMonitor ) findDestination (key string ) * DestinationMonitor {
263272 m .mu .RLock ()
264273 defer m .mu .RUnlock ()
@@ -274,6 +283,10 @@ func (m *OnChainMonitor) findDestination(key string) *DestinationMonitor {
274283 return nil
275284}
276285
286+ func (m * OnChainMonitor ) generateKey (chainID int64 , contractAddress common.Address , symbol string ) string {
287+ return utils .GenerateDestinationKey (chainID , contractAddress .Hex (), symbol )
288+ }
289+
277290func (m * OnChainMonitor ) getValueFromContract (dest * DestinationMonitor , symbol string ) (* big.Int , uint64 , error ) {
278291
279292 const getValueABI = `[{
@@ -336,13 +349,12 @@ func formatValue(v *big.Int) string {
336349 return v .String ()
337350}
338351
339- // ShouldProcess checks if a destination should be processed
340352func (m * OnChainMonitor ) ShouldProcess (chainID int64 , contractAddress common.Address , symbol string , incomingPrice * big.Int ) bool {
341353 if ! m .enabled {
342354 return true
343355 }
344356
345- key := utils . GenerateDestinationKey (chainID , contractAddress . Hex () , symbol )
357+ key := m . generateKey (chainID , contractAddress , symbol )
346358 dest := m .findDestination (key )
347359 if dest == nil {
348360 return true
@@ -365,45 +377,64 @@ func (m *OnChainMonitor) ShouldProcess(chainID int64, contractAddress common.Add
365377 return true
366378 }
367379
368- if lastPercentageChange != nil {
369- priceDevThreshold := m .GetPriceDeviationThreshold (chainID , contractAddress , symbol )
370- if priceDevThreshold != nil {
371- thresholdPercent := new (big.Float ).Mul (priceDevThreshold , big .NewFloat (100 ))
372- absChange := new (big.Float ).Abs (lastPercentageChange )
373- if absChange .Cmp (thresholdPercent ) > 0 {
374- logger .Infof ("Monitoring triggered: price deviation threshold exceeded for chain=%d contract=%s symbol=%s, " +
375- "change=%.2f%% threshold=%.2f%% value=%s" ,
376- chainID , contractAddress .Hex (), symbol , lastPercentageChange , thresholdPercent , formatValue (currentValue ))
377- return true
380+ // skip deviation checks if last update was within last 2 minutes
381+ timeSinceUpdate := time .Since (time .Unix (int64 (lastTimestamp ), 0 ))
382+ skipDeviationCheck := timeSinceUpdate < 2 * time .Minute
383+
384+ if skipDeviationCheck {
385+ logger .Debugf ("Skipping deviation check for chain=%d contract=%s symbol=%s (last update %v ago < 2 minutes)" ,
386+ chainID , contractAddress .Hex (), symbol , timeSinceUpdate )
387+ } else {
388+ if lastPercentageChange != nil {
389+ priceDevThreshold := m .getPriceDeviationThresholdWithOffset (chainID , contractAddress , symbol )
390+ if priceDevThreshold != nil {
391+ thresholdPercent := new (big.Float ).Mul (priceDevThreshold , big .NewFloat (100 ))
392+ absChange := new (big.Float ).Abs (lastPercentageChange )
393+ if absChange .Cmp (thresholdPercent ) > 0 {
394+ logger .Infof ("Monitoring triggered: price deviation threshold exceeded for chain=%d contract=%s symbol=%s, " +
395+ "change=%.2f%% threshold=%.2f%% value=%s" ,
396+ chainID , contractAddress .Hex (), symbol , lastPercentageChange , thresholdPercent , formatValue (currentValue ))
397+ return true
398+ }
378399 }
379400 }
380- }
381401
382- if incomingPrice != nil && currentValue != nil && currentValue .Sign () != 0 {
383- priceDevThreshold := m .GetPriceDeviationThreshold (chainID , contractAddress , symbol )
384- if priceDevThreshold != nil {
385- diff := new (big.Int ).Sub (incomingPrice , currentValue )
386- oldFloat := new (big.Float ).SetInt (currentValue )
387- diffFloat := new (big.Float ).SetInt (diff )
388- percentageChange := new (big.Float ).Quo (diffFloat , oldFloat )
389- percentageChange .Mul (percentageChange , big .NewFloat (100 ))
390- thresholdPercent := new (big.Float ).Mul (priceDevThreshold , big .NewFloat (100 ))
391- absChange := new (big.Float ).Abs (percentageChange )
392- if absChange .Cmp (thresholdPercent ) > 0 {
393- logger .Infof ("Monitoring triggered: price deviation threshold exceeded (incoming vs on-chain) for chain=%d contract=%s symbol=%s, " +
394- "change=%.2f%% threshold=%.2f%% onchain_value=%s incoming_value=%s" ,
395- chainID , contractAddress .Hex (), symbol , percentageChange , thresholdPercent , formatValue (currentValue ), incomingPrice .String ())
396- return true
402+ if incomingPrice != nil {
403+ freshValue , _ , err := m .getValueFromContract (dest , symbol )
404+ if err != nil {
405+ logger .Warnf ("Failed to fetch fresh on-chain value for chain=%d contract=%s symbol=%s, using cached value: %v" ,
406+ chainID , contractAddress .Hex (), symbol , err )
407+ // Fall back to cached value if fetch fails
408+ freshValue = currentValue
409+ }
410+
411+ if freshValue != nil && freshValue .Sign () != 0 {
412+ priceDevThreshold := m .getPriceDeviationThresholdWithOffset (chainID , contractAddress , symbol )
413+ if priceDevThreshold != nil {
414+ diff := new (big.Int ).Sub (incomingPrice , freshValue )
415+ oldFloat := new (big.Float ).SetInt (freshValue )
416+ diffFloat := new (big.Float ).SetInt (diff )
417+ percentageChange := new (big.Float ).Quo (diffFloat , oldFloat )
418+ percentageChange .Mul (percentageChange , big .NewFloat (100 ))
419+ thresholdPercent := new (big.Float ).Mul (priceDevThreshold , big .NewFloat (100 ))
420+ absChange := new (big.Float ).Abs (percentageChange )
421+ if absChange .Cmp (thresholdPercent ) > 0 {
422+ logger .Infof ("Monitoring triggered: price deviation threshold exceeded (incoming vs on-chain) for chain=%d contract=%s symbol=%s, " +
423+ "change=%.2f%% threshold=%.2f%% onchain_value=%s incoming_value=%s" ,
424+ chainID , contractAddress .Hex (), symbol , percentageChange , thresholdPercent , formatValue (freshValue ), incomingPrice .String ())
425+ return true
426+ }
427+ }
397428 }
398429 }
399- }
430+ } // End of skipDeviationCheck else block
400431
401432 if timeThreshold == 0 {
402433 timeThreshold = 5 * time .Minute
403434 }
404435 totalThreshold := timeThreshold + m .timeThresholdOffset
405436
406- timeSinceUpdate := time . Since ( time . Unix ( int64 ( lastTimestamp ), 0 ) )
437+ // Time threshold check always proceeds (not skipped by 2-minute rule )
407438 if timeSinceUpdate > totalThreshold {
408439 logger .Infof ("Monitoring triggered: time threshold exceeded for chain=%d contract=%s symbol=%s, " +
409440 "time_since_update=%v threshold=%v value=%s" ,
@@ -414,9 +445,8 @@ func (m *OnChainMonitor) ShouldProcess(chainID int64, contractAddress common.Add
414445 return false
415446}
416447
417- // GetPriceDeviationThreshold returns the price deviation threshold for a destination
418448func (m * OnChainMonitor ) GetPriceDeviationThreshold (chainID int64 , contractAddress common.Address , symbol string ) * big.Float {
419- key := utils . GenerateDestinationKey (chainID , contractAddress . Hex () , symbol )
449+ key := m . generateKey (chainID , contractAddress , symbol )
420450 dest := m .findDestination (key )
421451
422452 if dest == nil || dest .PriceDeviation == "" {
@@ -427,9 +457,28 @@ func (m *OnChainMonitor) GetPriceDeviationThreshold(chainID int64, contractAddre
427457 return new (big.Float ).Add (priceDeviation , m .priceDeviationOffset )
428458}
429459
430- // GetInactivityThreshold returns the inactivity threshold for a destination
460+ func (m * OnChainMonitor ) GetPriceDeviationThresholdWithOffset (chainID int64 , contractAddress common.Address , symbol string ) * big.Float {
461+ return m .getPriceDeviationThresholdWithOffset (chainID , contractAddress , symbol )
462+ }
463+
464+ func (m * OnChainMonitor ) getPriceDeviationThresholdWithOffset (chainID int64 , contractAddress common.Address , symbol string ) * big.Float {
465+ key := m .generateKey (chainID , contractAddress , symbol )
466+ dest := m .findDestination (key )
467+
468+ if dest == nil || dest .PriceDeviation == "" {
469+ baseThreshold := big .NewFloat (0.10 / 100.0 )
470+ offsetPercent := m .priceDeviationOffset
471+ offsetAmount := new (big.Float ).Mul (baseThreshold , offsetPercent )
472+ return new (big.Float ).Add (baseThreshold , offsetAmount )
473+ }
474+
475+ baseThreshold := parsePriceDeviation (dest .PriceDeviation )
476+ offsetAmount := new (big.Float ).Mul (baseThreshold , m .priceDeviationOffset )
477+ return new (big.Float ).Add (baseThreshold , offsetAmount )
478+ }
479+
431480func (m * OnChainMonitor ) GetInactivityThreshold (chainID int64 , contractAddress common.Address , symbol string ) time.Duration {
432- key := utils . GenerateDestinationKey (chainID , contractAddress . Hex () , symbol )
481+ key := m . generateKey (chainID , contractAddress , symbol )
433482 dest := m .findDestination (key )
434483
435484 timeThreshold := 5 * time .Minute
@@ -443,6 +492,30 @@ func (m *OnChainMonitor) GetInactivityThreshold(chainID int64, contractAddress c
443492 return timeThreshold + m .timeThresholdOffset
444493}
445494
495+ func (m * OnChainMonitor ) GetTimeThresholdOffset () time.Duration {
496+ return m .timeThresholdOffset
497+ }
498+
499+ func (m * OnChainMonitor ) GetMonitoringInfo (chainID int64 , contractAddress common.Address , symbol string ) (onChainValue * big.Int , lastTimestamp uint64 , timeThreshold time.Duration ) {
500+ key := m .generateKey (chainID , contractAddress , symbol )
501+ dest := m .findDestination (key )
502+ if dest == nil {
503+ return nil , 0 , 0
504+ }
505+
506+ dest .mu .RLock ()
507+ defer dest .mu .RUnlock ()
508+
509+ onChainValue = dest .lastValue
510+ lastTimestamp = dest .lastTimestamp
511+ timeThreshold = dest .TimeThreshold .Duration ()
512+ if timeThreshold == 0 {
513+ timeThreshold = 5 * time .Minute
514+ }
515+
516+ return onChainValue , lastTimestamp , timeThreshold
517+ }
518+
446519func ParsePriceDeviation (s string ) * big.Float {
447520 return parsePriceDeviation (s )
448521}
0 commit comments