Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Max EB Consolidation #294

Draft
wants to merge 2 commits into
base: unstable
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions app/dashboard/validators/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import DashboardWrapper from '../../../src/components/DashboardWrapper/Dashboard
import EditValidatorModal from '../../../src/components/EditValidatorModal/EditValidatorModal'
import Typography from '../../../src/components/Typography/Typography'
import AddValidatorView from '../../../src/components/ValidatorManagement/AddValidatorView/AddValidatorView'
import ConsolidateView from "../../../src/components/ValidatorManagement/ConsolidateView/ConsolidateView";
import CreateValidatorView from '../../../src/components/ValidatorManagement/CreateValidatorView/CreateValidatorView'
import MainView from '../../../src/components/ValidatorManagement/MainView'
import ValidatorModal from '../../../src/components/ValidatorModal/ValidatorModal'
Expand Down Expand Up @@ -67,7 +68,7 @@ const Main: FC<MainProps> = (props) => {
})

const router = useRouter()
const { SECONDS_PER_SLOT, SLOTS_PER_EPOCH } = beaconSpec
const { SECONDS_PER_SLOT, SLOTS_PER_EPOCH, DEPOSIT_CHAIN_ID } = beaconSpec
const setExchangeRate = useSetRecoilState(exchangeRates)
const [search, setSearch] = useState('')
const [activeValId, setValidatorId] = useRecoilState(activeValidatorId)
Expand Down Expand Up @@ -176,22 +177,23 @@ const Main: FC<MainProps> = (props) => {
}

const changeView = (view: ValidatorManagementView) => setView(view)
const viewAddValidator = () => changeView(ValidatorManagementView.ADD)
const viewMain = () => changeView(ValidatorManagementView.MAIN)

const goBack = () => {
let backView = ValidatorManagementView.MAIN
if (view === ValidatorManagementView.CREATE) {
viewAddValidator()
} else {
viewMain()
backView = ValidatorManagementView.ADD
}

changeView(backView)
}
const getPageTitle = (view: string) => {
switch (view) {
case ValidatorManagementView.CREATE:
return t('validatorManagement.titles.create')
case ValidatorManagementView.ADD:
return t('validatorManagement.titles.add')
case ValidatorManagementView.CONSOLIDATE:
return 'Consolidate'
default:
return t('validatorManagement.titles.main')
}
Expand All @@ -204,13 +206,15 @@ const Main: FC<MainProps> = (props) => {
)
case ValidatorManagementView.ADD:
return <AddValidatorView onChangeView={changeView} />
case ValidatorManagementView.CONSOLIDATE:
return <ConsolidateView chainId={Number(DEPOSIT_CHAIN_ID)} validators={validatorStates}/>
default:
return (
<MainView
validators={filteredValidators}
search={search}
onSetSearch={setSearch}
onChangeView={viewAddValidator}
onChangeView={changeView}
scrollPercentage={scrollPercentage}
/>
)
Expand Down
46 changes: 46 additions & 0 deletions contracts/consolidateContractAbt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export const contractAbi = [
{
type: "function",
name: "getFee",
stateMutability: "view",
inputs: [],
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256"
}
]
},
{
type: "function",
name: "submitRequest",
stateMutability: "payable",
inputs: [
{
internalType: "bytes",
name: "source",
type: "bytes"
},
{
internalType: "bytes",
name: "target",
type: "bytes"
}
],
outputs: []
},
{
type: "function",
name: "readRequests",
stateMutability: "nonpayable",
inputs: [],
outputs: [
{
internalType: "bytes",
name: "requestsData",
type: "bytes"
}
]
}
] as const
121 changes: 121 additions & 0 deletions src/components/ConsolidateValidator/Consolidate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {FC, useEffect, useMemo} from "react";
import {useAccount, useSendTransaction, useStorageAt, useBlockNumber, usePublicClient} from "wagmi";
import {CONSOLIDATION_CONTRACT} from "../../constants/constants";
import Button, {ButtonFace} from "../Button/Button";

export interface ConsolidateViewProps {
targetPubKey: string
sourcePubKey: string
chainId: number
}

const Consolidate:FC<ConsolidateViewProps> = ({targetPubKey, sourcePubKey, chainId}) => {
const { address } = useAccount();
const submitRequest = useSendTransaction();
const {data: blockNumber} = useBlockNumber();
const client = usePublicClient({
chainId,
});
const logLookbackRange = 10

const {data, refetch} = useStorageAt({
address: CONSOLIDATION_CONTRACT,
slot: "0x00",
chainId,
});

const getRequiredFee = (numerator: bigint): bigint => {
// https://eips.ethereum.org/EIPS/eip-7251#fee-calculation
let i = 1n;
let output = 0n;
let numeratorAccum = 1n * 17n; // factor * denominator

while (numeratorAccum > 0n) {
output += numeratorAccum;
numeratorAccum = (numeratorAccum * numerator) / (17n * i);
i += 1n;
}

return output / 17n;
}

useEffect(() => {
const interval = setInterval(() => {
refetch();
}, 2000);
return () => {
clearInterval(interval);
};
}, []);

useEffect(() => {
async function fetchLogs() {
if (!blockNumber) return;

const BLOCK_LOOKBACK = BigInt(logLookbackRange);
const fromBlock = blockNumber > BLOCK_LOOKBACK
? blockNumber - BLOCK_LOOKBACK
: 0n;

// Convert bigint to hex (eth_getLogs requires hex strings)
const fromBlockHex = `0x${fromBlock.toString(16)}`;
const toBlockHex = `0x${blockNumber.toString(16)}`;

try {
// Call eth_getLogs directly with request
const response = await client.request({
method: 'eth_getLogs',
params: [
{
address: CONSOLIDATION_CONTRACT, // Replace with your contract address
fromBlock: fromBlockHex,
toBlock: toBlockHex,
},
],
});

const averageBlogPerLookBack = response.length / logLookbackRange

console.log(averageBlogPerLookBack)

} catch (error) {
console.error('eth_getLogs error:', error);
}
}

fetchLogs();
}, [blockNumber, client]);

const requestFee = useMemo(() => {
if(!data) return 0n

return getRequiredFee(BigInt(data))
}, [data])

const submitConsolidation = async () => {
try {
submitRequest.sendTransactionAsync({
to: CONSOLIDATION_CONTRACT,
account: address,
chainId,
value: requestFee,
data: "0x" + sourcePubKey.substring(2) + targetPubKey.substring(2),
gas: 200000n,
}).then(tx => {
console.log(`Success, tx-hash: ${tx}`);
}).catch(error => {
console.log('error', error.message);
});
} catch (e) {
console.log(e)
}
}

return (
<div>
<Button type={ButtonFace.SECONDARY} onClick={submitConsolidation}>Submit</Button>
</div>
)
}

export default Consolidate
70 changes: 70 additions & 0 deletions src/components/HorizontalStepper/HorizontalStepper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {motion} from "framer-motion";
import Carousel from "nuka-carousel";
import {FC, ReactElement, useState, Children, isValidElement, Fragment} from "react";
import ProgressBar from "../ProgressBar/ProgressBar";
import Typography from "../Typography/Typography";

export interface StepperRenderProps {
incrementStep: () => void
decrementStep: () => void
step: number
}

export interface HorizontalStepperProps {
children: (props: StepperRenderProps) => ReactElement | ReactElement[];
steps: string[]
}

const HorizontalStepper:FC<HorizontalStepperProps> = ({children, steps}) => {
const totalSteps = steps.length
const [step, setStep] = useState(0)
const incrementStep = () => setStep((prevStep) => Math.min(prevStep + 1, totalSteps - 1))
const decrementStep = () => setStep((prevStep) => Math.max(prevStep - 1, 0))

const renderedChildren = children({ incrementStep, decrementStep, step })

let slidesArray = Children.toArray(renderedChildren).flatMap((child) => {
if (isValidElement(child) && child.type === Fragment) {
return Children.toArray(child.props.children)
}
return child
})

return (
<div className='flex-1'>
<div className='w-full'>
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className='w-full flex'>
{steps.map((step, index) => (
<div
key={index}
className='flex-1 h-11 bg-dark25 dark:bg-dark750 flex items-center justify-center border-r dark:border-r-dark600 last:border-r-0'
>
<div className='flex space-x-2 items-center'>
<div className='w-6 h-6 lg:w-3 lg:h-3 flex items-center justify-center border dark:border-dark300 text-dark900 rounded-full'>
<Typography type='text-caption' className='lg:text-xTiny'>
{index + 1}
</Typography>
</div>
<Typography className='hidden lg:block' type='text-caption1'>
{step}
</Typography>
</div>
</div>
))}
</motion.div>
<ProgressBar total={totalSteps} position={step + 1} />
</div>
<div className='w-full h-full relative createSlide'>
<Carousel swiping={false} slideIndex={step} dragging={false} withoutControls>
{slidesArray.map((child, index) => (
<div key={index}>
{child}
</div>
))}
</Carousel>
</div>
</div>
)
}

export default HorizontalStepper
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {FC, useMemo, useState} from "react";
import {ValidatorInfo} from "../../../types/validator";
import HorizontalStepper from "../../HorizontalStepper/HorizontalStepper";
import SelectSourceStep from "./Steps/SelectSourceStep/SelectSourceStep";
import SelectTargetStep from "./Steps/SelectTargetStep/SelectTargetStep";
import SubmitConsolidationStep from "./Steps/SubmitConsolidationStep/SubmitConsolidationStep";

export interface ConsolidateViewProps {
validators: ValidatorInfo[]
chainId: number
}

const ConsolidateView:FC<ConsolidateViewProps> = ({validators, chainId}) => {
const [targetValidator, setTargetValidator] = useState<ValidatorInfo | undefined>(undefined)
const [sourceValidators, setSourceValidators] = useState<ValidatorInfo[]>([])
const activeValidators = useMemo(() => {
return validators.filter(({status}) => status.includes('active'))
}, [validators])

const setTargetValidatorPubKey = (validator: ValidatorInfo) => setTargetValidator(validator)
const setSourceValidatorPubKeys = (validators: ValidatorInfo[]) => setSourceValidators(validators)

return (
<HorizontalStepper steps={['Select Target', 'Select Sources', 'Sign & Submit']}>
{({incrementStep, decrementStep}) => (
<>
<SelectTargetStep onNext={incrementStep} targetValidator={targetValidator} onSelect={setTargetValidatorPubKey} validators={activeValidators}/>
<SelectSourceStep onNext={incrementStep} onBack={decrementStep} onSelectTargetValidators={setSourceValidatorPubKeys} validators={activeValidators} targetValidator={targetValidator} />
<SubmitConsolidationStep chainId={chainId} targetValidator={targetValidator} sourceValidators={sourceValidators}/>
</>
)}
</HorizontalStepper>
)
}

export default ConsolidateView
Loading