@pythnetwork/price-pusher 7.1.0 → 8.0.0-alpha

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.
@@ -20,7 +20,7 @@ declare const _default: {
20
20
  "gas-limit": Options;
21
21
  "update-fee-multiplier": Options;
22
22
  };
23
- handler: (argv: any) => void;
23
+ handler: (argv: any) => Promise<void>;
24
24
  };
25
25
  export default _default;
26
26
  //# sourceMappingURL=command.d.ts.map
@@ -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;;;;;;;;;;;;;;;;;;;;;;oBA6EL,GAAG;;AApE9B,wBA+JE"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/evm/command.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;oBAgFC,GAAG;;AApEpC,wBAkKE"}
@@ -35,6 +35,9 @@ const controller_1 = require("../controller");
35
35
  const evm_1 = require("./evm");
36
36
  const custom_gas_station_1 = require("./custom-gas-station");
37
37
  const pino_1 = __importDefault(require("pino"));
38
+ const super_wallet_1 = require("./super-wallet");
39
+ const pyth_contract_1 = require("./pyth-contract");
40
+ const utils_1 = require("../utils");
38
41
  exports.default = {
39
42
  command: "evm",
40
43
  describe: "run price pusher for evm",
@@ -97,7 +100,7 @@ exports.default = {
97
100
  ...options.priceServiceConnectionLogLevel,
98
101
  ...options.controllerLogLevel,
99
102
  },
100
- handler: function (argv) {
103
+ handler: async function (argv) {
101
104
  // FIXME: type checks for this
102
105
  const { endpoint, priceConfigFile, priceServiceEndpoint, mnemonicFile, pythContractAddress, pushingFrequency, pollingFrequency, customGasStation, txSpeed, overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, gasLimit, updateFeeMultiplier, logLevel, priceServiceConnectionLogLevel, controllerLogLevel, } = argv;
103
106
  const logger = (0, pino_1.default)({ level: logLevel });
@@ -108,15 +111,18 @@ exports.default = {
108
111
  const mnemonic = fs_1.default.readFileSync(mnemonicFile, "utf-8").trim();
109
112
  const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
110
113
  const pythListener = new pyth_price_listener_1.PythPriceListener(priceServiceConnection, priceItems, logger.child({ module: "PythPriceListener" }));
111
- const pythContractFactory = new evm_1.PythContractFactory(endpoint, mnemonic, pythContractAddress);
112
- logger.info(`Pushing updates from wallet address: ${pythContractFactory
113
- .createWeb3PayerProvider()
114
- .getAddress()}`);
115
- const evmListener = new evm_1.EvmPriceListener(pythContractFactory, priceItems, logger.child({ module: "EvmPriceListener" }), {
114
+ const client = await (0, super_wallet_1.createClient)(endpoint, mnemonic);
115
+ const pythContract = (0, pyth_contract_1.createPythContract)(client, pythContractAddress);
116
+ logger.info(`Pushing updates from wallet address: ${client.account.address}`);
117
+ // It is possible to watch the events in the non-ws endpoints, either by getFilter
118
+ // or by getLogs, but it is very expensive and our polling mechanism does it
119
+ // in a more efficient way. So we only do it with ws endpoints.
120
+ const watchEvents = (0, utils_1.isWsEndpoint)(endpoint);
121
+ const evmListener = new evm_1.EvmPriceListener(pythContract, priceItems, watchEvents, logger.child({ module: "EvmPriceListener" }), {
116
122
  pollingFrequency,
117
123
  });
118
124
  const gasStation = (0, custom_gas_station_1.getCustomGasStation)(logger.child({ module: "CustomGasStation" }), customGasStation, txSpeed);
119
- const evmPusher = new evm_1.EvmPricePusher(priceServiceConnection, pythContractFactory, logger.child({ module: "EvmPricePusher" }), overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, updateFeeMultiplier, gasLimit, gasStation);
125
+ const evmPusher = new evm_1.EvmPricePusher(priceServiceConnection, client, pythContract, logger.child({ module: "EvmPricePusher" }), overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, updateFeeMultiplier, gasLimit, gasStation);
120
126
  const controller = new controller_1.Controller(priceConfigs, pythListener, evmListener, evmPusher, logger.child({ module: "Controller" }, { level: controllerLogLevel }), { pushingFrequency });
121
127
  controller.start();
122
128
  },
@@ -5,7 +5,7 @@ export declare class CustomGasStation {
5
5
  private chainMethods;
6
6
  private logger;
7
7
  constructor(logger: Logger, chain: number, speed: string);
8
- getCustomGasPrice(): Promise<string | undefined>;
8
+ getCustomGasPrice(): Promise<bigint | undefined>;
9
9
  private fetchMaticMainnetGasPrice;
10
10
  }
11
11
  export declare function getCustomGasStation(logger: Logger, customGasStation?: number, txSpeed?: string): CustomGasStation | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"custom-gas-station.d.ts","sourceRoot":"","sources":["../../src/evm/custom-gas-station.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAI9B,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,YAAY,CAElB;IACF,OAAO,CAAC,MAAM,CAAS;gBACX,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAMlD,iBAAiB;YAIT,yBAAyB;CAexC;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,gBAAgB,CAAC,EAAE,MAAM,EACzB,OAAO,CAAC,EAAE,MAAM,gCAKjB"}
1
+ {"version":3,"file":"custom-gas-station.d.ts","sourceRoot":"","sources":["../../src/evm/custom-gas-station.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAK9B,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,YAAY,CAElB;IACF,OAAO,CAAC,MAAM,CAAS;gBACX,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAMlD,iBAAiB;YAIT,yBAAyB;CAcxC;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,gBAAgB,CAAC,EAAE,MAAM,EACzB,OAAO,CAAC,EAAE,MAAM,gCAKjB"}
@@ -1,11 +1,8 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.getCustomGasStation = exports.CustomGasStation = void 0;
7
- const web3_1 = __importDefault(require("web3"));
8
4
  const utils_1 = require("../utils");
5
+ const viem_1 = require("viem");
9
6
  class CustomGasStation {
10
7
  chain;
11
8
  speed;
@@ -23,11 +20,10 @@ class CustomGasStation {
23
20
  }
24
21
  async fetchMaticMainnetGasPrice() {
25
22
  try {
26
- const res = await fetch("https://gasstation-mainnet.matic.network/v2");
23
+ const res = await fetch("https://gasstation.polygon.technology/v2");
27
24
  const jsonRes = await res.json();
28
25
  const gasPrice = jsonRes[this.speed].maxFee;
29
- const gweiGasPrice = web3_1.default.utils.toWei(gasPrice.toFixed(2), "Gwei");
30
- return gweiGasPrice.toString();
26
+ return (0, viem_1.parseGwei)(gasPrice.toFixed(2));
31
27
  }
32
28
  catch (err) {
33
29
  this.logger.error(err, "Failed to fetch gas price from Matic mainnet. Returning undefined");
package/lib/evm/evm.d.ts CHANGED
@@ -1,61 +1,37 @@
1
- import { Contract } from "web3-eth-contract";
2
1
  import { IPricePusher, PriceInfo, ChainPriceListener, PriceItem } from "../interface";
3
2
  import { DurationInSeconds } from "../utils";
4
- import HDWalletProvider from "@truffle/hdwallet-provider";
5
- import { HttpProvider, WebsocketProvider } from "web3-core";
6
3
  import { Logger } from "pino";
7
4
  import { PriceServiceConnection, HexString, UnixTimestamp } from "@pythnetwork/price-service-client";
8
5
  import { CustomGasStation } from "./custom-gas-station";
6
+ import { PythContract } from "./pyth-contract";
7
+ import { SuperWalletClient } from "./super-wallet";
9
8
  export declare class EvmPriceListener extends ChainPriceListener {
10
- private pythContractFactory;
11
9
  private pythContract;
10
+ private watchEvents;
12
11
  private logger;
13
- constructor(pythContractFactory: PythContractFactory, priceItems: PriceItem[], logger: Logger, config: {
12
+ constructor(pythContract: PythContract, priceItems: PriceItem[], watchEvents: boolean, logger: Logger, config: {
14
13
  pollingFrequency: DurationInSeconds;
15
14
  });
16
15
  start(): Promise<void>;
17
- private startSubscription;
16
+ private startWatching;
18
17
  private onPriceFeedUpdate;
19
18
  getOnChainPriceInfo(priceId: HexString): Promise<PriceInfo | undefined>;
20
19
  }
21
20
  export declare class EvmPricePusher implements IPricePusher {
22
21
  private connection;
22
+ private client;
23
+ private pythContract;
23
24
  private logger;
24
25
  private overrideGasPriceMultiplier;
25
26
  private overrideGasPriceMultiplierCap;
26
27
  private updateFeeMultiplier;
27
28
  private gasLimit?;
28
29
  private customGasStation?;
29
- private pythContract;
30
- private web3;
31
30
  private pusherAddress;
32
31
  private lastPushAttempt;
33
- constructor(connection: PriceServiceConnection, pythContractFactory: PythContractFactory, logger: Logger, overrideGasPriceMultiplier: number, overrideGasPriceMultiplierCap: number, updateFeeMultiplier: number, gasLimit?: number | undefined, customGasStation?: CustomGasStation);
32
+ constructor(connection: PriceServiceConnection, client: SuperWalletClient, pythContract: PythContract, logger: Logger, overrideGasPriceMultiplier: number, overrideGasPriceMultiplierCap: number, updateFeeMultiplier: number, gasLimit?: number | undefined, customGasStation?: CustomGasStation | undefined);
34
33
  updatePriceFeed(priceIds: string[], pubTimesToPush: UnixTimestamp[]): Promise<void>;
34
+ private waitForTransactionReceipt;
35
35
  private getPriceFeedsUpdateData;
36
36
  }
37
- export declare class PythContractFactory {
38
- private endpoint;
39
- private mnemonic;
40
- private pythContractAddress;
41
- constructor(endpoint: string, mnemonic: string, pythContractAddress: string);
42
- /**
43
- * This method creates a web3 Pyth contract with payer (based on HDWalletProvider). As this
44
- * provider is an HDWalletProvider it does not support subscriptions even if the
45
- * endpoint is a websocket endpoint.
46
- *
47
- * @returns Pyth contract
48
- */
49
- createPythContractWithPayer(): Contract;
50
- /**
51
- * This method creates a web3 Pyth contract with the given endpoint as its provider. If
52
- * the endpoint is a websocket endpoint the contract will support subscriptions.
53
- *
54
- * @returns Pyth contract
55
- */
56
- createPythContract(): Contract;
57
- hasWebsocketProvider(): boolean;
58
- createWeb3Provider(): HttpProvider | WebsocketProvider;
59
- createWeb3PayerProvider(): HDWalletProvider;
60
- }
61
37
  //# sourceMappingURL=evm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"evm.d.ts","sourceRoot":"","sources":["../../src/evm/evm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAa,MAAM,mBAAmB,CAAC;AACxD,OAAO,EACL,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,SAAS,EACV,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAgB,iBAAiB,EAAmB,MAAM,UAAU,CAAC;AAE5E,OAAO,gBAAgB,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B,OAAO,EACL,sBAAsB,EACtB,SAAS,EACT,aAAa,EACd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAIxD,qBAAa,gBAAiB,SAAQ,kBAAkB;IACtD,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,YAAY,CAAW;IAC/B,OAAO,CAAC,MAAM,CAAS;gBAGrB,mBAAmB,EAAE,mBAAmB,EACxC,UAAU,EAAE,SAAS,EAAE,EACvB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;KACrC;IAWG,KAAK;YAiBG,iBAAiB;IAc/B,OAAO,CAAC,iBAAiB;IAyBnB,mBAAmB,CACvB,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAuBlC;AAED,qBAAa,cAAe,YAAW,YAAY;IAQ/C,OAAO,CAAC,UAAU;IAElB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,0BAA0B;IAClC,OAAO,CAAC,6BAA6B;IACrC,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,QAAQ,CAAC;IAbnB,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,YAAY,CAAW;IAC/B,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,eAAe,CAA0B;gBAGvC,UAAU,EAAE,sBAAsB,EAC1C,mBAAmB,EAAE,mBAAmB,EAChC,MAAM,EAAE,MAAM,EACd,0BAA0B,EAAE,MAAM,EAClC,6BAA6B,EAAE,MAAM,EACrC,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,CAAC,oBAAQ,EACzB,gBAAgB,CAAC,EAAE,gBAAgB;IAa/B,eAAe,CACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,EAAE,aAAa,EAAE,GAC9B,OAAO,CAAC,IAAI,CAAC;YAgKF,uBAAuB;CAQtC;AAED,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,mBAAmB;gBAFnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM;IAGrC;;;;;;OAMG;IACH,2BAA2B,IAAI,QAAQ;IAcvC;;;;;OAKG;IACH,kBAAkB,IAAI,QAAQ;IAS9B,oBAAoB,IAAI,OAAO;IAI/B,kBAAkB,IAAI,YAAY,GAAG,iBAAiB;IA0BtD,uBAAuB;CAQxB"}
1
+ {"version":3,"file":"evm.d.ts","sourceRoot":"","sources":["../../src/evm/evm.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,SAAS,EACT,kBAAkB,EAClB,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAGL,iBAAiB,EAElB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAC9B,OAAO,EACL,sBAAsB,EACtB,SAAS,EACT,aAAa,EACd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAYxD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,qBAAa,gBAAiB,SAAQ,kBAAkB;IAEpD,OAAO,CAAC,YAAY;IAEpB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;gBAHN,YAAY,EAAE,YAAY,EAClC,UAAU,EAAE,SAAS,EAAE,EACf,WAAW,EAAE,OAAO,EACpB,MAAM,EAAE,MAAM,EACtB,MAAM,EAAE;QACN,gBAAgB,EAAE,iBAAiB,CAAC;KACrC;IAUG,KAAK;YAeG,aAAa;IAO3B,OAAO,CAAC,iBAAiB;IAuBnB,mBAAmB,CACvB,OAAO,EAAE,SAAS,GACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;CAuBlC;AAED,qBAAa,cAAe,YAAW,YAAY;IAK/C,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,0BAA0B;IAClC,OAAO,CAAC,6BAA6B;IACrC,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,QAAQ,CAAC;IACjB,OAAO,CAAC,gBAAgB,CAAC;IAZ3B,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,eAAe,CAA0B;gBAGvC,UAAU,EAAE,sBAAsB,EAClC,MAAM,EAAE,iBAAiB,EACzB,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,MAAM,EACd,0BAA0B,EAAE,MAAM,EAClC,6BAA6B,EAAE,MAAM,EACrC,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,CAAC,oBAAQ,EACjB,gBAAgB,CAAC,8BAAkB;IASvC,eAAe,CACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,EAAE,aAAa,EAAE,GAC9B,OAAO,CAAC,IAAI,CAAC;YAkOF,yBAAyB;YAuBzB,uBAAuB;CAQtC"}
package/lib/evm/evm.js CHANGED
@@ -1,31 +1,27 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PythContractFactory = exports.EvmPricePusher = exports.EvmPriceListener = void 0;
3
+ exports.EvmPricePusher = exports.EvmPriceListener = void 0;
7
4
  const interface_1 = require("../interface");
8
5
  const utils_1 = require("../utils");
9
- const AbstractPyth_json_1 = __importDefault(require("@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json"));
10
- const hdwallet_provider_1 = __importDefault(require("@truffle/hdwallet-provider"));
11
- const web3_1 = __importDefault(require("web3"));
12
- const utils_2 = require("../utils");
6
+ const viem_1 = require("viem");
13
7
  class EvmPriceListener extends interface_1.ChainPriceListener {
14
- pythContractFactory;
15
8
  pythContract;
9
+ watchEvents;
16
10
  logger;
17
- constructor(pythContractFactory, priceItems, logger, config) {
11
+ constructor(pythContract, priceItems, watchEvents, logger, config) {
18
12
  super(config.pollingFrequency, priceItems);
19
- this.pythContractFactory = pythContractFactory;
20
- this.pythContract = this.pythContractFactory.createPythContract();
13
+ this.pythContract = pythContract;
14
+ this.watchEvents = watchEvents;
15
+ this.logger = logger;
16
+ this.pythContract = pythContract;
21
17
  this.logger = logger;
22
18
  }
23
19
  // This method should be awaited on and once it finishes it has the latest value
24
20
  // for the given price feeds (if they exist).
25
21
  async start() {
26
- if (this.pythContractFactory.hasWebsocketProvider()) {
27
- this.logger.info("Subscribing to the target network pyth contract events...");
28
- this.startSubscription();
22
+ if (this.watchEvents) {
23
+ this.logger.info("Watching target network pyth contract events...");
24
+ this.startWatching();
29
25
  }
30
26
  else {
31
27
  this.logger.info("The target network RPC endpoint is not Websocket. " +
@@ -34,36 +30,27 @@ class EvmPriceListener extends interface_1.ChainPriceListener {
34
30
  // base class for polling
35
31
  await super.start();
36
32
  }
37
- async startSubscription() {
38
- for (const { id: priceId } of this.priceItems) {
39
- this.pythContract.events.PriceFeedUpdate({
40
- filter: {
41
- id: (0, utils_1.addLeading0x)(priceId),
42
- fresh: true,
43
- },
44
- }, this.onPriceFeedUpdate.bind(this));
45
- }
33
+ async startWatching() {
34
+ this.pythContract.watchEvent.PriceFeedUpdate({ id: this.priceItems.map((item) => (0, utils_1.addLeading0x)(item.id)) }, { strict: true, onLogs: this.onPriceFeedUpdate.bind(this) });
46
35
  }
47
- onPriceFeedUpdate(err, event) {
48
- if (err !== null) {
49
- this.logger.error(err, "PriceFeedUpdate EventEmitter received an error..");
50
- throw err;
36
+ onPriceFeedUpdate(logs) {
37
+ for (const log of logs) {
38
+ const priceId = (0, utils_1.removeLeading0x)((0, utils_1.assertDefined)(log.args.id));
39
+ const priceInfo = {
40
+ conf: (0, utils_1.assertDefined)(log.args.conf).toString(),
41
+ price: (0, utils_1.assertDefined)(log.args.price).toString(),
42
+ publishTime: Number((0, utils_1.assertDefined)(log.args.publishTime)),
43
+ };
44
+ this.logger.debug({ priceInfo }, `Received a new Evm PriceFeedUpdate event for price feed ${this.priceIdToAlias.get(priceId)} (${priceId}).`);
45
+ this.updateLatestPriceInfo(priceId, priceInfo);
51
46
  }
52
- const priceId = (0, utils_1.removeLeading0x)(event.returnValues.id);
53
- this.logger.debug(`Received a new Evm PriceFeedUpdate event for price feed ${this.priceIdToAlias.get(priceId)} (${priceId}).`);
54
- const priceInfo = {
55
- conf: event.returnValues.conf,
56
- price: event.returnValues.price,
57
- publishTime: Number(event.returnValues.publishTime),
58
- };
59
- this.updateLatestPriceInfo(priceId, priceInfo);
60
47
  }
61
48
  async getOnChainPriceInfo(priceId) {
62
49
  let priceRaw;
63
50
  try {
64
- priceRaw = await this.pythContract.methods
65
- .getPriceUnsafe((0, utils_1.addLeading0x)(priceId))
66
- .call();
51
+ priceRaw = await this.pythContract.read.getPriceUnsafe([
52
+ (0, utils_1.addLeading0x)(priceId),
53
+ ]);
67
54
  }
68
55
  catch (err) {
69
56
  this.logger.error(err, `Polling on-chain price for ${priceId} failed.`);
@@ -80,26 +67,26 @@ class EvmPriceListener extends interface_1.ChainPriceListener {
80
67
  exports.EvmPriceListener = EvmPriceListener;
81
68
  class EvmPricePusher {
82
69
  connection;
70
+ client;
71
+ pythContract;
83
72
  logger;
84
73
  overrideGasPriceMultiplier;
85
74
  overrideGasPriceMultiplierCap;
86
75
  updateFeeMultiplier;
87
76
  gasLimit;
88
77
  customGasStation;
89
- pythContract;
90
- web3;
91
78
  pusherAddress;
92
79
  lastPushAttempt;
93
- constructor(connection, pythContractFactory, logger, overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, updateFeeMultiplier, gasLimit, customGasStation) {
80
+ constructor(connection, client, pythContract, logger, overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, updateFeeMultiplier, gasLimit, customGasStation) {
94
81
  this.connection = connection;
82
+ this.client = client;
83
+ this.pythContract = pythContract;
95
84
  this.logger = logger;
96
85
  this.overrideGasPriceMultiplier = overrideGasPriceMultiplier;
97
86
  this.overrideGasPriceMultiplierCap = overrideGasPriceMultiplierCap;
98
87
  this.updateFeeMultiplier = updateFeeMultiplier;
99
88
  this.gasLimit = gasLimit;
100
89
  this.customGasStation = customGasStation;
101
- this.pythContract = pythContractFactory.createPythContractWithPayer();
102
- this.web3 = new web3_1.default(pythContractFactory.createWeb3PayerProvider());
103
90
  }
104
91
  // The pubTimes are passed here to use the values that triggered the push.
105
92
  // This is an optimization to avoid getting a newer value (as an update comes)
@@ -114,174 +101,156 @@ class EvmPricePusher {
114
101
  if (priceIds.length !== pubTimesToPush.length)
115
102
  throw new Error("Invalid arguments");
116
103
  const priceIdsWith0x = priceIds.map((priceId) => (0, utils_1.addLeading0x)(priceId));
117
- const priceFeedUpdateData = await this.getPriceFeedsUpdateData(priceIdsWith0x);
104
+ const priceFeedUpdateData = (await this.getPriceFeedsUpdateData(priceIdsWith0x));
118
105
  let updateFee;
119
106
  try {
120
- updateFee = await this.pythContract.methods
121
- .getUpdateFee(priceFeedUpdateData)
122
- .call();
123
- updateFee = Number(updateFee) * (this.updateFeeMultiplier || 1);
107
+ updateFee = await this.pythContract.read.getUpdateFee([
108
+ priceFeedUpdateData,
109
+ ]);
110
+ updateFee = BigInt(Math.round(Number(updateFee) * (this.updateFeeMultiplier || 1)));
124
111
  this.logger.debug(`Update fee: ${updateFee}`);
125
112
  }
126
113
  catch (e) {
127
114
  this.logger.error(e, "An unidentified error has occured when getting the update fee.");
128
115
  throw e;
129
116
  }
130
- let gasPrice = Number((await this.customGasStation?.getCustomGasPrice()) ||
131
- (await this.web3.eth.getGasPrice()));
117
+ const fees = await this.client.estimateFeesPerGas();
118
+ this.logger.debug({ fees }, "Estimated fees");
119
+ let gasPrice = Number(await this.customGasStation?.getCustomGasPrice()) ||
120
+ Number(fees.gasPrice) ||
121
+ Number(fees.maxFeePerGas);
132
122
  // Try to re-use the same nonce and increase the gas if the last tx is not landed yet.
133
123
  if (this.pusherAddress === undefined) {
134
- this.pusherAddress = (await this.web3.eth.getAccounts())[0];
124
+ this.pusherAddress = this.client.account.address;
135
125
  }
136
- const lastExecutedNonce = (await this.web3.eth.getTransactionCount(this.pusherAddress)) - 1;
126
+ const lastExecutedNonce = (await this.client.getTransactionCount({
127
+ address: this.pusherAddress,
128
+ })) - 1;
137
129
  let gasPriceToOverride = undefined;
138
130
  if (this.lastPushAttempt !== undefined) {
139
131
  if (this.lastPushAttempt.nonce <= lastExecutedNonce) {
140
132
  this.lastPushAttempt = undefined;
141
133
  }
142
134
  else {
143
- gasPriceToOverride = Math.ceil(this.lastPushAttempt.gasPrice * this.overrideGasPriceMultiplier);
135
+ gasPriceToOverride =
136
+ this.lastPushAttempt.gasPrice * this.overrideGasPriceMultiplier;
144
137
  }
145
138
  }
146
- if (gasPriceToOverride !== undefined && gasPriceToOverride > gasPrice) {
139
+ if (gasPriceToOverride !== undefined &&
140
+ gasPriceToOverride > Number(gasPrice)) {
147
141
  gasPrice = Math.min(gasPriceToOverride, gasPrice * this.overrideGasPriceMultiplierCap);
148
142
  }
149
143
  const txNonce = lastExecutedNonce + 1;
150
144
  this.logger.debug(`Using gas price: ${gasPrice} and nonce: ${txNonce}`);
151
- this.pythContract.methods
152
- .updatePriceFeedsIfNecessary(priceFeedUpdateData, priceIdsWith0x, pubTimesToPush)
153
- .send({
154
- value: updateFee,
155
- gasPrice,
156
- nonce: txNonce,
157
- gasLimit: this.gasLimit,
158
- })
159
- .on("transactionHash", (hash) => {
160
- this.logger.info({ hash }, "Price update successful");
161
- })
162
- .on("error", (err, receipt) => {
163
- if (err.message.includes("revert")) {
164
- // Since we are using custom error structs on solidity the rejection
165
- // doesn't return any information why the call has reverted. Assuming that
166
- // the update data is valid there is no possible rejection cause other than
167
- // the target chain price being already updated.
168
- this.logger.info({ err, receipt }, "Execution reverted. With high probability, the target chain price " +
169
- "has already updated, Skipping this push.");
170
- return;
171
- }
172
- if (err.message.includes("the tx doesn't have the correct nonce.") ||
173
- err.message.includes("nonce too low") ||
174
- err.message.includes("invalid nonce")) {
175
- this.logger.info({ err, receipt }, "The nonce is incorrect (are multiple users using this account?). Skipping this push.");
176
- return;
177
- }
178
- if (err.message.includes("max fee per gas less than block base fee")) {
179
- // We just have to handle this error and return.
180
- // LastPushAttempt was stored with the class
181
- // Next time the update will be executing, it will check the last attempt
182
- // and increase the gas price accordingly.
183
- this.logger.info({ err, receipt }, "The transaction failed with error: max fee per gas less than block base fee ");
184
- return;
185
- }
186
- if (err.message.includes("sender doesn't have enough funds to send tx.")) {
187
- this.logger.error({ err, receipt }, "Payer is out of balance, please top it up.");
188
- throw err;
189
- }
190
- if (err.message.includes("transaction underpriced")) {
191
- this.logger.error({ err, receipt }, "The gas price of the transaction is too low. Skipping this push. " +
192
- "You might want to use a custom gas station or increase the override gas price " +
193
- "multiplier to increase the likelihood of the transaction landing on-chain.");
194
- return;
195
- }
196
- if (err.message.includes("could not replace existing tx")) {
197
- this.logger.error({ err, receipt }, "A transaction with the same nonce has been mined and this one is no longer needed.");
198
- return;
145
+ const pubTimesToPushParam = pubTimesToPush.map((pubTime) => BigInt(pubTime));
146
+ try {
147
+ const { request } = await this.pythContract.simulate.updatePriceFeedsIfNecessary([priceFeedUpdateData, priceIdsWith0x, pubTimesToPushParam], {
148
+ value: updateFee,
149
+ gasPrice: BigInt(Math.ceil(gasPrice)),
150
+ nonce: txNonce,
151
+ gas: this.gasLimit !== undefined
152
+ ? BigInt(Math.ceil(this.gasLimit))
153
+ : undefined,
154
+ });
155
+ this.logger.debug({ request }, "Simulated request successfully");
156
+ const hash = await this.client.writeContract(request);
157
+ this.logger.info({ hash }, "Price update sent");
158
+ this.waitForTransactionReceipt(hash);
159
+ }
160
+ catch (err) {
161
+ this.logger.debug({ err }, "Simulating or sending transactions failed.");
162
+ if (err instanceof viem_1.BaseError) {
163
+ if (err.walk((e) => e instanceof viem_1.ContractFunctionRevertedError &&
164
+ e.data?.errorName === "NoFreshUpdate")) {
165
+ this.logger.info("Simulation reverted because none of the updates are fresh. This is an expected behaviour to save gas. Skipping this push.");
166
+ return;
167
+ }
168
+ if (err.walk((e) => e instanceof viem_1.InsufficientFundsError)) {
169
+ this.logger.error({ err }, "Wallet doesn't have enough balance. In rare cases, there might be issues with gas price " +
170
+ "calculation in the RPC.");
171
+ throw err;
172
+ }
173
+ if (err.walk((e) => e instanceof viem_1.FeeCapTooLowError) ||
174
+ err.walk((e) => e instanceof viem_1.InternalRpcError &&
175
+ e.details.includes("replacement transaction underpriced"))) {
176
+ this.logger.warn("The gas price of the transaction is too low or there is an existing transaction with higher gas with the same nonce. " +
177
+ "The price will be increased in the next push. Skipping this push. " +
178
+ "If this keeps happening or transactions are not landing you need to increase the override gas price " +
179
+ "multiplier and the cap to increase the likelihood of the transaction landing on-chain.");
180
+ return;
181
+ }
182
+ if (err.walk((e) => e instanceof viem_1.TransactionExecutionError &&
183
+ (e.details.includes("nonce too low") ||
184
+ e.message.includes("Nonce provided for the transaction")))) {
185
+ this.logger.info("The nonce is incorrect. This is an expected behaviour in high frequency or multi-instance setup. Skipping this push.");
186
+ return;
187
+ }
188
+ // We normally crash on unknown failures but we believe that this type of error is safe to skip. The other reason is that
189
+ // wometimes we see a TransactionExecutionError because of the nonce without any details and it is not catchable.
190
+ if (err.walk((e) => e instanceof viem_1.TransactionExecutionError)) {
191
+ this.logger.error({ err }, "Transaction execution failed. This is an expected behaviour in high frequency or multi-instance setup. " +
192
+ "Please review this error and file an issue if it is a bug. Skipping this push.");
193
+ return;
194
+ }
195
+ // The following errors are part of the legacy code and might not work as expected.
196
+ // We are keeping them in case they help with handling what is not covered above.
197
+ if (err.message.includes("the tx doesn't have the correct nonce.") ||
198
+ err.message.includes("nonce too low") ||
199
+ err.message.includes("invalid nonce")) {
200
+ this.logger.info("The nonce is incorrect (are multiple users using this account?). Skipping this push.");
201
+ return;
202
+ }
203
+ if (err.message.includes("max fee per gas less than block base fee")) {
204
+ // We just have to handle this error and return.
205
+ // LastPushAttempt was stored with the class
206
+ // Next time the update will be executing, it will check the last attempt
207
+ // and increase the gas price accordingly.
208
+ this.logger.warn("The transaction failed with error: max fee per gas less than block base fee. " +
209
+ "The fee will be increased in the next push. Skipping this push.");
210
+ return;
211
+ }
212
+ if (err.message.includes("sender doesn't have enough funds to send tx.")) {
213
+ this.logger.error("Payer is out of balance, please top it up.");
214
+ throw new Error("Please top up the wallet");
215
+ }
216
+ if (err.message.includes("could not replace existing tx")) {
217
+ this.logger.error("A transaction with the same nonce has been mined and this one is no longer needed. Skipping this push.");
218
+ return;
219
+ }
199
220
  }
200
- this.logger.error({ err, receipt }, "An unidentified error has occured.");
221
+ // If the error is not handled, we will crash the process.
222
+ this.logger.error({ err }, "The transaction failed with an unhandled error. crashing the process. " +
223
+ "Please review this error and file an issue if it is a bug.");
201
224
  throw err;
202
- });
225
+ }
203
226
  // Update lastAttempt
204
227
  this.lastPushAttempt = {
205
228
  nonce: txNonce,
206
229
  gasPrice: gasPrice,
207
230
  };
208
231
  }
209
- async getPriceFeedsUpdateData(priceIds) {
210
- const latestVaas = await this.connection.getLatestVaas(priceIds);
211
- return latestVaas.map((vaa) => "0x" + Buffer.from(vaa, "base64").toString("hex"));
212
- }
213
- }
214
- exports.EvmPricePusher = EvmPricePusher;
215
- class PythContractFactory {
216
- endpoint;
217
- mnemonic;
218
- pythContractAddress;
219
- constructor(endpoint, mnemonic, pythContractAddress) {
220
- this.endpoint = endpoint;
221
- this.mnemonic = mnemonic;
222
- this.pythContractAddress = pythContractAddress;
223
- }
224
- /**
225
- * This method creates a web3 Pyth contract with payer (based on HDWalletProvider). As this
226
- * provider is an HDWalletProvider it does not support subscriptions even if the
227
- * endpoint is a websocket endpoint.
228
- *
229
- * @returns Pyth contract
230
- */
231
- createPythContractWithPayer() {
232
- const provider = this.createWeb3PayerProvider();
233
- const web3 = new web3_1.default(provider);
234
- return new web3.eth.Contract(AbstractPyth_json_1.default, this.pythContractAddress, {
235
- from: provider.getAddress(0),
236
- });
237
- }
238
- /**
239
- * This method creates a web3 Pyth contract with the given endpoint as its provider. If
240
- * the endpoint is a websocket endpoint the contract will support subscriptions.
241
- *
242
- * @returns Pyth contract
243
- */
244
- createPythContract() {
245
- const provider = this.createWeb3Provider();
246
- const web3 = new web3_1.default(provider);
247
- return new web3.eth.Contract(AbstractPyth_json_1.default, this.pythContractAddress);
248
- }
249
- hasWebsocketProvider() {
250
- return (0, utils_2.isWsEndpoint)(this.endpoint);
251
- }
252
- createWeb3Provider() {
253
- if ((0, utils_2.isWsEndpoint)(this.endpoint)) {
254
- web3_1.default.providers.WebsocketProvider.prototype.sendAsync =
255
- web3_1.default.providers.WebsocketProvider.prototype.send;
256
- return new web3_1.default.providers.WebsocketProvider(this.endpoint, {
257
- clientConfig: {
258
- keepalive: true,
259
- keepaliveInterval: 30000,
260
- },
261
- reconnect: {
262
- auto: true,
263
- delay: 1000,
264
- onTimeout: true,
265
- },
266
- timeout: 30000,
232
+ async waitForTransactionReceipt(hash) {
233
+ try {
234
+ const receipt = await this.client.waitForTransactionReceipt({
235
+ hash: hash,
267
236
  });
237
+ switch (receipt.status) {
238
+ case "success":
239
+ this.logger.debug({ hash, receipt }, "Price update successful");
240
+ this.logger.info({ hash }, "Price update successful");
241
+ break;
242
+ default:
243
+ this.logger.info({ hash, receipt }, "Price update did not succeed or its transaction did not land. " +
244
+ "This is an expected behaviour in high frequency or multi-instance setup.");
245
+ }
268
246
  }
269
- else {
270
- web3_1.default.providers.HttpProvider.prototype.sendAsync =
271
- web3_1.default.providers.HttpProvider.prototype.send;
272
- return new web3_1.default.providers.HttpProvider(this.endpoint, {
273
- keepAlive: true,
274
- timeout: 30000,
275
- });
247
+ catch (err) {
248
+ this.logger.warn({ err }, "Failed to get transaction receipt");
276
249
  }
277
250
  }
278
- createWeb3PayerProvider() {
279
- return new hdwallet_provider_1.default({
280
- mnemonic: {
281
- phrase: this.mnemonic,
282
- },
283
- providerOrUrl: this.createWeb3Provider(),
284
- });
251
+ async getPriceFeedsUpdateData(priceIds) {
252
+ const latestVaas = await this.connection.getLatestVaas(priceIds);
253
+ return latestVaas.map((vaa) => "0x" + Buffer.from(vaa, "base64").toString("hex"));
285
254
  }
286
255
  }
287
- exports.PythContractFactory = PythContractFactory;
256
+ exports.EvmPricePusher = EvmPricePusher;