Skip to content

Commit

Permalink
Merge pull request #100 from valory-xyz/josh/balance-display-delay
Browse files Browse the repository at this point in the history
Balance display & calculation fixes, loading state fixes for header button
  • Loading branch information
truemiller authored May 17, 2024
2 parents 9bb5d3b + 9d96668 commit f59817c
Show file tree
Hide file tree
Showing 14 changed files with 187 additions and 81 deletions.
File renamed without changes.
8 changes: 7 additions & 1 deletion frontend/abi/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export * from './multicall3Abi';
export * from './agentMech';
export * from './erc20';
export * from './gnosisSafe';
export * from './multicall3';
export * from './serviceRegistryL2';
export * from './serviceRegistryTokenUtility';
export * from './serviceStakingTokenMechUsage';
File renamed without changes.
56 changes: 41 additions & 15 deletions frontend/components/Main/MainHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ const { Text } = Typography;
const LOADING_MESSAGE =
"It may take a while to start your agent, so feel free to close the app. We'll notify you once your agent is running.";

enum ServiceButtonLoadingState {
Starting,
Pausing,
NotLoading,
}

export const MainHeader = () => {
const { services, serviceStatus, setServiceStatus } = useServices();
const { getServiceTemplates } = useServiceTemplates();
Expand All @@ -27,16 +33,29 @@ export const MainHeader = () => {
setIsPaused: setIsBalancePollingPaused,
} = useBalance();

const [serviceButtonState, setServiceButtonState] = useState({
isLoading: false,
});
const [serviceButtonState, setServiceButtonState] =
useState<ServiceButtonLoadingState>(ServiceButtonLoadingState.NotLoading);

const serviceTemplate = useMemo(
() => getServiceTemplates()[0],
[getServiceTemplates],
);

const agentHead = useMemo(() => {
if (
serviceButtonState === ServiceButtonLoadingState.Starting ||
serviceButtonState === ServiceButtonLoadingState.Pausing
)
return (
<Badge status="processing" color="orange" dot offset={[-5, 32.5]}>
<Image
src="/happy-robot.svg"
alt="Happy Robot"
width={40}
height={40}
/>
</Badge>
);
if (serviceStatus === DeploymentStatus.DEPLOYED)
return (
<Badge status="processing" color="green" dot offset={[-5, 32.5]}>
Expand All @@ -53,12 +72,12 @@ export const MainHeader = () => {
<Image src="/sad-robot.svg" alt="Sad Robot" width={40} height={40} />
</Badge>
);
}, [serviceStatus]);
}, [serviceButtonState, serviceStatus]);

const handleStart = useCallback(async () => {
if (!wallets?.[0]) return;

setServiceButtonState({ isLoading: true });
setServiceButtonState(ServiceButtonLoadingState.Starting);
setIsBalancePollingPaused(true);

try {
Expand All @@ -81,11 +100,11 @@ export const MainHeader = () => {
}).then(() => {
setServiceStatus(DeploymentStatus.DEPLOYED);
setIsBalancePollingPaused(false);
setServiceButtonState({ isLoading: false });
setServiceButtonState(ServiceButtonLoadingState.NotLoading);
});
} catch (error) {
setIsBalancePollingPaused(false);
setServiceButtonState({ isLoading: false });
setServiceButtonState(ServiceButtonLoadingState.NotLoading);
}
}, [
masterSafeAddress,
Expand All @@ -95,17 +114,21 @@ export const MainHeader = () => {
wallets,
]);

const handleStop = useCallback(() => {
const handlePause = useCallback(() => {
if (!services) return;
if (services.length === 0) return;
setServiceButtonState({ isLoading: true });
setServiceButtonState(ServiceButtonLoadingState.Pausing);
ServicesService.stopDeployment(services[0].hash).then(() => {
setServiceStatus(DeploymentStatus.STOPPED);
setServiceButtonState({ isLoading: false });
setServiceButtonState(ServiceButtonLoadingState.NotLoading);
});
}, [services, setServiceStatus]);

const serviceToggleButton = useMemo(() => {
if (serviceButtonState.isLoading) {
if (
serviceButtonState === ServiceButtonLoadingState.Starting ||
serviceButtonState === ServiceButtonLoadingState.Pausing
) {
return (
<Popover
trigger={['hover', 'click']}
Expand All @@ -121,7 +144,10 @@ export const MainHeader = () => {
}
>
<Button type="default" size="large" ghost disabled loading>
Starting...
{serviceButtonState === ServiceButtonLoadingState.Starting &&
'Starting...'}
{serviceButtonState === ServiceButtonLoadingState.Pausing &&
'Stopping...'}
</Button>
</Popover>
);
Expand All @@ -130,7 +156,7 @@ export const MainHeader = () => {
if (serviceStatus === DeploymentStatus.DEPLOYED) {
return (
<Flex gap={10} align="center">
<Button type="default" size="large" onClick={handleStop}>
<Button type="default" size="large" onClick={handlePause}>
Pause
</Button>
<Typography.Text
Expand Down Expand Up @@ -187,12 +213,12 @@ export const MainHeader = () => {
</Button>
);
}, [
serviceButtonState.isLoading,
serviceButtonState,
serviceStatus,
handleStop,
totalOlasBalance,
totalEthBalance,
handleStart,
handlePause,
]);

return (
Expand Down
82 changes: 39 additions & 43 deletions frontend/context/BalanceProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { message } from 'antd';
import { ethers } from 'ethers';
import { isAddress } from 'ethers/lib/utils';
import { Contract as MulticallContract } from 'ethers-multicall';
import { isNumber } from 'lodash';
import {
createContext,
Expand All @@ -15,13 +13,12 @@ import {
} from 'react';
import { useInterval } from 'usehooks-ts';

import { SERVICE_REGISTRY_TOKEN_UTILITY_ABI } from '@/abi/serviceRegistryTokenUtility';
import { Chain, Wallet } from '@/client';
import { SERVICE_REGISTRY_TOKEN_UTILITY_CONTRACT } from '@/constants';
import { gnosisMulticallProvider } from '@/constants/providers';
import { Wallet } from '@/client';
import { TOKENS } from '@/constants/tokens';
import { ServiceRegistryL2ServiceState } from '@/enums/ServiceRegistryL2ServiceState';
import { Token } from '@/enums/Token';
import { EthersService } from '@/service';
import { AutonolasService } from '@/service/Autonolas';
import MulticallService from '@/service/Multicall';
import {
Address,
Expand Down Expand Up @@ -105,11 +102,15 @@ export const BalanceProvider = ({ children }: PropsWithChildren) => {
]);

const updateBalances = useCallback(async (): Promise<void> => {
if (!masterEoaAddress) return;
if (!serviceAddresses) return;

try {
const walletAddresses: Address[] = [];
if (masterEoaAddress) walletAddresses.push(masterEoaAddress);
if (masterSafeAddress) walletAddresses.push(masterSafeAddress);
if (serviceAddresses) walletAddresses.push(...serviceAddresses);

const walletBalances = await getWalletBalances(walletAddresses);
if (!walletBalances) return;

Expand All @@ -124,13 +125,38 @@ export const BalanceProvider = ({ children }: PropsWithChildren) => {
}

if (masterSafeAddress && serviceId) {
const serviceRegistryBalances = await getServiceRegistryBalances(
masterSafeAddress,
serviceId,
);

setOlasDepositBalance(serviceRegistryBalances.depositValue);
setOlasBondBalance(serviceRegistryBalances.bondValue);
const { depositValue, bondValue, serviceState } =
await AutonolasService.getServiceRegistryInfo(
masterSafeAddress,
serviceId,
);

switch (serviceState) {
case ServiceRegistryL2ServiceState.NonExistent:
setOlasBondBalance(0);
setOlasDepositBalance(0);
break;
case ServiceRegistryL2ServiceState.PreRegistration:
setOlasBondBalance(0);
setOlasDepositBalance(0);
break;
case ServiceRegistryL2ServiceState.ActiveRegistration:
setOlasBondBalance(0);
setOlasDepositBalance(depositValue);
break;
case ServiceRegistryL2ServiceState.FinishedRegistration:
setOlasBondBalance(bondValue);
setOlasDepositBalance(depositValue);
break;
case ServiceRegistryL2ServiceState.Deployed:
setOlasBondBalance(bondValue);
setOlasDepositBalance(depositValue);
break;
case ServiceRegistryL2ServiceState.TerminatedBonded:
setOlasBondBalance(bondValue);
setOlasDepositBalance(0);
break;
}
}

// update balance loaded state
Expand Down Expand Up @@ -242,33 +268,3 @@ export const getWalletBalances = async (

return tempWalletBalances;
};

const getServiceRegistryBalances = async (
masterEoa: Address,
serviceId: number,
): Promise<{ bondValue: number; depositValue: number }> => {
const serviceRegistryL2Contract = new MulticallContract(
SERVICE_REGISTRY_TOKEN_UTILITY_CONTRACT[Chain.GNOSIS],
SERVICE_REGISTRY_TOKEN_UTILITY_ABI,
);

const contractCalls = [
serviceRegistryL2Contract.getOperatorBalance(masterEoa, serviceId),
serviceRegistryL2Contract.mapServiceIdTokenDeposit(serviceId),
];

await gnosisMulticallProvider.init();

const [operatorBalanceResponse, serviceIdTokenDepositResponse] =
await gnosisMulticallProvider.all(contractCalls);

const [operatorBalance, serviceIdTokenDeposit] = [
parseFloat(ethers.utils.formatUnits(operatorBalanceResponse, 18)),
parseFloat(ethers.utils.formatUnits(serviceIdTokenDepositResponse[1], 18)),
];

return {
bondValue: operatorBalance,
depositValue: serviceIdTokenDeposit,
};
};
2 changes: 1 addition & 1 deletion frontend/context/RewardProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const RewardContext = createContext<{

export const RewardProvider = ({ children }: PropsWithChildren) => {
const { services } = useContext(ServicesContext);
const service = useMemo(() => services[0], [services]);
const service = useMemo(() => services?.[0], [services]);

const [availableRewardsForEpoch, setAvailableRewardsForEpoch] =
useState<number>();
Expand Down
14 changes: 7 additions & 7 deletions frontend/context/ServicesProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import { ServicesService } from '@/service';
import { Address } from '@/types';

type ServicesContextProps = {
services: Service[];
serviceAddresses: Address[];
setServices: Dispatch<SetStateAction<Service[]>>;
services?: Service[];
serviceAddresses?: Address[];
setServices: Dispatch<SetStateAction<Service[] | undefined>>;
serviceStatus: DeploymentStatus | undefined;
setServiceStatus: Dispatch<SetStateAction<DeploymentStatus | undefined>>;
updateServicesState: () => Promise<void>;
Expand All @@ -27,8 +27,8 @@ type ServicesContextProps = {
};

export const ServicesContext = createContext<ServicesContextProps>({
services: [],
serviceAddresses: [],
services: undefined,
serviceAddresses: undefined,
setServices: () => {},
serviceStatus: undefined,
setServiceStatus: () => {},
Expand All @@ -39,7 +39,7 @@ export const ServicesContext = createContext<ServicesContextProps>({
});

export const ServicesProvider = ({ children }: PropsWithChildren) => {
const [services, setServices] = useState<Service[]>([]);
const [services, setServices] = useState<Service[]>();

const [serviceStatus, setServiceStatus] = useState<
DeploymentStatus | undefined
Expand All @@ -64,7 +64,7 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => {
async (): Promise<void> =>
ServicesService.getServices()
.then((data: Service[]) => {
if (!Array.isArray(data) || !data?.length) return;
if (!Array.isArray(data)) return;
setServices(data);
setHasInitialLoaded(true);
})
Expand Down
9 changes: 9 additions & 0 deletions frontend/enums/ServiceRegistryL2ServiceState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Reflects the state of a service in the ServiceRegistryL2 contract (i.e. https://gnosisscan.io/address/0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8)
export enum ServiceRegistryL2ServiceState {
NonExistent,
PreRegistration,
ActiveRegistration,
FinishedRegistration,
Deployed,
TerminatedBonded,
}
1 change: 1 addition & 0 deletions frontend/enums/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './PageState';
export * from './ServiceRegistryL2ServiceState';
export * from './SettingsScreen';
export * from './SetupScreen';
export * from './Token';
9 changes: 6 additions & 3 deletions frontend/hooks/useServices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,21 @@ export const useServices = () => {
serviceHash: ServiceHash,
): Service | undefined => {
if (!hasInitialLoaded) return;

if (!services) return;
return services.find((service) => service.hash === serviceHash);
};

const getServicesFromState = (): Service[] =>
const getServicesFromState = (): Service[] | undefined =>
hasInitialLoaded ? services : [];

const updateServiceState = (serviceHash: ServiceHash) => {
ServicesService.getService(serviceHash).then((service: Service) => {
setServices((prev) => {
if (!prev) return [service];

const index = prev.findIndex((s) => s.hash === serviceHash); // findIndex returns -1 if not found
if (index === -1) return [...prev, service];

const newServices = [...prev];
newServices[index] = service;
return newServices;
Expand All @@ -73,7 +76,7 @@ export const useServices = () => {
};

const deleteServiceState = (serviceHash: ServiceHash) =>
setServices((prev) => prev.filter((s) => s.hash !== serviceHash));
setServices((prev) => prev?.filter((s) => s.hash !== serviceHash));

return {
services,
Expand Down
Loading

0 comments on commit f59817c

Please sign in to comment.