diff --git a/packages/core/src/common/utils/getFeeData.ts b/packages/core/src/common/utils/getFeeData.ts index daad209d..af1b9c16 100644 --- a/packages/core/src/common/utils/getFeeData.ts +++ b/packages/core/src/common/utils/getFeeData.ts @@ -1,4 +1,4 @@ -import { Client } from "viem"; +import { PublicClient, type BlockTag } from "viem"; export type FeeData = { lastBaseFeePerGas: null | bigint; @@ -7,35 +7,22 @@ export type FeeData = { gasPrice: null | bigint; }; -const getFeeHistory = ( - provider: Client, - blockCount: number, - latestBlock: string, - percentile?: number[] -): Promise<{ - baseFeePerGas: string[]; - gasUsedRatio: number[]; - oldestBlock: string; - reward: string[][]; -}> => { - return provider.request({ - method: "eth_feeHistory", - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - params: [`0x${blockCount.toString(16)}`, latestBlock, percentile], - }); -}; - -export const getFeeData = async (provider: Client): Promise => { +export const getFeeData = async (provider: PublicClient): Promise => { // we look back 5 blocks at fees of botton 25% txs // if you want to increase maxPriorityFee output increase percentile - const feeHistory = await getFeeHistory(provider, 5, "pending", [25]); + const feeHistory = await provider.getFeeHistory({ + blockCount: 5, + blockTag: "pending", + rewardPercentiles: [25], + }); // get average priority fee const maxPriorityFeePerGas = - feeHistory.reward - .map((fees) => (fees[0] ? BigInt(fees[0]) : 0n)) - .reduce((sum, fee) => sum + fee) / BigInt(feeHistory.reward.length); + feeHistory.reward && feeHistory.reward.length > 0 + ? feeHistory.reward + .map((fees) => (fees[0] ? BigInt(fees[0]) : 0n)) + .reduce((sum, fee) => sum + fee) / BigInt(feeHistory.reward.length) + : 0n; const lastBaseFeePerGas = feeHistory.baseFeePerGas[0] ? BigInt(feeHistory.baseFeePerGas[0]) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 0903b7b0..2a5f6f23 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -18,21 +18,18 @@ import { SUPPORTED_CHAINS } from "./contants"; import { LidoSDKCoreProps } from "./types"; export default class LidoSDKCore { - readonly chainId: (typeof SUPPORTED_CHAINS)[number] | undefined; - readonly rpcUrls: string[] = []; - readonly rpcProvider: PublicClient | undefined; - readonly web3Provider: WalletClient | undefined; - readonly chain: Chain | undefined; + readonly chainId: (typeof SUPPORTED_CHAINS)[number]; + readonly rpcUrls: string[] | undefined; + readonly rpcProvider: PublicClient; + readonly web3Provider: WalletClient; + readonly chain: Chain; constructor(props: LidoSDKCoreProps, version?: string) { - const { chain, chainId, rpcUrls, rpcProvider, web3Provider } = this.init( - props, - version - ); + const { chain, rpcProvider, web3Provider } = this.init(props, version); - this.chainId = chainId; + this.chainId = props.chainId; this.chain = chain; - this.rpcUrls = rpcUrls; + this.rpcUrls = props.rpcUrls; this.rpcProvider = rpcProvider; this.web3Provider = web3Provider; } @@ -40,24 +37,29 @@ export default class LidoSDKCore { @Initialize("Init:") @Logger("LOG:") private init(props: LidoSDKCoreProps, _version?: string) { - const { chainId, rpcUrls, web3Provider } = props; + const { chainId, rpcUrls, web3Provider, rpcProvider } = props; if (!SUPPORTED_CHAINS.includes(chainId)) { throw new Error(`Unsupported chain: ${chainId}`); } - if (rpcUrls.length === 0) { + if (!rpcProvider && rpcUrls.length === 0) { throw new Error("rpcUrls is required"); } + if (!rpcUrls && !rpcProvider) { + throw new Error("rpcUrls or rpcProvider is required"); + } + const chain = chainId === 1 ? mainnet : goerli; + const currentRpcProvider = + rpcProvider ?? this.createRpcProvider(chain, rpcUrls); + const currentWeb3Provider = web3Provider ?? this.createWeb3Provider(chain); return { - chainId, chain, - rpcUrls, - rpcProvider: this.createRpcProvider(chain, rpcUrls), - web3Provider: web3Provider ?? this.createWeb3Provider(chain), + rpcProvider: currentRpcProvider, + web3Provider: currentWeb3Provider, }; } @@ -141,7 +143,7 @@ export default class LidoSDKCore { @ErrorHandler("Utils:") @Logger("Utils:") - @Cache(30 * 1000) + @Cache(60 * 60 * 1000) public async isContract(address: Address): Promise { invariant(this.rpcProvider, "RPC provider is not defined"); const { isContract } = await checkIsContract(this.rpcProvider, address); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 0801e7b4..3b7b4592 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,9 +1,20 @@ -import { WalletClient } from "viem"; +import { WalletClient, PublicClient } from "viem"; import { SUPPORTED_CHAINS } from "./contants"; -export type LidoSDKCoreProps = { +type LidoSDKCorePropsRpcUrls = { chainId: (typeof SUPPORTED_CHAINS)[number]; rpcUrls: string[]; web3Provider?: WalletClient; + rpcProvider?: undefined; }; +type LidoSDKCorePropsRpcProvider = { + chainId: (typeof SUPPORTED_CHAINS)[number]; + rpcUrls: undefined; + web3Provider?: WalletClient; + rpcProvider: PublicClient; +}; + +export type LidoSDKCoreProps = + | LidoSDKCorePropsRpcUrls + | LidoSDKCorePropsRpcProvider; diff --git a/packages/stake/src/stake.ts b/packages/stake/src/stake.ts index 130f8b73..7dfc848c 100644 --- a/packages/stake/src/stake.ts +++ b/packages/stake/src/stake.ts @@ -21,7 +21,12 @@ import {} from "@lido-sdk/contracts/dist/cjs"; import { SUBMIT_EXTRA_GAS_TRANSACTION_RATIO } from "./common/constants"; import { version } from "./version"; import { abi } from "./abi/abi"; -import { LidoSDKStakeProps, StakeCallbackStage, StakeProps } from "./types"; +import { + LidoSDKStakeProps, + StakeCallbackStage, + StakeProps, + StakeResult, +} from "./types"; // TODO: move to constants @@ -76,23 +81,23 @@ export class LidoSDKStake { @ErrorHandler("Call:") @Logger("Call:") - public async stake(props: StakeProps): Promise { + public async stake(props: StakeProps): Promise { const address = await this.core.getWeb3Address(); const isContract = await this.core.isContract(address); - if (isContract) await this.stakeMultisig(props); - else await this.stakeEOA(props); + if (isContract) return await this.stakeMultisig(props); + else return await this.stakeEOA(props); } @ErrorHandler("Call:") @Logger("LOG:") - private async stakeEOA(props: StakeProps): Promise { + private async stakeEOA(props: StakeProps): Promise { const { value, callback, referralAddress = zeroAddress } = props; invariant(this.core.rpcProvider, "RPC provider is not defined"); invariant(this.core.web3Provider, "Web3 provider is not defined"); // Checking the daily protocol staking limit - await this.checkStakeLimit(value); + await this.validateStakeLimit(value); const { gasLimit, overrides } = await this.submitGasLimit( value, @@ -100,7 +105,7 @@ export class LidoSDKStake { ); const address = await this.core.getWeb3Address(); - callback(StakeCallbackStage.SIGN); + callback({ stage: StakeCallbackStage.SIGN }); const transaction = await this.getContractStETH().write.submit( [referralAddress], @@ -113,37 +118,49 @@ export class LidoSDKStake { } ); - callback(StakeCallbackStage.RECEIPT, transaction); + callback({ stage: StakeCallbackStage.RECEIPT, payload: transaction }); const transactionReceipt = await this.core.rpcProvider.waitForTransactionReceipt({ hash: transaction, }); - callback(StakeCallbackStage.CONFIRMATION, transactionReceipt); + callback({ + stage: StakeCallbackStage.CONFIRMATION, + payload: transactionReceipt, + }); const confirmations = await this.core.rpcProvider.getTransactionConfirmations({ hash: transactionReceipt.transactionHash, }); - callback(StakeCallbackStage.DONE, confirmations); + callback({ stage: StakeCallbackStage.DONE, payload: confirmations }); + + return { hash: transaction, receipt: transactionReceipt, confirmations }; } @ErrorHandler("Call:") @Logger("LOG:") - private async stakeMultisig(props: StakeProps): Promise { + private async stakeMultisig(props: StakeProps): Promise { const { value, callback, referralAddress = zeroAddress } = props; const address = await this.core.getWeb3Address(); - await this.getContractStETH().write.submit([referralAddress], { - value: parseEther(value), - chain: this.core.chain, - account: address, - }); + callback({ stage: StakeCallbackStage.SIGN }); + + const transaction = await this.getContractStETH().write.submit( + [referralAddress], + { + value: parseEther(value), + chain: this.core.chain, + account: address, + } + ); + + callback({ stage: StakeCallbackStage.MULTISIG_DONE }); - callback(StakeCallbackStage.MULTISIG_DONE); + return { hash: transaction }; } // Views @@ -201,7 +218,7 @@ export class LidoSDKStake { @ErrorHandler("Utils:") @Logger("Utils:") - private async checkStakeLimit(value: string): Promise { + private async validateStakeLimit(value: string): Promise { const currentStakeLimit = (await this.getStakeLimitInfo())[3]; const parsedValue = parseEther(value); diff --git a/packages/stake/src/types.ts b/packages/stake/src/types.ts index ec662606..c553a24c 100644 --- a/packages/stake/src/types.ts +++ b/packages/stake/src/types.ts @@ -1,4 +1,4 @@ -import { type Address } from "viem"; +import { type Address, type Hash, type TransactionReceipt } from "viem"; import { type LidoSDKCoreProps, type LidoSDKCore, @@ -16,12 +16,23 @@ export enum StakeCallbackStage { "MULTISIG_DONE" = "multisig_done", } -export type StageCallback = ( - stage: StakeCallbackStage, - payload?: unknown -) => void; +export type StakeCallbackProps = + | { stage: StakeCallbackStage.SIGN; payload?: undefined } + | { stage: StakeCallbackStage.RECEIPT; payload: Hash } + | { stage: StakeCallbackStage.CONFIRMATION; payload: TransactionReceipt } + | { stage: StakeCallbackStage.DONE; payload: bigint } + | { stage: StakeCallbackStage.MULTISIG_DONE; payload?: undefined }; + +export type StageCallback = (props: StakeCallbackProps) => void; + export type StakeProps = { value: string; callback: StageCallback; referralAddress?: Address; }; + +export type StakeResult = { + hash: Hash; + receipt?: TransactionReceipt; + confirmations?: bigint; +};