@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.
- package/lib/evm/command.d.ts +1 -1
- package/lib/evm/command.d.ts.map +1 -1
- package/lib/evm/command.js +13 -7
- package/lib/evm/custom-gas-station.d.ts +1 -1
- package/lib/evm/custom-gas-station.d.ts.map +1 -1
- package/lib/evm/custom-gas-station.js +3 -7
- package/lib/evm/evm.d.ts +9 -33
- package/lib/evm/evm.d.ts.map +1 -1
- package/lib/evm/evm.js +147 -178
- package/lib/evm/pyth-abi.d.ts +1001 -0
- package/lib/evm/pyth-abi.d.ts.map +1 -0
- package/lib/evm/pyth-abi.js +660 -0
- package/lib/evm/pyth-contract.d.ts +6 -0
- package/lib/evm/pyth-contract.d.ts.map +1 -0
- package/lib/evm/pyth-contract.js +11 -0
- package/lib/evm/super-wallet.d.ts +4 -0
- package/lib/evm/super-wallet.d.ts.map +1 -0
- package/lib/evm/super-wallet.js +63 -0
- package/lib/utils.d.ts +2 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +12 -7
- package/package.json +5 -8
package/lib/evm/command.d.ts
CHANGED
package/lib/evm/command.d.ts.map
CHANGED
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;
|
|
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"}
|
package/lib/evm/command.js
CHANGED
|
@@ -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
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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,
|
|
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<
|
|
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":"
|
|
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
|
|
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
|
-
|
|
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(
|
|
12
|
+
constructor(pythContract: PythContract, priceItems: PriceItem[], watchEvents: boolean, logger: Logger, config: {
|
|
14
13
|
pollingFrequency: DurationInSeconds;
|
|
15
14
|
});
|
|
16
15
|
start(): Promise<void>;
|
|
17
|
-
private
|
|
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,
|
|
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
|
package/lib/evm/evm.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evm.d.ts","sourceRoot":"","sources":["../../src/evm/evm.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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.
|
|
3
|
+
exports.EvmPricePusher = exports.EvmPriceListener = void 0;
|
|
7
4
|
const interface_1 = require("../interface");
|
|
8
5
|
const utils_1 = require("../utils");
|
|
9
|
-
const
|
|
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(
|
|
11
|
+
constructor(pythContract, priceItems, watchEvents, logger, config) {
|
|
18
12
|
super(config.pollingFrequency, priceItems);
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
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.
|
|
27
|
-
this.logger.info("
|
|
28
|
-
this.
|
|
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
|
|
38
|
-
|
|
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(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
65
|
-
|
|
66
|
-
|
|
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,
|
|
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.
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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 =
|
|
124
|
+
this.pusherAddress = this.client.account.address;
|
|
135
125
|
}
|
|
136
|
-
const lastExecutedNonce = (await this.
|
|
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 =
|
|
135
|
+
gasPriceToOverride =
|
|
136
|
+
this.lastPushAttempt.gasPrice * this.overrideGasPriceMultiplier;
|
|
144
137
|
}
|
|
145
138
|
}
|
|
146
|
-
if (gasPriceToOverride !== undefined &&
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
err.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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.
|
|
256
|
+
exports.EvmPricePusher = EvmPricePusher;
|