From e46c4614fb3e0af1c6b49c879a8088794df4afc4 Mon Sep 17 00:00:00 2001 From: Ali Behjati Date: Wed, 21 Aug 2024 13:05:49 +0200 Subject: [PATCH] refactor: remove PythContractFactory class --- apps/price_pusher/src/evm/command.ts | 25 +-- apps/price_pusher/src/evm/evm.ts | 168 ++------------------- apps/price_pusher/src/evm/pyth-contract.ts | 18 +++ apps/price_pusher/src/evm/super-wallet.ts | 71 +++++++++ 4 files changed, 115 insertions(+), 167 deletions(-) create mode 100644 apps/price_pusher/src/evm/pyth-contract.ts create mode 100644 apps/price_pusher/src/evm/super-wallet.ts diff --git a/apps/price_pusher/src/evm/command.ts b/apps/price_pusher/src/evm/command.ts index 183ff0ed6a..caef5bdeac 100644 --- a/apps/price_pusher/src/evm/command.ts +++ b/apps/price_pusher/src/evm/command.ts @@ -8,6 +8,9 @@ import { Controller } from "../controller"; import { EvmPriceListener, EvmPricePusher, PythContractFactory } from "./evm"; import { getCustomGasStation } from "./custom-gas-station"; import pino from "pino"; +import { createClient } from "./super-wallet"; +import { createPythContract } from "./pyth-contract"; +import { isWsEndpoint } from "../utils"; export default { command: "evm", @@ -121,21 +124,22 @@ export default { logger.child({ module: "PythPriceListener" }) ); - const pythContractFactory = await PythContractFactory.create( - endpoint, - mnemonic, - pythContractAddress - ); + const client = await createClient(endpoint, mnemonic); + const pythContract = createPythContract(client, pythContractAddress); logger.info( - `Pushing updates from wallet address: ${ - pythContractFactory.getAccount().address - }` + `Pushing updates from wallet address: ${client.account.address}` ); + // It is possible to watch the events in the non-ws endpoints, either by getFilter + // or by getLogs, but it is very expensive and our polling mechanism does it + // in a more efficient way. So we only do it with ws endpoints. + const watchEvents = isWsEndpoint(endpoint); + const evmListener = new EvmPriceListener( - pythContractFactory, + pythContract, priceItems, + watchEvents, logger.child({ module: "EvmPriceListener" }), { pollingFrequency, @@ -149,7 +153,8 @@ export default { ); const evmPusher = new EvmPricePusher( priceServiceConnection, - pythContractFactory, + client, + pythContract, logger.child({ module: "EvmPricePusher" }), overrideGasPriceMultiplier, overrideGasPriceMultiplierCap, diff --git a/apps/price_pusher/src/evm/evm.ts b/apps/price_pusher/src/evm/evm.ts index bc588cbf90..736c516f18 100644 --- a/apps/price_pusher/src/evm/evm.ts +++ b/apps/price_pusher/src/evm/evm.ts @@ -12,7 +12,6 @@ import { } from "../utils"; import { PythAbi } from "./pyth-abi"; import { Logger } from "pino"; -import { isWsEndpoint } from "../utils"; import { PriceServiceConnection, HexString, @@ -21,94 +20,38 @@ import { import { CustomGasStation } from "./custom-gas-station"; import { PushAttempt } from "../common"; import { - PublicClient, - Transport, - WalletClient, - createPublicClient, - createWalletClient, - getContract, - defineChain, - http, - webSocket, - Address, - GetContractReturnType, WatchContractEventOnLogsParameter, TransactionExecutionError, - Account, BaseError, ContractFunctionRevertedError, FeeCapTooLowError, InternalRpcError, InsufficientFundsError, - Chain, - publicActions, - Client, - RpcSchema, - WalletActions, - PublicActions, } from "viem"; -import { mnemonicToAccount } from "viem/accounts"; -import * as chains from "viem/chains"; - -type PythContract = GetContractReturnType< - typeof PythAbi, - PublicClient | WalletClient ->; - -export type SuperWalletClient< - transport extends Transport = Transport, - chain extends Chain | undefined = Chain, - account extends Account | undefined = Account -> = Client< - transport, - chain, - account, - RpcSchema, - PublicActions & WalletActions ->; - -const UNKNOWN_CHAIN_CONFIG = { - name: "Unknown", - nativeCurrency: { - name: "Unknown", - symbol: "Unknown", - decimals: 18, - }, - rpcUrls: { - default: { - http: [], - }, - }, -}; +import { PythContract } from "./pyth-contract"; +import { SuperWalletClient } from "./super-wallet"; export class EvmPriceListener extends ChainPriceListener { - private pythContractFactory: PythContractFactory; - private pythContract: PythContract; - 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() { - // It is possible to watch the events in the non-ws endpoints, either by getFilter - // or by getLogs, but it is very expensive and our polling mechanism does it - // in a more efficient way. - if (this.pythContractFactory.hasWebsocketProvider()) { + if (this.watchEvents) { this.logger.info("Watching target network pyth contract events..."); this.startWatching(); } else { @@ -180,26 +123,20 @@ export class EvmPriceListener extends ChainPriceListener { } export class EvmPricePusher implements IPricePusher { - private customGasStation?: CustomGasStation; - private client: SuperWalletClient; - private pythContract: PythContract; 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.createPythContract(); - this.client = pythContractFactory.createClient(); - } + 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) @@ -468,86 +405,3 @@ export class EvmPricePusher implements IPricePusher { ); } } - -export class PythContractFactory { - private endpoint: string; - private mnemonic: string; - private pythContractAddress: Address; - private chainId: number; - - private constructor( - endpoint: string, - mnemonic: string, - pythContractAddress: Address, - chainId: number - ) { - this.endpoint = endpoint; - this.mnemonic = mnemonic; - this.pythContractAddress = pythContractAddress; - this.chainId = chainId; - } - - static async create( - endpoint: string, - mnemonic: string, - pythContractAddress: Address - ): Promise { - const chainId = await createPublicClient({ - transport: PythContractFactory.getTransport(endpoint), - }).getChainId(); - return new PythContractFactory( - endpoint, - mnemonic, - pythContractAddress, - chainId - ); - } - - /** - * This method creates a web3 Pyth contract with payer (based on mnemonic). - * - * @returns Pyth contract - */ - createPythContract(): PythContract { - return getContract({ - address: this.pythContractAddress, - abi: PythAbi, - client: this.createClient(), - }); - } - - hasWebsocketProvider(): boolean { - return isWsEndpoint(this.endpoint); - } - - getAccount(): Account { - return mnemonicToAccount(this.mnemonic); - } - - createClient(): SuperWalletClient { - return createWalletClient({ - transport: PythContractFactory.getTransport(this.endpoint), - account: mnemonicToAccount(this.mnemonic), - chain: PythContractFactory.getChain(this.chainId), - }).extend(publicActions); - } - - // Get the chain corresponding to the chainId. If the chain is not found, it will return - // an unknown chain which should work fine in most of the cases. We might need to update - // the viem package to support new chains if they don't work as expected with the unknown - // chain. - private static getChain(chainId: number): Chain { - return ( - Object.values(chains).find((chain) => chain.id === chainId) || - defineChain({ id: chainId, ...UNKNOWN_CHAIN_CONFIG }) - ); - } - - private static getTransport(endpoint: string): Transport { - if (isWsEndpoint(endpoint)) { - return webSocket(endpoint); - } else { - return http(endpoint); - } - } -} diff --git a/apps/price_pusher/src/evm/pyth-contract.ts b/apps/price_pusher/src/evm/pyth-contract.ts new file mode 100644 index 0000000000..f5092696e4 --- /dev/null +++ b/apps/price_pusher/src/evm/pyth-contract.ts @@ -0,0 +1,18 @@ +import { getContract, Address, GetContractReturnType } from "viem"; +import { PythAbi } from "./pyth-abi"; +import { SuperWalletClient } from "./super-wallet"; + +export type PythContract = GetContractReturnType< + typeof PythAbi, + SuperWalletClient +>; + +export const createPythContract = ( + client: SuperWalletClient, + address: Address +): PythContract => + getContract({ + client, + abi: PythAbi, + address, + }); diff --git a/apps/price_pusher/src/evm/super-wallet.ts b/apps/price_pusher/src/evm/super-wallet.ts new file mode 100644 index 0000000000..9462ec81fa --- /dev/null +++ b/apps/price_pusher/src/evm/super-wallet.ts @@ -0,0 +1,71 @@ +import { + createPublicClient, + createWalletClient, + defineChain, + http, + webSocket, + Account, + Chain, + publicActions, + Client, + RpcSchema, + WalletActions, + PublicActions, + WebSocketTransport, + HttpTransport, + Transport, +} from "viem"; +import { mnemonicToAccount } from "viem/accounts"; +import * as chains from "viem/chains"; +import { isWsEndpoint } from "../utils"; + +const UNKNOWN_CHAIN_CONFIG = { + name: "Unknown", + nativeCurrency: { + name: "Unknown", + symbol: "Unknown", + decimals: 18, + }, + rpcUrls: { + default: { + http: [], + }, + }, +}; + +export type SuperWalletClient = Client< + Transport, + Chain, + Account, + RpcSchema, + PublicActions & WalletActions +>; + +// Get the transport based on the endpoint +const getTransport = (endpoint: string): WebSocketTransport | HttpTransport => + isWsEndpoint(endpoint) ? webSocket(endpoint) : http(endpoint); + +// Get the chain corresponding to the chainId. If the chain is not found, it will return +// an unknown chain which should work fine in most of the cases. We might need to update +// the viem package to support new chains if they don't work as expected with the unknown +// chain. +const getChainById = (chainId: number): Chain => + Object.values(chains).find((chain) => chain.id === chainId) || + defineChain({ id: chainId, ...UNKNOWN_CHAIN_CONFIG }); + +export const createClient = async ( + endpoint: string, + mnemonic: string +): Promise => { + const transport = getTransport(endpoint); + + const chainId = await createPublicClient({ + transport, + }).getChainId(); + + return createWalletClient({ + transport, + account: mnemonicToAccount(mnemonic), + chain: getChainById(chainId), + }).extend(publicActions); +};