@@ -80,6 +80,7 @@ export class PythCrankerBot implements Bot {
8080 public dryRun : boolean ;
8181 private feedIdToPriceFeedMap : Map < string , PriceFeed > = new Map ( ) ;
8282 public defaultIntervalMs : number ;
83+ public minIntervalMs : number ; // stores the minimum update interval for any feed, will ensure bot runs at least this interval
8384
8485 private blockhashSubscriber : BlockhashSubscriber ;
8586 private health : boolean = true ;
@@ -101,6 +102,7 @@ export class PythCrankerBot implements Bot {
101102 this . name = crankConfigs . botId ;
102103 this . dryRun = crankConfigs . dryRun ;
103104 this . defaultIntervalMs = crankConfigs . intervalMs ?? 30_000 ;
105+ this . minIntervalMs = this . defaultIntervalMs ;
104106 if ( ! globalConfig . hermesEndpoint ) {
105107 throw new Error ( 'Missing hermesEndpoint in global config' ) ;
106108 }
@@ -169,101 +171,129 @@ export class PythCrankerBot implements Bot {
169171 }
170172 ) ;
171173
172- if ( ! this . crankConfigs . overridePythIds ) {
173- for ( const marketConfig of perpMarketConfigs ) {
174- const feedId = marketConfig . pythFeedId ;
175- if ( ! feedId ) {
176- logger . warn ( `No pyth feed id for market ${ marketConfig . symbol } ` ) ;
177- continue ;
178- }
179- const perpMarket = this . driftClient . getPerpMarketAccount (
180- marketConfig . marketIndex
181- ) ;
182- if ( ! perpMarket ) {
183- logger . warn ( `No perp market for market ${ marketConfig . symbol } ` ) ;
184- continue ;
185- }
186- if ( isOneOfVariant ( perpMarket . status , [ 'delisted' , 'settlement' ] ) ) {
187- logger . warn (
188- `Skipping perp market ${ marketConfig . symbol } is delisted`
189- ) ;
190- continue ;
191- }
192-
193- const updateConfigs = updateDefault ;
194- const earlyUpdateConfigs = earlyUpdateDefault ;
195- if ( isOneOfVariant ( perpMarket . contractTier , [ 'a' , 'b' ] ) ) {
196- updateConfigs . timeDiffMs = 15_000 ;
197- earlyUpdateConfigs . timeDiffMs = 10_000 ;
198- }
199- const pubkey = getPythPullOraclePublicKey (
200- this . driftClient . program . programId ,
201- getFeedIdUint8Array ( feedId )
202- ) ;
174+ // Build default feedIdsToCrank from all perp markets
175+ for ( const marketConfig of perpMarketConfigs ) {
176+ const feedId = marketConfig . pythFeedId ;
177+ if ( ! feedId ) {
178+ logger . warn ( `No pyth feed id for market ${ marketConfig . symbol } ` ) ;
179+ continue ;
180+ }
181+ const perpMarket = this . driftClient . getPerpMarketAccount (
182+ marketConfig . marketIndex
183+ ) ;
184+ if ( ! perpMarket ) {
185+ logger . warn ( `No perp market for market ${ marketConfig . symbol } ` ) ;
186+ continue ;
187+ }
188+ if ( isOneOfVariant ( perpMarket . status , [ 'delisted' , 'settlement' ] ) ) {
189+ logger . warn ( `Skipping perp market ${ marketConfig . symbol } is delisted` ) ;
190+ continue ;
191+ }
203192
204- this . feedIdsToCrank . push ( {
205- baseSymbol : marketConfig . baseAssetSymbol . toUpperCase ( ) ,
206- feedId,
207- updateConfig :
208- this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. update ?? updateConfigs ,
209- earlyUpdateConfig :
210- this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. earlyUpdate ??
211- earlyUpdateConfigs ,
212- accountAddress : pubkey ,
213- } ) ;
193+ const updateConfigs = { ...updateDefault } ;
194+ const earlyUpdateConfigs = { ...earlyUpdateDefault } ;
195+ if ( isOneOfVariant ( perpMarket . contractTier , [ 'a' , 'b' ] ) ) {
196+ updateConfigs . timeDiffMs = 15_000 ;
197+ earlyUpdateConfigs . timeDiffMs = 10_000 ;
214198 }
215199
216- for ( const marketConfig of spotMarketConfigs ) {
217- if (
218- this . feedIdsToCrank . findIndex (
219- ( feedId ) => feedId . baseSymbol === marketConfig . symbol
220- ) !== - 1
221- )
222- continue ;
200+ const pubkey = getPythPullOraclePublicKey (
201+ this . driftClient . program . programId ,
202+ getFeedIdUint8Array ( feedId )
203+ ) ;
223204
224- const feedId = marketConfig . pythFeedId ;
225- if ( ! feedId ) {
226- logger . warn ( `No pyth feed id for market ${ marketConfig . symbol } ` ) ;
227- continue ;
228- }
229- const updateConfigs = updateDefault ;
230- const earlyUpdateConfigs = earlyUpdateDefault ;
231- if (
232- isOneOfVariant ( marketConfig . oracleSource , [
233- 'pythPullStableCoin' ,
234- 'pythStableCoin' ,
235- ] )
236- ) {
237- updateConfigs . timeDiffMs = 15_000 ;
238- updateConfigs . priceDiffPct = 0.1 ;
239- earlyUpdateConfigs . timeDiffMs = 10_000 ;
240- earlyUpdateConfigs . priceDiffPct = 0.05 ;
241- }
242- const pubkey = getPythPullOraclePublicKey (
243- this . driftClient . program . programId ,
244- getFeedIdUint8Array ( feedId )
245- ) ;
246- this . feedIdsToCrank . push ( {
247- baseSymbol : marketConfig . symbol . toUpperCase ( ) ,
248- feedId,
249- updateConfig :
250- this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. update ?? updateConfigs ,
251- earlyUpdateConfig :
252- this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. earlyUpdate ??
253- earlyUpdateConfigs ,
254- accountAddress : pubkey ,
255- } ) ;
205+ const finalUpdateConfig =
206+ this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. update ?? updateConfigs ;
207+ const finalEarlyUpdateConfig =
208+ this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. earlyUpdate ??
209+ earlyUpdateConfigs ;
210+
211+ this . minIntervalMs = Math . min (
212+ this . minIntervalMs ,
213+ finalUpdateConfig . timeDiffMs ,
214+ finalEarlyUpdateConfig . timeDiffMs
215+ ) ;
216+
217+ this . feedIdsToCrank . push ( {
218+ baseSymbol : marketConfig . baseAssetSymbol . toUpperCase ( ) ,
219+ feedId,
220+ updateConfig : finalUpdateConfig ,
221+ earlyUpdateConfig : finalEarlyUpdateConfig ,
222+ accountAddress : pubkey ,
223+ } ) ;
224+ }
225+
226+ // Add spot markets to default feedIdsToCrank
227+ for ( const marketConfig of spotMarketConfigs ) {
228+ if (
229+ this . feedIdsToCrank . findIndex (
230+ ( feedId ) => feedId . baseSymbol === marketConfig . symbol
231+ ) !== - 1
232+ )
233+ continue ;
234+
235+ const feedId = marketConfig . pythFeedId ;
236+ if ( ! feedId ) {
237+ logger . warn ( `No pyth feed id for market ${ marketConfig . symbol } ` ) ;
238+ continue ;
239+ }
240+ const updateConfigs = { ...updateDefault } ;
241+ const earlyUpdateConfigs = { ...earlyUpdateDefault } ;
242+ if (
243+ isOneOfVariant ( marketConfig . oracleSource , [
244+ 'pythPullStableCoin' ,
245+ 'pythStableCoin' ,
246+ ] )
247+ ) {
248+ updateConfigs . timeDiffMs = 15_000 ;
249+ updateConfigs . priceDiffPct = 0.1 ;
250+ earlyUpdateConfigs . timeDiffMs = 10_000 ;
251+ earlyUpdateConfigs . priceDiffPct = 0.05 ;
256252 }
257- } else {
253+
254+ const pubkey = getPythPullOraclePublicKey (
255+ this . driftClient . program . programId ,
256+ getFeedIdUint8Array ( feedId )
257+ ) ;
258+
259+ const finalUpdateConfig =
260+ this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. update ?? updateConfigs ;
261+ const finalEarlyUpdateConfig =
262+ this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. earlyUpdate ??
263+ earlyUpdateConfigs ;
264+
265+ this . minIntervalMs = Math . min (
266+ this . minIntervalMs ,
267+ finalUpdateConfig . timeDiffMs ,
268+ finalEarlyUpdateConfig . timeDiffMs
269+ ) ;
270+
271+ this . feedIdsToCrank . push ( {
272+ baseSymbol : marketConfig . symbol . toUpperCase ( ) ,
273+ feedId,
274+ updateConfig : finalUpdateConfig ,
275+ earlyUpdateConfig : finalEarlyUpdateConfig ,
276+ accountAddress : pubkey ,
277+ } ) ;
278+ }
279+
280+ // Override configs for specific feed IDs if provided
281+ if ( this . crankConfigs . overridePythIds ) {
258282 for ( const feedId of this . crankConfigs . overridePythIds ) {
283+ const existingFeedIndex = this . feedIdsToCrank . findIndex (
284+ ( feed ) => feed . feedId === feedId
285+ ) ;
286+
287+ // Find the market config for this feed
259288 const marketConfig = perpMarketConfigs . find (
260289 ( market ) => market . pythFeedId === feedId
261290 ) ;
262291 const spotMarketConfig = spotMarketConfigs . find (
263292 ( market ) => market . pythFeedId === feedId
264293 ) ;
294+
265295 if ( ! marketConfig && ! spotMarketConfig ) {
266- logger . warn ( `No market config found for feed id ${ feedId } ` ) ;
296+ logger . warn ( `No market config found for override feed id ${ feedId } ` ) ;
267297 continue ;
268298 }
269299
@@ -275,18 +305,35 @@ export class PythCrankerBot implements Bot {
275305 ? marketConfig
276306 : spotMarketConfig ;
277307
278- const updateConfigs = updateDefault ;
279- const earlyUpdateConfigs = earlyUpdateDefault ;
280- this . feedIdsToCrank . push ( {
308+ const updateConfigs = { ...updateDefault } ;
309+ const earlyUpdateConfigs = { ...earlyUpdateDefault } ;
310+
311+ const finalUpdateConfig =
312+ this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. update ?? updateConfigs ;
313+ const finalEarlyUpdateConfig =
314+ this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. earlyUpdate ??
315+ earlyUpdateConfigs ;
316+
317+ const overrideFeed = {
281318 baseSymbol : baseSymbol ,
282319 feedId,
283- updateConfig :
284- this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. update ?? updateConfigs ,
285- earlyUpdateConfig :
286- this . crankConfigs ?. updateConfigs ?. [ feedId ] ?. earlyUpdate ??
287- earlyUpdateConfigs ,
320+ updateConfig : finalUpdateConfig ,
321+ earlyUpdateConfig : finalEarlyUpdateConfig ,
288322 accountAddress : marketConfigToUse ! . oracle ,
289- } ) ;
323+ } ;
324+
325+ if ( existingFeedIndex !== - 1 ) {
326+ // Override existing feed
327+ this . feedIdsToCrank [ existingFeedIndex ] = overrideFeed ;
328+ } else {
329+ // Add new feed
330+ this . feedIdsToCrank . push ( overrideFeed ) ;
331+ }
332+ this . minIntervalMs = Math . min (
333+ this . minIntervalMs ,
334+ finalUpdateConfig . timeDiffMs ,
335+ finalEarlyUpdateConfig . timeDiffMs
336+ ) ;
290337 }
291338 }
292339
@@ -309,13 +356,18 @@ export class PythCrankerBot implements Bot {
309356 }
310357
311358 async startIntervalLoop ( intervalMs : number | undefined ) : Promise < void > {
312- logger . info ( `Starting ${ this . name } bot with interval ${ intervalMs } ms` ) ;
313- await sleepMs ( 5000 ) ;
359+ const startInterval = Math . min (
360+ intervalMs ?? this . defaultIntervalMs ,
361+ this . minIntervalMs
362+ ) ;
363+ logger . info (
364+ `Starting ${ this . name } bot with interval ${ startInterval } ms (default: ${ this . defaultIntervalMs } ms, min: ${ this . minIntervalMs } ms)`
365+ ) ;
314366 await this . runCrankLoop ( ) ;
315367
316368 setInterval ( async ( ) => {
317369 await this . runCrankLoop ( ) ;
318- } , intervalMs ) ;
370+ } , startInterval ) ;
319371 }
320372
321373 async getVaaForPriceFeedIds ( feedIds : string [ ] ) : Promise < string > {
0 commit comments