Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add checks against contract no funds and full slots cases #162

Merged
merged 29 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
825583d
chore: Update package.json and yarn.lock with zustand dependency
mohandast52 May 30, 2024
be4eb87
chore: Add StakingValidations component and getStakingContractInfo fu…
mohandast52 May 31, 2024
cb35f72
chore: Update StakingContractInfo type in Autonolas.ts
mohandast52 May 31, 2024
788edae
chore: Add useStakingContractInfo hook for fetching staking contract …
mohandast52 May 31, 2024
93c93e3
chore: Refactor useStakingContractInfo hook and fetchStakingContractI…
mohandast52 May 31, 2024
9c77e1c
chore: Refactor StakingValidations component and fetchStakingContract…
mohandast52 May 31, 2024
1cafb91
chore: Add StakingValidations component to Main
mohandast52 May 31, 2024
df6c489
feat: Add canStartAgent check to MainHeader component
mohandast52 May 31, 2024
79108c8
chore: Remove unused maxNumServices property in useStakingContractInf…
mohandast52 May 31, 2024
4a384ab
conflict fixes
mohandast52 Jun 3, 2024
073d18b
Merge branch 'main' of github.aaakk.us.kg-personal:valory-xyz/olas-operate-ap…
mohandast52 Jun 5, 2024
abb11e2
feat: Update file paths and imports for MainHeader and StakingValidat…
mohandast52 Jun 6, 2024
f126199
Merge branch 'main' of github.aaakk.us.kg-personal:valory-xyz/olas-operate-ap…
mohandast52 Jun 26, 2024
936fe4a
chore: Update StakingContractInfo to use serviceIds instead of getSer…
mohandast52 Jun 26, 2024
ee44d09
chore: Update MainHeader component with new features and improvements
mohandast52 Jun 26, 2024
83a2358
chore: Update AgentEvictedPopover content
mohandast52 Jun 26, 2024
6556a8d
chore: Implement isAgentEvicted flag in StakingContractInfo store
mohandast52 Jun 26, 2024
696f71d
chore: Remove StakingValidations component from MainHeader
mohandast52 Jun 26, 2024
2d95886
chore: Update MainHeader component with new features and improvements…
mohandast52 Jun 26, 2024
2cc967a
chore: Add FirstRunModal component to MainHeader
mohandast52 Jun 26, 2024
630b06a
chore: Refactor MainHeader component and dependencies
mohandast52 Jun 27, 2024
98c7d2f
chore: Update MainHeader component with useStakingContractInfo hook
mohandast52 Jun 27, 2024
f956a4a
Refactor MainHeader component and dependencies
mohandast52 Jun 27, 2024
6e18beb
chore: move to constants and components
mohandast52 Jun 27, 2024
905304f
Refactor MainHeader component and dependencies
mohandast52 Jul 1, 2024
0300647
refractor: StakingContractProvider context added
mohandast52 Jul 1, 2024
8466d40
refractor: remove zustand
mohandast52 Jul 1, 2024
fbbfbf9
chore: move to variable
mohandast52 Jul 1, 2024
a1605a1
chore: address review changes
mohandast52 Jul 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = ({
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to a separate file - FirstRunModal.tsx

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]);
mohandast52 marked this conversation as resolved.
Show resolved Hide resolved

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,
),
);
mohandast52 marked this conversation as resolved.
Show resolved Hide resolved

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
Loading