From f7fcd5ec84416471b942dc1e8b8ec856d8b42c27 Mon Sep 17 00:00:00 2001 From: Mohan Date: Tue, 5 Nov 2024 23:27:39 +0530 Subject: [PATCH 01/85] feat: Inform users that there are no available slots on staking contract and prompt them to switch (#421) * fix: improve error messages in AgentButton component * feat: add NoAvailableSlotsOnTheContract component * feat: integrate NoAvailableSlotsOnTheContract component and enhance service slot checks * feat: implement useCanStartUpdateStakingContract hook and update related components * feat: add available slots display and max-width styling to StakingContractDetails * feat: add POPOVER_WIDTH_LARGE constant and apply max-width styling to CountdownUntilMigration component * fix: correct spelling of 'occurred' in error notifications in AgentButton component * refactor: rename useCanStartUpdateStakingContract hook to useCanUpdateStakingContract and update related components * feat: update StakingContractUpdate and NoAvailableSlotsOnTheContract components to accept stakingProgramId prop and remove useCanUpdateStakingContract hook * chore: bump version to 0.1.0-rc186 in package.json and pyproject.toml --- .../MainPage/header/AgentButton.tsx | 15 ++++-- frontend/components/MainPage/index.tsx | 7 ++- .../NoAvailableSlotsOnTheContract.tsx | 51 +++++++++++++++++++ .../MainPage/sections/AlertSections/index.tsx | 9 ++++ .../sections/StakingContractUpdate.tsx | 19 +++---- .../CountdownUntilMigration.tsx | 3 +- .../StakingContractDetails.tsx | 6 +++ .../StakingContractSection/useMigrate.tsx | 18 +++++++ frontend/constants/symbols.ts | 2 + frontend/constants/width.ts | 2 + frontend/styles/globals.scss | 4 +- frontend/types/Autonolas.ts | 1 + package.json | 2 +- pyproject.toml | 2 +- 14 files changed, 120 insertions(+), 21 deletions(-) create mode 100644 frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx diff --git a/frontend/components/MainPage/header/AgentButton.tsx b/frontend/components/MainPage/header/AgentButton.tsx index f2f76511..eb24147e 100644 --- a/frontend/components/MainPage/header/AgentButton.tsx +++ b/frontend/components/MainPage/header/AgentButton.tsx @@ -89,7 +89,7 @@ const AgentRunningButton = () => { await ServicesService.stopDeployment(service.hash); } catch (error) { console.error(error); - showNotification?.('Error while stopping agent'); + showNotification?.('Some error occurred while stopping agent'); } finally { // Resume polling, will update to correct status regardless of success setIsServicePollingPaused(false); @@ -145,6 +145,7 @@ const AgentNotRunningButton = () => { isAgentEvicted, setIsPaused: setIsStakingContractInfoPollingPaused, updateActiveStakingContractInfo, + hasEnoughServiceSlots, } = useStakingContractInfo(); const { activeStakingProgramId, defaultStakingProgramId } = useStakingProgram(); @@ -192,7 +193,7 @@ const AgentNotRunningButton = () => { } catch (error) { console.error(error); setServiceStatus(undefined); - showNotification?.('Error while creating safe'); + showNotification?.('Some error occurred while creating safe'); setIsStakingContractInfoPollingPaused(false); setIsServicePollingPaused(false); setIsBalancePollingPaused(false); @@ -211,7 +212,7 @@ const AgentNotRunningButton = () => { } catch (error) { console.error(error); setServiceStatus(undefined); - showNotification?.('Error while deploying service'); + showNotification?.('Some error occurred while deploying service'); setIsServicePollingPaused(false); setIsBalancePollingPaused(false); setIsStakingContractInfoPollingPaused(false); @@ -223,7 +224,9 @@ const AgentNotRunningButton = () => { showNotification?.(`Your agent is running!`); } catch (error) { console.error(error); - showNotification?.('Error while showing "running" notification'); + showNotification?.( + 'Some error occurred while showing "running" notification', + ); } // Can assume successful deployment @@ -278,6 +281,9 @@ const AgentNotRunningButton = () => { if (!requiredOlas) return false; + // If no slots available, agent cannot be started + if (!hasEnoughServiceSlots) return false; + // case where service exists & user has initial funded if (service && storeState?.isInitialFunded) { if (!safeOlasBalanceWithStaked) return false; @@ -302,6 +308,7 @@ const AgentNotRunningButton = () => { requiredOlas, totalEthBalance, isLowBalance, + hasEnoughServiceSlots, ]); const buttonProps: ButtonProps = { diff --git a/frontend/components/MainPage/index.tsx b/frontend/components/MainPage/index.tsx index 7e971999..93ad0587 100644 --- a/frontend/components/MainPage/index.tsx +++ b/frontend/components/MainPage/index.tsx @@ -8,6 +8,7 @@ import { useBalance } from '@/hooks/useBalance'; import { useMasterSafe } from '@/hooks/useMasterSafe'; import { usePageState } from '@/hooks/usePageState'; import { useServices } from '@/hooks/useServices'; +import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; import { useStakingProgram } from '@/hooks/useStakingProgram'; import { MainHeader } from './header'; @@ -26,6 +27,7 @@ export const Main = () => { const { updateServicesState } = useServices(); const { updateBalances, isLoaded, setIsLoaded } = useBalance(); const { activeStakingProgramId: currentStakingProgram } = useStakingProgram(); + const { hasEnoughServiceSlots } = useStakingContractInfo(); useEffect(() => { if (!isLoaded) { @@ -37,6 +39,7 @@ export const Main = () => { const hideMainOlasBalanceTopBorder = [ !backupSafeAddress, currentStakingProgram === StakingProgramId.Alpha, + !hasEnoughServiceSlots, ].some((condition) => !!condition); return ( @@ -71,7 +74,9 @@ export const Main = () => { - + {currentStakingProgram && ( + + )} diff --git a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx new file mode 100644 index 00000000..68e38f80 --- /dev/null +++ b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx @@ -0,0 +1,51 @@ +import { Flex, Typography } from 'antd'; + +import { useMigrate } from '@/components/ManageStakingPage/StakingContractSection/useMigrate'; +import { Pages } from '@/enums/PageState'; +import { StakingProgramId } from '@/enums/StakingProgram'; +import { usePageState } from '@/hooks/usePageState'; +import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; + +import { CustomAlert } from '../../../Alert'; + +const { Text } = Typography; + +type NoAvailableSlotsOnTheContractProps = { + stakingProgramId: StakingProgramId; +}; +export const NoAvailableSlotsOnTheContract = ({ + stakingProgramId, +}: NoAvailableSlotsOnTheContractProps) => { + const { goto } = usePageState(); + const { hasEnoughServiceSlots } = useStakingContractInfo(); + const { canUpdateStakingContract } = useMigrate(stakingProgramId); + + if (hasEnoughServiceSlots) return null; + + return ( + + + No available slots on the contract + + + Select a contract with available slots to be able to start your + agent. + + {canUpdateStakingContract && ( + goto(Pages.ManageStaking)} + > + Change staking contract + + )} + + } + /> + ); +}; diff --git a/frontend/components/MainPage/sections/AlertSections/index.tsx b/frontend/components/MainPage/sections/AlertSections/index.tsx index 905a375a..6772c384 100644 --- a/frontend/components/MainPage/sections/AlertSections/index.tsx +++ b/frontend/components/MainPage/sections/AlertSections/index.tsx @@ -1,12 +1,16 @@ import { CardSection } from '@/components/styled/CardSection'; +import { useStakingProgram } from '@/hooks/useStakingProgram'; import { AddBackupWalletAlert } from './AddBackupWalletAlert'; import { AvoidSuspensionAlert } from './AvoidSuspensionAlert'; import { LowTradingBalanceAlert } from './LowTradingBalanceAlert'; import { NewStakingProgramAlert } from './NewStakingProgramAlert'; +import { NoAvailableSlotsOnTheContract } from './NoAvailableSlotsOnTheContract'; import { UpdateAvailableAlert } from './UpdateAvailableAlert'; export const AlertSections = () => { + const { activeStakingProgramId } = useStakingProgram(); + return ( @@ -14,6 +18,11 @@ export const AlertSections = () => { + {activeStakingProgramId && ( + + )} ); }; diff --git a/frontend/components/MainPage/sections/StakingContractUpdate.tsx b/frontend/components/MainPage/sections/StakingContractUpdate.tsx index b769c470..3594f29e 100644 --- a/frontend/components/MainPage/sections/StakingContractUpdate.tsx +++ b/frontend/components/MainPage/sections/StakingContractUpdate.tsx @@ -4,37 +4,32 @@ import { useMemo } from 'react'; import { STAKING_PROGRAM_META } from '@/constants/stakingProgramMeta'; import { Pages } from '@/enums/PageState'; -import { useBalance } from '@/hooks/useBalance'; -import { useNeedsFunds } from '@/hooks/useNeedsFunds'; +import { StakingProgramId } from '@/enums/StakingProgram'; import { usePageState } from '@/hooks/usePageState'; import { useStakingProgram } from '@/hooks/useStakingProgram'; +import { useMigrate } from '../../ManageStakingPage/StakingContractSection/useMigrate'; import { CardSection } from '../../styled/CardSection'; const { Text } = Typography; -export const StakingContractUpdate = () => { +type StakingContractUpdateProps = { stakingProgramId: StakingProgramId }; +export const StakingContractUpdate = ({ + stakingProgramId, +}: StakingContractUpdateProps) => { const { goto } = usePageState(); - const { isBalanceLoaded, isLowBalance } = useBalance(); - const { needsInitialFunding } = useNeedsFunds(); const { activeStakingProgramMeta, isActiveStakingProgramLoaded, defaultStakingProgramId, } = useStakingProgram(); + const { canUpdateStakingContract } = useMigrate(stakingProgramId); const stakingContractName = useMemo(() => { if (activeStakingProgramMeta) return activeStakingProgramMeta.name; return STAKING_PROGRAM_META[defaultStakingProgramId].name; }, [activeStakingProgramMeta, defaultStakingProgramId]); - const canUpdateStakingContract = useMemo(() => { - if (!isBalanceLoaded) return false; - if (isLowBalance) return false; - if (needsInitialFunding) return false; - return true; - }, [isBalanceLoaded, isLowBalance, needsInitialFunding]); - const stakingButton = useMemo(() => { if (!isActiveStakingProgramLoaded) return ; return ( diff --git a/frontend/components/ManageStakingPage/StakingContractSection/CountdownUntilMigration.tsx b/frontend/components/ManageStakingPage/StakingContractSection/CountdownUntilMigration.tsx index d8f71c07..28a8accf 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/CountdownUntilMigration.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/CountdownUntilMigration.tsx @@ -3,6 +3,7 @@ import { isNil } from 'lodash'; import { useState } from 'react'; import { useInterval } from 'usehooks-ts'; +import { POPOVER_WIDTH_LARGE } from '@/constants/width'; import { StakingContractInfo } from '@/types/Autonolas'; const { Text } = Typography; @@ -41,7 +42,7 @@ export const CountdownUntilMigration = ({ : countdownDisplayFormat(secondsUntilReady); return ( - + Can't switch because you unstaked too recently. This may be because your agent was suspended. Keep running your agent and you'll be able to switch in diff --git a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx index 05020e20..607807cf 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx @@ -2,6 +2,7 @@ import { Alert, Skeleton } from 'antd'; import { useMemo } from 'react'; import { InfoBreakdownList } from '@/components/InfoBreakdown'; +import { NA } from '@/constants/symbols'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; @@ -19,6 +20,10 @@ export const StakingContractDetails = ({ const details = stakingContractInfoRecord[stakingProgramId]; return [ + { + left: 'Available slots', + right: details.maxNumServices || NA, + }, { left: 'Rewards per epoch', right: `~ ${details.rewardsPerWorkPeriod?.toFixed(2)} OLAS`, @@ -26,6 +31,7 @@ export const StakingContractDetails = ({ { left: 'Estimated Annual Percentage Yield (APY)', right: `${details.apy}%`, + leftClassName: 'max-width-200', }, { left: 'Required OLAS for staking', diff --git a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx index 624b9288..ef67412e 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx @@ -4,6 +4,7 @@ import { useMemo } from 'react'; import { DeploymentStatus } from '@/client'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useBalance } from '@/hooks/useBalance'; +import { useNeedsFunds } from '@/hooks/useNeedsFunds'; import { useServices } from '@/hooks/useServices'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; @@ -40,9 +41,11 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { isBalanceLoaded, masterSafeBalance: safeBalance, totalOlasStakedBalance, + isLowBalance, } = useBalance(); const { activeStakingProgramId, activeStakingProgramMeta } = useStakingProgram(); + const { needsInitialFunding } = useNeedsFunds(); const { activeStakingContractInfo, @@ -50,6 +53,7 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { isServiceStakedForMinimumDuration, isStakingContractInfoLoaded, stakingContractInfoRecord, + hasEnoughServiceSlots, } = useStakingContractInfo(); const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; @@ -231,8 +235,22 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { stakingProgramId, ]); + const canUpdateStakingContract = useMemo(() => { + if (!isBalanceLoaded) return false; + if (isLowBalance) return false; + if (needsInitialFunding) return false; + if (!hasEnoughServiceSlots) return false; + return true; + }, [ + isBalanceLoaded, + isLowBalance, + needsInitialFunding, + hasEnoughServiceSlots, + ]); + return { migrateValidation, firstDeployValidation, + canUpdateStakingContract, }; }; diff --git a/frontend/constants/symbols.ts b/frontend/constants/symbols.ts index 14c16e9f..5deb8016 100644 --- a/frontend/constants/symbols.ts +++ b/frontend/constants/symbols.ts @@ -1,3 +1,5 @@ +export const NA = 'n/a'; + export const UNICODE_SYMBOLS = { OLAS: '☴', EXTERNAL_LINK: '↗', diff --git a/frontend/constants/width.ts b/frontend/constants/width.ts index 65295b69..2bb0e5eb 100644 --- a/frontend/constants/width.ts +++ b/frontend/constants/width.ts @@ -1,3 +1,5 @@ export const MODAL_WIDTH = 412; export const POPOVER_WIDTH_MEDIUM = 260; + +export const POPOVER_WIDTH_LARGE = 340; diff --git a/frontend/styles/globals.scss b/frontend/styles/globals.scss index 8ac389c5..ee408e2b 100644 --- a/frontend/styles/globals.scss +++ b/frontend/styles/globals.scss @@ -262,10 +262,12 @@ ul.alert-list { .w-3\/4 { width: 75% !important; } - .w-full { width: 100% !important; } +.max-width-200 { + max-width: 200px; +} .loading-ellipses:after { overflow: hidden; diff --git a/frontend/types/Autonolas.ts b/frontend/types/Autonolas.ts index 9b1d04dc..1fdfd43c 100644 --- a/frontend/types/Autonolas.ts +++ b/frontend/types/Autonolas.ts @@ -12,6 +12,7 @@ export type StakingRewardsInfo = { export type StakingContractInfo = { availableRewards: number; + /* number of slots available for staking */ maxNumServices: number; serviceIds: number[]; /** minimum staking duration (in seconds) */ diff --git a/package.json b/package.json index 28133381..b9dbb657 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,5 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.1.0-rc184" + "version": "0.1.0-rc186" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0a48a9d4..f5109942 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.1.0-rc184" +version = "0.1.0-rc186" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" From 15972287ab7207d2e5a80dff25d4876db110dacf Mon Sep 17 00:00:00 2001 From: jmoreira-valory Date: Wed, 6 Nov 2024 10:07:43 +0100 Subject: [PATCH 02/85] [no ci] chore: update --- operate/cli.py | 50 ++++++++++++++++++++++++++++++++++++++ operate/services/manage.py | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/operate/cli.py b/operate/cli.py index e8fab901..36805f1c 100644 --- a/operate/cli.py +++ b/operate/cli.py @@ -591,6 +591,56 @@ async def _get_services(request: Request) -> JSONResponse: """Get available services.""" return JSONResponse(content=operate.service_manager().json) + @app.post("/api/v2/services") + @with_retries + async def _create_services_v2(request: Request) -> JSONResponse: + """Create a service.""" + if operate.password is None: + return USER_NOT_LOGGED_IN_ERROR + template = await request.json() + manager = operate.service_manager() + output = manager.create(service_template=template) + # if len(manager.json) > 0: + # old_hash = manager.json[0]["hash"] + # if old_hash == template["hash"]: + # logger.info(f'Loading service {template["hash"]}') + # service = manager.load_or_create( + # hash=template["hash"], + # service_template=template, + # ) + # else: + # logger.info(f"Updating service from {old_hash} to " + template["hash"]) + # service = manager.update_service( + # old_hash=old_hash, + # new_hash=template["hash"], + # service_template=template, + # ) + # else: + # logger.info(f'Creating service {template["hash"]}') + # service = manager.load_or_create( + # hash=template["hash"], + # service_template=template, + # ) + + # if template.get("deploy", False): + + # def _fn() -> None: + # # deploy_service_onchain_from_safe includes stake_service_on_chain_from_safe + # manager.deploy_service_onchain_from_safe(hash=service.hash) + # manager.fund_service(hash=service.hash) + + # # TODO Optimus patch, chain_id="10" + # chain_id = "10" + # manager.deploy_service_locally(hash=service.hash, chain_id=chain_id) + + # await run_in_executor(_fn) + # schedule_funding_job(service=service.hash) + # schedule_healthcheck_job(service=service.hash) + + return JSONResponse( + content=output + ) + @app.post("/api/services") @with_retries async def _create_services(request: Request) -> JSONResponse: diff --git a/operate/services/manage.py b/operate/services/manage.py index 7db66db8..86e6e6ac 100644 --- a/operate/services/manage.py +++ b/operate/services/manage.py @@ -192,6 +192,51 @@ def load_or_create( return service + def create( + self, + service_template: t.Optional[ServiceTemplate] = None, + keys: t.Optional[t.List[Key]] = None, + ) -> Service: + """ + Create a service + + :param service_template: Service template + :param keys: Keys + :return: Service instance + """ + path = self.path / hash + # if path.exists(): + # service = Service.load(path=path) + + # if service_template is not None: + # service.update_user_params_from_template( + # service_template=service_template + # ) + + # return service + + if service_template is None: + raise ValueError( + "'service_template' cannot be None when creating a new service" + ) + + service = Service.new( + hash=hash, + keys=keys or [], + storage=self.path, + service_template=service_template, + ) + + if not service.keys: + service.keys = [ + self.keys_manager.get(self.keys_manager.create()) + for _ in range(service.helper.config.number_of_agents) + ] + service.store() + + return service + + def _get_on_chain_state(self, service: Service, chain_id: str) -> OnChainState: chain_config = service.chain_configs[chain_id] chain_data = chain_config.chain_data From 15035259cac2299a1757c5c8ec2d9ebf9fdc75f1 Mon Sep 17 00:00:00 2001 From: Mohan Date: Wed, 6 Nov 2024 15:17:04 +0530 Subject: [PATCH 03/85] feat: Inform users that there are no available slots on staking contract and prompt them to switch (#421) (#423) * fix: improve error messages in AgentButton component * feat: add NoAvailableSlotsOnTheContract component * feat: integrate NoAvailableSlotsOnTheContract component and enhance service slot checks * feat: implement useCanStartUpdateStakingContract hook and update related components * feat: add available slots display and max-width styling to StakingContractDetails * feat: add POPOVER_WIDTH_LARGE constant and apply max-width styling to CountdownUntilMigration component * fix: correct spelling of 'occurred' in error notifications in AgentButton component * refactor: rename useCanStartUpdateStakingContract hook to useCanUpdateStakingContract and update related components * feat: update StakingContractUpdate and NoAvailableSlotsOnTheContract components to accept stakingProgramId prop and remove useCanUpdateStakingContract hook * chore: bump version to 0.1.0-rc186 in package.json and pyproject.toml Co-authored-by: Josh Miller <31908788+truemiller@users.noreply.github.com> --- .../MainPage/header/AgentButton.tsx | 15 ++++-- frontend/components/MainPage/index.tsx | 7 ++- .../NoAvailableSlotsOnTheContract.tsx | 51 +++++++++++++++++++ .../MainPage/sections/AlertSections/index.tsx | 9 ++++ .../sections/StakingContractUpdate.tsx | 19 +++---- .../CountdownUntilMigration.tsx | 3 +- .../StakingContractDetails.tsx | 6 +++ .../StakingContractSection/useMigrate.tsx | 18 +++++++ frontend/constants/symbols.ts | 2 + frontend/constants/width.ts | 2 + frontend/styles/globals.scss | 4 +- frontend/types/Autonolas.ts | 1 + package.json | 2 +- pyproject.toml | 2 +- 14 files changed, 120 insertions(+), 21 deletions(-) create mode 100644 frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx diff --git a/frontend/components/MainPage/header/AgentButton.tsx b/frontend/components/MainPage/header/AgentButton.tsx index f2f76511..eb24147e 100644 --- a/frontend/components/MainPage/header/AgentButton.tsx +++ b/frontend/components/MainPage/header/AgentButton.tsx @@ -89,7 +89,7 @@ const AgentRunningButton = () => { await ServicesService.stopDeployment(service.hash); } catch (error) { console.error(error); - showNotification?.('Error while stopping agent'); + showNotification?.('Some error occurred while stopping agent'); } finally { // Resume polling, will update to correct status regardless of success setIsServicePollingPaused(false); @@ -145,6 +145,7 @@ const AgentNotRunningButton = () => { isAgentEvicted, setIsPaused: setIsStakingContractInfoPollingPaused, updateActiveStakingContractInfo, + hasEnoughServiceSlots, } = useStakingContractInfo(); const { activeStakingProgramId, defaultStakingProgramId } = useStakingProgram(); @@ -192,7 +193,7 @@ const AgentNotRunningButton = () => { } catch (error) { console.error(error); setServiceStatus(undefined); - showNotification?.('Error while creating safe'); + showNotification?.('Some error occurred while creating safe'); setIsStakingContractInfoPollingPaused(false); setIsServicePollingPaused(false); setIsBalancePollingPaused(false); @@ -211,7 +212,7 @@ const AgentNotRunningButton = () => { } catch (error) { console.error(error); setServiceStatus(undefined); - showNotification?.('Error while deploying service'); + showNotification?.('Some error occurred while deploying service'); setIsServicePollingPaused(false); setIsBalancePollingPaused(false); setIsStakingContractInfoPollingPaused(false); @@ -223,7 +224,9 @@ const AgentNotRunningButton = () => { showNotification?.(`Your agent is running!`); } catch (error) { console.error(error); - showNotification?.('Error while showing "running" notification'); + showNotification?.( + 'Some error occurred while showing "running" notification', + ); } // Can assume successful deployment @@ -278,6 +281,9 @@ const AgentNotRunningButton = () => { if (!requiredOlas) return false; + // If no slots available, agent cannot be started + if (!hasEnoughServiceSlots) return false; + // case where service exists & user has initial funded if (service && storeState?.isInitialFunded) { if (!safeOlasBalanceWithStaked) return false; @@ -302,6 +308,7 @@ const AgentNotRunningButton = () => { requiredOlas, totalEthBalance, isLowBalance, + hasEnoughServiceSlots, ]); const buttonProps: ButtonProps = { diff --git a/frontend/components/MainPage/index.tsx b/frontend/components/MainPage/index.tsx index 7e971999..93ad0587 100644 --- a/frontend/components/MainPage/index.tsx +++ b/frontend/components/MainPage/index.tsx @@ -8,6 +8,7 @@ import { useBalance } from '@/hooks/useBalance'; import { useMasterSafe } from '@/hooks/useMasterSafe'; import { usePageState } from '@/hooks/usePageState'; import { useServices } from '@/hooks/useServices'; +import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; import { useStakingProgram } from '@/hooks/useStakingProgram'; import { MainHeader } from './header'; @@ -26,6 +27,7 @@ export const Main = () => { const { updateServicesState } = useServices(); const { updateBalances, isLoaded, setIsLoaded } = useBalance(); const { activeStakingProgramId: currentStakingProgram } = useStakingProgram(); + const { hasEnoughServiceSlots } = useStakingContractInfo(); useEffect(() => { if (!isLoaded) { @@ -37,6 +39,7 @@ export const Main = () => { const hideMainOlasBalanceTopBorder = [ !backupSafeAddress, currentStakingProgram === StakingProgramId.Alpha, + !hasEnoughServiceSlots, ].some((condition) => !!condition); return ( @@ -71,7 +74,9 @@ export const Main = () => { - + {currentStakingProgram && ( + + )} diff --git a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx new file mode 100644 index 00000000..68e38f80 --- /dev/null +++ b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx @@ -0,0 +1,51 @@ +import { Flex, Typography } from 'antd'; + +import { useMigrate } from '@/components/ManageStakingPage/StakingContractSection/useMigrate'; +import { Pages } from '@/enums/PageState'; +import { StakingProgramId } from '@/enums/StakingProgram'; +import { usePageState } from '@/hooks/usePageState'; +import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; + +import { CustomAlert } from '../../../Alert'; + +const { Text } = Typography; + +type NoAvailableSlotsOnTheContractProps = { + stakingProgramId: StakingProgramId; +}; +export const NoAvailableSlotsOnTheContract = ({ + stakingProgramId, +}: NoAvailableSlotsOnTheContractProps) => { + const { goto } = usePageState(); + const { hasEnoughServiceSlots } = useStakingContractInfo(); + const { canUpdateStakingContract } = useMigrate(stakingProgramId); + + if (hasEnoughServiceSlots) return null; + + return ( + + + No available slots on the contract + + + Select a contract with available slots to be able to start your + agent. + + {canUpdateStakingContract && ( + goto(Pages.ManageStaking)} + > + Change staking contract + + )} + + } + /> + ); +}; diff --git a/frontend/components/MainPage/sections/AlertSections/index.tsx b/frontend/components/MainPage/sections/AlertSections/index.tsx index 905a375a..6772c384 100644 --- a/frontend/components/MainPage/sections/AlertSections/index.tsx +++ b/frontend/components/MainPage/sections/AlertSections/index.tsx @@ -1,12 +1,16 @@ import { CardSection } from '@/components/styled/CardSection'; +import { useStakingProgram } from '@/hooks/useStakingProgram'; import { AddBackupWalletAlert } from './AddBackupWalletAlert'; import { AvoidSuspensionAlert } from './AvoidSuspensionAlert'; import { LowTradingBalanceAlert } from './LowTradingBalanceAlert'; import { NewStakingProgramAlert } from './NewStakingProgramAlert'; +import { NoAvailableSlotsOnTheContract } from './NoAvailableSlotsOnTheContract'; import { UpdateAvailableAlert } from './UpdateAvailableAlert'; export const AlertSections = () => { + const { activeStakingProgramId } = useStakingProgram(); + return ( @@ -14,6 +18,11 @@ export const AlertSections = () => { + {activeStakingProgramId && ( + + )} ); }; diff --git a/frontend/components/MainPage/sections/StakingContractUpdate.tsx b/frontend/components/MainPage/sections/StakingContractUpdate.tsx index b769c470..3594f29e 100644 --- a/frontend/components/MainPage/sections/StakingContractUpdate.tsx +++ b/frontend/components/MainPage/sections/StakingContractUpdate.tsx @@ -4,37 +4,32 @@ import { useMemo } from 'react'; import { STAKING_PROGRAM_META } from '@/constants/stakingProgramMeta'; import { Pages } from '@/enums/PageState'; -import { useBalance } from '@/hooks/useBalance'; -import { useNeedsFunds } from '@/hooks/useNeedsFunds'; +import { StakingProgramId } from '@/enums/StakingProgram'; import { usePageState } from '@/hooks/usePageState'; import { useStakingProgram } from '@/hooks/useStakingProgram'; +import { useMigrate } from '../../ManageStakingPage/StakingContractSection/useMigrate'; import { CardSection } from '../../styled/CardSection'; const { Text } = Typography; -export const StakingContractUpdate = () => { +type StakingContractUpdateProps = { stakingProgramId: StakingProgramId }; +export const StakingContractUpdate = ({ + stakingProgramId, +}: StakingContractUpdateProps) => { const { goto } = usePageState(); - const { isBalanceLoaded, isLowBalance } = useBalance(); - const { needsInitialFunding } = useNeedsFunds(); const { activeStakingProgramMeta, isActiveStakingProgramLoaded, defaultStakingProgramId, } = useStakingProgram(); + const { canUpdateStakingContract } = useMigrate(stakingProgramId); const stakingContractName = useMemo(() => { if (activeStakingProgramMeta) return activeStakingProgramMeta.name; return STAKING_PROGRAM_META[defaultStakingProgramId].name; }, [activeStakingProgramMeta, defaultStakingProgramId]); - const canUpdateStakingContract = useMemo(() => { - if (!isBalanceLoaded) return false; - if (isLowBalance) return false; - if (needsInitialFunding) return false; - return true; - }, [isBalanceLoaded, isLowBalance, needsInitialFunding]); - const stakingButton = useMemo(() => { if (!isActiveStakingProgramLoaded) return ; return ( diff --git a/frontend/components/ManageStakingPage/StakingContractSection/CountdownUntilMigration.tsx b/frontend/components/ManageStakingPage/StakingContractSection/CountdownUntilMigration.tsx index d8f71c07..28a8accf 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/CountdownUntilMigration.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/CountdownUntilMigration.tsx @@ -3,6 +3,7 @@ import { isNil } from 'lodash'; import { useState } from 'react'; import { useInterval } from 'usehooks-ts'; +import { POPOVER_WIDTH_LARGE } from '@/constants/width'; import { StakingContractInfo } from '@/types/Autonolas'; const { Text } = Typography; @@ -41,7 +42,7 @@ export const CountdownUntilMigration = ({ : countdownDisplayFormat(secondsUntilReady); return ( - + Can't switch because you unstaked too recently. This may be because your agent was suspended. Keep running your agent and you'll be able to switch in diff --git a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx index 05020e20..607807cf 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx @@ -2,6 +2,7 @@ import { Alert, Skeleton } from 'antd'; import { useMemo } from 'react'; import { InfoBreakdownList } from '@/components/InfoBreakdown'; +import { NA } from '@/constants/symbols'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; @@ -19,6 +20,10 @@ export const StakingContractDetails = ({ const details = stakingContractInfoRecord[stakingProgramId]; return [ + { + left: 'Available slots', + right: details.maxNumServices || NA, + }, { left: 'Rewards per epoch', right: `~ ${details.rewardsPerWorkPeriod?.toFixed(2)} OLAS`, @@ -26,6 +31,7 @@ export const StakingContractDetails = ({ { left: 'Estimated Annual Percentage Yield (APY)', right: `${details.apy}%`, + leftClassName: 'max-width-200', }, { left: 'Required OLAS for staking', diff --git a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx index 624b9288..ef67412e 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx @@ -4,6 +4,7 @@ import { useMemo } from 'react'; import { DeploymentStatus } from '@/client'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useBalance } from '@/hooks/useBalance'; +import { useNeedsFunds } from '@/hooks/useNeedsFunds'; import { useServices } from '@/hooks/useServices'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; @@ -40,9 +41,11 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { isBalanceLoaded, masterSafeBalance: safeBalance, totalOlasStakedBalance, + isLowBalance, } = useBalance(); const { activeStakingProgramId, activeStakingProgramMeta } = useStakingProgram(); + const { needsInitialFunding } = useNeedsFunds(); const { activeStakingContractInfo, @@ -50,6 +53,7 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { isServiceStakedForMinimumDuration, isStakingContractInfoLoaded, stakingContractInfoRecord, + hasEnoughServiceSlots, } = useStakingContractInfo(); const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; @@ -231,8 +235,22 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { stakingProgramId, ]); + const canUpdateStakingContract = useMemo(() => { + if (!isBalanceLoaded) return false; + if (isLowBalance) return false; + if (needsInitialFunding) return false; + if (!hasEnoughServiceSlots) return false; + return true; + }, [ + isBalanceLoaded, + isLowBalance, + needsInitialFunding, + hasEnoughServiceSlots, + ]); + return { migrateValidation, firstDeployValidation, + canUpdateStakingContract, }; }; diff --git a/frontend/constants/symbols.ts b/frontend/constants/symbols.ts index 14c16e9f..5deb8016 100644 --- a/frontend/constants/symbols.ts +++ b/frontend/constants/symbols.ts @@ -1,3 +1,5 @@ +export const NA = 'n/a'; + export const UNICODE_SYMBOLS = { OLAS: '☴', EXTERNAL_LINK: '↗', diff --git a/frontend/constants/width.ts b/frontend/constants/width.ts index 65295b69..2bb0e5eb 100644 --- a/frontend/constants/width.ts +++ b/frontend/constants/width.ts @@ -1,3 +1,5 @@ export const MODAL_WIDTH = 412; export const POPOVER_WIDTH_MEDIUM = 260; + +export const POPOVER_WIDTH_LARGE = 340; diff --git a/frontend/styles/globals.scss b/frontend/styles/globals.scss index 8ac389c5..ee408e2b 100644 --- a/frontend/styles/globals.scss +++ b/frontend/styles/globals.scss @@ -262,10 +262,12 @@ ul.alert-list { .w-3\/4 { width: 75% !important; } - .w-full { width: 100% !important; } +.max-width-200 { + max-width: 200px; +} .loading-ellipses:after { overflow: hidden; diff --git a/frontend/types/Autonolas.ts b/frontend/types/Autonolas.ts index 9b1d04dc..1fdfd43c 100644 --- a/frontend/types/Autonolas.ts +++ b/frontend/types/Autonolas.ts @@ -12,6 +12,7 @@ export type StakingRewardsInfo = { export type StakingContractInfo = { availableRewards: number; + /* number of slots available for staking */ maxNumServices: number; serviceIds: number[]; /** minimum staking duration (in seconds) */ diff --git a/package.json b/package.json index 28133381..b9dbb657 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,5 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.1.0-rc184" + "version": "0.1.0-rc186" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0a48a9d4..f5109942 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.1.0-rc184" +version = "0.1.0-rc186" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" From f0e06b9cdb95c4324fe0f6e43d296db6ca72d419 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 10:31:26 +0000 Subject: [PATCH 04/85] fix: handle nil case for service slot availability check in NoAvailableSlotsOnTheContract component --- .../sections/AlertSections/NoAvailableSlotsOnTheContract.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx index 68e38f80..cc4b470f 100644 --- a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx +++ b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx @@ -1,4 +1,5 @@ import { Flex, Typography } from 'antd'; +import { isNil } from 'lodash'; import { useMigrate } from '@/components/ManageStakingPage/StakingContractSection/useMigrate'; import { Pages } from '@/enums/PageState'; @@ -20,7 +21,7 @@ export const NoAvailableSlotsOnTheContract = ({ const { hasEnoughServiceSlots } = useStakingContractInfo(); const { canUpdateStakingContract } = useMigrate(stakingProgramId); - if (hasEnoughServiceSlots) return null; + if (hasEnoughServiceSlots || isNil(hasEnoughServiceSlots)) return null; return ( Date: Wed, 6 Nov 2024 11:09:00 +0000 Subject: [PATCH 05/85] fix: update staking program handling in Main component to use active and default IDs --- frontend/components/MainPage/index.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/components/MainPage/index.tsx b/frontend/components/MainPage/index.tsx index 93ad0587..a6fe872f 100644 --- a/frontend/components/MainPage/index.tsx +++ b/frontend/components/MainPage/index.tsx @@ -26,7 +26,8 @@ export const Main = () => { const { backupSafeAddress } = useMasterSafe(); const { updateServicesState } = useServices(); const { updateBalances, isLoaded, setIsLoaded } = useBalance(); - const { activeStakingProgramId: currentStakingProgram } = useStakingProgram(); + const { activeStakingProgramId, defaultStakingProgramId } = + useStakingProgram(); const { hasEnoughServiceSlots } = useStakingContractInfo(); useEffect(() => { @@ -38,7 +39,7 @@ export const Main = () => { const hideMainOlasBalanceTopBorder = [ !backupSafeAddress, - currentStakingProgram === StakingProgramId.Alpha, + activeStakingProgramId === StakingProgramId.Alpha, !hasEnoughServiceSlots, ].some((condition) => !!condition); @@ -74,9 +75,9 @@ export const Main = () => { - {currentStakingProgram && ( - - )} + From 981c93f4eb57d10c7f32c043e2a2cb9e370108ef Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 11:09:13 +0000 Subject: [PATCH 06/85] refactor: simplify NoAvailableSlotsOnTheContract component by removing unused props and logic --- .../NoAvailableSlotsOnTheContract.tsx | 30 +++++++------------ .../MainPage/sections/AlertSections/index.tsx | 9 +----- .../sections/StakingContractUpdate.tsx | 23 ++------------ 3 files changed, 15 insertions(+), 47 deletions(-) diff --git a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx index cc4b470f..23c992b1 100644 --- a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx +++ b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx @@ -1,9 +1,6 @@ import { Flex, Typography } from 'antd'; -import { isNil } from 'lodash'; -import { useMigrate } from '@/components/ManageStakingPage/StakingContractSection/useMigrate'; import { Pages } from '@/enums/PageState'; -import { StakingProgramId } from '@/enums/StakingProgram'; import { usePageState } from '@/hooks/usePageState'; import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; @@ -11,17 +8,14 @@ import { CustomAlert } from '../../../Alert'; const { Text } = Typography; -type NoAvailableSlotsOnTheContractProps = { - stakingProgramId: StakingProgramId; -}; -export const NoAvailableSlotsOnTheContract = ({ - stakingProgramId, -}: NoAvailableSlotsOnTheContractProps) => { +export const NoAvailableSlotsOnTheContract = () => { const { goto } = usePageState(); const { hasEnoughServiceSlots } = useStakingContractInfo(); - const { canUpdateStakingContract } = useMigrate(stakingProgramId); - if (hasEnoughServiceSlots || isNil(hasEnoughServiceSlots)) return null; + const { activeStakingProgramId, defaultStakingProgramId } = + useStakingProgram(); + + if (hasEnoughServiceSlots) return null; return ( - {canUpdateStakingContract && ( - goto(Pages.ManageStaking)} - > - Change staking contract - - )} + goto(Pages.ManageStaking)} + > + Change staking contract + } /> diff --git a/frontend/components/MainPage/sections/AlertSections/index.tsx b/frontend/components/MainPage/sections/AlertSections/index.tsx index 6772c384..a8c70417 100644 --- a/frontend/components/MainPage/sections/AlertSections/index.tsx +++ b/frontend/components/MainPage/sections/AlertSections/index.tsx @@ -1,5 +1,4 @@ import { CardSection } from '@/components/styled/CardSection'; -import { useStakingProgram } from '@/hooks/useStakingProgram'; import { AddBackupWalletAlert } from './AddBackupWalletAlert'; import { AvoidSuspensionAlert } from './AvoidSuspensionAlert'; @@ -9,8 +8,6 @@ import { NoAvailableSlotsOnTheContract } from './NoAvailableSlotsOnTheContract'; import { UpdateAvailableAlert } from './UpdateAvailableAlert'; export const AlertSections = () => { - const { activeStakingProgramId } = useStakingProgram(); - return ( @@ -18,11 +15,7 @@ export const AlertSections = () => { - {activeStakingProgramId && ( - - )} + ); }; diff --git a/frontend/components/MainPage/sections/StakingContractUpdate.tsx b/frontend/components/MainPage/sections/StakingContractUpdate.tsx index 3594f29e..ae667b2c 100644 --- a/frontend/components/MainPage/sections/StakingContractUpdate.tsx +++ b/frontend/components/MainPage/sections/StakingContractUpdate.tsx @@ -1,5 +1,5 @@ import { RightOutlined } from '@ant-design/icons'; -import { Button, Flex, Popover, Skeleton, Typography } from 'antd'; +import { Button, Flex, Skeleton, Typography } from 'antd'; import { useMemo } from 'react'; import { STAKING_PROGRAM_META } from '@/constants/stakingProgramMeta'; @@ -36,19 +36,13 @@ export const StakingContractUpdate = ({ ); - }, [ - goto, - isActiveStakingProgramLoaded, - stakingContractName, - canUpdateStakingContract, - ]); + }, [goto, isActiveStakingProgramLoaded, stakingContractName]); return ( @@ -60,18 +54,7 @@ export const StakingContractUpdate = ({ > Staking contract - {canUpdateStakingContract ? ( - stakingButton - ) : ( - Fund your agent to manage staking contracts} - > - {stakingButton} - - )} + {stakingButton} ); From 65fc297eaf99abf9210c4d23fa27d921d99b5b4d Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 11:09:47 +0000 Subject: [PATCH 07/85] refactor: remove stakingProgramId prop from StakingContractUpdate component for simplification --- .../MainPage/sections/StakingContractUpdate.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/components/MainPage/sections/StakingContractUpdate.tsx b/frontend/components/MainPage/sections/StakingContractUpdate.tsx index ae667b2c..e8ad184c 100644 --- a/frontend/components/MainPage/sections/StakingContractUpdate.tsx +++ b/frontend/components/MainPage/sections/StakingContractUpdate.tsx @@ -4,26 +4,20 @@ import { useMemo } from 'react'; import { STAKING_PROGRAM_META } from '@/constants/stakingProgramMeta'; import { Pages } from '@/enums/PageState'; -import { StakingProgramId } from '@/enums/StakingProgram'; import { usePageState } from '@/hooks/usePageState'; import { useStakingProgram } from '@/hooks/useStakingProgram'; -import { useMigrate } from '../../ManageStakingPage/StakingContractSection/useMigrate'; import { CardSection } from '../../styled/CardSection'; const { Text } = Typography; -type StakingContractUpdateProps = { stakingProgramId: StakingProgramId }; -export const StakingContractUpdate = ({ - stakingProgramId, -}: StakingContractUpdateProps) => { +export const StakingContractUpdate = () => { const { goto } = usePageState(); const { activeStakingProgramMeta, isActiveStakingProgramLoaded, defaultStakingProgramId, } = useStakingProgram(); - const { canUpdateStakingContract } = useMigrate(stakingProgramId); const stakingContractName = useMemo(() => { if (activeStakingProgramMeta) return activeStakingProgramMeta.name; From d2d9f5cae90cc7644e238d67dcf99bb71a1f16a6 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 11:10:09 +0000 Subject: [PATCH 08/85] refactor: remove defaultStakingProgramId usage in Main component for clarity --- frontend/components/MainPage/index.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/components/MainPage/index.tsx b/frontend/components/MainPage/index.tsx index a6fe872f..5c52b57c 100644 --- a/frontend/components/MainPage/index.tsx +++ b/frontend/components/MainPage/index.tsx @@ -26,8 +26,7 @@ export const Main = () => { const { backupSafeAddress } = useMasterSafe(); const { updateServicesState } = useServices(); const { updateBalances, isLoaded, setIsLoaded } = useBalance(); - const { activeStakingProgramId, defaultStakingProgramId } = - useStakingProgram(); + const { activeStakingProgramId } = useStakingProgram(); const { hasEnoughServiceSlots } = useStakingContractInfo(); useEffect(() => { @@ -75,9 +74,7 @@ export const Main = () => { - + From c4598d894ca1abb27e45ef531add0feb151f4fa7 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 13:24:00 +0000 Subject: [PATCH 09/85] refactor: update staking program status enum and related logic for clarity --- .../MainPage/header/AgentButton.tsx | 50 +++++--- .../header/CannotStartAgentPopover.tsx | 25 +++- frontend/components/MainPage/index.tsx | 41 +++++-- .../NoAvailableSlotsOnTheContract.tsx | 40 +++++-- .../sections/StakingContractUpdate.tsx | 29 ++++- .../CantMigrateAlert.tsx | 9 +- .../StakingContractSection/MigrateButton.tsx | 24 ++-- .../StakingContractDetails.tsx | 34 +++--- .../StakingContractTag.tsx | 5 +- .../StakingContractSection/index.tsx | 26 +++-- .../StakingContractSection/useMigrate.tsx | 108 +++++++++++++----- .../context/StakingContractInfoProvider.tsx | 21 ++-- frontend/context/StakingProgramProvider.tsx | 13 ++- frontend/enums/StakingProgramStatus.ts | 3 +- frontend/hooks/useStakingContractInfo.ts | 35 +++--- frontend/hooks/useStakingProgram.ts | 2 + frontend/utils/service.ts | 4 +- 17 files changed, 331 insertions(+), 138 deletions(-) diff --git a/frontend/components/MainPage/header/AgentButton.tsx b/frontend/components/MainPage/header/AgentButton.tsx index eb24147e..af3d1098 100644 --- a/frontend/components/MainPage/header/AgentButton.tsx +++ b/frontend/components/MainPage/header/AgentButton.tsx @@ -10,7 +10,10 @@ import { useElectronApi } from '@/hooks/useElectronApi'; import { useReward } from '@/hooks/useReward'; import { useServices } from '@/hooks/useServices'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; -import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; +import { + useStakingContractContext, + useStakingContractInfo, +} from '@/hooks/useStakingContractInfo'; import { useStakingProgram } from '@/hooks/useStakingProgram'; import { useStore } from '@/hooks/useStore'; import { useWallet } from '@/hooks/useWallet'; @@ -140,16 +143,23 @@ const AgentNotRunningButton = () => { updateBalances, } = useBalance(); const { storeState } = useStore(); + const { - isEligibleForStaking, - isAgentEvicted, + isStakingContractInfoLoaded, setIsPaused: setIsStakingContractInfoPollingPaused, updateActiveStakingContractInfo, - hasEnoughServiceSlots, - } = useStakingContractInfo(); + } = useStakingContractContext(); + const { activeStakingProgramId, defaultStakingProgramId } = useStakingProgram(); + const { + isEligibleForStaking, + isAgentEvicted, + hasEnoughServiceSlots, + isServiceStaked, + } = useStakingContractInfo(activeStakingProgramId ?? defaultStakingProgramId); + // const minStakingDeposit = // stakingContractInfoRecord?.[activeStakingProgram ?? defaultStakingProgram] // ?.minStakingDeposit; @@ -266,6 +276,8 @@ const AgentNotRunningButton = () => { ]); const isDeployable = useMemo(() => { + if (!isStakingContractInfoLoaded) return false; + // if the agent is NOT running and the balance is too low, // user should not be able to start the agent const isServiceInactive = @@ -282,7 +294,7 @@ const AgentNotRunningButton = () => { if (!requiredOlas) return false; // If no slots available, agent cannot be started - if (!hasEnoughServiceSlots) return false; + if (!hasEnoughServiceSlots && !isServiceStaked) return false; // case where service exists & user has initial funded if (service && storeState?.isInitialFunded) { @@ -299,16 +311,18 @@ const AgentNotRunningButton = () => { return hasEnoughOlas && hasEnoughEth; }, [ + isStakingContractInfoLoaded, serviceStatus, + isLowBalance, + requiredOlas, + hasEnoughServiceSlots, + isServiceStaked, service, storeState?.isInitialFunded, isEligibleForStaking, isAgentEvicted, safeOlasBalanceWithStaked, - requiredOlas, totalEthBalance, - isLowBalance, - hasEnoughServiceSlots, ]); const buttonProps: ButtonProps = { @@ -324,11 +338,20 @@ const AgentNotRunningButton = () => { }; export const AgentButton = () => { - const { service, serviceStatus, hasInitialLoaded } = useServices(); - const { isEligibleForStaking, isAgentEvicted } = useStakingContractInfo(); + const { + service, + serviceStatus, + hasInitialLoaded: isServicesLoaded, + } = useServices(); + const { activeStakingProgramId, defaultStakingProgramId } = + useStakingProgram(); + const { isStakingContractInfoLoaded } = useStakingContractContext(); + const { isEligibleForStaking, isAgentEvicted } = useStakingContractInfo( + activeStakingProgramId ?? defaultStakingProgramId, + ); return useMemo(() => { - if (!hasInitialLoaded) { + if (!isServicesLoaded || !isStakingContractInfoLoaded) { return ); - }, [goto, isActiveStakingProgramLoaded, stakingContractName]); + }, [ + goto, + isActiveStakingProgramLoaded, + isStakingContractInfoLoaded, + serviceIsTransitioning, + stakingContractName, + ]); return ( @@ -47,8 +67,7 @@ export const StakingContractUpdate = () => { style={{ width: '100%' }} > Staking contract - - {stakingButton} + {gotoManageStakingButton} ); diff --git a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx index adfa9c0f..d75f6a6a 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx @@ -5,7 +5,10 @@ import { CustomAlert } from '@/components/Alert'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useBalance } from '@/hooks/useBalance'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; -import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; +import { + useStakingContractContext, + useStakingContractInfo, +} from '@/hooks/useStakingContractInfo'; import { getMinimumStakedAmountRequired } from '@/utils/service'; import { CantMigrateReason } from './useMigrate'; @@ -18,7 +21,8 @@ const AlertInsufficientMigrationFunds = ({ stakingProgramId, }: CantMigrateAlertProps) => { const { serviceTemplate } = useServiceTemplates(); - const { isServiceStaked } = useStakingContractInfo(); + const { isStakingContractInfoLoaded } = useStakingContractContext(); + const { isServiceStaked } = useStakingContractInfo(stakingProgramId); const { masterSafeBalance: safeBalance, totalOlasStakedBalance } = useBalance(); @@ -27,6 +31,7 @@ const AlertInsufficientMigrationFunds = ({ stakingProgramId, ); + if (!isStakingContractInfoLoaded) return null; if (isNil(totalOlasRequiredForStaking)) return null; if (isNil(safeBalance?.OLAS)) return null; if (isNil(totalOlasStakedBalance)) return null; diff --git a/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx b/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx index 2f4f7e46..7c0f7106 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx @@ -30,10 +30,18 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { hasInitialLoaded: isServicesLoaded, service, } = useServices(); + + const { + activeStakingProgramId, + defaultStakingProgramId, + setDefaultStakingProgramId, + } = useStakingProgram(); const { setIsPaused: setIsBalancePollingPaused } = useBalance(); - const { updateActiveStakingProgramId: updateStakingProgram } = - useStakingProgram(); - const { activeStakingContractInfo } = useStakingContractInfo(); + const { updateActiveStakingProgramId } = useStakingProgram(); + + const { stakingContractInfo: currentStakingContractInfo } = + useStakingContractInfo(activeStakingProgramId ?? defaultStakingProgramId); + const { setMigrationModalOpen } = useModals(); const { migrateValidation, firstDeployValidation } = @@ -43,7 +51,6 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { const isFirstDeploy = useMemo(() => { if (!isServicesLoaded) return false; if (service) return false; - return true; }, [isServicesLoaded, service]); @@ -54,17 +61,17 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { if ( validation.reason === CantMigrateReason.NotStakedForMinimumDuration && - !isNil(activeStakingContractInfo) + !isNil(currentStakingContractInfo) ) { return ( ); } return validation.reason; - }, [activeStakingContractInfo, validation]); + }, [currentStakingContractInfo, validation]); return ( @@ -75,6 +82,7 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { onClick={async () => { setIsServicePollingPaused(true); setIsBalancePollingPaused(true); + setDefaultStakingProgramId(stakingProgramId); try { setServiceStatus(DeploymentStatus.DEPLOYING); @@ -88,7 +96,7 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { stakingProgramId === StakingProgramId.BetaMechMarketplace, }); - await updateStakingProgram(); + await updateActiveStakingProgramId(); setMigrationModalOpen(true); } catch (error) { diff --git a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx index 607807cf..2d4f9ff9 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx @@ -2,27 +2,29 @@ import { Alert, Skeleton } from 'antd'; import { useMemo } from 'react'; import { InfoBreakdownList } from '@/components/InfoBreakdown'; -import { NA } from '@/constants/symbols'; import { StakingProgramId } from '@/enums/StakingProgram'; -import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; +import { useStakingContractContext } from '@/hooks/useStakingContractInfo'; export const StakingContractDetails = ({ stakingProgramId, }: { stakingProgramId: StakingProgramId; }) => { - const { stakingContractInfoRecord } = useStakingContractInfo(); + const { stakingContractInfoRecord, isStakingContractInfoLoaded } = + useStakingContractContext(); - const balances = useMemo(() => { - if (!stakingContractInfoRecord) return null; - if (!stakingProgramId) return null; - if (!stakingContractInfoRecord?.[stakingProgramId]) return null; + const list = useMemo(() => { + if (!isStakingContractInfoLoaded) return; + if (!stakingContractInfoRecord) return; + if (!stakingProgramId) return; + if (!stakingContractInfoRecord?.[stakingProgramId]) return; const details = stakingContractInfoRecord[stakingProgramId]; + return [ { left: 'Available slots', - right: details.maxNumServices || NA, + right: `${details.maxNumServices! - details.serviceIds!.length} / ${details.maxNumServices}`, }, { left: 'Rewards per epoch', @@ -38,13 +40,17 @@ export const StakingContractDetails = ({ right: `${details.olasStakeRequired} OLAS`, }, ]; - }, [stakingContractInfoRecord, stakingProgramId]); + }, [ + isStakingContractInfoLoaded, + stakingContractInfoRecord, + stakingProgramId, + ]); - if (!stakingContractInfoRecord) { + if (!isStakingContractInfoLoaded) { return ; } - if (!balances) { + if (!stakingContractInfoRecord) { return ( + ); }; diff --git a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractTag.tsx b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractTag.tsx index cd9e525d..316f3a60 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractTag.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractTag.tsx @@ -7,8 +7,11 @@ export const StakingContractTag = ({ }: { status: StakingProgramStatus | null; }) => { - if (status === StakingProgramStatus.Selected) { + if (status === StakingProgramStatus.Active) { return Active; } + if (status === StakingProgramStatus.Default) { + return Default; + } return null; }; diff --git a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx index d7759ff1..ce45bdc7 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx @@ -38,29 +38,31 @@ export const StakingContractSection = ({ const stakingProgramMeta = STAKING_PROGRAM_META[stakingProgramId]; - /** - * Returns `true` if this stakingProgram is active, - * or user is unstaked and this is the default - */ - const isActiveStakingProgram = useMemo(() => { - if (activeStakingProgramId === null) - return defaultStakingProgramId === stakingProgramId; - return activeStakingProgramId === stakingProgramId; - }, [activeStakingProgramId, defaultStakingProgramId, stakingProgramId]); + // /** + // * Returns `true` if this stakingProgram is active, + // * or user is unstaked and this is the default + // */ + // const isActiveStakingProgram = useMemo(() => { + // if (activeStakingProgramId === null) + // return defaultStakingProgramId === stakingProgramId; + // return activeStakingProgramId === stakingProgramId; + // }, [activeStakingProgramId, defaultStakingProgramId, stakingProgramId]); const contractTagStatus = useMemo(() => { if (activeStakingProgramId === stakingProgramId) - return StakingProgramStatus.Selected; + return StakingProgramStatus.Active; // Pearl is not staked, set as Selected if default if (!activeStakingProgramId && stakingProgramId === defaultStakingProgramId) - return StakingProgramStatus.Selected; + return StakingProgramStatus.Default; // Otherwise, no tag return null; }, [activeStakingProgramId, defaultStakingProgramId, stakingProgramId]); - const showMigrateButton = !isActiveStakingProgram; + const showMigrateButton = + stakingProgramId !== (activeStakingProgramId ?? defaultStakingProgramId); + const showFundingButton = useMemo(() => { if (migrateValidation.canMigrate) return false; return ( diff --git a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx index ef67412e..736df82e 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx @@ -7,7 +7,10 @@ import { useBalance } from '@/hooks/useBalance'; import { useNeedsFunds } from '@/hooks/useNeedsFunds'; import { useServices } from '@/hooks/useServices'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; -import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; +import { + useStakingContractContext, + useStakingContractInfo, +} from '@/hooks/useStakingContractInfo'; import { useStakingProgram } from '@/hooks/useStakingProgram'; import { getMinimumStakedAmountRequired } from '@/utils/service'; @@ -47,16 +50,15 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { useStakingProgram(); const { needsInitialFunding } = useNeedsFunds(); + const { stakingContractInfoRecord, isStakingContractInfoLoaded } = + useStakingContractContext(); + const { - activeStakingContractInfo, + stakingContractInfo, isServiceStaked, isServiceStakedForMinimumDuration, - isStakingContractInfoLoaded, - stakingContractInfoRecord, hasEnoughServiceSlots, - } = useStakingContractInfo(); - - const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; + } = useStakingContractInfo(stakingProgramId); const { hasInitialLoaded: isServicesLoaded } = useServices(); @@ -94,11 +96,25 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { return { canMigrate: false, reason: CantMigrateReason.LoadingServices }; } + // Services must be not be running or in a transitional state + if ( + [ + DeploymentStatus.DEPLOYED, + DeploymentStatus.DEPLOYING, + DeploymentStatus.STOPPING, + ].some((status) => status === serviceStatus) + ) { + return { + canMigrate: false, + reason: CantMigrateReason.PearlCurrentlyRunning, + }; + } + if (!isBalanceLoaded) { return { canMigrate: false, reason: CantMigrateReason.LoadingBalance }; } - if (isServicesLoaded && !isStakingContractInfoLoaded) { + if (!isStakingContractInfoLoaded) { return { canMigrate: false, reason: CantMigrateReason.LoadingStakingContractInfo, @@ -120,6 +136,16 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } + if ( + (stakingContractInfo.serviceIds ?? [])?.length >= + (stakingContractInfo.maxNumServices ?? 0) + ) { + return { + canMigrate: false, + reason: CantMigrateReason.NoAvailableStakingSlots, + }; + } + if ((stakingContractInfo.availableRewards ?? 0) <= 0) { return { canMigrate: false, @@ -144,20 +170,6 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } - // Services must be not be running or in a transitional state - if ( - [ - DeploymentStatus.DEPLOYED, - DeploymentStatus.DEPLOYING, - DeploymentStatus.STOPPING, - ].some((status) => status === serviceStatus) - ) { - return { - canMigrate: false, - reason: CantMigrateReason.PearlCurrentlyRunning, - }; - } - if (activeStakingProgramId === null && !isServiceStaked) { return { canMigrate: true }; } @@ -171,7 +183,7 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } - if (activeStakingContractInfo && !isServiceStakedForMinimumDuration) { + if (stakingContractInfo && !isServiceStakedForMinimumDuration) { return { canMigrate: false, reason: CantMigrateReason.NotStakedForMinimumDuration, @@ -189,7 +201,6 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { hasEnoughOlasToMigrate, isServiceStaked, activeStakingProgramMeta?.canMigrateTo, - activeStakingContractInfo, isServiceStakedForMinimumDuration, serviceStatus, ]); @@ -199,19 +210,52 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { return { canMigrate: false, reason: CantMigrateReason.LoadingServices }; } + // Services must be not be running or in a transitional state + if ( + [ + DeploymentStatus.DEPLOYED, + DeploymentStatus.DEPLOYING, + DeploymentStatus.STOPPING, + ].some((status) => status === serviceStatus) + ) { + return { + canMigrate: false, + reason: CantMigrateReason.PearlCurrentlyRunning, + }; + } + if (!isBalanceLoaded) { return { canMigrate: false, reason: CantMigrateReason.LoadingBalance }; } - if (!hasEnoughOlasForFirstRun) { + // staking contract requirements + + if (!isStakingContractInfoLoaded) { return { canMigrate: false, - reason: CantMigrateReason.InsufficientOlasToMigrate, + reason: CantMigrateReason.LoadingStakingContractInfo, }; } const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; + if (!stakingContractInfo) { + return { + canMigrate: false, + reason: CantMigrateReason.CannotFindStakingContractInfo, + }; + } + + if ( + (stakingContractInfo.serviceIds ?? [])?.length >= + (stakingContractInfo.maxNumServices ?? 0) + ) { + return { + canMigrate: false, + reason: CantMigrateReason.NoAvailableStakingSlots, + }; + } + if ((stakingContractInfo?.availableRewards ?? 0) <= 0) { return { canMigrate: false, @@ -226,13 +270,23 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } + // fund requirements + + if (!hasEnoughOlasForFirstRun) { + return { + canMigrate: false, + reason: CantMigrateReason.InsufficientOlasToMigrate, + }; + } + return { canMigrate: true }; }, [ isServicesLoaded, isBalanceLoaded, - hasEnoughOlasForFirstRun, + isStakingContractInfoLoaded, stakingContractInfoRecord, stakingProgramId, + hasEnoughOlasForFirstRun, ]); const canUpdateStakingContract = useMemo(() => { diff --git a/frontend/context/StakingContractInfoProvider.tsx b/frontend/context/StakingContractInfoProvider.tsx index 8533046d..504ed66b 100644 --- a/frontend/context/StakingContractInfoProvider.tsx +++ b/frontend/context/StakingContractInfoProvider.tsx @@ -74,14 +74,6 @@ export const StakingContractInfoProvider = ({ ).then(setActiveStakingContractInfo); }, [activeStakingProgramId, serviceId]); - useInterval( - async () => { - await updateStakingContractInfoRecord().catch(console.error); - await updateActiveStakingContractInfo().catch(console.error); - }, - isPaused ? null : FIVE_SECONDS_INTERVAL, - ); - /** Updates general staking contract information, not user or service specific */ const updateStakingContractInfoRecord = async () => { const stakingPrograms = Object.values(StakingProgramId); @@ -108,15 +100,22 @@ export const StakingContractInfoProvider = ({ setStakingContractInfoRecord(stakingContractInfoRecord); setIsStakingContractInfoLoaded(true); } catch (e) { - console.error(e); + console.error({ e }); } }; useEffect(() => { - // Load generic staking contract info record on mount - updateStakingContractInfoRecord(); + updateStakingContractInfoRecord().catch(console.error); }, []); + useInterval( + async () => { + await updateStakingContractInfoRecord().catch(console.error); + await updateActiveStakingContractInfo().catch(console.error); + }, + isPaused ? null : FIVE_SECONDS_INTERVAL, + ); + return ( Promise; + setDefaultStakingProgramId: (stakingProgramId: StakingProgramId) => void; }>({ activeStakingProgramId: undefined, - defaultStakingProgramId: DEFAULT_STAKING_PROGRAM_ID, + defaultStakingProgramId: INITIAL_DEFAULT_STAKING_PROGRAM_ID, updateActiveStakingProgramId: async () => {}, + setDefaultStakingProgramId: () => {}, }); /** Determines the current active staking program, if any */ @@ -25,6 +27,10 @@ export const StakingProgramProvider = ({ children }: PropsWithChildren) => { const [activeStakingProgramId, setActiveStakingProgramId] = useState(); + const [defaultStakingProgramId, setDefaultStakingProgramId] = useState( + INITIAL_DEFAULT_STAKING_PROGRAM_ID, + ); + const updateActiveStakingProgramId = useCallback(async () => { // if no service nft, not staked const serviceId = @@ -52,7 +58,8 @@ export const StakingProgramProvider = ({ children }: PropsWithChildren) => { value={{ activeStakingProgramId, updateActiveStakingProgramId, - defaultStakingProgramId: DEFAULT_STAKING_PROGRAM_ID, + defaultStakingProgramId, + setDefaultStakingProgramId, }} > {children} diff --git a/frontend/enums/StakingProgramStatus.ts b/frontend/enums/StakingProgramStatus.ts index d161434b..7e7364d0 100644 --- a/frontend/enums/StakingProgramStatus.ts +++ b/frontend/enums/StakingProgramStatus.ts @@ -1,3 +1,4 @@ export enum StakingProgramStatus { - Selected = 'current', + Active = 'current', + Default = 'default', } diff --git a/frontend/hooks/useStakingContractInfo.ts b/frontend/hooks/useStakingContractInfo.ts index 96860360..e8de2168 100644 --- a/frontend/hooks/useStakingContractInfo.ts +++ b/frontend/hooks/useStakingContractInfo.ts @@ -2,10 +2,9 @@ import { isNil } from 'lodash'; import { useContext } from 'react'; import { StakingContractInfoContext } from '@/context/StakingContractInfoProvider'; +import { StakingProgramId } from '@/enums/StakingProgram'; -import { useServices } from './useServices'; - -export const useStakingContractInfo = () => { +export const useStakingContractContext = () => { const { activeStakingContractInfo, isPaused, @@ -14,17 +13,20 @@ export const useStakingContractInfo = () => { updateActiveStakingContractInfo, setIsPaused, } = useContext(StakingContractInfoContext); + return { + activeStakingContractInfo, + isPaused, + isStakingContractInfoLoaded, + stakingContractInfoRecord, + updateActiveStakingContractInfo, + setIsPaused, + }; +}; - const { service } = useServices(); +export const useStakingContractInfo = (stakingProgramId: StakingProgramId) => { + const { stakingContractInfoRecord } = useStakingContractContext(); - // TODO: find a better way to handle this, currently stops react lifecycle hooks being implemented below it - if (!service || !activeStakingContractInfo) - return { - stakingContractInfoRecord, - updateActiveStakingContractInfo, - setIsPaused, - isPaused, - }; + const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; const { serviceStakingState, @@ -33,7 +35,7 @@ export const useStakingContractInfo = () => { maxNumServices, minimumStakingDuration, availableRewards, - } = activeStakingContractInfo; + } = stakingContractInfo ?? {}; const isRewardsAvailable = availableRewards ?? 0 > 0; @@ -81,18 +83,13 @@ export const useStakingContractInfo = () => { (serviceStakingStartTime ?? 0) + (minimumStakingDuration ?? 0); return { - activeStakingContractInfo, hasEnoughServiceSlots, isAgentEvicted, evictionExpiresAt, isEligibleForStaking, - isPaused, isRewardsAvailable, isServiceStakedForMinimumDuration, isServiceStaked, - isStakingContractInfoLoaded, - stakingContractInfoRecord, - updateActiveStakingContractInfo, - setIsPaused, + stakingContractInfo, }; }; diff --git a/frontend/hooks/useStakingProgram.ts b/frontend/hooks/useStakingProgram.ts index da4f3e8c..13dc2168 100644 --- a/frontend/hooks/useStakingProgram.ts +++ b/frontend/hooks/useStakingProgram.ts @@ -14,6 +14,7 @@ export const useStakingProgram = () => { activeStakingProgramId, defaultStakingProgramId, updateActiveStakingProgramId, + setDefaultStakingProgramId, } = useContext(StakingProgramContext); const isActiveStakingProgramLoaded = activeStakingProgramId !== undefined; @@ -54,5 +55,6 @@ export const useStakingProgram = () => { defaultStakingProgramMeta, isActiveStakingProgramLoaded, updateActiveStakingProgramId, + setDefaultStakingProgramId, }; }; diff --git a/frontend/utils/service.ts b/frontend/utils/service.ts index 53ec7fc7..3b3fe45b 100644 --- a/frontend/utils/service.ts +++ b/frontend/utils/service.ts @@ -1,11 +1,11 @@ import { ServiceTemplate } from '@/client'; -import { DEFAULT_STAKING_PROGRAM_ID } from '@/context/StakingProgramProvider'; +import { INITIAL_DEFAULT_STAKING_PROGRAM_ID } from '@/context/StakingProgramProvider'; import { StakingProgramId } from '@/enums/StakingProgram'; /** TODO: update from hardcoded, workaround for quick release */ export const getMinimumStakedAmountRequired = ( serviceTemplate?: ServiceTemplate, //TODO: remove, as unused - stakingProgramId: StakingProgramId = DEFAULT_STAKING_PROGRAM_ID, + stakingProgramId: StakingProgramId = INITIAL_DEFAULT_STAKING_PROGRAM_ID, ): number | undefined => { if (stakingProgramId === StakingProgramId.Alpha) { return 20; From 15be025e1a1fb67507aa6f283eac9c3e87c3d5ff Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:18:16 +0000 Subject: [PATCH 10/85] fix: backup wallet alert wait for owners to be loaded --- .../MainPage/sections/AlertSections/AddBackupWalletAlert.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx b/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx index 23952138..7ee41c49 100644 --- a/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx +++ b/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx @@ -1,4 +1,5 @@ import { Flex, Typography } from 'antd'; +import { isNil } from 'lodash'; import { Pages } from '@/enums/PageState'; import { useMasterSafe } from '@/hooks/useMasterSafe'; @@ -10,8 +11,9 @@ const { Text } = Typography; export const AddBackupWalletAlert = () => { const { goto } = usePageState(); - const { backupSafeAddress } = useMasterSafe(); + const { backupSafeAddress, masterSafeOwners } = useMasterSafe(); + if (isNil(masterSafeOwners)) return null; if (backupSafeAddress) return null; return ( From c8ff7df2b4414c5e0dc4dcd390fa015fe2993886 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:18:38 +0000 Subject: [PATCH 11/85] fix: support both xdai and olas gas requirements on first deploy --- .../CantMigrateAlert.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx index d75f6a6a..80741fdb 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx @@ -2,8 +2,10 @@ import { Flex, Typography } from 'antd'; import { isNil } from 'lodash'; import { CustomAlert } from '@/components/Alert'; +import { LOW_MASTER_SAFE_BALANCE } from '@/constants/thresholds'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useBalance } from '@/hooks/useBalance'; +import { useNeedsFunds } from '@/hooks/useNeedsFunds'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; import { useStakingContractContext, @@ -25,6 +27,7 @@ const AlertInsufficientMigrationFunds = ({ const { isServiceStaked } = useStakingContractInfo(stakingProgramId); const { masterSafeBalance: safeBalance, totalOlasStakedBalance } = useBalance(); + const { serviceFundRequirements, isInitialFunded } = useNeedsFunds(); const totalOlasRequiredForStaking = getMinimumStakedAmountRequired( serviceTemplate, @@ -40,18 +43,24 @@ const AlertInsufficientMigrationFunds = ({ ? totalOlasRequiredForStaking - (totalOlasStakedBalance + safeBalance.OLAS) // when staked : totalOlasRequiredForStaking - safeBalance.OLAS; // when not staked + const requiredXdaiDeposit = isInitialFunded + ? LOW_MASTER_SAFE_BALANCE - safeBalance.ETH // is already funded allow minimal maintenance + : serviceFundRequirements.eth - safeBalance.ETH; // otherwise require full initial funding requirements + return ( - - An additional {requiredOlasDeposit} OLAS is required to switch - + Additional funds required - Add {requiredOlasDeposit} OLAS to your account to - meet the contract requirements and switch. +
    + {requiredOlasDeposit > 0 &&
  • {requiredOlasDeposit} OLAS
  • } + {requiredXdaiDeposit > 0 &&
  • {requiredXdaiDeposit} XDAI
  • } +
+ Add the required funds to your account to meet the staking + requirements.
} @@ -107,7 +116,10 @@ export const CantMigrateAlert = ({ return ; } - if (cantMigrateReason === CantMigrateReason.InsufficientOlasToMigrate) { + if ( + cantMigrateReason === CantMigrateReason.InsufficientOlasToMigrate || + cantMigrateReason === CantMigrateReason.InsufficientGasToMigrate + ) { return ( ); From 85f0fc9fcc964250b867523e2875cb45dc85748b Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:18:55 +0000 Subject: [PATCH 12/85] fix: support both gas and olas checks --- .../ManageStakingPage/StakingContractSection/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx index ce45bdc7..410b934d 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx @@ -66,7 +66,9 @@ export const StakingContractSection = ({ const showFundingButton = useMemo(() => { if (migrateValidation.canMigrate) return false; return ( - migrateValidation.reason === CantMigrateReason.InsufficientOlasToMigrate + migrateValidation.reason === + CantMigrateReason.InsufficientOlasToMigrate || + migrateValidation.reason === CantMigrateReason.InsufficientGasToMigrate ); }, [migrateValidation]); From 63c37ef7591dc59111e9a92507c50b3383d4c300 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:19:10 +0000 Subject: [PATCH 13/85] feat: add insufficient gas enum --- .../StakingContractSection/useMigrate.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx index 736df82e..87e7e8e3 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx @@ -19,6 +19,7 @@ export enum CantMigrateReason { LoadingBalance = 'Loading balance...', LoadingStakingContractInfo = 'Loading staking contract information...', InsufficientOlasToMigrate = 'Insufficient OLAS to switch', + InsufficientGasToMigrate = 'Insufficient XDAI to switch', // TODO: make chain agnostic MigrationNotSupported = 'Switching to this program is not currently supported', NoAvailableRewards = 'This program has no rewards available', NoAvailableStakingSlots = 'The program has no more available slots', @@ -62,6 +63,8 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { const { hasInitialLoaded: isServicesLoaded } = useServices(); + const { hasEnoughEthForInitialFunding } = useNeedsFunds(); + const minimumOlasRequiredToMigrate = useMemo( () => getMinimumStakedAmountRequired(serviceTemplate, stakingProgramId), [serviceTemplate, stakingProgramId], @@ -206,6 +209,10 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { ]); const firstDeployValidation = useMemo(() => { + /** + * @todo fix temporary check for xDai balance on first deploy (same as initial funding requirement) + */ + if (!isServicesLoaded) { return { canMigrate: false, reason: CantMigrateReason.LoadingServices }; } @@ -270,8 +277,6 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } - // fund requirements - if (!hasEnoughOlasForFirstRun) { return { canMigrate: false, @@ -279,14 +284,23 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } + if (!hasEnoughEthForInitialFunding) { + return { + canMigrate: false, + reason: CantMigrateReason.InsufficientGasToMigrate, + }; + } + return { canMigrate: true }; }, [ isServicesLoaded, isBalanceLoaded, + hasEnoughEthForInitialFunding, isStakingContractInfoLoaded, stakingContractInfoRecord, stakingProgramId, hasEnoughOlasForFirstRun, + serviceStatus, ]); const canUpdateStakingContract = useMemo(() => { From 77459c39b1b16016b0b20134f8cb43b3940540bc Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:19:35 +0000 Subject: [PATCH 14/85] chore: move isInitialFunded higher --- frontend/hooks/useNeedsFunds.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/hooks/useNeedsFunds.ts b/frontend/hooks/useNeedsFunds.ts index 78117fda..76886062 100644 --- a/frontend/hooks/useNeedsFunds.ts +++ b/frontend/hooks/useNeedsFunds.ts @@ -17,6 +17,8 @@ export const useNeedsFunds = () => { ); const { storeState } = useStore(); + const isInitialFunded = storeState?.isInitialFunded; + const { isBalanceLoaded, masterSafeBalance: safeBalance, @@ -51,7 +53,6 @@ export const useNeedsFunds = () => { serviceFundRequirements?.olas, ]); - const isInitialFunded = storeState?.isInitialFunded; const needsInitialFunding: boolean = useMemo(() => { if (isInitialFunded) return false; if (!isBalanceLoaded) return false; From 37988d9aaf96632f7215519861ae8141eea21971 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:30:48 +0000 Subject: [PATCH 15/85] bump: rc187 --- package.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b9dbb657..205bf10e 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,5 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.1.0-rc186" + "version": "0.1.0-rc187" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f5109942..e5f356ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.1.0-rc186" +version = "0.1.0-rc187" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" From d1d23a3a1e1bc39aa8d7a775753b67ccd02c04c7 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 15:16:17 +0000 Subject: [PATCH 16/85] fix: fallback to activeStakingProgramInfo if activeStakingProgramId exists --- .../NoAvailableSlotsOnTheContract.tsx | 3 ++- .../context/StakingContractInfoProvider.tsx | 23 ++++++++++++++----- frontend/hooks/useStakingContractInfo.ts | 14 ++++++++--- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx index 5174f22e..8975155e 100644 --- a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx +++ b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx @@ -24,7 +24,7 @@ export const NoAvailableSlotsOnTheContract = () => { } = useStakingProgram(); const { isStakingContractInfoLoaded } = useStakingContractContext(); - const { hasEnoughServiceSlots } = useStakingContractInfo( + const { hasEnoughServiceSlots, isServiceStaked } = useStakingContractInfo( activeStakingProgramId ?? defaultStakingProgramId, ); @@ -43,6 +43,7 @@ export const NoAvailableSlotsOnTheContract = () => { if (!isStakingContractInfoLoaded) return null; if (hasEnoughServiceSlots) return null; + if (isServiceStaked) return null; return ( ; + isActiveStakingContractInfoLoaded: boolean; isPaused: boolean; - isStakingContractInfoLoaded: boolean; stakingContractInfoRecord?: Record< StakingProgramId, Partial >; + isStakingContractInfoRecordLoaded: boolean; updateActiveStakingContractInfo: () => Promise; setIsPaused: Dispatch>; }; @@ -36,7 +37,8 @@ export const StakingContractInfoContext = createContext({ activeStakingContractInfo: undefined, isPaused: false, - isStakingContractInfoLoaded: false, + isStakingContractInfoRecordLoaded: false, + isActiveStakingContractInfoLoaded: false, stakingContractInfoRecord: undefined, updateActiveStakingContractInfo: async () => {}, setIsPaused: () => {}, @@ -49,8 +51,14 @@ export const StakingContractInfoProvider = ({ const { activeStakingProgramId } = useContext(StakingProgramContext); const [isPaused, setIsPaused] = useState(false); - const [isStakingContractInfoLoaded, setIsStakingContractInfoLoaded] = - useState(false); + const [ + isStakingContractInfoRecordLoaded, + setIsStakingContractInfoRecordLoaded, + ] = useState(false); + const [ + isActiveStakingContractInfoLoaded, + setIsActiveStakingContractInfoLoaded, + ] = useState(false); const [activeStakingContractInfo, setActiveStakingContractInfo] = useState>(); @@ -72,6 +80,8 @@ export const StakingContractInfoProvider = ({ serviceId, activeStakingProgramId, ).then(setActiveStakingContractInfo); + + setIsActiveStakingContractInfoLoaded(true); }, [activeStakingProgramId, serviceId]); /** Updates general staking contract information, not user or service specific */ @@ -98,7 +108,7 @@ export const StakingContractInfoProvider = ({ ); setStakingContractInfoRecord(stakingContractInfoRecord); - setIsStakingContractInfoLoaded(true); + setIsStakingContractInfoRecordLoaded(true); } catch (e) { console.error({ e }); } @@ -120,7 +130,8 @@ export const StakingContractInfoProvider = ({ { const { activeStakingContractInfo, isPaused, - isStakingContractInfoLoaded, + isStakingContractInfoRecordLoaded: isStakingContractInfoLoaded, stakingContractInfoRecord, updateActiveStakingContractInfo, setIsPaused, @@ -24,9 +26,15 @@ export const useStakingContractContext = () => { }; export const useStakingContractInfo = (stakingProgramId: StakingProgramId) => { - const { stakingContractInfoRecord } = useStakingContractContext(); + const { activeStakingProgramId } = useStakingProgram(); + + const { stakingContractInfoRecord, activeStakingContractInfo } = + useStakingContractContext(); - const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; + const stakingContractInfo = + activeStakingProgramId === stakingProgramId + ? stakingContractInfoRecord?.[stakingProgramId] + : activeStakingContractInfo; const { serviceStakingState, From 40d72aa08311f6994414c8bfa5ee8f71c375e4a0 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 16:10:37 +0000 Subject: [PATCH 17/85] refactor: add useActiveStakingContractInfo, this record is updated seperately with active service id, where as other contract info records are not --- .../MainPage/header/AgentButton.tsx | 32 ++++---- .../header/CannotStartAgentPopover.tsx | 28 +++---- frontend/components/MainPage/index.tsx | 4 +- .../NoAvailableSlotsOnTheContract.tsx | 12 +-- .../sections/StakingContractUpdate.tsx | 6 +- .../CantMigrateAlert.tsx | 8 +- .../CountdownUntilMigration.tsx | 8 +- .../StakingContractSection/MigrateButton.tsx | 31 +++++-- .../StakingContractDetails.tsx | 8 +- .../StakingContractSection/useMigrate.tsx | 23 ++++-- frontend/hooks/useStakingContractInfo.ts | 82 +++++++++++-------- 11 files changed, 135 insertions(+), 107 deletions(-) diff --git a/frontend/components/MainPage/header/AgentButton.tsx b/frontend/components/MainPage/header/AgentButton.tsx index af3d1098..f0846bb6 100644 --- a/frontend/components/MainPage/header/AgentButton.tsx +++ b/frontend/components/MainPage/header/AgentButton.tsx @@ -11,6 +11,7 @@ import { useReward } from '@/hooks/useReward'; import { useServices } from '@/hooks/useServices'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; import { + useActiveStakingContractInfo, useStakingContractContext, useStakingContractInfo, } from '@/hooks/useStakingContractInfo'; @@ -145,7 +146,7 @@ const AgentNotRunningButton = () => { const { storeState } = useStore(); const { - isStakingContractInfoLoaded, + isStakingContractInfoRecordLoaded, setIsPaused: setIsStakingContractInfoPollingPaused, updateActiveStakingContractInfo, } = useStakingContractContext(); @@ -153,12 +154,12 @@ const AgentNotRunningButton = () => { const { activeStakingProgramId, defaultStakingProgramId } = useStakingProgram(); - const { - isEligibleForStaking, - isAgentEvicted, - hasEnoughServiceSlots, - isServiceStaked, - } = useStakingContractInfo(activeStakingProgramId ?? defaultStakingProgramId); + const { isEligibleForStaking, isAgentEvicted, isServiceStaked } = + useActiveStakingContractInfo(); + + const { hasEnoughServiceSlots } = useStakingContractInfo( + activeStakingProgramId ?? defaultStakingProgramId, + ); // const minStakingDeposit = // stakingContractInfoRecord?.[activeStakingProgram ?? defaultStakingProgram] @@ -276,7 +277,7 @@ const AgentNotRunningButton = () => { ]); const isDeployable = useMemo(() => { - if (!isStakingContractInfoLoaded) return false; + if (!isStakingContractInfoRecordLoaded) return false; // if the agent is NOT running and the balance is too low, // user should not be able to start the agent @@ -311,7 +312,7 @@ const AgentNotRunningButton = () => { return hasEnoughOlas && hasEnoughEth; }, [ - isStakingContractInfoLoaded, + isStakingContractInfoRecordLoaded, serviceStatus, isLowBalance, requiredOlas, @@ -343,15 +344,12 @@ export const AgentButton = () => { serviceStatus, hasInitialLoaded: isServicesLoaded, } = useServices(); - const { activeStakingProgramId, defaultStakingProgramId } = - useStakingProgram(); - const { isStakingContractInfoLoaded } = useStakingContractContext(); - const { isEligibleForStaking, isAgentEvicted } = useStakingContractInfo( - activeStakingProgramId ?? defaultStakingProgramId, - ); + const { isStakingContractInfoRecordLoaded } = useStakingContractContext(); + const { isEligibleForStaking, isAgentEvicted } = + useActiveStakingContractInfo(); return useMemo(() => { - if (!isServicesLoaded || !isStakingContractInfoLoaded) { + if (!isServicesLoaded || !isStakingContractInfoRecordLoaded) { return