Skip to content

Commit

Permalink
Update service imports and add checkServiceIsFunded function
Browse files Browse the repository at this point in the history
  • Loading branch information
truemiller committed Mar 7, 2024
1 parent 9c3b7c4 commit 53dcee4
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dispatch, SetStateAction } from 'react';
import { FundRequirement } from './FundRequirement';
import { Address, SpawnData } from '@/types';
import EthersService from '@/service/Ethers';
import { EthersService } from '@/service';

type FundRequirementERC20Props = {
address: Address;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Dispatch, SetStateAction } from 'react';
import { FundRequirement } from './FundRequirement';
import { Address, SpawnData } from '@/types';
import EthersService from '@/service/Ethers';
import { EthersService } from '@/service';

type FundRequirementETHProps = {
address: Address;
Expand Down
49 changes: 46 additions & 3 deletions frontend/components/Spawn/SpawnAgentFunding.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,62 @@
import { Funding } from './Funding/Funding';
import { SpawnScreen } from '@/enums';
import { FundRequirementETH } from './Funding/FundRequirement/FundRequirementETH';
import { useSpawn } from '@/hooks';
import { useAppInfo, useSpawn } from '@/hooks';
import { Spin } from 'antd';
import { useState, useEffect } from 'react';
import MulticallService from '@/service/Multicall';
import { Address, AddressNumberRecord } from '@/types';

type SpawnAgentFundingProps = {
nextPage: SpawnScreen;
};

export const SpawnAgentFunding = (props: SpawnAgentFundingProps) => {
const {
spawnData: { agentFundRequirements: fundRequirements },
setSpawnData,
spawnData: { rpc, agentFundRequirements },
} = useSpawn();
const { userPublicKey } = useAppInfo();
const [isInitialLoaded, setIsInitialLoaded] = useState(false);

useEffect(() => {
if (!isInitialLoaded && userPublicKey) {
const agentAddresses = Object.keys(agentFundRequirements) as Address[];
MulticallService.getEthBalances(agentAddresses, rpc).then(
(balances: AddressNumberRecord) => {
setSpawnData((prev) => ({
...prev,
agentFundRequirements: agentAddresses.reduce(
(acc, address) => ({
...acc,
[address]: {
...agentFundRequirements[address],
received: balances[address] > 1,
},
}),
{},
),
}));
setIsInitialLoaded(true);
},
);
}
}, [
agentFundRequirements,
isInitialLoaded,
rpc,
setSpawnData,
userPublicKey,
]);

// if not inital loaded, show loader
if (agentFundRequirements === undefined || !isInitialLoaded) {
return <Spin />;
}

return (
<Funding
fundRequirements={fundRequirements}
fundRequirements={agentFundRequirements}
statement="Please fund the agent wallets to continue."
symbol={'XDAI'} // hardcoded while only trader is available
FundRequirementComponent={FundRequirementETH}
Expand Down
39 changes: 19 additions & 20 deletions frontend/components/YourAgents/ServiceCard/ServiceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,7 @@ type ServiceCardProps = {
};

export const ServiceCard = ({ service }: ServiceCardProps) => {
const {
stopService,
deployService,
deleteServices,
getServiceStatus,
deleteServiceState,
} = useServices();
const { deleteServiceState } = useServices();
const { getServiceTemplate } = useServiceTemplates();

const [serviceStatus, setServiceStatus] = useState<
Expand Down Expand Up @@ -107,10 +101,12 @@ export const ServiceCard = ({ service }: ServiceCardProps) => {
if (isDeleting) return;
setIsDeleting(true);
ServicesService.deleteServices({ hashes: [service.hash] })
.catch(() => message.error('Failed to delete service'))
.finally(() => {
.then(() => {
message.success('Service deleted successfully');
deleteServiceState(service.hash);
});
})
.catch(() => message.error('Failed to delete service'))
.finally(() => setIsDeleting(false));
}, [deleteServiceState, isDeleting, service.hash]);

const buttons = useMemo(
Expand All @@ -132,10 +128,10 @@ export const ServiceCard = ({ service }: ServiceCardProps) => {
<>
Are you sure you want to delete this service?
<br />
Your agent&apos;s private keys will be lost.
Your funds may be lost.
</>
}
placement="leftBottom"
placement="topLeft"
onConfirm={handleDelete}
>
<Button danger loading={isDeleting}>
Expand All @@ -154,12 +150,7 @@ export const ServiceCard = ({ service }: ServiceCardProps) => {
serviceStatus === DeploymentStatus.STOPPED ||
serviceStatus === DeploymentStatus.BUILT
) {
return (
<Flex gap={16}>
{buttons.start}
{buttons.delete}
</Flex>
);
return <Flex gap={16}>{buttons.start}</Flex>;
}
return <Spin />;
}, [buttons.delete, buttons.start, buttons.stop, serviceStatus]);
Expand All @@ -175,7 +166,10 @@ export const ServiceCard = ({ service }: ServiceCardProps) => {
);

useInterval(
() => EthersService.checkRpc(service.ledger.rpc).then(setIsRpcValid),
() =>
EthersService.checkRpc(service.ledger.rpc)
.then(setIsRpcValid)
.catch(() => setIsRpcValid(false)),
SERVICE_CARD_RPC_POLLING_INTERVAL,
);

Expand All @@ -192,7 +186,12 @@ export const ServiceCard = ({ service }: ServiceCardProps) => {
</div>
)}

<ServiceCardSettings serviceHash={service.hash} />
<ServiceCardSettings
service={service}
serviceTemplate={serviceTemplate}
isRpcValid={isRpcValid}
isLoading={isStarting || isStopping || isDeleting}
/>

<Flex gap={16}>
<Image
Expand Down
64 changes: 56 additions & 8 deletions frontend/components/YourAgents/ServiceCard/ServiceCardSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,81 @@
import { Service, ServiceTemplate } from '@/client';
import { SpawnScreen } from '@/enums';
import { useServices } from '@/hooks';
import { ServicesService } from '@/service';
import { SettingOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { Button, Dropdown, MenuProps, Popconfirm, message } from 'antd';
import Link from 'next/link';
import { useMemo } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useInterval } from 'usehooks-ts';

export const ServiceCardSettings = ({
serviceHash,
service,
serviceTemplate,
isRpcValid,
isLoading,
}: {
serviceHash: string;
service: Service;
serviceTemplate: ServiceTemplate;
isRpcValid: boolean | undefined;
isLoading: boolean;
}) => {
const { deleteServiceState, checkServiceIsFunded } = useServices();
const [isServiceFunded, setIsServiceFunded] = useState<boolean>(true);

const handleDelete = useCallback(() => {
ServicesService.deleteServices({ hashes: [service.hash] })
.then(() => {
message.success('Service deleted successfully');
deleteServiceState(service.hash);
})
.catch(() => message.error('Failed to delete service'));
}, [deleteServiceState, service.hash]);

const items: MenuProps['items'] = useMemo(
() => [
{
key: '1',
label: (
<Link
href={`/spawn/${serviceHash}?screen=${SpawnScreen.AGENT_FUNDING}`}
href={`/spawn/${service.hash}?screen=${SpawnScreen.AGENT_FUNDING}`}
>
Fund agent
</Link>
),
disabled: isServiceFunded,
},
{
key: '2',
label: (
<Popconfirm
title="Delete service"
description={
<>
Are you sure you want to delete this service?
<br />
Your funds may be lost.
</>
}
placement="leftBottom"
arrow={false}
onConfirm={handleDelete}
>
<Link href={'#'}>Delete this agent</Link>
</Popconfirm>
),
danger: true,
},
{ key: '2', label: <Link href="#">Delete Agent</Link>, danger: true },
],
[serviceHash],
[handleDelete, isServiceFunded, service.hash],
);

useInterval(() => {
isRpcValid &&
checkServiceIsFunded(service, serviceTemplate).then(setIsServiceFunded);
}, 3000);

return (
<Dropdown menu={{ items }} trigger={['click']} placement="bottomRight">
<Dropdown menu={{ items }} placement="bottomRight" disabled={isLoading}>
<Button style={{ position: 'absolute', top: 0, right: 0 }} type="text">
<SettingOutlined />
</Button>
Expand Down
39 changes: 38 additions & 1 deletion frontend/hooks/useServices.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
import { Service, ServiceHash } from '@/client';
import { Service, ServiceHash, ServiceTemplate } from '@/client';
import { ServicesContext } from '@/context';
import { ServicesService } from '@/service';
import MulticallService from '@/service/Multicall';
import { Address, AddressBooleanRecord } from '@/types';
import { useContext } from 'react';

const checkServiceIsFunded = async (
service: Service,
serviceTemplate: ServiceTemplate,
): Promise<boolean> => {
const {
chain_data: { instances, multisig },
} = service;

if (!instances || !multisig) return Promise.resolve(false);

const addresses = [...instances, multisig];

const balances = await MulticallService.getEthBalances(
addresses,
service.ledger.rpc,
);

if (!balances) return Promise.resolve(false);

const fundRequirements: AddressBooleanRecord = addresses.reduce(
(acc: AddressBooleanRecord, address: Address) => ({
...acc,
[address]: instances.includes(address)
? balances[address] >
serviceTemplate.configuration.fund_requirements.agent
: balances[address] >
serviceTemplate.configuration.fund_requirements.safe,
}),
{},
);

return Promise.resolve(Object.values(fundRequirements).every((f) => f));
};

export const useServices = () => {
const { services, updateServicesState, hasInitialLoaded, setServices } =
useContext(ServicesContext);
Expand Down Expand Up @@ -37,6 +73,7 @@ export const useServices = () => {
return {
getServiceFromState,
getServicesFromState,
checkServiceIsFunded,
updateServicesState,
updateServiceState,
deleteServiceState,
Expand Down

0 comments on commit 53dcee4

Please sign in to comment.