diff --git a/abi/partialStakingRouter.abi.json b/abi/partialStakingRouter.abi.json new file mode 100644 index 000000000..3d98c089e --- /dev/null +++ b/abi/partialStakingRouter.abi.json @@ -0,0 +1,125 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_depositContract", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "stakingModuleId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "stakingModuleFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "treasuryFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "setBy", + "type": "address" + } + ], + "name": "StakingModuleFeesSet", + "type": "event" + }, + { + "inputs": [], + "name": "FEE_PRECISION_POINTS", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TOTAL_BASIS_POINTS", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLido", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStakingFeeAggregateDistribution", + "outputs": [ + { "internalType": "uint96", "name": "modulesFee", "type": "uint96" }, + { "internalType": "uint96", "name": "treasuryFee", "type": "uint96" }, + { "internalType": "uint256", "name": "basePrecision", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStakingFeeAggregateDistributionE4Precision", + "outputs": [ + { "internalType": "uint16", "name": "modulesFee", "type": "uint16" }, + { "internalType": "uint16", "name": "treasuryFee", "type": "uint16" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStakingRewardsDistribution", + "outputs": [ + { + "internalType": "address[]", + "name": "recipients", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "stakingModuleIds", + "type": "uint256[]" + }, + { + "internalType": "uint96[]", + "name": "stakingModuleFees", + "type": "uint96[]" + }, + { "internalType": "uint96", "name": "totalFee", "type": "uint96" }, + { + "internalType": "uint256", + "name": "precisionPoints", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalFeeE4Precision", + "outputs": [ + { "internalType": "uint16", "name": "totalFee", "type": "uint16" } + ], + "stateMutability": "view", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/consts/staking-router.ts b/consts/staking-router.ts new file mode 100644 index 000000000..8cd8c95e1 --- /dev/null +++ b/consts/staking-router.ts @@ -0,0 +1,23 @@ +import { CHAINS } from '@lido-sdk/constants'; +import { contractHooksFactory } from '@lido-sdk/react'; +import invariant from 'tiny-invariant'; + +import { PartialStakingRouterAbi__factory } from 'generated/factories/PartialStakingRouterAbi__factory'; + +export const STAKING_ROUTER_BY_NETWORK: { + [key in CHAINS]?: string; +} = { + [CHAINS.Mainnet]: '0xFdDf38947aFB03C621C71b06C9C70bce73f12999', + [CHAINS.Holesky]: '0xd6EbF043D30A7fe46D1Db32BA90a0A51207FE229', +}; + +export const getStakingRouterAddress = (chainId: CHAINS): string => { + const address = STAKING_ROUTER_BY_NETWORK[chainId]; + invariant(address, 'chain is not supported'); + return address; +}; + +export const stakingRouter = contractHooksFactory( + PartialStakingRouterAbi__factory, + (chainId) => getStakingRouterAddress(chainId), +); diff --git a/features/stake/stake-faq/list/lido-fee.tsx b/features/stake/stake-faq/list/lido-fee.tsx index afb74b737..6e0fa1eb8 100644 --- a/features/stake/stake-faq/list/lido-fee.tsx +++ b/features/stake/stake-faq/list/lido-fee.tsx @@ -1,19 +1,16 @@ import { FC } from 'react'; import { Accordion } from '@lidofinance/lido-ui'; -import { useContractSWR, useSTETHContractRPC } from '@lido-sdk/react'; import { DATA_UNAVAILABLE } from 'consts/text'; +import { useProtocolFee } from 'shared/hooks/use-protocol-fee'; export const LidoFee: FC = () => { - const contractRpc = useSTETHContractRPC(); - const lidoFee = useContractSWR({ - contract: contractRpc, - method: 'getFee', - }); + const protocolFee = useProtocolFee(); + const feeValue = - lidoFee.initialLoading || !lidoFee.data + protocolFee.initialLoading || !protocolFee.data ? DATA_UNAVAILABLE - : `${lidoFee.data / 100}%`; + : `${protocolFee.data}%`; return ( diff --git a/features/stake/stake-form/stake-form-info.tsx b/features/stake/stake-form/stake-form-info.tsx index 9f4ff107b..d2252f674 100644 --- a/features/stake/stake-form/stake-form-info.tsx +++ b/features/stake/stake-form/stake-form-info.tsx @@ -2,25 +2,20 @@ import { useWatch } from 'react-hook-form'; import { Zero } from '@ethersproject/constants'; import { DataTable, DataTableRow } from '@lidofinance/lido-ui'; -import { useContractSWR, useSTETHContractRPC } from '@lido-sdk/react'; import { DATA_UNAVAILABLE } from 'consts/text'; -import { STRATEGY_CONSTANT } from 'consts/swr-strategies'; import { FormatPrice, FormatToken } from 'shared/formatters'; import { useEthUsd } from 'shared/hooks/use-eth-usd'; +import { useProtocolFee } from 'shared/hooks/use-protocol-fee'; import { StakeFormInput, useStakeFormData } from './stake-form-context'; export const StakeFormInfo = () => { const { gasCost, loading } = useStakeFormData(); const amount = useWatch({ name: 'amount' }); - const contractRpc = useSTETHContractRPC(); - const lidoFee = useContractSWR({ - contract: contractRpc, - method: 'getFee', - config: STRATEGY_CONSTANT, - }); + const { usdAmount, initialLoading: isEthUsdLoading } = useEthUsd(gasCost); + const protocolFee = useProtocolFee(); return ( @@ -40,11 +35,11 @@ export const StakeFormInfo = () => { - {!lidoFee.data ? DATA_UNAVAILABLE : `${lidoFee.data / 100}%`} + {!protocolFee.data ? DATA_UNAVAILABLE : `${protocolFee.data}%`} ); diff --git a/shared/hooks/use-protocol-fee.ts b/shared/hooks/use-protocol-fee.ts new file mode 100644 index 000000000..c5b2a06cc --- /dev/null +++ b/shared/hooks/use-protocol-fee.ts @@ -0,0 +1,46 @@ +import { formatEther } from '@ethersproject/units'; + +import { CHAINS } from '@lido-sdk/constants'; +import { useLidoSWR, useSDK } from '@lido-sdk/react'; +import { useStakingRouter } from './use-stakign-router-contract'; +import { PartialStakingRouterAbi } from 'generated/PartialStakingRouterAbi'; + +import { config } from 'config'; +import { STRATEGY_CONSTANT } from 'consts/swr-strategies'; + +export const useProtocolFee = () => { + const { chainId } = useSDK(); + const { contractRpc } = useStakingRouter(); + + return useLidoSWR( + ['swr:useProtocolFee', chainId, contractRpc, config.enableQaHelpers], + // @ts-expect-error broken lidoSWR typings + async ( + _key: string, + _chainId: CHAINS, + contractRpc: PartialStakingRouterAbi, + shouldMock: boolean, + ) => { + const mockDataString = window.localStorage.getItem('protocolFee'); + + if (shouldMock && mockDataString) { + try { + const mockData = JSON.parse(mockDataString); + return mockData; + } catch (e) { + console.warn('Failed to load mock data'); + console.warn(e); + } + } + + const fee = await contractRpc.getStakingFeeAggregateDistribution(); + const value = Number(formatEther(fee.modulesFee.add(fee.treasuryFee))); + + return value.toFixed(0); + }, + { + ...STRATEGY_CONSTANT, + refreshInterval: 60000, + }, + ); +}; diff --git a/shared/hooks/use-stakign-router-contract.ts b/shared/hooks/use-stakign-router-contract.ts new file mode 100644 index 000000000..12bc17df4 --- /dev/null +++ b/shared/hooks/use-stakign-router-contract.ts @@ -0,0 +1,7 @@ +import { stakingRouter } from 'consts/staking-router'; + +export const useStakingRouter = () => { + const contractRpc = stakingRouter.useContractRPC(); + + return { contractRpc }; +};