@aztec/sequencer-client 3.0.0-rc.5 → 4.0.0-nightly.20260107

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dest/client/sequencer-client.d.ts +9 -8
  2. package/dest/client/sequencer-client.d.ts.map +1 -1
  3. package/dest/client/sequencer-client.js +28 -24
  4. package/dest/config.d.ts +7 -1
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +63 -26
  7. package/dest/global_variable_builder/global_builder.d.ts +16 -8
  8. package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
  9. package/dest/global_variable_builder/global_builder.js +37 -28
  10. package/dest/index.d.ts +2 -2
  11. package/dest/index.d.ts.map +1 -1
  12. package/dest/index.js +1 -1
  13. package/dest/publisher/config.d.ts +3 -3
  14. package/dest/publisher/config.d.ts.map +1 -1
  15. package/dest/publisher/config.js +2 -2
  16. package/dest/publisher/sequencer-publisher-factory.d.ts +3 -3
  17. package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
  18. package/dest/publisher/sequencer-publisher-factory.js +1 -1
  19. package/dest/publisher/sequencer-publisher-metrics.d.ts +3 -3
  20. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
  21. package/dest/publisher/sequencer-publisher.d.ts +27 -23
  22. package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
  23. package/dest/publisher/sequencer-publisher.js +119 -64
  24. package/dest/sequencer/block_builder.d.ts +1 -3
  25. package/dest/sequencer/block_builder.d.ts.map +1 -1
  26. package/dest/sequencer/block_builder.js +4 -2
  27. package/dest/sequencer/checkpoint_builder.d.ts +63 -0
  28. package/dest/sequencer/checkpoint_builder.d.ts.map +1 -0
  29. package/dest/sequencer/checkpoint_builder.js +131 -0
  30. package/dest/sequencer/checkpoint_proposal_job.d.ts +74 -0
  31. package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
  32. package/dest/sequencer/checkpoint_proposal_job.js +642 -0
  33. package/dest/sequencer/checkpoint_voter.d.ts +34 -0
  34. package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
  35. package/dest/sequencer/checkpoint_voter.js +85 -0
  36. package/dest/sequencer/events.d.ts +46 -0
  37. package/dest/sequencer/events.d.ts.map +1 -0
  38. package/dest/sequencer/events.js +1 -0
  39. package/dest/sequencer/index.d.ts +5 -1
  40. package/dest/sequencer/index.d.ts.map +1 -1
  41. package/dest/sequencer/index.js +4 -0
  42. package/dest/sequencer/metrics.d.ts +21 -1
  43. package/dest/sequencer/metrics.d.ts.map +1 -1
  44. package/dest/sequencer/metrics.js +154 -0
  45. package/dest/sequencer/sequencer.d.ts +91 -125
  46. package/dest/sequencer/sequencer.d.ts.map +1 -1
  47. package/dest/sequencer/sequencer.js +581 -582
  48. package/dest/sequencer/timetable.d.ts +54 -14
  49. package/dest/sequencer/timetable.d.ts.map +1 -1
  50. package/dest/sequencer/timetable.js +148 -59
  51. package/dest/sequencer/types.d.ts +3 -0
  52. package/dest/sequencer/types.d.ts.map +1 -0
  53. package/dest/sequencer/types.js +1 -0
  54. package/dest/sequencer/utils.d.ts +14 -8
  55. package/dest/sequencer/utils.d.ts.map +1 -1
  56. package/dest/sequencer/utils.js +7 -4
  57. package/dest/test/index.d.ts +3 -1
  58. package/dest/test/index.d.ts.map +1 -1
  59. package/dest/test/mock_checkpoint_builder.d.ts +83 -0
  60. package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
  61. package/dest/test/mock_checkpoint_builder.js +179 -0
  62. package/dest/test/utils.d.ts +49 -0
  63. package/dest/test/utils.d.ts.map +1 -0
  64. package/dest/test/utils.js +94 -0
  65. package/package.json +27 -27
  66. package/src/client/sequencer-client.ts +24 -31
  67. package/src/config.ts +68 -25
  68. package/src/global_variable_builder/global_builder.ts +47 -41
  69. package/src/index.ts +2 -0
  70. package/src/publisher/config.ts +3 -3
  71. package/src/publisher/sequencer-publisher-factory.ts +3 -3
  72. package/src/publisher/sequencer-publisher-metrics.ts +2 -2
  73. package/src/publisher/sequencer-publisher.ts +170 -74
  74. package/src/sequencer/README.md +531 -0
  75. package/src/sequencer/block_builder.ts +4 -1
  76. package/src/sequencer/checkpoint_builder.ts +217 -0
  77. package/src/sequencer/checkpoint_proposal_job.ts +706 -0
  78. package/src/sequencer/checkpoint_voter.ts +105 -0
  79. package/src/sequencer/events.ts +27 -0
  80. package/src/sequencer/index.ts +4 -0
  81. package/src/sequencer/metrics.ts +202 -0
  82. package/src/sequencer/sequencer.ts +300 -779
  83. package/src/sequencer/timetable.ts +173 -79
  84. package/src/sequencer/types.ts +6 -0
  85. package/src/sequencer/utils.ts +18 -9
  86. package/src/test/index.ts +2 -0
  87. package/src/test/mock_checkpoint_builder.ts +247 -0
  88. package/src/test/utils.ts +137 -0
@@ -0,0 +1,105 @@
1
+ import type { SlotNumber } from '@aztec/foundation/branded-types';
2
+ import type { EthAddress } from '@aztec/foundation/eth-address';
3
+ import type { Logger } from '@aztec/foundation/log';
4
+ import type { SlasherClientInterface } from '@aztec/slasher';
5
+ import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
6
+ import type { ResolvedSequencerConfig } from '@aztec/stdlib/interfaces/server';
7
+ import type { ValidatorClient } from '@aztec/validator-client';
8
+
9
+ import type { TypedDataDefinition } from 'viem';
10
+
11
+ import type { SequencerPublisher } from '../publisher/sequencer-publisher.js';
12
+ import type { SequencerMetrics } from './metrics.js';
13
+ import type { SequencerRollupConstants } from './types.js';
14
+
15
+ /**
16
+ * Handles governance and slashing voting for a given slot.
17
+ */
18
+ export class CheckpointVoter {
19
+ private slotTimestamp: bigint;
20
+ private signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>;
21
+
22
+ constructor(
23
+ private readonly slot: SlotNumber,
24
+ private readonly publisher: SequencerPublisher,
25
+ private readonly attestorAddress: EthAddress,
26
+ private readonly validatorClient: ValidatorClient,
27
+ private readonly slasherClient: SlasherClientInterface | undefined,
28
+ private readonly l1Constants: SequencerRollupConstants,
29
+ private readonly config: ResolvedSequencerConfig,
30
+ private readonly metrics: SequencerMetrics,
31
+ private readonly log: Logger,
32
+ ) {
33
+ this.slotTimestamp = getTimestampForSlot(this.slot, this.l1Constants);
34
+ this.signer = (msg: TypedDataDefinition) =>
35
+ this.validatorClient.signWithAddress(this.attestorAddress, msg).then(s => s.toString());
36
+ }
37
+
38
+ /**
39
+ * Enqueues governance and slashing votes with the publisher.
40
+ * Returns a tuple of promises that resolve to whether each vote was successfully enqueued.
41
+ */
42
+ enqueueVotes(): [Promise<boolean | undefined>, Promise<boolean | undefined>] {
43
+ try {
44
+ const enqueueGovernancePromise = this.enqueueGovernanceVote();
45
+ const enqueueSlashingPromise = this.enqueueSlashingVote();
46
+
47
+ return [enqueueGovernancePromise, enqueueSlashingPromise];
48
+ } catch (err) {
49
+ this.log.error(`Error enqueueing governance and slashing votes`, err);
50
+ return [Promise.resolve(false), Promise.resolve(false)];
51
+ }
52
+ }
53
+
54
+ private async enqueueGovernanceVote(): Promise<boolean | undefined> {
55
+ const governanceProposerPayload = this.config.governanceProposerPayload;
56
+ if (!governanceProposerPayload || governanceProposerPayload.isZero()) {
57
+ return undefined;
58
+ }
59
+
60
+ this.log.info(`Enqueuing vote for ${governanceProposerPayload} governance for slot ${this.slot}`, {
61
+ slot: this.slot,
62
+ governanceProposerPayload: governanceProposerPayload.toString(),
63
+ });
64
+
65
+ try {
66
+ return await this.publisher.enqueueGovernanceCastSignal(
67
+ governanceProposerPayload,
68
+ this.slot,
69
+ this.slotTimestamp,
70
+ this.attestorAddress,
71
+ this.signer,
72
+ );
73
+ } catch (err) {
74
+ this.log.error(`Error enqueuing governance vote`, err, { slot: this.slot });
75
+ return false;
76
+ }
77
+ }
78
+
79
+ private async enqueueSlashingVote(): Promise<boolean | undefined> {
80
+ try {
81
+ const actions = await this.slasherClient?.getProposerActions(this.slot);
82
+ if (!actions || actions.length === 0) {
83
+ return undefined;
84
+ }
85
+
86
+ this.log.info(`Enqueuing vote for ${actions.length} slashing actions for slot ${this.slot}`, {
87
+ slot: this.slot,
88
+ actionCount: actions.length,
89
+ });
90
+
91
+ this.metrics.recordSlashingAttempt(actions.length);
92
+
93
+ return await this.publisher.enqueueSlashingActions(
94
+ actions,
95
+ this.slot,
96
+ this.slotTimestamp,
97
+ this.attestorAddress,
98
+ this.signer,
99
+ );
100
+ } catch (err) {
101
+ this.log.error(`Error enqueuing slashing vote`, err, { slot: this.slot });
102
+ return false;
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,27 @@
1
+ import type { BlockNumber, CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
+
3
+ import type { Action } from '../publisher/sequencer-publisher.js';
4
+ import type { SequencerState } from './utils.js';
5
+
6
+ export type SequencerEvents = {
7
+ ['state-changed']: (args: {
8
+ oldState: SequencerState;
9
+ newState: SequencerState;
10
+ secondsIntoSlot?: number;
11
+ slot?: SlotNumber;
12
+ }) => void;
13
+ ['proposer-rollup-check-failed']: (args: { reason: string; slot: SlotNumber }) => void;
14
+ ['block-tx-count-check-failed']: (args: { minTxs: number; availableTxs: number; slot: SlotNumber }) => void;
15
+ ['block-build-failed']: (args: { reason: string; slot: SlotNumber }) => void;
16
+ ['block-proposed']: (args: { blockNumber: BlockNumber; slot: SlotNumber }) => void;
17
+ ['checkpoint-empty']: (args: { slot: SlotNumber }) => void;
18
+ ['checkpoint-publish-failed']: (args: {
19
+ slot: SlotNumber;
20
+ successfulActions?: Action[];
21
+ failedActions?: Action[];
22
+ sentActions?: Action[];
23
+ expiredActions?: Action[];
24
+ }) => void;
25
+ ['checkpoint-published']: (args: { checkpoint: CheckpointNumber; slot: SlotNumber }) => void;
26
+ ['checkpoint-error']: (args: { error: Error }) => void;
27
+ };
@@ -1,3 +1,7 @@
1
1
  export * from './block_builder.js';
2
+ export * from './checkpoint_builder.js';
3
+ export * from './checkpoint_proposal_job.js';
4
+ export * from './checkpoint_voter.js';
2
5
  export * from './config.js';
6
+ export * from './events.js';
3
7
  export * from './sequencer.js';
@@ -1,5 +1,6 @@
1
1
  import { EthAddress } from '@aztec/aztec.js/addresses';
2
2
  import type { RollupContract } from '@aztec/ethereum/contracts';
3
+ import type { L1FeeAnalysisResult } from '@aztec/ethereum/l1-fee-analysis';
3
4
  import type { SlotNumber } from '@aztec/foundation/branded-types';
4
5
  import {
5
6
  Attributes,
@@ -17,6 +18,7 @@ import { type Hex, formatUnits } from 'viem';
17
18
 
18
19
  import type { SequencerState } from './utils.js';
19
20
 
21
+ // TODO(palla/mbps): Review all metrics and add any missing ones per checkpoint
20
22
  export class SequencerMetrics {
21
23
  public readonly tracer: Tracer;
22
24
  private meter: Meter;
@@ -40,7 +42,21 @@ export class SequencerMetrics {
40
42
  private blockProposalFailed: UpDownCounter;
41
43
  private blockProposalSuccess: UpDownCounter;
42
44
  private blockProposalPrecheckFailed: UpDownCounter;
45
+ private checkpointSuccess: UpDownCounter;
43
46
  private slashingAttempts: UpDownCounter;
47
+ private blockAttestationDelay: Histogram;
48
+
49
+ // Fisherman fee analysis metrics
50
+ private fishermanWouldBeIncluded: UpDownCounter;
51
+ private fishermanTimeBeforeBlock: Histogram;
52
+ private fishermanPendingBlobTxCount: Histogram;
53
+ private fishermanIncludedBlobTxCount: Histogram;
54
+ private fishermanCalculatedPriorityFee: Histogram;
55
+ private fishermanPriorityFeeDelta: Histogram;
56
+ private fishermanEstimatedCost: Histogram;
57
+ private fishermanEstimatedOverpayment: Histogram;
58
+ private fishermanMinedBlobTxPriorityFee: Histogram;
59
+ private fishermanMinedBlobTxTotalCost: Histogram;
44
60
 
45
61
  private lastSeenSlot?: SlotNumber;
46
62
 
@@ -76,6 +92,12 @@ export class SequencerMetrics {
76
92
  },
77
93
  );
78
94
 
95
+ this.blockAttestationDelay = this.meter.createHistogram(Metrics.SEQUENCER_BLOCK_ATTESTATION_DELAY, {
96
+ unit: 'ms',
97
+ description: 'The time difference between block proposal and minimal attestation count reached,',
98
+ valueType: ValueType.INT,
99
+ });
100
+
79
101
  // Init gauges and counters
80
102
  this.blockCounter.add(0, {
81
103
  [Attributes.STATUS]: 'failed',
@@ -138,6 +160,11 @@ export class SequencerMetrics {
138
160
  description: 'The number of times block proposal succeeded (including validation builds)',
139
161
  });
140
162
 
163
+ this.checkpointSuccess = this.meter.createUpDownCounter(Metrics.SEQUENCER_CHECKPOINT_SUCCESS_COUNT, {
164
+ valueType: ValueType.INT,
165
+ description: 'The number of times checkpoint publishing succeeded',
166
+ });
167
+
141
168
  this.blockProposalPrecheckFailed = this.meter.createUpDownCounter(
142
169
  Metrics.SEQUENCER_BLOCK_PROPOSAL_PRECHECK_FAILED_COUNT,
143
170
  {
@@ -150,6 +177,82 @@ export class SequencerMetrics {
150
177
  valueType: ValueType.INT,
151
178
  description: 'The number of slashing action attempts',
152
179
  });
180
+
181
+ // Fisherman fee analysis metrics
182
+ this.fishermanWouldBeIncluded = this.meter.createUpDownCounter(Metrics.FISHERMAN_FEE_ANALYSIS_WOULD_BE_INCLUDED, {
183
+ valueType: ValueType.INT,
184
+ description: 'Whether the transaction would have been included in the block',
185
+ });
186
+
187
+ this.fishermanTimeBeforeBlock = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_TIME_BEFORE_BLOCK, {
188
+ unit: 'ms',
189
+ description: 'Time in ms between fee analysis and block being mined',
190
+ valueType: ValueType.INT,
191
+ });
192
+
193
+ this.fishermanPendingBlobTxCount = this.meter.createHistogram(
194
+ Metrics.FISHERMAN_FEE_ANALYSIS_PENDING_BLOB_TX_COUNT,
195
+ {
196
+ description: 'Number of blob transactions seen in the pending block',
197
+ valueType: ValueType.INT,
198
+ },
199
+ );
200
+
201
+ this.fishermanIncludedBlobTxCount = this.meter.createHistogram(
202
+ Metrics.FISHERMAN_FEE_ANALYSIS_INCLUDED_BLOB_TX_COUNT,
203
+ {
204
+ description: 'Number of blob transactions that got included in the mined block',
205
+ valueType: ValueType.INT,
206
+ },
207
+ );
208
+
209
+ this.fishermanCalculatedPriorityFee = this.meter.createHistogram(
210
+ Metrics.FISHERMAN_FEE_ANALYSIS_CALCULATED_PRIORITY_FEE,
211
+ {
212
+ unit: 'gwei',
213
+ description: 'Priority fee calculated by each strategy',
214
+ valueType: ValueType.DOUBLE,
215
+ },
216
+ );
217
+
218
+ this.fishermanPriorityFeeDelta = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_PRIORITY_FEE_DELTA, {
219
+ unit: 'gwei',
220
+ description: 'Difference between our priority fee and minimum included priority fee',
221
+ valueType: ValueType.DOUBLE,
222
+ });
223
+
224
+ this.fishermanEstimatedCost = this.meter.createHistogram(Metrics.FISHERMAN_FEE_ANALYSIS_ESTIMATED_COST, {
225
+ unit: 'eth',
226
+ description: 'Estimated total cost in ETH for the transaction with this strategy',
227
+ valueType: ValueType.DOUBLE,
228
+ });
229
+
230
+ this.fishermanEstimatedOverpayment = this.meter.createHistogram(
231
+ Metrics.FISHERMAN_FEE_ANALYSIS_ESTIMATED_OVERPAYMENT,
232
+ {
233
+ unit: 'eth',
234
+ description: 'Estimated overpayment in ETH vs minimum required for inclusion',
235
+ valueType: ValueType.DOUBLE,
236
+ },
237
+ );
238
+
239
+ this.fishermanMinedBlobTxPriorityFee = this.meter.createHistogram(
240
+ Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_PRIORITY_FEE,
241
+ {
242
+ unit: 'gwei',
243
+ description: 'Priority fee per gas for blob transactions in mined blocks',
244
+ valueType: ValueType.DOUBLE,
245
+ },
246
+ );
247
+
248
+ this.fishermanMinedBlobTxTotalCost = this.meter.createHistogram(
249
+ Metrics.FISHERMAN_FEE_ANALYSIS_MINED_BLOB_TX_TOTAL_COST,
250
+ {
251
+ unit: 'eth',
252
+ description: 'Total cost in ETH for blob transactions in mined blocks',
253
+ valueType: ValueType.DOUBLE,
254
+ },
255
+ );
153
256
  }
154
257
 
155
258
  public recordRequiredAttestations(requiredAttestationsCount: number, allowanceMs: number) {
@@ -161,6 +264,10 @@ export class SequencerMetrics {
161
264
  this.timeToCollectAttestations.record(0);
162
265
  }
163
266
 
267
+ public recordBlockAttestationDelay(duration: number) {
268
+ this.blockAttestationDelay.record(duration);
269
+ }
270
+
164
271
  public recordCollectedAttestations(count: number, durationMs: number) {
165
272
  this.collectedAttestions.record(count);
166
273
  this.timeToCollectAttestations.record(Math.ceil(durationMs));
@@ -218,6 +325,10 @@ export class SequencerMetrics {
218
325
  }
219
326
  }
220
327
 
328
+ recordCheckpointSuccess() {
329
+ this.checkpointSuccess.add(1);
330
+ }
331
+
221
332
  recordBlockProposalFailed(reason?: string) {
222
333
  this.blockProposalFailed.add(1, {
223
334
  ...(reason && { [Attributes.ERROR_TYPE]: reason }),
@@ -237,4 +348,95 @@ export class SequencerMetrics {
237
348
  recordSlashingAttempt(actionCount: number) {
238
349
  this.slashingAttempts.add(actionCount);
239
350
  }
351
+
352
+ /**
353
+ * Records metrics for a completed fisherman fee analysis
354
+ * @param analysis - The completed fee analysis result
355
+ */
356
+ recordFishermanFeeAnalysis(analysis: L1FeeAnalysisResult) {
357
+ // In fisherman mode, we should always have strategy results
358
+ if (!analysis.computedPrices.strategyResults || analysis.computedPrices.strategyResults.length === 0) {
359
+ // This should never happen in fisherman mode - log an error
360
+ // We don't record metrics without strategy IDs as that defeats the purpose
361
+ throw new Error(
362
+ `No strategy results found in fisherman fee analysis ${analysis.id}. This indicates a bug in the fee analysis.`,
363
+ );
364
+ }
365
+
366
+ // Record metrics for each strategy separately
367
+ for (const strategyResult of analysis.computedPrices.strategyResults) {
368
+ const strategyAttributes = {
369
+ [Attributes.FISHERMAN_FEE_STRATEGY_ID]: strategyResult.strategyId,
370
+ };
371
+
372
+ // Record pending block snapshot data (once per strategy for comparison)
373
+ this.fishermanPendingBlobTxCount.record(analysis.pendingSnapshot.pendingBlobTxCount, strategyAttributes);
374
+
375
+ // Record mined block data if available
376
+ if (analysis.minedBlock) {
377
+ this.fishermanIncludedBlobTxCount.record(analysis.minedBlock.includedBlobTxCount, strategyAttributes);
378
+
379
+ // Record actual fees from blob transactions in the mined block
380
+ for (const blobTx of analysis.minedBlock.includedBlobTxs) {
381
+ // Record priority fee per gas in Gwei
382
+ const priorityFeeGwei = Number(blobTx.maxPriorityFeePerGas) / 1e9;
383
+ this.fishermanMinedBlobTxPriorityFee.record(priorityFeeGwei, strategyAttributes);
384
+
385
+ // Calculate total cost in ETH
386
+ // Cost = (gas * (baseFee + priorityFee)) + (blobCount * blobGasPerBlob * blobBaseFee)
387
+ const baseFee = analysis.minedBlock.baseFeePerGas;
388
+ const effectiveGasPrice = baseFee + blobTx.maxPriorityFeePerGas;
389
+
390
+ // Calculate execution cost using actual gas limit from the transaction
391
+ const executionCost = blobTx.gas * effectiveGasPrice;
392
+
393
+ // Calculate blob cost using maxFeePerBlobGas * blobCount * GAS_PER_BLOB
394
+ const blobCost = blobTx.maxFeePerBlobGas * BigInt(blobTx.blobCount) * 131072n; // 128KB per blob
395
+
396
+ const totalCostWei = executionCost + blobCost;
397
+ const totalCostEth = Number(totalCostWei) / 1e18;
398
+
399
+ this.fishermanMinedBlobTxTotalCost.record(totalCostEth, strategyAttributes);
400
+ }
401
+ }
402
+
403
+ // Record the calculated priority fee for this strategy
404
+ const calculatedPriorityFeeGwei = Number(strategyResult.calculatedPriorityFee) / 1e9;
405
+ this.fishermanCalculatedPriorityFee.record(calculatedPriorityFeeGwei, strategyAttributes);
406
+
407
+ // Record analysis results if available
408
+ if (analysis.analysis) {
409
+ this.fishermanTimeBeforeBlock.record(Math.ceil(analysis.analysis.timeBeforeBlockMs), strategyAttributes);
410
+
411
+ // Record strategy-specific inclusion result
412
+ if (strategyResult.wouldBeIncluded !== undefined) {
413
+ if (strategyResult.wouldBeIncluded) {
414
+ this.fishermanWouldBeIncluded.add(1, { ...strategyAttributes, [Attributes.OK]: true });
415
+ } else {
416
+ this.fishermanWouldBeIncluded.add(1, {
417
+ ...strategyAttributes,
418
+ [Attributes.OK]: false,
419
+ ...(strategyResult.exclusionReason && { [Attributes.ERROR_TYPE]: strategyResult.exclusionReason }),
420
+ });
421
+ }
422
+ }
423
+
424
+ // Record strategy-specific priority fee delta
425
+ if (strategyResult.priorityFeeDelta !== undefined) {
426
+ const priorityFeeDeltaGwei = Number(strategyResult.priorityFeeDelta) / 1e9;
427
+ this.fishermanPriorityFeeDelta.record(priorityFeeDeltaGwei, strategyAttributes);
428
+ }
429
+
430
+ // Record estimated cost if available
431
+ if (strategyResult.estimatedCostEth !== undefined) {
432
+ this.fishermanEstimatedCost.record(strategyResult.estimatedCostEth, strategyAttributes);
433
+ }
434
+
435
+ // Record estimated overpayment if available
436
+ if (strategyResult.estimatedOverpaymentEth !== undefined) {
437
+ this.fishermanEstimatedOverpayment.record(strategyResult.estimatedOverpaymentEth, strategyAttributes);
438
+ }
439
+ }
440
+ }
441
+ }
240
442
  }