@@ -272,6 +272,39 @@ export function _clearFeeTokenMetaCache(): void {
272272 feeTokenMetaCache . clear ( ) ;
273273}
274274
275+ // ---------------------------------------------------------------------------
276+ // Test mocks: referenceRateFeedID & reportExpiry (for self-heal testing)
277+ // ---------------------------------------------------------------------------
278+ const _testRateFeedIDs = new Map < string , string | null > ( ) ;
279+
280+ /** @internal Test-only: pre-set a mock referenceRateFeedID for a pool. */
281+ export function _setMockRateFeedID (
282+ chainId : number ,
283+ poolAddress : string ,
284+ rateFeedID : string | null ,
285+ ) : void {
286+ _testRateFeedIDs . set ( `${ chainId } :${ poolAddress . toLowerCase ( ) } ` , rateFeedID ) ;
287+ }
288+
289+ export function _clearMockRateFeedIDs ( ) : void {
290+ _testRateFeedIDs . clear ( ) ;
291+ }
292+
293+ const _testReportExpiry = new Map < string , bigint | null > ( ) ;
294+
295+ /** @internal Test-only: pre-set a mock report expiry for a rateFeedID. */
296+ export function _setMockReportExpiry (
297+ chainId : number ,
298+ rateFeedID : string ,
299+ expiry : bigint | null ,
300+ ) : void {
301+ _testReportExpiry . set ( `${ chainId } :${ rateFeedID . toLowerCase ( ) } ` , expiry ) ;
302+ }
303+
304+ export function _clearMockReportExpiry ( ) : void {
305+ _testReportExpiry . clear ( ) ;
306+ }
307+
275308// ---------------------------------------------------------------------------
276309// Pure backfill helpers (exported for unit testing)
277310// ---------------------------------------------------------------------------
@@ -406,6 +439,10 @@ async function fetchReportExpiry(
406439 rateFeedID : string ,
407440 blockNumber : bigint ,
408441) : Promise < bigint | null > {
442+ // Check test mock first
443+ const mockKey = `${ chainId } :${ rateFeedID . toLowerCase ( ) } ` ;
444+ if ( _testReportExpiry . has ( mockKey ) ) return _testReportExpiry . get ( mockKey ) ! ;
445+
409446 let address : `0x${string } `;
410447 try {
411448 address = SORTED_ORACLES_ADDRESS ( chainId ) ;
@@ -735,6 +772,10 @@ async function fetchReferenceRateFeedID(
735772 chainId : number ,
736773 poolAddress : string ,
737774) : Promise < string | null > {
775+ // Check test mock first
776+ const mockKey = `${ chainId } :${ poolAddress . toLowerCase ( ) } ` ;
777+ if ( _testRateFeedIDs . has ( mockKey ) ) return _testRateFeedIDs . get ( mockKey ) ! ;
778+
738779 try {
739780 const client = getRpcClient ( chainId ) ;
740781 const result = await client . readContract ( {
@@ -1064,6 +1105,7 @@ const getOrCreatePool = async (
10641105
10651106const upsertPool = async ( {
10661107 context,
1108+ chainId,
10671109 poolId,
10681110 token0,
10691111 token1,
@@ -1077,6 +1119,7 @@ const upsertPool = async ({
10771119 tokenDecimals,
10781120} : {
10791121 context : PoolContext ;
1122+ chainId : number ;
10801123 poolId : string ;
10811124 token0 ?: string ;
10821125 token1 ?: string ;
@@ -1091,6 +1134,22 @@ const upsertPool = async ({
10911134} ) : Promise < Pool > => {
10921135 const existing = await getOrCreatePool ( context , poolId , { token0, token1 } ) ;
10931136
1137+ // Self-heal: if referenceRateFeedID is missing (transient RPC failure at
1138+ // pool creation), retry now so oracle events can start flowing.
1139+ let healedOracleDelta : Partial < typeof DEFAULT_ORACLE_FIELDS > | undefined ;
1140+ if (
1141+ existing . referenceRateFeedID === "" &&
1142+ existing . source !== "" &&
1143+ ! existing . source ?. includes ( "virtual" )
1144+ ) {
1145+ const rateFeedID = await fetchReferenceRateFeedID ( chainId , poolId ) ;
1146+ if ( rateFeedID ) {
1147+ healedOracleDelta = { referenceRateFeedID : rateFeedID } ;
1148+ const expiry = await fetchReportExpiry ( chainId , rateFeedID , blockNumber ) ;
1149+ if ( expiry !== null ) healedOracleDelta . oracleExpiry = expiry ;
1150+ }
1151+ }
1152+
10941153 let next : Pool = {
10951154 ...existing ,
10961155 token0 : token0 ?? existing . token0 ,
@@ -1102,7 +1161,8 @@ const upsertPool = async ({
11021161 notionalVolume0 : existing . notionalVolume0 + ( swapDelta ?. volume0 ?? 0n ) ,
11031162 notionalVolume1 : existing . notionalVolume1 + ( swapDelta ?. volume1 ?? 0n ) ,
11041163 rebalanceCount : existing . rebalanceCount + ( rebalanceDelta ? 1 : 0 ) ,
1105- // Merge oracle delta if provided
1164+ // Merge healed oracle fields first, then explicit delta takes precedence
1165+ ...( healedOracleDelta ?? { } ) ,
11061166 ...( oracleDelta ?? { } ) ,
11071167 // Persist token decimals if provided (set once at pool creation)
11081168 token0Decimals : tokenDecimals ?. token0Decimals ?? existing . token0Decimals ,
@@ -1270,6 +1330,7 @@ FPMMFactory.FPMMDeployed.handler(async ({ event, context }) => {
12701330
12711331 const pool = await upsertPool ( {
12721332 context,
1333+ chainId : event . chainId ,
12731334 poolId,
12741335 token0,
12751336 token1,
@@ -1327,6 +1388,7 @@ FPMM.Swap.handler(async ({ event, context }) => {
13271388
13281389 const pool = await upsertPool ( {
13291390 context,
1391+ chainId : event . chainId ,
13301392 poolId,
13311393 source : "fpmm_swap" ,
13321394 blockNumber,
@@ -1454,6 +1516,7 @@ FPMM.Mint.handler(async ({ event, context }) => {
14541516
14551517 const pool = await upsertPool ( {
14561518 context,
1519+ chainId : event . chainId ,
14571520 poolId,
14581521 source : "fpmm_mint" ,
14591522 blockNumber,
@@ -1493,6 +1556,7 @@ FPMM.Burn.handler(async ({ event, context }) => {
14931556
14941557 const pool = await upsertPool ( {
14951558 context,
1559+ chainId : event . chainId ,
14961560 poolId,
14971561 source : "fpmm_burn" ,
14981562 blockNumber,
@@ -1565,6 +1629,7 @@ FPMM.UpdateReserves.handler(async ({ event, context }) => {
15651629
15661630 const pool = await upsertPool ( {
15671631 context,
1632+ chainId : event . chainId ,
15681633 poolId,
15691634 source : "fpmm_update_reserves" ,
15701635 blockNumber,
@@ -1658,6 +1723,7 @@ FPMM.Rebalanced.handler(async ({ event, context }) => {
16581723
16591724 const pool = await upsertPool ( {
16601725 context,
1726+ chainId : event . chainId ,
16611727 poolId,
16621728 source : "fpmm_rebalanced" ,
16631729 blockNumber,
@@ -2010,6 +2076,7 @@ VirtualPoolFactory.VirtualPoolDeployed.handler(async ({ event, context }) => {
20102076 // VirtualPools don't have oracle functions; set N/A health status
20112077 await upsertPool ( {
20122078 context,
2079+ chainId : event . chainId ,
20132080 poolId,
20142081 token0,
20152082 token1,
@@ -2043,6 +2110,7 @@ VirtualPoolFactory.PoolDeprecated.handler(async ({ event, context }) => {
20432110
20442111 await upsertPool ( {
20452112 context,
2113+ chainId : event . chainId ,
20462114 poolId,
20472115 source : "virtual_pool_factory" ,
20482116 blockNumber : asBigInt ( event . block . number ) ,
@@ -2093,6 +2161,7 @@ VirtualPool.Swap.handler(async ({ event, context }) => {
20932161
20942162 const pool = await upsertPool ( {
20952163 context,
2164+ chainId : event . chainId ,
20962165 poolId,
20972166 source : "fpmm_swap" , // reuse source key; VirtualPool inherits same priority
20982167 blockNumber,
@@ -2134,6 +2203,7 @@ VirtualPool.Mint.handler(async ({ event, context }) => {
21342203
21352204 const pool = await upsertPool ( {
21362205 context,
2206+ chainId : event . chainId ,
21372207 poolId,
21382208 source : "fpmm_mint" ,
21392209 blockNumber,
@@ -2173,6 +2243,7 @@ VirtualPool.Burn.handler(async ({ event, context }) => {
21732243
21742244 const pool = await upsertPool ( {
21752245 context,
2246+ chainId : event . chainId ,
21762247 poolId,
21772248 source : "fpmm_burn" ,
21782249 blockNumber,
@@ -2213,6 +2284,7 @@ VirtualPool.UpdateReserves.handler(async ({ event, context }) => {
22132284 // VirtualPools have no oracle; just update reserves
22142285 const pool = await upsertPool ( {
22152286 context,
2287+ chainId : event . chainId ,
22162288 poolId,
22172289 source : "fpmm_update_reserves" ,
22182290 blockNumber,
@@ -2253,6 +2325,7 @@ VirtualPool.Rebalanced.handler(async ({ event, context }) => {
22532325
22542326 const pool = await upsertPool ( {
22552327 context,
2328+ chainId : event . chainId ,
22562329 poolId,
22572330 source : "fpmm_rebalanced" ,
22582331 blockNumber,
0 commit comments