Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/staging' into feat/simple-ota
Browse files Browse the repository at this point in the history
  • Loading branch information
truemiller committed Oct 14, 2024
2 parents 43ac687 + a744c2e commit c820f9a
Show file tree
Hide file tree
Showing 17 changed files with 202 additions and 49 deletions.
1 change: 1 addition & 0 deletions electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ function showNotification(title, body) {
async function beforeQuit() {
if (operateDaemonPid) {
try {
await fetch(`http://localhost:${appConfig.ports.prod.operate}/stop_all_services`);
await killProcesses(operateDaemonPid);
} catch (e) {
logger.electron(e);
Expand Down
44 changes: 33 additions & 11 deletions frontend/components/MainPage/header/CannotStartAgentPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { InfoCircleOutlined } from '@ant-design/icons';
import { Popover, PopoverProps, Typography } from 'antd';
import { Flex, Popover, PopoverProps, Typography } from 'antd';

import { COLOR } from '@/constants/colors';
import { UNICODE_SYMBOLS } from '@/constants/symbols';
import { SUPPORT_URL } from '@/constants/urls';
import { useStakingContractInfo } from '@/hooks/useStakingContractInfo';
import { formatToShortDateTime } from '@/utils/time';

const { Paragraph, Text } = Typography;

Expand Down Expand Up @@ -42,16 +43,37 @@ export const CannotStartAgentDueToUnexpectedError = () => (
);

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 is suspended from work"
content={<div style={{ maxWidth: 340 }}>{evictedDescription}</div>}
>
{cannotStartAgentText}
</Popover>
);
"You didn't run your agent enough and it missed its targets multiple times. You can run the agent again when the eviction period ends.";
const AgentEvictedPopover = () => {
const { evictionExpiresAt } = useStakingContractInfo();

return (
<Popover
{...otherPopoverProps}
title="Your agent is evicted"
content={
<Flex
vertical
gap={8}
className="text-sm-all"
style={{ maxWidth: 340 }}
>
<Paragraph className="text-sm m-0">{evictedDescription}</Paragraph>
{evictionExpiresAt && (
<Paragraph className="m-0">
<Text className="text-sm">Eviction ends at</Text>{' '}
<Text strong className="text-sm">
{formatToShortDateTime(evictionExpiresAt * 1000)}
</Text>
</Paragraph>
)}
</Flex>
}
>
{cannotStartAgentText}
</Popover>
);
};

const JoinOlasCommunity = () => (
<div style={{ maxWidth: 340 }}>
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/MainPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { GasBalanceSection } from './sections/GasBalanceSection';
import { KeepAgentRunningSection } from './sections/KeepAgentRunningSection';
import { MainNeedsFunds } from './sections/NeedsFundsSection';
import { MainOlasBalance } from './sections/OlasBalanceSection';
import { MainRewards } from './sections/RewardsSection';
import { MainRewards } from './sections/RewardsSection/RewardsSection';
import { StakingContractUpdate } from './sections/StakingContractUpdate';

export const Main = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons';
import { Button, Flex, Modal, Skeleton, Tag, Tooltip, Typography } from 'antd';
import { RightOutlined } from '@ant-design/icons';
import { Button, Flex, Modal, Skeleton, Tag, Typography } from 'antd';
import Image from 'next/image';
import { useCallback, useEffect, useRef, useState } from 'react';

Expand All @@ -11,10 +11,11 @@ import { useReward } from '@/hooks/useReward';
import { useStore } from '@/hooks/useStore';
import { balanceFormat } from '@/utils/numberFormatters';

import { ConfettiAnimation } from '../../Confetti/ConfettiAnimation';
import { CardSection } from '../../styled/CardSection';
import { ConfettiAnimation } from '../../../Confetti/ConfettiAnimation';
import { CardSection } from '../../../styled/CardSection';
import { StakingRewardsThisEpoch } from './StakingRewardsThisEpoch';

const { Text, Title, Paragraph } = Typography;
const { Text, Title } = Typography;

const Loader = () => (
<Flex vertical gap={8}>
Expand All @@ -35,20 +36,7 @@ const DisplayRewards = () => {

return (
<CardSection vertical gap={8} padding="16px 24px" align="start">
<Text type="secondary">
Staking rewards this epoch&nbsp;
<Tooltip
arrow={false}
title={
<Paragraph className="text-sm m-0">
The agent&apos;s working period lasts at least 24 hours, but its
start and end point may not be at the same time every day.
</Paragraph>
}
>
<InfoCircleOutlined />
</Tooltip>
</Text>
<StakingRewardsThisEpoch />

{isBalanceLoaded ? (
<Flex align="center" gap={12}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { InfoCircleOutlined } from '@ant-design/icons';
import { useQuery } from '@tanstack/react-query';
import { Popover, Typography } from 'antd';
import { gql, request } from 'graphql-request';
import { z } from 'zod';

import { SUBGRAPH_URL } from '@/constants/urls';
import { POPOVER_WIDTH_MEDIUM } from '@/constants/width';
import { useStakingProgram } from '@/hooks/useStakingProgram';
import { formatToTime } from '@/utils/time';

const { Text } = Typography;

const EpochTimeResponseSchema = z.object({
epochLength: z.string(),
blockTimestamp: z.string(),
});
type EpochTimeResponse = z.infer<typeof EpochTimeResponseSchema>;

const useEpochEndTime = () => {
const { activeStakingProgramAddress } = useStakingProgram();
const latestEpochTimeQuery = gql`
query {
checkpoints(
orderBy: epoch
orderDirection: desc
first: 1
where: {
contractAddress: "${activeStakingProgramAddress}"
}
) {
epochLength
blockTimestamp
}
}
`;

const { data, isLoading } = useQuery({
queryKey: ['latestEpochTime'],
queryFn: async () => {
const response = (await request(SUBGRAPH_URL, latestEpochTimeQuery)) as {
checkpoints: EpochTimeResponse[];
};
return EpochTimeResponseSchema.parse(response.checkpoints[0]);
},
select: (data) => {
// last epoch end time + epoch length
return Number(data.blockTimestamp) + Number(data.epochLength);
},
enabled: !!activeStakingProgramAddress,
});

return { data, isLoading };
};

export const StakingRewardsThisEpoch = () => {
const { data: epochEndTimeInMs } = useEpochEndTime();
const { activeStakingProgramMeta } = useStakingProgram();

return (
<Text type="secondary">
Staking rewards this epoch&nbsp;
<Popover
arrow={false}
content={
<div style={{ maxWidth: POPOVER_WIDTH_MEDIUM }}>
The epoch for {activeStakingProgramMeta?.name} ends each day at ~{' '}
<Text className="text-sm" strong>
{epochEndTimeInMs
? `${formatToTime(epochEndTimeInMs * 1000)} (UTC)`
: '--'}
</Text>
</div>
}
>
<InfoCircleOutlined />
</Popover>
</Text>
);
};
4 changes: 1 addition & 3 deletions frontend/components/RewardsHistory/useRewardsHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { z } from 'zod';
import { Chain } from '@/client';
import { SERVICE_STAKING_TOKEN_MECH_USAGE_CONTRACT_ADDRESSES } from '@/constants/contractAddresses';
import { STAKING_PROGRAM_META } from '@/constants/stakingProgramMeta';
import { SUBGRAPH_URL } from '@/constants/urls';
import { StakingProgramId } from '@/enums/StakingProgram';
import { useServices } from '@/hooks/useServices';

Expand All @@ -30,9 +31,6 @@ const beta2Address =
SERVICE_STAKING_TOKEN_MECH_USAGE_CONTRACT_ADDRESSES[Chain.GNOSIS]
.pearl_beta_2;

const SUBGRAPH_URL =
'https://api.studio.thegraph.com/query/81855/pearl-staking-rewards-history/version/latest';

const fetchRewardsQuery = gql`
{
allRewards: checkpoints(orderBy: epoch, orderDirection: desc) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/constants/headers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const CONTENT_TYPE_JSON_UTF8 = {
'Content-Type': 'application/json; charset=utf-8',
};
} as const;
3 changes: 3 additions & 0 deletions frontend/constants/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export const COW_SWAP_GNOSIS_XDAI_OLAS_URL: string =
export const FAQ_URL = 'https://olas.network/operate#faq';
export const DOWNLOAD_URL = 'https://olas.network/operate#download';

export const SUBGRAPH_URL =
'https://api.studio.thegraph.com/query/81855/pearl-staking-rewards-history/version/latest';

// discord
export const SUPPORT_URL =
'https://discord.com/channels/899649805582737479/1244588374736502847';
Expand Down
2 changes: 2 additions & 0 deletions frontend/constants/width.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const MODAL_WIDTH = 412;

export const POPOVER_WIDTH_MEDIUM = 260;
7 changes: 5 additions & 2 deletions frontend/hooks/useStakingContractInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ export const useStakingContractInfo = () => {
serviceIds.length < maxNumServices;

const hasEnoughRewardsAndSlots = isRewardsAvailable && hasEnoughServiceSlots;

const isAgentEvicted = serviceStakingState === 2;

const isServiceStaked =
!!serviceStakingStartTime && serviceStakingState === 1;

Expand Down Expand Up @@ -78,10 +76,15 @@ export const useStakingContractInfo = () => {
!isNil(hasEnoughRewardsAndSlots) &&
(isAgentEvicted ? isServiceStakedForMinimumDuration : true);

// Eviction expire time in seconds
const evictionExpiresAt =
(serviceStakingStartTime ?? 0) + (minimumStakingDuration ?? 0);

return {
activeStakingContractInfo,
hasEnoughServiceSlots,
isAgentEvicted,
evictionExpiresAt,
isEligibleForStaking,
isPaused,
isRewardsAvailable,
Expand Down
12 changes: 11 additions & 1 deletion frontend/hooks/useStakingProgram.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useContext, useMemo } from 'react';

import { Chain } from '@/client';
import { SERVICE_STAKING_TOKEN_MECH_USAGE_CONTRACT_ADDRESSES } from '@/constants/contractAddresses';
import { STAKING_PROGRAM_META } from '@/constants/stakingProgramMeta';
import { StakingProgramContext } from '@/context/StakingProgramContext';

Expand All @@ -23,16 +25,24 @@ export const useStakingProgram = () => {
* returns `null` if not actively staked
*/
const activeStakingProgramMeta = useMemo(() => {
if (activeStakingProgramId === undefined) return undefined;
if (activeStakingProgramId === undefined) return;
if (activeStakingProgramId === null) return null;
return STAKING_PROGRAM_META[activeStakingProgramId];
}, [activeStakingProgramId]);

const defaultStakingProgramMeta =
STAKING_PROGRAM_META[defaultStakingProgramId];

const activeStakingProgramAddress = useMemo(() => {
if (!activeStakingProgramId) return;
return SERVICE_STAKING_TOKEN_MECH_USAGE_CONTRACT_ADDRESSES[Chain.GNOSIS][
activeStakingProgramId
];
}, [activeStakingProgramId]);

return {
activeStakingProgramId,
activeStakingProgramAddress,
activeStakingProgramMeta,
defaultStakingProgramId,
defaultStakingProgramMeta,
Expand Down
4 changes: 1 addition & 3 deletions frontend/styles/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -181,18 +181,16 @@ textarea,
margin-right: auto !important;
}

// font size
.text-xl {
font-size: 20px;
}

.text-base {
font-size: 16px !important;
}

.text-sm {
font-size: 14px !important;
}

.text-xs {
font-size: 12px !important;
}
Expand Down
3 changes: 3 additions & 0 deletions frontend/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export const mainTheme: ThemeConfig = {
Typography: {
colorTextDescription: '#4D596A',
},
Popover: {
fontSize: 14,
},
Tag: {
colorSuccess: '#135200',
},
Expand Down
26 changes: 20 additions & 6 deletions frontend/utils/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,36 @@ export const getTimeAgo = (timestampInSeconds: number) => {
* @returns formatted date in the format of 'MMM DD'
* @example 1626825600 => 'Jul 21'
*/
export const formatToMonthDay = (timeInSeconds: number) => {
if (!isNumber(timeInSeconds)) return '--';
return new Date(timeInSeconds).toLocaleDateString('en-US', {
export const formatToMonthDay = (timeInMs: number) => {
if (!isNumber(timeInMs)) return '--';
return new Date(timeInMs).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
});
};

/**
* @returns formatted time in the format of 'HH:MM AM/PM'
* @example 1626825600 => '12:00 PM'
*/
export const formatToTime = (timeInMs: number) => {
if (!isNumber(timeInMs)) return '--';
return new Date(timeInMs).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: 'numeric',
hour12: true,
timeZone: 'UTC',
});
};

/**
*
* @returns formatted date and time in the format of 'MMM DD, HH:MM AM/PM'
* @example 1626825600 => 'Jul 21, 12:00 PM'
*/
export const formatToShortDateTime = (timeInSeconds?: number) => {
if (!isNumber(timeInSeconds)) return '--';
return new Date(timeInSeconds).toLocaleDateString('en-US', {
export const formatToShortDateTime = (timeInMs?: number) => {
if (!isNumber(timeInMs)) return '--';
return new Date(timeInMs).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
hour: 'numeric',
Expand Down
Loading

0 comments on commit c820f9a

Please sign in to comment.