|
|
@@ -1,18 +1,17 @@
|
|
|
-import { Contract, EventData } from "web3-eth-contract";
|
|
|
import {
|
|
|
IPricePusher,
|
|
|
PriceInfo,
|
|
|
ChainPriceListener,
|
|
|
PriceItem,
|
|
|
} from "../interface";
|
|
|
-import { TransactionReceipt } from "ethereum-protocol";
|
|
|
-import { addLeading0x, DurationInSeconds, removeLeading0x } from "../utils";
|
|
|
-import AbstractPythAbi from "@pythnetwork/pyth-sdk-solidity/abis/AbstractPyth.json";
|
|
|
-import HDWalletProvider from "@truffle/hdwallet-provider";
|
|
|
-import Web3 from "web3";
|
|
|
-import { HttpProvider, WebsocketProvider } from "web3-core";
|
|
|
+import {
|
|
|
+ addLeading0x,
|
|
|
+ assertDefined,
|
|
|
+ DurationInSeconds,
|
|
|
+ removeLeading0x,
|
|
|
+} from "../utils";
|
|
|
+import { PythAbi } from "./pyth-abi";
|
|
|
import { Logger } from "pino";
|
|
|
-import { isWsEndpoint } from "../utils";
|
|
|
import {
|
|
|
PriceServiceConnection,
|
|
|
HexString,
|
|
|
@@ -20,36 +19,41 @@ import {
|
|
|
} from "@pythnetwork/price-service-client";
|
|
|
import { CustomGasStation } from "./custom-gas-station";
|
|
|
import { PushAttempt } from "../common";
|
|
|
-import { ProviderOrUrl } from "@truffle/hdwallet-provider/dist/constructor/types";
|
|
|
+import {
|
|
|
+ WatchContractEventOnLogsParameter,
|
|
|
+ TransactionExecutionError,
|
|
|
+ BaseError,
|
|
|
+ ContractFunctionRevertedError,
|
|
|
+ FeeCapTooLowError,
|
|
|
+ InternalRpcError,
|
|
|
+ InsufficientFundsError,
|
|
|
+} from "viem";
|
|
|
+
|
|
|
+import { PythContract } from "./pyth-contract";
|
|
|
+import { SuperWalletClient } from "./super-wallet";
|
|
|
|
|
|
export class EvmPriceListener extends ChainPriceListener {
|
|
|
- private pythContractFactory: PythContractFactory;
|
|
|
- private pythContract: Contract;
|
|
|
- private logger: Logger;
|
|
|
-
|
|
|
constructor(
|
|
|
- pythContractFactory: PythContractFactory,
|
|
|
+ private pythContract: PythContract,
|
|
|
priceItems: PriceItem[],
|
|
|
- logger: Logger,
|
|
|
+ private watchEvents: boolean,
|
|
|
+ private logger: Logger,
|
|
|
config: {
|
|
|
pollingFrequency: DurationInSeconds;
|
|
|
}
|
|
|
) {
|
|
|
super(config.pollingFrequency, priceItems);
|
|
|
|
|
|
- this.pythContractFactory = pythContractFactory;
|
|
|
- this.pythContract = this.pythContractFactory.createPythContract();
|
|
|
+ this.pythContract = pythContract;
|
|
|
this.logger = logger;
|
|
|
}
|
|
|
|
|
|
// This method should be awaited on and once it finishes it has the latest value
|
|
|
// for the given price feeds (if they exist).
|
|
|
async start() {
|
|
|
- if (this.pythContractFactory.hasWebsocketProvider()) {
|
|
|
- this.logger.info(
|
|
|
- "Subscribing to the target network pyth contract events..."
|
|
|
- );
|
|
|
- this.startSubscription();
|
|
|
+ if (this.watchEvents) {
|
|
|
+ this.logger.info("Watching target network pyth contract events...");
|
|
|
+ this.startWatching();
|
|
|
} else {
|
|
|
this.logger.info(
|
|
|
"The target network RPC endpoint is not Websocket. " +
|
|
|
@@ -61,53 +65,44 @@ export class EvmPriceListener extends ChainPriceListener {
|
|
|
await super.start();
|
|
|
}
|
|
|
|
|
|
- private async startSubscription() {
|
|
|
- for (const { id: priceId } of this.priceItems) {
|
|
|
- this.pythContract.events.PriceFeedUpdate(
|
|
|
- {
|
|
|
- filter: {
|
|
|
- id: addLeading0x(priceId),
|
|
|
- fresh: true,
|
|
|
- },
|
|
|
- },
|
|
|
- this.onPriceFeedUpdate.bind(this)
|
|
|
- );
|
|
|
- }
|
|
|
+ private async startWatching() {
|
|
|
+ this.pythContract.watchEvent.PriceFeedUpdate(
|
|
|
+ { id: this.priceItems.map((item) => addLeading0x(item.id)) },
|
|
|
+ { strict: true, onLogs: this.onPriceFeedUpdate.bind(this) }
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- private onPriceFeedUpdate(err: Error | null, event: EventData) {
|
|
|
- if (err !== null) {
|
|
|
- this.logger.error(
|
|
|
- err,
|
|
|
- "PriceFeedUpdate EventEmitter received an error.."
|
|
|
+ private onPriceFeedUpdate(
|
|
|
+ logs: WatchContractEventOnLogsParameter<typeof PythAbi, "PriceFeedUpdate">
|
|
|
+ ) {
|
|
|
+ for (const log of logs) {
|
|
|
+ const priceId = removeLeading0x(assertDefined(log.args.id));
|
|
|
+
|
|
|
+ const priceInfo: PriceInfo = {
|
|
|
+ conf: assertDefined(log.args.conf).toString(),
|
|
|
+ price: assertDefined(log.args.price).toString(),
|
|
|
+ publishTime: Number(assertDefined(log.args.publishTime)),
|
|
|
+ };
|
|
|
+
|
|
|
+ this.logger.debug(
|
|
|
+ { priceInfo },
|
|
|
+ `Received a new Evm PriceFeedUpdate event for price feed ${this.priceIdToAlias.get(
|
|
|
+ priceId
|
|
|
+ )} (${priceId}).`
|
|
|
);
|
|
|
- throw err;
|
|
|
- }
|
|
|
|
|
|
- const priceId = removeLeading0x(event.returnValues.id);
|
|
|
- this.logger.debug(
|
|
|
- `Received a new Evm PriceFeedUpdate event for price feed ${this.priceIdToAlias.get(
|
|
|
- priceId
|
|
|
- )} (${priceId}).`
|
|
|
- );
|
|
|
-
|
|
|
- const priceInfo: PriceInfo = {
|
|
|
- conf: event.returnValues.conf,
|
|
|
- price: event.returnValues.price,
|
|
|
- publishTime: Number(event.returnValues.publishTime),
|
|
|
- };
|
|
|
-
|
|
|
- this.updateLatestPriceInfo(priceId, priceInfo);
|
|
|
+ this.updateLatestPriceInfo(priceId, priceInfo);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
async getOnChainPriceInfo(
|
|
|
priceId: HexString
|
|
|
): Promise<PriceInfo | undefined> {
|
|
|
- let priceRaw;
|
|
|
+ let priceRaw: any;
|
|
|
try {
|
|
|
- priceRaw = await this.pythContract.methods
|
|
|
- .getPriceUnsafe(addLeading0x(priceId))
|
|
|
- .call();
|
|
|
+ priceRaw = await this.pythContract.read.getPriceUnsafe([
|
|
|
+ addLeading0x(priceId),
|
|
|
+ ]);
|
|
|
} catch (err) {
|
|
|
this.logger.error(err, `Polling on-chain price for ${priceId} failed.`);
|
|
|
return undefined;
|
|
|
@@ -128,26 +123,20 @@ export class EvmPriceListener extends ChainPriceListener {
|
|
|
}
|
|
|
|
|
|
export class EvmPricePusher implements IPricePusher {
|
|
|
- private customGasStation?: CustomGasStation;
|
|
|
- private pythContract: Contract;
|
|
|
- private web3: Web3;
|
|
|
- private pusherAddress: string | undefined;
|
|
|
+ private pusherAddress: `0x${string}` | undefined;
|
|
|
private lastPushAttempt: PushAttempt | undefined;
|
|
|
|
|
|
constructor(
|
|
|
private connection: PriceServiceConnection,
|
|
|
- pythContractFactory: PythContractFactory,
|
|
|
+ private client: SuperWalletClient,
|
|
|
+ private pythContract: PythContract,
|
|
|
private logger: Logger,
|
|
|
private overrideGasPriceMultiplier: number,
|
|
|
private overrideGasPriceMultiplierCap: number,
|
|
|
private updateFeeMultiplier: number,
|
|
|
private gasLimit?: number,
|
|
|
- customGasStation?: CustomGasStation
|
|
|
- ) {
|
|
|
- this.customGasStation = customGasStation;
|
|
|
- this.pythContract = pythContractFactory.createPythContractWithPayer();
|
|
|
- this.web3 = new Web3(pythContractFactory.createWeb3PayerProvider() as any);
|
|
|
- }
|
|
|
+ private customGasStation?: CustomGasStation
|
|
|
+ ) {}
|
|
|
|
|
|
// The pubTimes are passed here to use the values that triggered the push.
|
|
|
// This is an optimization to avoid getting a newer value (as an update comes)
|
|
|
@@ -168,17 +157,19 @@ export class EvmPricePusher implements IPricePusher {
|
|
|
|
|
|
const priceIdsWith0x = priceIds.map((priceId) => addLeading0x(priceId));
|
|
|
|
|
|
- const priceFeedUpdateData = await this.getPriceFeedsUpdateData(
|
|
|
+ const priceFeedUpdateData = (await this.getPriceFeedsUpdateData(
|
|
|
priceIdsWith0x
|
|
|
- );
|
|
|
+ )) as `0x${string}`[];
|
|
|
|
|
|
let updateFee;
|
|
|
|
|
|
try {
|
|
|
- updateFee = await this.pythContract.methods
|
|
|
- .getUpdateFee(priceFeedUpdateData)
|
|
|
- .call();
|
|
|
- updateFee = Number(updateFee) * (this.updateFeeMultiplier || 1);
|
|
|
+ updateFee = await this.pythContract.read.getUpdateFee([
|
|
|
+ priceFeedUpdateData,
|
|
|
+ ]);
|
|
|
+ updateFee = BigInt(
|
|
|
+ Math.round(Number(updateFee) * (this.updateFeeMultiplier || 1))
|
|
|
+ );
|
|
|
this.logger.debug(`Update fee: ${updateFee}`);
|
|
|
} catch (e: any) {
|
|
|
this.logger.error(
|
|
|
@@ -188,17 +179,24 @@ export class EvmPricePusher implements IPricePusher {
|
|
|
throw e;
|
|
|
}
|
|
|
|
|
|
- let gasPrice = Number(
|
|
|
- (await this.customGasStation?.getCustomGasPrice()) ||
|
|
|
- (await this.web3.eth.getGasPrice())
|
|
|
- );
|
|
|
+ const fees = await this.client.estimateFeesPerGas();
|
|
|
+
|
|
|
+ this.logger.debug({ fees }, "Estimated fees");
|
|
|
+
|
|
|
+ let gasPrice =
|
|
|
+ Number(await this.customGasStation?.getCustomGasPrice()) ||
|
|
|
+ Number(fees.gasPrice) ||
|
|
|
+ Number(fees.maxFeePerGas);
|
|
|
|
|
|
// Try to re-use the same nonce and increase the gas if the last tx is not landed yet.
|
|
|
if (this.pusherAddress === undefined) {
|
|
|
- this.pusherAddress = (await this.web3.eth.getAccounts())[0];
|
|
|
+ this.pusherAddress = this.client.account.address;
|
|
|
}
|
|
|
+
|
|
|
const lastExecutedNonce =
|
|
|
- (await this.web3.eth.getTransactionCount(this.pusherAddress)) - 1;
|
|
|
+ (await this.client.getTransactionCount({
|
|
|
+ address: this.pusherAddress,
|
|
|
+ })) - 1;
|
|
|
|
|
|
let gasPriceToOverride = undefined;
|
|
|
|
|
|
@@ -206,13 +204,15 @@ export class EvmPricePusher implements IPricePusher {
|
|
|
if (this.lastPushAttempt.nonce <= lastExecutedNonce) {
|
|
|
this.lastPushAttempt = undefined;
|
|
|
} else {
|
|
|
- gasPriceToOverride = Math.ceil(
|
|
|
- this.lastPushAttempt.gasPrice * this.overrideGasPriceMultiplier
|
|
|
- );
|
|
|
+ gasPriceToOverride =
|
|
|
+ this.lastPushAttempt.gasPrice * this.overrideGasPriceMultiplier;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (gasPriceToOverride !== undefined && gasPriceToOverride > gasPrice) {
|
|
|
+ if (
|
|
|
+ gasPriceToOverride !== undefined &&
|
|
|
+ gasPriceToOverride > Number(gasPrice)
|
|
|
+ ) {
|
|
|
gasPrice = Math.min(
|
|
|
gasPriceToOverride,
|
|
|
gasPrice * this.overrideGasPriceMultiplierCap
|
|
|
@@ -223,42 +223,108 @@ export class EvmPricePusher implements IPricePusher {
|
|
|
|
|
|
this.logger.debug(`Using gas price: ${gasPrice} and nonce: ${txNonce}`);
|
|
|
|
|
|
- this.pythContract.methods
|
|
|
- .updatePriceFeedsIfNecessary(
|
|
|
- priceFeedUpdateData,
|
|
|
- priceIdsWith0x,
|
|
|
- pubTimesToPush
|
|
|
- )
|
|
|
- .send({
|
|
|
- value: updateFee,
|
|
|
- gasPrice,
|
|
|
- nonce: txNonce,
|
|
|
- gasLimit: this.gasLimit,
|
|
|
- })
|
|
|
- .on("transactionHash", (hash: string) => {
|
|
|
- this.logger.info({ hash }, "Price update successful");
|
|
|
- })
|
|
|
- .on("error", (err: Error, receipt?: TransactionReceipt) => {
|
|
|
- if (err.message.includes("revert")) {
|
|
|
- // Since we are using custom error structs on solidity the rejection
|
|
|
- // doesn't return any information why the call has reverted. Assuming that
|
|
|
- // the update data is valid there is no possible rejection cause other than
|
|
|
- // the target chain price being already updated.
|
|
|
+ const pubTimesToPushParam = pubTimesToPush.map((pubTime) =>
|
|
|
+ BigInt(pubTime)
|
|
|
+ );
|
|
|
+
|
|
|
+ try {
|
|
|
+ const { request } =
|
|
|
+ await this.pythContract.simulate.updatePriceFeedsIfNecessary(
|
|
|
+ [priceFeedUpdateData, priceIdsWith0x, pubTimesToPushParam],
|
|
|
+ {
|
|
|
+ value: updateFee,
|
|
|
+ gasPrice: BigInt(Math.ceil(gasPrice)),
|
|
|
+ nonce: txNonce,
|
|
|
+ gas:
|
|
|
+ this.gasLimit !== undefined
|
|
|
+ ? BigInt(Math.ceil(this.gasLimit))
|
|
|
+ : undefined,
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ this.logger.debug({ request }, "Simulated request successfully");
|
|
|
+
|
|
|
+ const hash = await this.client.writeContract(request);
|
|
|
+
|
|
|
+ this.logger.info({ hash }, "Price update sent");
|
|
|
+
|
|
|
+ this.waitForTransactionReceipt(hash);
|
|
|
+ } catch (err: any) {
|
|
|
+ this.logger.debug({ err }, "Simulating or sending transactions failed.");
|
|
|
+
|
|
|
+ if (err instanceof BaseError) {
|
|
|
+ if (
|
|
|
+ err.walk(
|
|
|
+ (e) =>
|
|
|
+ e instanceof ContractFunctionRevertedError &&
|
|
|
+ e.data?.errorName === "NoFreshUpdate"
|
|
|
+ )
|
|
|
+ ) {
|
|
|
this.logger.info(
|
|
|
- { err, receipt },
|
|
|
- "Execution reverted. With high probability, the target chain price " +
|
|
|
- "has already updated, Skipping this push."
|
|
|
+ "Simulation reverted because none of the updates are fresh. This is an expected behaviour to save gas. Skipping this push."
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (err.walk((e) => e instanceof InsufficientFundsError)) {
|
|
|
+ this.logger.error(
|
|
|
+ { err },
|
|
|
+ "Wallet doesn't have enough balance. In rare cases, there might be issues with gas price " +
|
|
|
+ "calculation in the RPC."
|
|
|
+ );
|
|
|
+ throw err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ err.walk((e) => e instanceof FeeCapTooLowError) ||
|
|
|
+ err.walk(
|
|
|
+ (e) =>
|
|
|
+ e instanceof InternalRpcError &&
|
|
|
+ e.details.includes("replacement transaction underpriced")
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ 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. " +
|
|
|
+ "The price will be increased in the next push. Skipping this push. " +
|
|
|
+ "If this keeps happening or transactions are not landing you need to increase the override gas price " +
|
|
|
+ "multiplier and the cap to increase the likelihood of the transaction landing on-chain."
|
|
|
);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
+ if (
|
|
|
+ err.walk(
|
|
|
+ (e) =>
|
|
|
+ e instanceof TransactionExecutionError &&
|
|
|
+ (e.details.includes("nonce too low") ||
|
|
|
+ e.message.includes("Nonce provided for the transaction"))
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ this.logger.info(
|
|
|
+ "The nonce is incorrect. This is an expected behaviour in high frequency or multi-instance setup. Skipping this push."
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // We normally crash on unknown failures but we believe that this type of error is safe to skip. The other reason is that
|
|
|
+ // wometimes we see a TransactionExecutionError because of the nonce without any details and it is not catchable.
|
|
|
+ if (err.walk((e) => e instanceof TransactionExecutionError)) {
|
|
|
+ this.logger.error(
|
|
|
+ { err },
|
|
|
+ "Transaction execution failed. This is an expected behaviour in high frequency or multi-instance setup. " +
|
|
|
+ "Please review this error and file an issue if it is a bug. Skipping this push."
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // The following errors are part of the legacy code and might not work as expected.
|
|
|
+ // We are keeping them in case they help with handling what is not covered above.
|
|
|
if (
|
|
|
err.message.includes("the tx doesn't have the correct nonce.") ||
|
|
|
err.message.includes("nonce too low") ||
|
|
|
err.message.includes("invalid nonce")
|
|
|
) {
|
|
|
this.logger.info(
|
|
|
- { err, receipt },
|
|
|
"The nonce is incorrect (are multiple users using this account?). Skipping this push."
|
|
|
);
|
|
|
return;
|
|
|
@@ -269,9 +335,9 @@ export class EvmPricePusher implements IPricePusher {
|
|
|
// LastPushAttempt was stored with the class
|
|
|
// Next time the update will be executing, it will check the last attempt
|
|
|
// and increase the gas price accordingly.
|
|
|
- this.logger.info(
|
|
|
- { err, receipt },
|
|
|
- "The transaction failed with error: max fee per gas less than block base fee "
|
|
|
+ this.logger.warn(
|
|
|
+ "The transaction failed with error: max fee per gas less than block base fee. " +
|
|
|
+ "The fee will be increased in the next push. Skipping this push."
|
|
|
);
|
|
|
return;
|
|
|
}
|
|
|
@@ -279,37 +345,26 @@ export class EvmPricePusher implements IPricePusher {
|
|
|
if (
|
|
|
err.message.includes("sender doesn't have enough funds to send tx.")
|
|
|
) {
|
|
|
- this.logger.error(
|
|
|
- { err, receipt },
|
|
|
- "Payer is out of balance, please top it up."
|
|
|
- );
|
|
|
- throw err;
|
|
|
- }
|
|
|
-
|
|
|
- if (err.message.includes("transaction underpriced")) {
|
|
|
- this.logger.error(
|
|
|
- { err, receipt },
|
|
|
- "The gas price of the transaction is too low. Skipping this push. " +
|
|
|
- "You might want to use a custom gas station or increase the override gas price " +
|
|
|
- "multiplier to increase the likelihood of the transaction landing on-chain."
|
|
|
- );
|
|
|
- return;
|
|
|
+ this.logger.error("Payer is out of balance, please top it up.");
|
|
|
+ throw new Error("Please top up the wallet");
|
|
|
}
|
|
|
|
|
|
if (err.message.includes("could not replace existing tx")) {
|
|
|
this.logger.error(
|
|
|
- { err, receipt },
|
|
|
- "A transaction with the same nonce has been mined and this one is no longer needed."
|
|
|
+ "A transaction with the same nonce has been mined and this one is no longer needed. Skipping this push."
|
|
|
);
|
|
|
return;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- this.logger.error(
|
|
|
- { err, receipt },
|
|
|
- "An unidentified error has occured."
|
|
|
- );
|
|
|
- throw err;
|
|
|
- });
|
|
|
+ // If the error is not handled, we will crash the process.
|
|
|
+ this.logger.error(
|
|
|
+ { err },
|
|
|
+ "The transaction failed with an unhandled error. crashing the process. " +
|
|
|
+ "Please review this error and file an issue if it is a bug."
|
|
|
+ );
|
|
|
+ throw err;
|
|
|
+ }
|
|
|
|
|
|
// Update lastAttempt
|
|
|
this.lastPushAttempt = {
|
|
|
@@ -318,6 +373,29 @@ export class EvmPricePusher implements IPricePusher {
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ private async waitForTransactionReceipt(hash: `0x${string}`): Promise<void> {
|
|
|
+ try {
|
|
|
+ const receipt = await this.client.waitForTransactionReceipt({
|
|
|
+ hash: hash,
|
|
|
+ });
|
|
|
+
|
|
|
+ switch (receipt.status) {
|
|
|
+ case "success":
|
|
|
+ this.logger.debug({ hash, receipt }, "Price update successful");
|
|
|
+ this.logger.info({ hash }, "Price update successful");
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ this.logger.info(
|
|
|
+ { hash, receipt },
|
|
|
+ "Price update did not succeed or its transaction did not land. " +
|
|
|
+ "This is an expected behaviour in high frequency or multi-instance setup."
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } catch (err: any) {
|
|
|
+ this.logger.warn({ err }, "Failed to get transaction receipt");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private async getPriceFeedsUpdateData(
|
|
|
priceIds: HexString[]
|
|
|
): Promise<string[]> {
|
|
|
@@ -327,86 +405,3 @@ export class EvmPricePusher implements IPricePusher {
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-export class PythContractFactory {
|
|
|
- constructor(
|
|
|
- private endpoint: string,
|
|
|
- private mnemonic: string,
|
|
|
- private pythContractAddress: string
|
|
|
- ) {}
|
|
|
-
|
|
|
- /**
|
|
|
- * This method creates a web3 Pyth contract with payer (based on HDWalletProvider). As this
|
|
|
- * provider is an HDWalletProvider it does not support subscriptions even if the
|
|
|
- * endpoint is a websocket endpoint.
|
|
|
- *
|
|
|
- * @returns Pyth contract
|
|
|
- */
|
|
|
- createPythContractWithPayer(): Contract {
|
|
|
- const provider = this.createWeb3PayerProvider();
|
|
|
-
|
|
|
- const web3 = new Web3(provider as any);
|
|
|
-
|
|
|
- return new web3.eth.Contract(
|
|
|
- AbstractPythAbi as any,
|
|
|
- this.pythContractAddress,
|
|
|
- {
|
|
|
- from: provider.getAddress(0),
|
|
|
- }
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * This method creates a web3 Pyth contract with the given endpoint as its provider. If
|
|
|
- * the endpoint is a websocket endpoint the contract will support subscriptions.
|
|
|
- *
|
|
|
- * @returns Pyth contract
|
|
|
- */
|
|
|
- createPythContract(): Contract {
|
|
|
- const provider = this.createWeb3Provider();
|
|
|
- const web3 = new Web3(provider);
|
|
|
- return new web3.eth.Contract(
|
|
|
- AbstractPythAbi as any,
|
|
|
- this.pythContractAddress
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- hasWebsocketProvider(): boolean {
|
|
|
- return isWsEndpoint(this.endpoint);
|
|
|
- }
|
|
|
-
|
|
|
- createWeb3Provider(): HttpProvider | WebsocketProvider {
|
|
|
- if (isWsEndpoint(this.endpoint)) {
|
|
|
- Web3.providers.WebsocketProvider.prototype.sendAsync =
|
|
|
- Web3.providers.WebsocketProvider.prototype.send;
|
|
|
- return new Web3.providers.WebsocketProvider(this.endpoint, {
|
|
|
- clientConfig: {
|
|
|
- keepalive: true,
|
|
|
- keepaliveInterval: 30000,
|
|
|
- },
|
|
|
- reconnect: {
|
|
|
- auto: true,
|
|
|
- delay: 1000,
|
|
|
- onTimeout: true,
|
|
|
- },
|
|
|
- timeout: 30000,
|
|
|
- });
|
|
|
- } else {
|
|
|
- Web3.providers.HttpProvider.prototype.sendAsync =
|
|
|
- Web3.providers.HttpProvider.prototype.send;
|
|
|
- return new Web3.providers.HttpProvider(this.endpoint, {
|
|
|
- keepAlive: true,
|
|
|
- timeout: 30000,
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- createWeb3PayerProvider() {
|
|
|
- return new HDWalletProvider({
|
|
|
- mnemonic: {
|
|
|
- phrase: this.mnemonic,
|
|
|
- },
|
|
|
- providerOrUrl: this.createWeb3Provider() as ProviderOrUrl,
|
|
|
- });
|
|
|
- }
|
|
|
-}
|