@pythnetwork/price-pusher 9.1.0 → 9.2.0

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 (58) hide show
  1. package/README.md +115 -0
  2. package/lib/aptos/balance-tracker.d.ts +46 -0
  3. package/lib/aptos/balance-tracker.d.ts.map +1 -0
  4. package/lib/aptos/balance-tracker.js +61 -0
  5. package/lib/aptos/command.d.ts +2 -0
  6. package/lib/aptos/command.d.ts.map +1 -1
  7. package/lib/aptos/command.js +46 -9
  8. package/lib/controller.d.ts +3 -0
  9. package/lib/controller.d.ts.map +1 -1
  10. package/lib/controller.js +37 -1
  11. package/lib/evm/balance-tracker.d.ts +42 -0
  12. package/lib/evm/balance-tracker.d.ts.map +1 -0
  13. package/lib/evm/balance-tracker.js +49 -0
  14. package/lib/evm/command.d.ts +2 -0
  15. package/lib/evm/command.d.ts.map +1 -1
  16. package/lib/evm/command.js +47 -10
  17. package/lib/evm/pyth-contract.d.ts.map +1 -1
  18. package/lib/evm/super-wallet.d.ts.map +1 -1
  19. package/lib/evm/super-wallet.js +17 -7
  20. package/lib/fuel/command.js +17 -7
  21. package/lib/index.js +3 -0
  22. package/lib/injective/command.d.ts +1 -0
  23. package/lib/injective/command.d.ts.map +1 -1
  24. package/lib/injective/command.js +26 -10
  25. package/lib/injective/injective.d.ts +2 -0
  26. package/lib/injective/injective.d.ts.map +1 -1
  27. package/lib/injective/injective.js +22 -7
  28. package/lib/interface.d.ts +51 -0
  29. package/lib/interface.d.ts.map +1 -1
  30. package/lib/interface.js +80 -1
  31. package/lib/metrics.d.ts +22 -0
  32. package/lib/metrics.d.ts.map +1 -0
  33. package/lib/metrics.js +113 -0
  34. package/lib/near/command.js +17 -7
  35. package/lib/options.d.ts +6 -0
  36. package/lib/options.d.ts.map +1 -1
  37. package/lib/options.js +17 -1
  38. package/lib/price-config.d.ts.map +1 -1
  39. package/lib/price-config.js +5 -1
  40. package/lib/solana/balance-tracker.d.ts +42 -0
  41. package/lib/solana/balance-tracker.d.ts.map +1 -0
  42. package/lib/solana/balance-tracker.js +51 -0
  43. package/lib/solana/command.d.ts +2 -0
  44. package/lib/solana/command.d.ts.map +1 -1
  45. package/lib/solana/command.js +48 -10
  46. package/lib/solana/solana.d.ts.map +1 -1
  47. package/lib/solana/solana.js +1 -1
  48. package/lib/sui/balance-tracker.d.ts +39 -0
  49. package/lib/sui/balance-tracker.d.ts.map +1 -0
  50. package/lib/sui/balance-tracker.js +54 -0
  51. package/lib/sui/command.d.ts +2 -0
  52. package/lib/sui/command.d.ts.map +1 -1
  53. package/lib/sui/command.js +50 -12
  54. package/lib/sui/sui.d.ts.map +1 -1
  55. package/lib/ton/command.js +17 -7
  56. package/lib/utils.d.ts +2 -2
  57. package/lib/utils.d.ts.map +1 -1
  58. package/package.json +16 -9
package/README.md CHANGED
@@ -114,6 +114,7 @@ pnpm run start injective --grpc-endpoint https://grpc-endpoint.com \
114
114
  --network testnet \
115
115
  [--gas-price 160000000] \
116
116
  [--gas-multiplier 1.1] \
117
+ [--priceIds-process-chunk-size 100] \
117
118
  [--pushing-frequency 10] \
118
119
  [--polling-frequency 5]
119
120
 
@@ -259,3 +260,117 @@ pushed twice and you won't pay additional costs most of the time.** However, the
259
260
  conditions in the RPCs because they are often behind a load balancer which can sometimes cause rejected
260
261
  transactions to land on-chain. You can reduce the chances of additional cost overhead by reducing the
261
262
  pushing frequency.
263
+
264
+ ## Prometheus Metrics
265
+
266
+ The price_pusher now supports Prometheus metrics to monitor the health and performance of the price update service. Metrics are exposed via an HTTP endpoint that can be scraped by Prometheus.
267
+
268
+ ### Available Metrics
269
+
270
+ The following metrics are available:
271
+
272
+ - **pyth_price_last_published_time** (Gauge): The last published time of a price feed in unix timestamp, labeled by price_id and alias
273
+ - **pyth_price_update_attempts_total** (Counter): Total number of price update attempts with their trigger condition and status, labeled by price_id, alias, trigger, and status
274
+ - **pyth_price_feeds_total** (Gauge): Total number of price feeds being monitored
275
+ - **pyth_wallet_balance** (Gauge): Current wallet balance of the price pusher in native token units, labeled by wallet_address and network
276
+
277
+ ### Configuration
278
+
279
+ Metrics are enabled by default and can be configured using the following command-line options:
280
+
281
+ - `--enable-metrics`: Enable or disable the Prometheus metrics server (default: true)
282
+ - `--metrics-port`: Port for the Prometheus metrics server (default: 9090)
283
+
284
+ Example:
285
+
286
+ ```bash
287
+ pnpm run dev evm --config config.evm.mainnet.json --metrics-port 9091
288
+ ```
289
+
290
+ ### Running Locally with Docker
291
+
292
+ You can run the monitoring stack (Prometheus and Grafana) using the provided docker-compose configuration:
293
+
294
+ 1. Use the sample docker-compose file for metrics:
295
+
296
+ ```bash
297
+ docker-compose -f docker-compose.metrics.sample.yaml up
298
+ ```
299
+
300
+ This will start:
301
+ - Prometheus server on port 9090 with the alerts configured in alerts.sample.yml
302
+ - Grafana server on port 3000 with default credentials (admin/admin)
303
+
304
+ The docker-compose.metrics.sample.yaml file includes a pre-configured Grafana dashboard (see the [Dashboard](#dashboard) section below) that displays all the metrics mentioned above. This dashboard provides monitoring of your price pusher operations with panels for configured feeds, active feeds, wallet balance, update statistics, and error tracking. The dashboard is automatically provisioned when you start the stack with docker-compose.
305
+
306
+ ### Example Grafana Queries
307
+
308
+ Here are some example Grafana queries to monitor your price feeds:
309
+
310
+ 1. Last published time for each price feed:
311
+
312
+ ```
313
+ pyth_price_last_published_time
314
+ ```
315
+
316
+ 2. Number of price updates in the last hour:
317
+
318
+ ```
319
+ sum(increase(pyth_price_update_attempts_total{status="success"}[1h]))
320
+ ```
321
+
322
+ 3. Price feeds not updated in the last hour:
323
+
324
+ ```
325
+ time() - pyth_price_last_published_time > 3600
326
+ ```
327
+
328
+ 4. Distribution of update conditions:
329
+
330
+ ```
331
+ sum by (condition) (increase(pyth_update_conditions_total[$__range]))
332
+ ```
333
+
334
+ 5. Monitor wallet balances:
335
+
336
+ ```
337
+ pyth_wallet_balance
338
+ ```
339
+
340
+ 6. Detect low wallet balances (below 0.1 tokens):
341
+
342
+ ```
343
+ pyth_wallet_balance < 0.1
344
+ ```
345
+
346
+ ### Dashboard
347
+
348
+ The docker-compose setup includes a pre-configured Grafana dashboard (`grafana-dashboard.sample.json`) that provides monitoring of your price pusher operations. The dashboard includes the following panels:
349
+
350
+ - **Configured Price Feeds**: Shows the number of price feeds configured in your price-config file.
351
+ - **Active Price Feeds**: Displays the number of price feeds currently being actively monitored.
352
+ - **Time Since Last Update**: Shows how long it's been since the last successful price update was published on-chain.
353
+ - **Price Feeds List**: A table listing all configured price feeds with their details.
354
+ - **Successful Updates (Current Range)**: Graph showing the number of successful price updates over the current range with timeline.
355
+ - **Update Conditions Distribution**: Pie chart showing the distribution of update conditions (YES/NO/EARLY) over the selected time range.
356
+ - **Wallet Balance**: Current balance of your wallet in native token units.
357
+ - **Wallet Balance Over Time**: Graph tracking your wallet balance over time to monitor consumption.
358
+ - **Failed Updates (Current Range)**: Graph showing the number of failed price updates over the current range with timeline.
359
+
360
+ When you first start the monitoring stack, the dashboard may show "No data" in the panels until the price pusher has been running for some time and has collected sufficient metrics.
361
+
362
+ This dashboard is automatically provisioned when you start the docker-compose stack and provides visibility into the health and performance of your price pusher deployment.
363
+
364
+ ### Alerting
365
+
366
+ The price pusher includes pre-configured Prometheus alerting rules in the `alerts.sample.yml` file. These rules monitor various aspects of the price pusher's operation, including:
367
+
368
+ - Price feeds not being updated for an extended period (>1 hour)
369
+ - High error rates in price update attempts
370
+ - No successful price updates across all feeds in the last 30 minutes
371
+ - Service availability monitoring
372
+ - Low wallet balances with two severity levels:
373
+ - Warning: Balance below 0.1 native tokens
374
+ - Critical: Balance below 0.01 native tokens (transactions may fail soon)
375
+
376
+ When using the docker-compose setup, these alerts are automatically loaded into Prometheus and can be viewed in the Alerting section of Grafana after setting up the Prometheus data source.
@@ -0,0 +1,46 @@
1
+ import { BaseBalanceTracker, BaseBalanceTrackerConfig, IBalanceTracker } from "../interface";
2
+ import { DurationInSeconds } from "../utils";
3
+ import { PricePusherMetrics } from "../metrics";
4
+ import { Logger } from "pino";
5
+ /**
6
+ * Aptos-specific configuration for balance tracker
7
+ */
8
+ export interface AptosBalanceTrackerConfig extends BaseBalanceTrackerConfig {
9
+ /** Aptos node endpoint URL */
10
+ endpoint: string;
11
+ /** Aptos account address */
12
+ address: string;
13
+ /** Optional decimal places for APT token (default: 8) */
14
+ decimals?: number;
15
+ }
16
+ /**
17
+ * Aptos-specific implementation of the balance tracker
18
+ */
19
+ export declare class AptosBalanceTracker extends BaseBalanceTracker {
20
+ private client;
21
+ private aptosAddress;
22
+ private decimals;
23
+ constructor(config: AptosBalanceTrackerConfig);
24
+ /**
25
+ * Aptos-specific implementation of balance update
26
+ * Fetches the native APT balance for the configured address
27
+ */
28
+ protected updateBalance(): Promise<void>;
29
+ }
30
+ /**
31
+ * Parameters for creating an Aptos balance tracker
32
+ */
33
+ export interface CreateAptosBalanceTrackerParams {
34
+ endpoint: string;
35
+ address: string;
36
+ network: string;
37
+ updateInterval: DurationInSeconds;
38
+ metrics: PricePusherMetrics;
39
+ logger: Logger;
40
+ decimals?: number;
41
+ }
42
+ /**
43
+ * Factory function to create a balance tracker for Aptos chain
44
+ */
45
+ export declare function createAptosBalanceTracker(params: CreateAptosBalanceTrackerParams): IBalanceTracker;
46
+ //# sourceMappingURL=balance-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balance-tracker.d.ts","sourceRoot":"","sources":["../../src/aptos/balance-tracker.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,eAAe,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B;;GAEG;AACH,MAAM,WAAW,yBAA0B,SAAQ,wBAAwB;IACzE,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,kBAAkB;IACzD,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,yBAAyB;IAY7C;;;OAGG;cACa,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAkC/C;AAED;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,iBAAiB,CAAC;IAClC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,+BAA+B,GACtC,eAAe,CAUjB"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AptosBalanceTracker = void 0;
4
+ exports.createAptosBalanceTracker = createAptosBalanceTracker;
5
+ const aptos_1 = require("aptos");
6
+ const interface_1 = require("../interface");
7
+ /**
8
+ * Aptos-specific implementation of the balance tracker
9
+ */
10
+ class AptosBalanceTracker extends interface_1.BaseBalanceTracker {
11
+ client;
12
+ aptosAddress;
13
+ decimals;
14
+ constructor(config) {
15
+ super({
16
+ ...config,
17
+ logger: config.logger.child({ module: "AptosBalanceTracker" }),
18
+ });
19
+ this.client = new aptos_1.AptosClient(config.endpoint);
20
+ this.aptosAddress = config.address;
21
+ // APT has 8 decimal places by default
22
+ this.decimals = config.decimals ?? 8;
23
+ }
24
+ /**
25
+ * Aptos-specific implementation of balance update
26
+ * Fetches the native APT balance for the configured address
27
+ */
28
+ async updateBalance() {
29
+ try {
30
+ // Get account resource to check the balance
31
+ const accountResource = await this.client.getAccountResource(this.aptosAddress, "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>");
32
+ // Extract the balance value from the account resource
33
+ const rawBalance = accountResource.data.coin.value;
34
+ // Convert the balance to a bigint
35
+ const balance = BigInt(rawBalance);
36
+ // Calculate the normalized balance for display
37
+ const normalizedBalance = Number(balance) / Math.pow(10, this.decimals);
38
+ // Update metrics with the new balance
39
+ this.metrics.updateWalletBalance(this.address, this.network, normalizedBalance);
40
+ this.logger.debug(`Updated Aptos wallet balance: ${this.address} = ${normalizedBalance.toString()} APT (raw: ${balance.toString()})`);
41
+ }
42
+ catch (error) {
43
+ this.logger.error({ error }, "Error fetching Aptos wallet balance for metrics");
44
+ }
45
+ }
46
+ }
47
+ exports.AptosBalanceTracker = AptosBalanceTracker;
48
+ /**
49
+ * Factory function to create a balance tracker for Aptos chain
50
+ */
51
+ function createAptosBalanceTracker(params) {
52
+ return new AptosBalanceTracker({
53
+ endpoint: params.endpoint,
54
+ address: params.address,
55
+ network: params.network,
56
+ updateInterval: params.updateInterval,
57
+ metrics: params.metrics,
58
+ logger: params.logger,
59
+ decimals: params.decimals,
60
+ });
61
+ }
@@ -3,6 +3,8 @@ declare const _default: {
3
3
  command: string;
4
4
  describe: string;
5
5
  builder: {
6
+ "metrics-port": Options;
7
+ "enable-metrics": Options;
6
8
  "controller-log-level": Options;
7
9
  "log-level": Options;
8
10
  "pushing-frequency": Options;
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/aptos/command.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;kBAoBvB,OAAO;yCAOP,OAAO;;oBAUiB,GAAG;;AA5BpC,wBAyGE"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/aptos/command.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;;;kBAuBvB,OAAO;yCAOP,OAAO;;oBAYiB,GAAG;;AA9BpC,wBAuIE"}
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
37
  };
@@ -36,6 +46,8 @@ const aptos_1 = require("./aptos");
36
46
  const aptos_2 = require("aptos");
37
47
  const pino_1 = __importDefault(require("pino"));
38
48
  const utils_1 = require("../utils");
49
+ const metrics_1 = require("../metrics");
50
+ const balance_tracker_1 = require("./balance-tracker");
39
51
  exports.default = {
40
52
  command: "aptos",
41
53
  describe: "run price pusher for aptos",
@@ -61,13 +73,22 @@ exports.default = {
61
73
  ...options.pushingFrequency,
62
74
  ...options.logLevel,
63
75
  ...options.controllerLogLevel,
76
+ ...options.enableMetrics,
77
+ ...options.metricsPort,
64
78
  },
65
79
  handler: async function (argv) {
66
80
  // FIXME: type checks for this
67
- const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pythContractAddress, pushingFrequency, pollingFrequency, overrideGasPriceMultiplier, logLevel, controllerLogLevel, } = argv;
81
+ const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pythContractAddress, pushingFrequency, pollingFrequency, overrideGasPriceMultiplier, logLevel, controllerLogLevel, enableMetrics, metricsPort, } = argv;
68
82
  const logger = (0, pino_1.default)({ level: logLevel });
69
83
  const priceConfigs = (0, price_config_1.readPriceConfigFile)(priceConfigFile);
70
84
  const hermesClient = new hermes_client_1.HermesClient(priceServiceEndpoint);
85
+ // Initialize metrics if enabled
86
+ let metrics;
87
+ if (enableMetrics) {
88
+ metrics = new metrics_1.PricePusherMetrics(logger.child({ module: "Metrics" }));
89
+ metrics.start(metricsPort);
90
+ logger.info(`Metrics server started on port ${metricsPort}`);
91
+ }
71
92
  const mnemonic = fs_1.default.readFileSync(mnemonicFile, "utf-8").trim();
72
93
  const account = aptos_2.AptosAccount.fromDerivePath(aptos_1.APTOS_ACCOUNT_HD_PATH, mnemonic);
73
94
  logger.info(`Pushing from account address: ${account.address()}`);
@@ -83,7 +104,23 @@ exports.default = {
83
104
  const pythListener = new pyth_price_listener_1.PythPriceListener(hermesClient, priceItems, logger.child({ module: "PythPriceListener" }));
84
105
  const aptosListener = new aptos_1.AptosPriceListener(pythContractAddress, endpoint, priceItems, logger.child({ module: "AptosPriceListener" }), { pollingFrequency });
85
106
  const aptosPusher = new aptos_1.AptosPricePusher(hermesClient, logger.child({ module: "AptosPricePusher" }), pythContractAddress, endpoint, mnemonic, overrideGasPriceMultiplier);
86
- const controller = new controller_1.Controller(priceConfigs, pythListener, aptosListener, aptosPusher, logger.child({ module: "Controller" }, { level: controllerLogLevel }), { pushingFrequency });
107
+ const controller = new controller_1.Controller(priceConfigs, pythListener, aptosListener, aptosPusher, logger.child({ module: "Controller" }, { level: controllerLogLevel }), {
108
+ pushingFrequency,
109
+ metrics,
110
+ });
111
+ // Create and start the balance tracker if metrics are enabled
112
+ if (metrics) {
113
+ const balanceTracker = (0, balance_tracker_1.createAptosBalanceTracker)({
114
+ address: account.address().toString(),
115
+ endpoint,
116
+ network: "aptos",
117
+ updateInterval: pushingFrequency,
118
+ metrics,
119
+ logger: logger.child({ module: "AptosBalanceTracker" }),
120
+ });
121
+ // Start the balance tracker
122
+ await balanceTracker.start();
123
+ }
87
124
  controller.start();
88
125
  },
89
126
  };
@@ -2,6 +2,7 @@ import { DurationInSeconds } from "./utils";
2
2
  import { IPriceListener, IPricePusher } from "./interface";
3
3
  import { PriceConfig } from "./price-config";
4
4
  import { Logger } from "pino";
5
+ import { PricePusherMetrics } from "./metrics";
5
6
  export declare class Controller {
6
7
  private priceConfigs;
7
8
  private sourcePriceListener;
@@ -9,8 +10,10 @@ export declare class Controller {
9
10
  private targetChainPricePusher;
10
11
  private logger;
11
12
  private pushingFrequency;
13
+ private metrics?;
12
14
  constructor(priceConfigs: PriceConfig[], sourcePriceListener: IPriceListener, targetPriceListener: IPriceListener, targetChainPricePusher: IPricePusher, logger: Logger, config: {
13
15
  pushingFrequency: DurationInSeconds;
16
+ metrics?: PricePusherMetrics;
14
17
  });
15
18
  start(): Promise<void>;
16
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAS,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAiC,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B,qBAAa,UAAU;IAGnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,MAAM;IANhB,OAAO,CAAC,gBAAgB,CAAoB;gBAElC,YAAY,EAAE,WAAW,EAAE,EAC3B,mBAAmB,EAAE,cAAc,EACnC,mBAAmB,EAAE,cAAc,EACnC,sBAAsB,EAAE,YAAY,EACpC,MAAM,EAAE,MAAM,EACtB,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;KACrC;IAKG,KAAK;CAgEZ"}
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAS,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAiC,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,qBAAa,UAAU;IAKnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,sBAAsB;IAC9B,OAAO,CAAC,MAAM;IARhB,OAAO,CAAC,gBAAgB,CAAoB;IAC5C,OAAO,CAAC,OAAO,CAAC,CAAqB;gBAG3B,YAAY,EAAE,WAAW,EAAE,EAC3B,mBAAmB,EAAE,cAAc,EACnC,mBAAmB,EAAE,cAAc,EACnC,sBAAsB,EAAE,YAAY,EACpC,MAAM,EAAE,MAAM,EACtB,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;QACpC,OAAO,CAAC,EAAE,kBAAkB,CAAC;KAC9B;IASG,KAAK;CAqIZ"}
package/lib/controller.js CHANGED
@@ -10,6 +10,7 @@ class Controller {
10
10
  targetChainPricePusher;
11
11
  logger;
12
12
  pushingFrequency;
13
+ metrics;
13
14
  constructor(priceConfigs, sourcePriceListener, targetPriceListener, targetChainPricePusher, logger, config) {
14
15
  this.priceConfigs = priceConfigs;
15
16
  this.sourcePriceListener = sourcePriceListener;
@@ -17,6 +18,9 @@ class Controller {
17
18
  this.targetChainPricePusher = targetChainPricePusher;
18
19
  this.logger = logger;
19
20
  this.pushingFrequency = config.pushingFrequency;
21
+ this.metrics = config.metrics;
22
+ // Set the number of price feeds if metrics are enabled
23
+ this.metrics?.setPriceFeedsTotal(this.priceConfigs.length);
20
24
  }
21
25
  async start() {
22
26
  // start the listeners
@@ -34,9 +38,18 @@ class Controller {
34
38
  const pubTimesToPush = [];
35
39
  for (const priceConfig of this.priceConfigs) {
36
40
  const priceId = priceConfig.id;
41
+ const alias = priceConfig.alias;
37
42
  const targetLatestPrice = this.targetPriceListener.getLatestPriceInfo(priceId);
38
43
  const sourceLatestPrice = this.sourcePriceListener.getLatestPriceInfo(priceId);
44
+ // Update metrics for the last published time if available
45
+ if (this.metrics && targetLatestPrice) {
46
+ this.metrics.updateLastPublishedTime(priceId, alias, targetLatestPrice);
47
+ }
39
48
  const priceShouldUpdate = (0, price_config_1.shouldUpdate)(priceConfig, sourceLatestPrice, targetLatestPrice, this.logger);
49
+ // Record update condition in metrics
50
+ if (this.metrics) {
51
+ this.metrics.recordUpdateCondition(priceId, alias, priceShouldUpdate);
52
+ }
40
53
  if (priceShouldUpdate == price_config_1.UpdateCondition.YES) {
41
54
  pushThresholdMet = true;
42
55
  }
@@ -55,7 +68,30 @@ class Controller {
55
68
  }, "Some of the checks triggered pushing update. Will push the updates for some feeds.");
56
69
  // note that the priceIds are without leading "0x"
57
70
  const priceIds = pricesToPush.map((priceConfig) => priceConfig.id);
58
- this.targetChainPricePusher.updatePriceFeed(priceIds, pubTimesToPush);
71
+ try {
72
+ await this.targetChainPricePusher.updatePriceFeed(priceIds, pubTimesToPush);
73
+ // Record successful updates
74
+ if (this.metrics) {
75
+ for (const config of pricesToPush) {
76
+ const triggerValue = (0, price_config_1.shouldUpdate)(config, this.sourcePriceListener.getLatestPriceInfo(config.id), this.targetPriceListener.getLatestPriceInfo(config.id), this.logger) === price_config_1.UpdateCondition.YES
77
+ ? "yes"
78
+ : "early";
79
+ this.metrics.recordPriceUpdate(config.id, config.alias, triggerValue);
80
+ }
81
+ }
82
+ }
83
+ catch (error) {
84
+ this.logger.error({ error, priceIds }, "Error pushing price updates to chain");
85
+ // Record errors in metrics
86
+ if (this.metrics) {
87
+ for (const config of pricesToPush) {
88
+ const triggerValue = (0, price_config_1.shouldUpdate)(config, this.sourcePriceListener.getLatestPriceInfo(config.id), this.targetPriceListener.getLatestPriceInfo(config.id), this.logger) === price_config_1.UpdateCondition.YES
89
+ ? "yes"
90
+ : "early";
91
+ this.metrics.recordPriceUpdateError(config.id, config.alias, triggerValue);
92
+ }
93
+ }
94
+ }
59
95
  }
60
96
  else {
61
97
  this.logger.info("None of the checks were triggered. No push needed.");
@@ -0,0 +1,42 @@
1
+ import { SuperWalletClient } from "./super-wallet";
2
+ import { BaseBalanceTracker, BaseBalanceTrackerConfig, IBalanceTracker } from "../interface";
3
+ import { DurationInSeconds } from "../utils";
4
+ import { PricePusherMetrics } from "../metrics";
5
+ import { Logger } from "pino";
6
+ /**
7
+ * EVM-specific configuration for balance tracker
8
+ */
9
+ export interface EvmBalanceTrackerConfig extends BaseBalanceTrackerConfig {
10
+ /** EVM wallet client */
11
+ client: SuperWalletClient;
12
+ /** EVM address with 0x prefix */
13
+ address: `0x${string}`;
14
+ }
15
+ /**
16
+ * EVM-specific implementation of the balance tracker
17
+ */
18
+ export declare class EvmBalanceTracker extends BaseBalanceTracker {
19
+ private client;
20
+ private evmAddress;
21
+ constructor(config: EvmBalanceTrackerConfig);
22
+ /**
23
+ * EVM-specific implementation of balance update
24
+ */
25
+ protected updateBalance(): Promise<void>;
26
+ }
27
+ /**
28
+ * Parameters for creating an EVM balance tracker
29
+ */
30
+ export interface CreateEvmBalanceTrackerParams {
31
+ client: SuperWalletClient;
32
+ address: `0x${string}`;
33
+ network: string;
34
+ updateInterval: DurationInSeconds;
35
+ metrics: PricePusherMetrics;
36
+ logger: Logger;
37
+ }
38
+ /**
39
+ * Factory function to create a balance tracker for EVM chains
40
+ */
41
+ export declare function createEvmBalanceTracker(params: CreateEvmBalanceTrackerParams): IBalanceTracker;
42
+ //# sourceMappingURL=balance-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balance-tracker.d.ts","sourceRoot":"","sources":["../../src/evm/balance-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,eAAe,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,wBAAwB;IACvE,wBAAwB;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,iCAAiC;IACjC,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,kBAAkB;IACvD,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,UAAU,CAAgB;gBAEtB,MAAM,EAAE,uBAAuB;IAU3C;;OAEG;cACa,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAiB/C;AAED;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,iBAAiB,CAAC;IAClC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,6BAA6B,GACpC,eAAe,CASjB"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EvmBalanceTracker = void 0;
4
+ exports.createEvmBalanceTracker = createEvmBalanceTracker;
5
+ const interface_1 = require("../interface");
6
+ /**
7
+ * EVM-specific implementation of the balance tracker
8
+ */
9
+ class EvmBalanceTracker extends interface_1.BaseBalanceTracker {
10
+ client;
11
+ evmAddress;
12
+ constructor(config) {
13
+ super({
14
+ ...config,
15
+ logger: config.logger.child({ module: "EvmBalanceTracker" }),
16
+ });
17
+ this.client = config.client;
18
+ this.evmAddress = config.address;
19
+ }
20
+ /**
21
+ * EVM-specific implementation of balance update
22
+ */
23
+ async updateBalance() {
24
+ try {
25
+ const balance = await this.client.getBalance({
26
+ address: this.evmAddress,
27
+ });
28
+ this.metrics.updateWalletBalance(this.address, this.network, balance);
29
+ this.logger.debug(`Updated EVM wallet balance: ${this.address} = ${balance.toString()}`);
30
+ }
31
+ catch (error) {
32
+ this.logger.error({ error }, "Error fetching EVM wallet balance for metrics");
33
+ }
34
+ }
35
+ }
36
+ exports.EvmBalanceTracker = EvmBalanceTracker;
37
+ /**
38
+ * Factory function to create a balance tracker for EVM chains
39
+ */
40
+ function createEvmBalanceTracker(params) {
41
+ return new EvmBalanceTracker({
42
+ client: params.client,
43
+ address: params.address,
44
+ network: params.network,
45
+ updateInterval: params.updateInterval,
46
+ metrics: params.metrics,
47
+ logger: params.logger,
48
+ });
49
+ }
@@ -3,6 +3,8 @@ declare const _default: {
3
3
  command: string;
4
4
  describe: string;
5
5
  builder: {
6
+ "metrics-port": Options;
7
+ "enable-metrics": Options;
6
8
  "controller-log-level": Options;
7
9
  "log-level": Options;
8
10
  "pushing-frequency": Options;
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/evm/command.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;kBAyBvB,OAAO;8BAMP,OAAO;oBAMP,OAAO;yCAUP,OAAO;6CAQP,OAAO;qBAKP,OAAO;qBAKP,OAAO;iCASP,OAAO;;oBAUiB,GAAG;;AAxEpC,wBAgLE"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/evm/command.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;;;kBA2BvB,OAAO;8BAMP,OAAO;oBAMP,OAAO;yCAUP,OAAO;6CAQP,OAAO;qBAKP,OAAO;qBAKP,OAAO;iCASP,OAAO;;oBAYiB,GAAG;;AA1EpC,wBA8ME"}
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
37
  };
@@ -38,6 +48,8 @@ const pino_1 = __importDefault(require("pino"));
38
48
  const super_wallet_1 = require("./super-wallet");
39
49
  const pyth_contract_1 = require("./pyth-contract");
40
50
  const utils_1 = require("../utils");
51
+ const metrics_1 = require("../metrics");
52
+ const balance_tracker_1 = require("./balance-tracker");
41
53
  exports.default = {
42
54
  command: "evm",
43
55
  describe: "run price pusher for evm",
@@ -103,10 +115,12 @@ exports.default = {
103
115
  ...options.pushingFrequency,
104
116
  ...options.logLevel,
105
117
  ...options.controllerLogLevel,
118
+ ...options.enableMetrics,
119
+ ...options.metricsPort,
106
120
  },
107
121
  handler: async function (argv) {
108
122
  // FIXME: type checks for this
109
- const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pythContractAddress, pushingFrequency, pollingFrequency, customGasStation, txSpeed, overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, gasLimit, gasPrice, updateFeeMultiplier, logLevel, controllerLogLevel, } = argv;
123
+ const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pythContractAddress, pushingFrequency, pollingFrequency, customGasStation, txSpeed, overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, gasLimit, gasPrice, updateFeeMultiplier, logLevel, controllerLogLevel, enableMetrics, metricsPort, } = argv;
110
124
  console.log("***** priceServiceEndpoint *****", priceServiceEndpoint);
111
125
  const logger = (0, pino_1.default)({
112
126
  level: logLevel,
@@ -123,6 +137,13 @@ exports.default = {
123
137
  .join(", ")}`);
124
138
  }
125
139
  priceItems = existingPriceItems;
140
+ // Initialize metrics if enabled
141
+ let metrics;
142
+ if (enableMetrics) {
143
+ metrics = new metrics_1.PricePusherMetrics(logger.child({ module: "Metrics" }));
144
+ metrics.start(metricsPort);
145
+ logger.info(`Metrics server started on port ${metricsPort}`);
146
+ }
126
147
  const pythListener = new pyth_price_listener_1.PythPriceListener(hermesClient, priceItems, logger.child({ module: "PythPriceListener" }));
127
148
  const client = await (0, super_wallet_1.createClient)(endpoint, mnemonic);
128
149
  const pythContract = (0, pyth_contract_1.createPythContract)(client, pythContractAddress);
@@ -136,7 +157,23 @@ exports.default = {
136
157
  });
137
158
  const gasStation = (0, custom_gas_station_1.getCustomGasStation)(logger.child({ module: "CustomGasStation" }), customGasStation, txSpeed);
138
159
  const evmPusher = new evm_1.EvmPricePusher(hermesClient, client, pythContract, logger.child({ module: "EvmPricePusher" }), overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, updateFeeMultiplier, gasLimit, gasStation, gasPrice);
139
- const controller = new controller_1.Controller(priceConfigs, pythListener, evmListener, evmPusher, logger.child({ module: "Controller" }, { level: controllerLogLevel }), { pushingFrequency });
140
- controller.start();
160
+ const controller = new controller_1.Controller(priceConfigs, pythListener, evmListener, evmPusher, logger.child({ module: "Controller" }, { level: controllerLogLevel }), {
161
+ pushingFrequency,
162
+ metrics,
163
+ });
164
+ // Create and start the balance tracker if metrics are enabled
165
+ if (metrics) {
166
+ const balanceTracker = (0, balance_tracker_1.createEvmBalanceTracker)({
167
+ client,
168
+ address: client.account.address,
169
+ network: await client.getChainId().then((id) => id.toString()),
170
+ updateInterval: pushingFrequency,
171
+ metrics,
172
+ logger,
173
+ });
174
+ // Start the balance tracker
175
+ await balanceTracker.start();
176
+ }
177
+ await controller.start();
141
178
  },
142
179
  };
@@ -1 +1 @@
1
- {"version":3,"file":"pyth-contract.d.ts","sourceRoot":"","sources":["../../src/evm/pyth-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,OAAO,EAAE,qBAAqB,EAAE,MAAM,MAAM,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,YAAY,GAAG,qBAAqB,CAC9C,OAAO,OAAO,EACd,iBAAiB,CAClB,CAAC;AAEF,eAAO,MAAM,kBAAkB,WACrB,iBAAiB,WAChB,OAAO,KACf,YAKC,CAAC"}
1
+ {"version":3,"file":"pyth-contract.d.ts","sourceRoot":"","sources":["../../src/evm/pyth-contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,OAAO,EAAE,qBAAqB,EAAE,MAAM,MAAM,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,YAAY,GAAG,qBAAqB,CAC9C,OAAO,OAAO,EACd,iBAAiB,CAClB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,iBAAiB,EACzB,SAAS,OAAO,KACf,YAKC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"super-wallet.d.ts","sourceRoot":"","sources":["../../src/evm/super-wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,OAAO,EACP,KAAK,EAEL,MAAM,EACN,SAAS,EACT,aAAa,EACb,aAAa,EAGb,SAAS,EACV,MAAM,MAAM,CAAC;AAmBd,MAAM,MAAM,iBAAiB,GAAG,MAAM,CACpC,SAAS,EACT,KAAK,EACL,OAAO,EACP,SAAS,EACT,aAAa,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CACzE,CAAC;AAcF,eAAO,MAAM,YAAY,aACb,MAAM,YACN,MAAM,KACf,OAAO,CAAC,iBAAiB,CAY3B,CAAC"}
1
+ {"version":3,"file":"super-wallet.d.ts","sourceRoot":"","sources":["../../src/evm/super-wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,OAAO,EACP,KAAK,EAEL,MAAM,EACN,SAAS,EACT,aAAa,EACb,aAAa,EAGb,SAAS,EACV,MAAM,MAAM,CAAC;AAmBd,MAAM,MAAM,iBAAiB,GAAG,MAAM,CACpC,SAAS,EACT,KAAK,EACL,OAAO,EACP,SAAS,EACT,aAAa,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CACzE,CAAC;AAcF,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,OAAO,CAAC,iBAAiB,CAY3B,CAAC"}