Skip to content

Commit

Permalink
Merge pull request #162 from valory-xyz/mohan/checks-against-contract…
Browse files Browse the repository at this point in the history
…-no-funds

feat: Add checks against contract no funds and full slots cases
  • Loading branch information
mohandast52 authored Jul 2, 2024
2 parents afc92da + a1605a1 commit 2bcbc0c
Show file tree
Hide file tree
Showing 11 changed files with 354 additions and 115 deletions.
2 changes: 1 addition & 1 deletion frontend/components/Main/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useBalance, usePageState, useServices } from '@/hooks';
import { KeepAgentRunning } from './KeepAgentRunning';
import { MainAddFunds } from './MainAddFunds';
import { MainGasBalance } from './MainGasBalance';
import { MainHeader } from './MainHeader';
import { MainHeader } from './MainHeader/MainHeader';
import { MainNeedsFunds } from './MainNeedsFunds';
import { MainOlasBalance } from './MainOlasBalance';
import { MainRewards } from './MainRewards';
Expand Down
80 changes: 80 additions & 0 deletions frontend/components/Main/MainHeader/CannotStartAgent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { InfoCircleOutlined } from '@ant-design/icons';
import { Popover, PopoverProps, Typography } from 'antd';

import { COLOR, SUPPORT_URL } from '@/constants';
import { UNICODE_SYMBOLS } from '@/constants/unicode';
import { useStakingContractInfo } from '@/hooks/useStakingContractInfo';

const { Paragraph, Text } = Typography;

const cannotStartAgentText = (
<Text style={{ color: COLOR.RED }}>
Cannot start agent&nbsp;
<InfoCircleOutlined />
</Text>
);

const evictedDescription =
"You didn't run your agent enough and it missed its targets multiple times. Please wait a few days and try to run your agent again.";
const AgentEvictedPopover = () => (
<Popover
{...otherPopoverProps}
title="Your agent was evicted"
content={<div style={{ maxWidth: 340 }}>{evictedDescription}</div>}
>
{cannotStartAgentText}
</Popover>
);

const otherPopoverProps: PopoverProps = {
arrow: false,
placement: 'bottomRight',
};

const JoinOlasCommunity = () => (
<div style={{ maxWidth: 340 }}>
<Paragraph>
Join the Olas community Discord server to report or stay up to date on the
issue.
</Paragraph>

<a href={SUPPORT_URL} target="_blank" rel="noreferrer">
Olas community Discord server {UNICODE_SYMBOLS.EXTERNAL_LINK}
</a>
</div>
);

const NoRewardsAvailablePopover = () => (
<Popover
{...otherPopoverProps}
title="No rewards available"
content={<JoinOlasCommunity />}
>
{cannotStartAgentText}
</Popover>
);

const NoJobsAvailablePopover = () => (
<Popover
{...otherPopoverProps}
title="No jobs available"
content={<JoinOlasCommunity />}
>
{cannotStartAgentText}
</Popover>
);

export const CannotStartAgent = () => {
const {
canStartAgent,
hasEnoughServiceSlots,
isRewardsAvailable,
isAgentEvicted,
} = useStakingContractInfo();

if (canStartAgent) return null;
if (!hasEnoughServiceSlots) return <NoJobsAvailablePopover />;
if (!isRewardsAvailable) return <NoRewardsAvailablePopover />;
if (isAgentEvicted) return <AgentEvictedPopover />;
throw new Error('Cannot start agent, please contact support');
};
52 changes: 52 additions & 0 deletions frontend/components/Main/MainHeader/FirstRunModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Button, Flex, Modal, Typography } from 'antd';
import Image from 'next/image';
import { FC } from 'react';

import { useReward } from '@/hooks/useReward';

const { Title, Paragraph } = Typography;

type FirstRunModalProps = { open: boolean; onClose: () => void };

export const FirstRunModal: FC<FirstRunModalProps> = ({ open, onClose }) => {
const { minimumStakedAmountRequired } = useReward();

if (!open) return null;
return (
<Modal
open={open}
width={412}
onCancel={onClose}
footer={[
<Button
key="ok"
type="primary"
block
size="large"
className="mt-8"
onClick={onClose}
>
Got it
</Button>,
]}
>
<Flex align="center" justify="center">
<Image
src="/splash-robot-head.png"
width={100}
height={100}
alt="OLAS logo"
/>
</Flex>
<Title level={5} className="mt-12 text-center">
{`Your agent is running and you&apos;ve staked ${minimumStakedAmountRequired} OLAS!`}
</Title>
<Paragraph>Your agent is working towards earning rewards.</Paragraph>
<Paragraph>
Pearl is designed to make it easy for you to earn staking rewards every
day. Simply leave the app and agent running in the background for ~1hr a
day.
</Paragraph>
</Modal>
);
};
Original file line number Diff line number Diff line change
@@ -1,84 +1,76 @@
import { InfoCircleOutlined } from '@ant-design/icons';
import { Badge, Button, Flex, Modal, Popover, Typography } from 'antd';
import { formatUnits } from 'ethers/lib/utils';
import { Badge, Button, Flex, Popover, Typography } from 'antd';
import Image from 'next/image';
import { useCallback, useEffect, useMemo, useState } from 'react';

import { Chain, DeploymentStatus } from '@/client';
import { COLOR, LOW_BALANCE, SERVICE_TEMPLATES } from '@/constants';
import { COLOR, LOW_BALANCE } from '@/constants';
import { useBalance, useServiceTemplates } from '@/hooks';
import { useElectronApi } from '@/hooks/useElectronApi';
import { useReward } from '@/hooks/useReward';
import { useServices } from '@/hooks/useServices';
import { useStakingContractInfo } from '@/hooks/useStakingContractInfo';
import { useStore } from '@/hooks/useStore';
import { useWallet } from '@/hooks/useWallet';
import { ServicesService } from '@/service';
import { WalletService } from '@/service/Wallet';

const { Text, Title, Paragraph } = Typography;
import { CannotStartAgent } from './CannotStartAgent';
import { requiredGas, requiredOlas } from './constants';
import { FirstRunModal } from './FirstRunModal';

const { Text } = Typography;

const LOADING_MESSAGE =
'Starting the agent may take a while, so feel free to minimize the app. We’ll notify you once it’s running. Please, don’t quit the app.';
const StartingButtonPopover = () => (
<Popover
trigger={['hover', 'click']}
placement="bottomLeft"
showArrow={false}
content={
<Flex vertical={false} gap={8} style={{ maxWidth: 260 }}>
<Text>
<InfoCircleOutlined style={{ color: COLOR.BLUE }} />
</Text>
<Text>{LOADING_MESSAGE}</Text>
</Flex>
}
>
<Button type="default" size="large" ghost disabled loading>
Starting...
</Button>
</Popover>
);

enum ServiceButtonLoadingState {
Starting,
Pausing,
NotLoading,
}

const FirstRunModal = ({
open,
onClose,
}: {
open: boolean;
onClose: () => void;
}) => {
const { minimumStakedAmountRequired } = useReward();
const useSetupTrayIcon = () => {
const { safeBalance } = useBalance();
const { serviceStatus } = useServices();
const { setTrayIcon } = useElectronApi();

if (!open) return null;
return (
<Modal
open={open}
width={412}
onCancel={onClose}
footer={[
<Button
key="ok"
type="primary"
block
size="large"
className="mt-8"
onClick={onClose}
>
Got it
</Button>,
]}
>
<Flex align="center" justify="center">
<Image
src="/splash-robot-head.png"
width={100}
height={100}
alt="OLAS logo"
/>
</Flex>
<Title level={5} className="mt-12 text-center">
{`Your agent is running and you&apos;ve staked ${minimumStakedAmountRequired} OLAS!`}
</Title>
<Paragraph>Your agent is working towards earning rewards.</Paragraph>
<Paragraph>
Pearl is designed to make it easy for you to earn staking rewards every
day. Simply leave the app and agent running in the background for ~1hr a
day.
</Paragraph>
</Modal>
);
useEffect(() => {
if (safeBalance && safeBalance.ETH < LOW_BALANCE) {
setTrayIcon?.('low-gas');
} else if (serviceStatus === DeploymentStatus.DEPLOYED) {
setTrayIcon?.('running');
} else if (serviceStatus === DeploymentStatus.STOPPED) {
setTrayIcon?.('paused');
}
}, [safeBalance, serviceStatus, setTrayIcon]);

return null;
};

export const MainHeader = () => {
const { storeState } = useStore();
const { services, serviceStatus, setServiceStatus } = useServices();
const { showNotification, setTrayIcon } = useElectronApi();
const { showNotification } = useElectronApi();
const { getServiceTemplates } = useServiceTemplates();
const { wallets, masterSafeAddress } = useWallet();
const {
Expand All @@ -94,6 +86,12 @@ export const MainHeader = () => {

const { minimumStakedAmountRequired } = useReward();

const { isStakingContractInfoLoading, canStartAgent } =
useStakingContractInfo();

// hook to setup tray icon
useSetupTrayIcon();

const safeOlasBalanceWithStaked = useMemo(() => {
if (safeBalance?.OLAS === undefined) return;
if (totalOlasStakedBalance === undefined) return;
Expand All @@ -108,16 +106,6 @@ export const MainHeader = () => {
[getServiceTemplates],
);

useEffect(() => {
if (safeBalance && safeBalance.ETH < LOW_BALANCE) {
setTrayIcon?.('low-gas');
} else if (serviceStatus === DeploymentStatus.DEPLOYED) {
setTrayIcon?.('running');
} else if (serviceStatus === DeploymentStatus.STOPPED) {
setTrayIcon?.('paused');
}
}, [safeBalance, serviceStatus, setTrayIcon]);

const agentHead = useMemo(() => {
if (
serviceButtonState === ServiceButtonLoadingState.Starting ||
Expand Down Expand Up @@ -227,25 +215,7 @@ export const MainHeader = () => {
}

if (serviceButtonState === ServiceButtonLoadingState.Starting) {
return (
<Popover
trigger={['hover', 'click']}
placement="bottomLeft"
showArrow={false}
content={
<Flex vertical={false} gap={8} style={{ maxWidth: 260 }}>
<Text>
<InfoCircleOutlined style={{ color: COLOR.BLUE }} />
</Text>
<Text>{LOADING_MESSAGE}</Text>
</Flex>
}
>
<Button type="default" size="large" ghost disabled loading>
Starting...
</Button>
</Popover>
);
return <StartingButtonPopover />;
}

if (serviceStatus === DeploymentStatus.DEPLOYED) {
Expand All @@ -272,29 +242,6 @@ export const MainHeader = () => {
);
}

const olasCostOfBond = Number(
formatUnits(
`${SERVICE_TEMPLATES[0].configuration.olas_cost_of_bond}`,
18,
),
);

const olasRequiredToStake = Number(
formatUnits(
`${SERVICE_TEMPLATES[0].configuration.olas_required_to_stake}`,
18,
),
);

const requiredOlas = olasCostOfBond + olasRequiredToStake;

const requiredGas = Number(
formatUnits(
`${SERVICE_TEMPLATES[0].configuration.monthly_gas_estimate}`,
18,
),
);

const isDeployable = (() => {
// case where required values are undefined (not fetched from the server)
if (totalEthBalance === undefined) return false;
Expand Down Expand Up @@ -327,7 +274,12 @@ export const MainHeader = () => {
}

return (
<Button type="primary" size="large" onClick={handleStart}>
<Button
type="primary"
size="large"
disabled={!canStartAgent}
onClick={handleStart}
>
Start agent {!serviceExists && '& stake'}
</Button>
);
Expand All @@ -341,12 +293,15 @@ export const MainHeader = () => {
services,
storeState?.isInitialFunded,
totalEthBalance,
canStartAgent,
]);

return (
<Flex justify="start" align="center" gap={10}>
{agentHead}
{serviceToggleButton}
{isStakingContractInfoLoading ? null : (
<>{canStartAgent ? serviceToggleButton : <CannotStartAgent />}</>
)}
<FirstRunModal open={isModalOpen} onClose={handleModalClose} />
</Flex>
);
Expand Down
Loading

0 comments on commit 2bcbc0c

Please sign in to comment.