From 10b467fb38ade229d2db3352ec08e4596c66370f Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 5 Nov 2024 13:15:39 +0000 Subject: [PATCH 001/146] chore: import axios --- frontend/package.json | 3 ++- frontend/yarn.lock | 52 ++++++++++++++++++++++++++++++++++++++----- package.json | 4 ++-- yarn.lock | 39 ++++++++++++++++++++++++++------ 4 files changed, 83 insertions(+), 15 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index d5b99af4..cb0ae806 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,6 +6,7 @@ "@fontsource/inter": "^5.0.17", "@tanstack/react-query": "^5.29.0", "antd": "^5.14.0", + "axios": "^1.7.7", "ethers": "5.7.2", "ethers-multicall": "^0.2.3", "graphql": "^16.8.1", @@ -56,4 +57,4 @@ "start": "next start" }, "version": "0.1.0" -} \ No newline at end of file +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 7723c1c3..5f4411fc 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1993,6 +1993,15 @@ axe-core@=4.7.0: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== +axios@^1.7.7: + version "1.7.7" + resolved "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -3230,6 +3239,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -4937,6 +4951,11 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" @@ -5676,8 +5695,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - name string-width-cjs +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5741,7 +5768,14 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -6216,8 +6250,16 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== diff --git a/package.json b/package.json index 2d290e65..41d42f01 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@tanstack/react-query": "^5.29.0", "adm-zip": "^0.5.12", "antd": "^5.14.0", - "axios": "^1.7.2", + "axios": "^1.7.7", "child_process": "^1.0.2", "cross-env": "^7.0.3", "dotenv": "^16.4.5", @@ -64,4 +64,4 @@ "build:pearl": "sh build_pearl.sh" }, "version": "0.1.0-rc184" -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index b8e7bcff..19228650 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1570,10 +1570,10 @@ atomically@^1.7.0: resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe" integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w== -axios@^1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" - integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== +axios@^1.7.7: + version "1.7.7" + resolved "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -5086,7 +5086,16 @@ string-convert@^0.2.0: resolved "https://registry.yarnpkg.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5111,7 +5120,14 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -5491,7 +5507,16 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From ee7f55bdc9768195cc56b57067875dccf1f5edf9 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 5 Nov 2024 13:15:55 +0000 Subject: [PATCH 002/146] refactor: rename middleware enums --- frontend/client/enums.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/client/enums.ts b/frontend/client/enums.ts index f880c942..9fced3f3 100644 --- a/frontend/client/enums.ts +++ b/frontend/client/enums.ts @@ -1,4 +1,4 @@ -export enum Action { +export enum MiddlewareAction { STATUS = 0, BUILD = 1, DEPLOY = 2, @@ -15,12 +15,12 @@ export enum MiddlewareChain { MODE = 6, } -export enum Ledger { +export enum MiddlewareLedger { ETHEREUM = 0, SOLANA = 1, } -export enum DeploymentStatus { +export enum MiddlewareDeploymentStatus { CREATED = 0, BUILT = 1, DEPLOYING = 2, @@ -30,7 +30,7 @@ export enum DeploymentStatus { DELETED = 6, } -export enum AccountIsSetup { +export enum MiddlewareAccountIsSetup { True, False, Loading, From 80a86f9465a53b2809cafbc71ac062c1ca1202a9 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 5 Nov 2024 13:16:10 +0000 Subject: [PATCH 003/146] chore: enum refactored imports --- frontend/client/types.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/client/types.ts b/frontend/client/types.ts index 805909cb..e461348d 100644 --- a/frontend/client/types.ts +++ b/frontend/client/types.ts @@ -1,13 +1,17 @@ import { StakingProgramId } from '@/enums/StakingProgram'; import { Address } from '@/types/Address'; -import { DeploymentStatus, Ledger, MiddlewareChain } from './enums'; +import { + MiddlewareChain, + MiddlewareDeploymentStatus, + MiddlewareLedger, +} from './enums'; export type ServiceHash = string; export type LedgerConfig = { rpc: string; - type: Ledger; + type: MiddlewareLedger; chain: MiddlewareChain; }; @@ -36,7 +40,7 @@ export type ChainData = { }; }; -export type Service = { +export type MiddlewareServiceResponse = { name: string; hash: string; keys: ServiceKeys[]; @@ -84,7 +88,7 @@ export type DeployedNodes = { }; export type Deployment = { - status: DeploymentStatus; + status: MiddlewareDeploymentStatus; nodes: DeployedNodes; }; @@ -128,7 +132,7 @@ export type AppInfo = { export type WalletResponse = { address: Address; safe_chains: MiddlewareChain[]; - ledger_type: Ledger; + ledger_type: MiddlewareLedger; safes: { [middlewareChainId in (typeof MiddlewareChain)[keyof typeof MiddlewareChain]]: Address; }; From 22321c67a00958561bf5ec7999f0b81ca80dc039 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 5 Nov 2024 13:17:30 +0000 Subject: [PATCH 004/146] chore: enum refactored imports --- .../MainPage/header/AgentButton.tsx | 32 +++++++------- .../components/MainPage/header/AgentHead.tsx | 8 ++-- frontend/components/MainPage/header/index.tsx | 8 ++-- .../sections/KeepAgentRunningSection.tsx | 4 +- .../StakingContractSection/MigrateButton.tsx | 4 +- .../StakingContractSection/useMigrate.tsx | 8 ++-- .../components/SetupPage/SetupWelcome.tsx | 22 +++++----- .../context/SystemNotificationTriggers.tsx | 4 +- frontend/hooks/useServices.ts | 42 +++++++++++-------- frontend/service/Services.ts | 15 +++++-- 10 files changed, 80 insertions(+), 67 deletions(-) diff --git a/frontend/components/MainPage/header/AgentButton.tsx b/frontend/components/MainPage/header/AgentButton.tsx index 74886dfc..85f9b3e8 100644 --- a/frontend/components/MainPage/header/AgentButton.tsx +++ b/frontend/components/MainPage/header/AgentButton.tsx @@ -2,7 +2,7 @@ import { InfoCircleOutlined } from '@ant-design/icons'; import { Button, ButtonProps, Flex, Popover, Tooltip, Typography } from 'antd'; import { useCallback, useMemo } from 'react'; -import { DeploymentStatus, MiddlewareChain } from '@/client'; +import { MiddlewareChain, MiddlewareDeploymentStatus } from '@/client'; import { COLOR } from '@/constants/colors'; import { DEFAULT_STAKING_PROGRAM_ID } from '@/context/StakingProgramProvider'; import { StakingProgramId } from '@/enums/StakingProgram'; @@ -85,7 +85,7 @@ const AgentRunningButton = () => { setIsServicePollingPaused(true); // Optimistically update service status - setServiceStatus(DeploymentStatus.STOPPING); + setServiceStatus(MiddlewareDeploymentStatus.STOPPING); try { await ServicesService.stopDeployment(service.hash); } catch (error) { @@ -178,7 +178,7 @@ const AgentNotRunningButton = () => { setIsStakingContractInfoPollingPaused(true); // Mock "DEPLOYING" status (service polling will update this once resumed) - setServiceStatus(DeploymentStatus.DEPLOYING); + setServiceStatus(MiddlewareDeploymentStatus.DEPLOYING); // Get the active staking program id; default id if there's no agent yet const stakingProgramId: StakingProgramId = @@ -227,7 +227,7 @@ const AgentNotRunningButton = () => { } // Can assume successful deployment - setServiceStatus(DeploymentStatus.DEPLOYED); + setServiceStatus(MiddlewareDeploymentStatus.DEPLOYED); // TODO: remove this workaround, middleware should respond when agent is staked & confirmed running after `createService` call await delayInSeconds(5); @@ -265,15 +265,15 @@ const AgentNotRunningButton = () => { // if the agent is NOT running and the balance is too low, // user should not be able to start the agent const isServiceInactive = - serviceStatus === DeploymentStatus.BUILT || - serviceStatus === DeploymentStatus.STOPPED; + serviceStatus === MiddlewareDeploymentStatus.BUILT || + serviceStatus === MiddlewareDeploymentStatus.STOPPED; if (isServiceInactive && isLowBalance) { return false; } - if (serviceStatus === DeploymentStatus.DEPLOYED) return false; - if (serviceStatus === DeploymentStatus.DEPLOYING) return false; - if (serviceStatus === DeploymentStatus.STOPPING) return false; + if (serviceStatus === MiddlewareDeploymentStatus.DEPLOYED) return false; + if (serviceStatus === MiddlewareDeploymentStatus.DEPLOYING) return false; + if (serviceStatus === MiddlewareDeploymentStatus.STOPPING) return false; if (!requiredOlas) return false; @@ -324,15 +324,15 @@ export const AgentButton = () => { return ); - }, [ - goto, - isActiveStakingProgramLoaded, - stakingContractName, - canUpdateStakingContract, - ]); + }, [goto, isActiveStakingProgramLoaded, stakingContractName]); return ( @@ -60,18 +54,7 @@ export const StakingContractUpdate = ({ > Staking contract - {canUpdateStakingContract ? ( - stakingButton - ) : ( - Fund your agent to manage staking contracts} - > - {stakingButton} - - )} + {stakingButton} ); From 65fc297eaf99abf9210c4d23fa27d921d99b5b4d Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 11:09:47 +0000 Subject: [PATCH 017/146] refactor: remove stakingProgramId prop from StakingContractUpdate component for simplification --- .../MainPage/sections/StakingContractUpdate.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/components/MainPage/sections/StakingContractUpdate.tsx b/frontend/components/MainPage/sections/StakingContractUpdate.tsx index ae667b2c..e8ad184c 100644 --- a/frontend/components/MainPage/sections/StakingContractUpdate.tsx +++ b/frontend/components/MainPage/sections/StakingContractUpdate.tsx @@ -4,26 +4,20 @@ import { useMemo } from 'react'; import { STAKING_PROGRAM_META } from '@/constants/stakingProgramMeta'; import { Pages } from '@/enums/PageState'; -import { StakingProgramId } from '@/enums/StakingProgram'; import { usePageState } from '@/hooks/usePageState'; import { useStakingProgram } from '@/hooks/useStakingProgram'; -import { useMigrate } from '../../ManageStakingPage/StakingContractSection/useMigrate'; import { CardSection } from '../../styled/CardSection'; const { Text } = Typography; -type StakingContractUpdateProps = { stakingProgramId: StakingProgramId }; -export const StakingContractUpdate = ({ - stakingProgramId, -}: StakingContractUpdateProps) => { +export const StakingContractUpdate = () => { const { goto } = usePageState(); const { activeStakingProgramMeta, isActiveStakingProgramLoaded, defaultStakingProgramId, } = useStakingProgram(); - const { canUpdateStakingContract } = useMigrate(stakingProgramId); const stakingContractName = useMemo(() => { if (activeStakingProgramMeta) return activeStakingProgramMeta.name; From d2d9f5cae90cc7644e238d67dcf99bb71a1f16a6 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 11:10:09 +0000 Subject: [PATCH 018/146] refactor: remove defaultStakingProgramId usage in Main component for clarity --- frontend/components/MainPage/index.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/components/MainPage/index.tsx b/frontend/components/MainPage/index.tsx index a6fe872f..5c52b57c 100644 --- a/frontend/components/MainPage/index.tsx +++ b/frontend/components/MainPage/index.tsx @@ -26,8 +26,7 @@ export const Main = () => { const { backupSafeAddress } = useMasterSafe(); const { updateServicesState } = useServices(); const { updateBalances, isLoaded, setIsLoaded } = useBalance(); - const { activeStakingProgramId, defaultStakingProgramId } = - useStakingProgram(); + const { activeStakingProgramId } = useStakingProgram(); const { hasEnoughServiceSlots } = useStakingContractInfo(); useEffect(() => { @@ -75,9 +74,7 @@ export const Main = () => { - + From c4598d894ca1abb27e45ef531add0feb151f4fa7 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 13:24:00 +0000 Subject: [PATCH 019/146] refactor: update staking program status enum and related logic for clarity --- .../MainPage/header/AgentButton.tsx | 50 +++++--- .../header/CannotStartAgentPopover.tsx | 25 +++- frontend/components/MainPage/index.tsx | 41 +++++-- .../NoAvailableSlotsOnTheContract.tsx | 40 +++++-- .../sections/StakingContractUpdate.tsx | 29 ++++- .../CantMigrateAlert.tsx | 9 +- .../StakingContractSection/MigrateButton.tsx | 24 ++-- .../StakingContractDetails.tsx | 34 +++--- .../StakingContractTag.tsx | 5 +- .../StakingContractSection/index.tsx | 26 +++-- .../StakingContractSection/useMigrate.tsx | 108 +++++++++++++----- .../context/StakingContractInfoProvider.tsx | 21 ++-- frontend/context/StakingProgramProvider.tsx | 13 ++- frontend/enums/StakingProgramStatus.ts | 3 +- frontend/hooks/useStakingContractInfo.ts | 35 +++--- frontend/hooks/useStakingProgram.ts | 2 + frontend/utils/service.ts | 4 +- 17 files changed, 331 insertions(+), 138 deletions(-) diff --git a/frontend/components/MainPage/header/AgentButton.tsx b/frontend/components/MainPage/header/AgentButton.tsx index eb24147e..af3d1098 100644 --- a/frontend/components/MainPage/header/AgentButton.tsx +++ b/frontend/components/MainPage/header/AgentButton.tsx @@ -10,7 +10,10 @@ import { useElectronApi } from '@/hooks/useElectronApi'; import { useReward } from '@/hooks/useReward'; import { useServices } from '@/hooks/useServices'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; -import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; +import { + useStakingContractContext, + useStakingContractInfo, +} from '@/hooks/useStakingContractInfo'; import { useStakingProgram } from '@/hooks/useStakingProgram'; import { useStore } from '@/hooks/useStore'; import { useWallet } from '@/hooks/useWallet'; @@ -140,16 +143,23 @@ const AgentNotRunningButton = () => { updateBalances, } = useBalance(); const { storeState } = useStore(); + const { - isEligibleForStaking, - isAgentEvicted, + isStakingContractInfoLoaded, setIsPaused: setIsStakingContractInfoPollingPaused, updateActiveStakingContractInfo, - hasEnoughServiceSlots, - } = useStakingContractInfo(); + } = useStakingContractContext(); + const { activeStakingProgramId, defaultStakingProgramId } = useStakingProgram(); + const { + isEligibleForStaking, + isAgentEvicted, + hasEnoughServiceSlots, + isServiceStaked, + } = useStakingContractInfo(activeStakingProgramId ?? defaultStakingProgramId); + // const minStakingDeposit = // stakingContractInfoRecord?.[activeStakingProgram ?? defaultStakingProgram] // ?.minStakingDeposit; @@ -266,6 +276,8 @@ const AgentNotRunningButton = () => { ]); const isDeployable = useMemo(() => { + if (!isStakingContractInfoLoaded) return false; + // if the agent is NOT running and the balance is too low, // user should not be able to start the agent const isServiceInactive = @@ -282,7 +294,7 @@ const AgentNotRunningButton = () => { if (!requiredOlas) return false; // If no slots available, agent cannot be started - if (!hasEnoughServiceSlots) return false; + if (!hasEnoughServiceSlots && !isServiceStaked) return false; // case where service exists & user has initial funded if (service && storeState?.isInitialFunded) { @@ -299,16 +311,18 @@ const AgentNotRunningButton = () => { return hasEnoughOlas && hasEnoughEth; }, [ + isStakingContractInfoLoaded, serviceStatus, + isLowBalance, + requiredOlas, + hasEnoughServiceSlots, + isServiceStaked, service, storeState?.isInitialFunded, isEligibleForStaking, isAgentEvicted, safeOlasBalanceWithStaked, - requiredOlas, totalEthBalance, - isLowBalance, - hasEnoughServiceSlots, ]); const buttonProps: ButtonProps = { @@ -324,11 +338,20 @@ const AgentNotRunningButton = () => { }; export const AgentButton = () => { - const { service, serviceStatus, hasInitialLoaded } = useServices(); - const { isEligibleForStaking, isAgentEvicted } = useStakingContractInfo(); + const { + service, + serviceStatus, + hasInitialLoaded: isServicesLoaded, + } = useServices(); + const { activeStakingProgramId, defaultStakingProgramId } = + useStakingProgram(); + const { isStakingContractInfoLoaded } = useStakingContractContext(); + const { isEligibleForStaking, isAgentEvicted } = useStakingContractInfo( + activeStakingProgramId ?? defaultStakingProgramId, + ); return useMemo(() => { - if (!hasInitialLoaded) { + if (!isServicesLoaded || !isStakingContractInfoLoaded) { return ); - }, [goto, isActiveStakingProgramLoaded, stakingContractName]); + }, [ + goto, + isActiveStakingProgramLoaded, + isStakingContractInfoLoaded, + serviceIsTransitioning, + stakingContractName, + ]); return ( @@ -47,8 +67,7 @@ export const StakingContractUpdate = () => { style={{ width: '100%' }} > Staking contract - - {stakingButton} + {gotoManageStakingButton} ); diff --git a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx index adfa9c0f..d75f6a6a 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx @@ -5,7 +5,10 @@ import { CustomAlert } from '@/components/Alert'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useBalance } from '@/hooks/useBalance'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; -import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; +import { + useStakingContractContext, + useStakingContractInfo, +} from '@/hooks/useStakingContractInfo'; import { getMinimumStakedAmountRequired } from '@/utils/service'; import { CantMigrateReason } from './useMigrate'; @@ -18,7 +21,8 @@ const AlertInsufficientMigrationFunds = ({ stakingProgramId, }: CantMigrateAlertProps) => { const { serviceTemplate } = useServiceTemplates(); - const { isServiceStaked } = useStakingContractInfo(); + const { isStakingContractInfoLoaded } = useStakingContractContext(); + const { isServiceStaked } = useStakingContractInfo(stakingProgramId); const { masterSafeBalance: safeBalance, totalOlasStakedBalance } = useBalance(); @@ -27,6 +31,7 @@ const AlertInsufficientMigrationFunds = ({ stakingProgramId, ); + if (!isStakingContractInfoLoaded) return null; if (isNil(totalOlasRequiredForStaking)) return null; if (isNil(safeBalance?.OLAS)) return null; if (isNil(totalOlasStakedBalance)) return null; diff --git a/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx b/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx index 2f4f7e46..7c0f7106 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx @@ -30,10 +30,18 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { hasInitialLoaded: isServicesLoaded, service, } = useServices(); + + const { + activeStakingProgramId, + defaultStakingProgramId, + setDefaultStakingProgramId, + } = useStakingProgram(); const { setIsPaused: setIsBalancePollingPaused } = useBalance(); - const { updateActiveStakingProgramId: updateStakingProgram } = - useStakingProgram(); - const { activeStakingContractInfo } = useStakingContractInfo(); + const { updateActiveStakingProgramId } = useStakingProgram(); + + const { stakingContractInfo: currentStakingContractInfo } = + useStakingContractInfo(activeStakingProgramId ?? defaultStakingProgramId); + const { setMigrationModalOpen } = useModals(); const { migrateValidation, firstDeployValidation } = @@ -43,7 +51,6 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { const isFirstDeploy = useMemo(() => { if (!isServicesLoaded) return false; if (service) return false; - return true; }, [isServicesLoaded, service]); @@ -54,17 +61,17 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { if ( validation.reason === CantMigrateReason.NotStakedForMinimumDuration && - !isNil(activeStakingContractInfo) + !isNil(currentStakingContractInfo) ) { return ( ); } return validation.reason; - }, [activeStakingContractInfo, validation]); + }, [currentStakingContractInfo, validation]); return ( @@ -75,6 +82,7 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { onClick={async () => { setIsServicePollingPaused(true); setIsBalancePollingPaused(true); + setDefaultStakingProgramId(stakingProgramId); try { setServiceStatus(DeploymentStatus.DEPLOYING); @@ -88,7 +96,7 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { stakingProgramId === StakingProgramId.BetaMechMarketplace, }); - await updateStakingProgram(); + await updateActiveStakingProgramId(); setMigrationModalOpen(true); } catch (error) { diff --git a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx index 607807cf..2d4f9ff9 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx @@ -2,27 +2,29 @@ import { Alert, Skeleton } from 'antd'; import { useMemo } from 'react'; import { InfoBreakdownList } from '@/components/InfoBreakdown'; -import { NA } from '@/constants/symbols'; import { StakingProgramId } from '@/enums/StakingProgram'; -import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; +import { useStakingContractContext } from '@/hooks/useStakingContractInfo'; export const StakingContractDetails = ({ stakingProgramId, }: { stakingProgramId: StakingProgramId; }) => { - const { stakingContractInfoRecord } = useStakingContractInfo(); + const { stakingContractInfoRecord, isStakingContractInfoLoaded } = + useStakingContractContext(); - const balances = useMemo(() => { - if (!stakingContractInfoRecord) return null; - if (!stakingProgramId) return null; - if (!stakingContractInfoRecord?.[stakingProgramId]) return null; + const list = useMemo(() => { + if (!isStakingContractInfoLoaded) return; + if (!stakingContractInfoRecord) return; + if (!stakingProgramId) return; + if (!stakingContractInfoRecord?.[stakingProgramId]) return; const details = stakingContractInfoRecord[stakingProgramId]; + return [ { left: 'Available slots', - right: details.maxNumServices || NA, + right: `${details.maxNumServices! - details.serviceIds!.length} / ${details.maxNumServices}`, }, { left: 'Rewards per epoch', @@ -38,13 +40,17 @@ export const StakingContractDetails = ({ right: `${details.olasStakeRequired} OLAS`, }, ]; - }, [stakingContractInfoRecord, stakingProgramId]); + }, [ + isStakingContractInfoLoaded, + stakingContractInfoRecord, + stakingProgramId, + ]); - if (!stakingContractInfoRecord) { + if (!isStakingContractInfoLoaded) { return ; } - if (!balances) { + if (!stakingContractInfoRecord) { return ( + ); }; diff --git a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractTag.tsx b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractTag.tsx index cd9e525d..316f3a60 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractTag.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractTag.tsx @@ -7,8 +7,11 @@ export const StakingContractTag = ({ }: { status: StakingProgramStatus | null; }) => { - if (status === StakingProgramStatus.Selected) { + if (status === StakingProgramStatus.Active) { return Active; } + if (status === StakingProgramStatus.Default) { + return Default; + } return null; }; diff --git a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx index d7759ff1..ce45bdc7 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx @@ -38,29 +38,31 @@ export const StakingContractSection = ({ const stakingProgramMeta = STAKING_PROGRAM_META[stakingProgramId]; - /** - * Returns `true` if this stakingProgram is active, - * or user is unstaked and this is the default - */ - const isActiveStakingProgram = useMemo(() => { - if (activeStakingProgramId === null) - return defaultStakingProgramId === stakingProgramId; - return activeStakingProgramId === stakingProgramId; - }, [activeStakingProgramId, defaultStakingProgramId, stakingProgramId]); + // /** + // * Returns `true` if this stakingProgram is active, + // * or user is unstaked and this is the default + // */ + // const isActiveStakingProgram = useMemo(() => { + // if (activeStakingProgramId === null) + // return defaultStakingProgramId === stakingProgramId; + // return activeStakingProgramId === stakingProgramId; + // }, [activeStakingProgramId, defaultStakingProgramId, stakingProgramId]); const contractTagStatus = useMemo(() => { if (activeStakingProgramId === stakingProgramId) - return StakingProgramStatus.Selected; + return StakingProgramStatus.Active; // Pearl is not staked, set as Selected if default if (!activeStakingProgramId && stakingProgramId === defaultStakingProgramId) - return StakingProgramStatus.Selected; + return StakingProgramStatus.Default; // Otherwise, no tag return null; }, [activeStakingProgramId, defaultStakingProgramId, stakingProgramId]); - const showMigrateButton = !isActiveStakingProgram; + const showMigrateButton = + stakingProgramId !== (activeStakingProgramId ?? defaultStakingProgramId); + const showFundingButton = useMemo(() => { if (migrateValidation.canMigrate) return false; return ( diff --git a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx index ef67412e..736df82e 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx @@ -7,7 +7,10 @@ import { useBalance } from '@/hooks/useBalance'; import { useNeedsFunds } from '@/hooks/useNeedsFunds'; import { useServices } from '@/hooks/useServices'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; -import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; +import { + useStakingContractContext, + useStakingContractInfo, +} from '@/hooks/useStakingContractInfo'; import { useStakingProgram } from '@/hooks/useStakingProgram'; import { getMinimumStakedAmountRequired } from '@/utils/service'; @@ -47,16 +50,15 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { useStakingProgram(); const { needsInitialFunding } = useNeedsFunds(); + const { stakingContractInfoRecord, isStakingContractInfoLoaded } = + useStakingContractContext(); + const { - activeStakingContractInfo, + stakingContractInfo, isServiceStaked, isServiceStakedForMinimumDuration, - isStakingContractInfoLoaded, - stakingContractInfoRecord, hasEnoughServiceSlots, - } = useStakingContractInfo(); - - const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; + } = useStakingContractInfo(stakingProgramId); const { hasInitialLoaded: isServicesLoaded } = useServices(); @@ -94,11 +96,25 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { return { canMigrate: false, reason: CantMigrateReason.LoadingServices }; } + // Services must be not be running or in a transitional state + if ( + [ + DeploymentStatus.DEPLOYED, + DeploymentStatus.DEPLOYING, + DeploymentStatus.STOPPING, + ].some((status) => status === serviceStatus) + ) { + return { + canMigrate: false, + reason: CantMigrateReason.PearlCurrentlyRunning, + }; + } + if (!isBalanceLoaded) { return { canMigrate: false, reason: CantMigrateReason.LoadingBalance }; } - if (isServicesLoaded && !isStakingContractInfoLoaded) { + if (!isStakingContractInfoLoaded) { return { canMigrate: false, reason: CantMigrateReason.LoadingStakingContractInfo, @@ -120,6 +136,16 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } + if ( + (stakingContractInfo.serviceIds ?? [])?.length >= + (stakingContractInfo.maxNumServices ?? 0) + ) { + return { + canMigrate: false, + reason: CantMigrateReason.NoAvailableStakingSlots, + }; + } + if ((stakingContractInfo.availableRewards ?? 0) <= 0) { return { canMigrate: false, @@ -144,20 +170,6 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } - // Services must be not be running or in a transitional state - if ( - [ - DeploymentStatus.DEPLOYED, - DeploymentStatus.DEPLOYING, - DeploymentStatus.STOPPING, - ].some((status) => status === serviceStatus) - ) { - return { - canMigrate: false, - reason: CantMigrateReason.PearlCurrentlyRunning, - }; - } - if (activeStakingProgramId === null && !isServiceStaked) { return { canMigrate: true }; } @@ -171,7 +183,7 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } - if (activeStakingContractInfo && !isServiceStakedForMinimumDuration) { + if (stakingContractInfo && !isServiceStakedForMinimumDuration) { return { canMigrate: false, reason: CantMigrateReason.NotStakedForMinimumDuration, @@ -189,7 +201,6 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { hasEnoughOlasToMigrate, isServiceStaked, activeStakingProgramMeta?.canMigrateTo, - activeStakingContractInfo, isServiceStakedForMinimumDuration, serviceStatus, ]); @@ -199,19 +210,52 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { return { canMigrate: false, reason: CantMigrateReason.LoadingServices }; } + // Services must be not be running or in a transitional state + if ( + [ + DeploymentStatus.DEPLOYED, + DeploymentStatus.DEPLOYING, + DeploymentStatus.STOPPING, + ].some((status) => status === serviceStatus) + ) { + return { + canMigrate: false, + reason: CantMigrateReason.PearlCurrentlyRunning, + }; + } + if (!isBalanceLoaded) { return { canMigrate: false, reason: CantMigrateReason.LoadingBalance }; } - if (!hasEnoughOlasForFirstRun) { + // staking contract requirements + + if (!isStakingContractInfoLoaded) { return { canMigrate: false, - reason: CantMigrateReason.InsufficientOlasToMigrate, + reason: CantMigrateReason.LoadingStakingContractInfo, }; } const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; + if (!stakingContractInfo) { + return { + canMigrate: false, + reason: CantMigrateReason.CannotFindStakingContractInfo, + }; + } + + if ( + (stakingContractInfo.serviceIds ?? [])?.length >= + (stakingContractInfo.maxNumServices ?? 0) + ) { + return { + canMigrate: false, + reason: CantMigrateReason.NoAvailableStakingSlots, + }; + } + if ((stakingContractInfo?.availableRewards ?? 0) <= 0) { return { canMigrate: false, @@ -226,13 +270,23 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } + // fund requirements + + if (!hasEnoughOlasForFirstRun) { + return { + canMigrate: false, + reason: CantMigrateReason.InsufficientOlasToMigrate, + }; + } + return { canMigrate: true }; }, [ isServicesLoaded, isBalanceLoaded, - hasEnoughOlasForFirstRun, + isStakingContractInfoLoaded, stakingContractInfoRecord, stakingProgramId, + hasEnoughOlasForFirstRun, ]); const canUpdateStakingContract = useMemo(() => { diff --git a/frontend/context/StakingContractInfoProvider.tsx b/frontend/context/StakingContractInfoProvider.tsx index 8533046d..504ed66b 100644 --- a/frontend/context/StakingContractInfoProvider.tsx +++ b/frontend/context/StakingContractInfoProvider.tsx @@ -74,14 +74,6 @@ export const StakingContractInfoProvider = ({ ).then(setActiveStakingContractInfo); }, [activeStakingProgramId, serviceId]); - useInterval( - async () => { - await updateStakingContractInfoRecord().catch(console.error); - await updateActiveStakingContractInfo().catch(console.error); - }, - isPaused ? null : FIVE_SECONDS_INTERVAL, - ); - /** Updates general staking contract information, not user or service specific */ const updateStakingContractInfoRecord = async () => { const stakingPrograms = Object.values(StakingProgramId); @@ -108,15 +100,22 @@ export const StakingContractInfoProvider = ({ setStakingContractInfoRecord(stakingContractInfoRecord); setIsStakingContractInfoLoaded(true); } catch (e) { - console.error(e); + console.error({ e }); } }; useEffect(() => { - // Load generic staking contract info record on mount - updateStakingContractInfoRecord(); + updateStakingContractInfoRecord().catch(console.error); }, []); + useInterval( + async () => { + await updateStakingContractInfoRecord().catch(console.error); + await updateActiveStakingContractInfo().catch(console.error); + }, + isPaused ? null : FIVE_SECONDS_INTERVAL, + ); + return ( Promise; + setDefaultStakingProgramId: (stakingProgramId: StakingProgramId) => void; }>({ activeStakingProgramId: undefined, - defaultStakingProgramId: DEFAULT_STAKING_PROGRAM_ID, + defaultStakingProgramId: INITIAL_DEFAULT_STAKING_PROGRAM_ID, updateActiveStakingProgramId: async () => {}, + setDefaultStakingProgramId: () => {}, }); /** Determines the current active staking program, if any */ @@ -25,6 +27,10 @@ export const StakingProgramProvider = ({ children }: PropsWithChildren) => { const [activeStakingProgramId, setActiveStakingProgramId] = useState(); + const [defaultStakingProgramId, setDefaultStakingProgramId] = useState( + INITIAL_DEFAULT_STAKING_PROGRAM_ID, + ); + const updateActiveStakingProgramId = useCallback(async () => { // if no service nft, not staked const serviceId = @@ -52,7 +58,8 @@ export const StakingProgramProvider = ({ children }: PropsWithChildren) => { value={{ activeStakingProgramId, updateActiveStakingProgramId, - defaultStakingProgramId: DEFAULT_STAKING_PROGRAM_ID, + defaultStakingProgramId, + setDefaultStakingProgramId, }} > {children} diff --git a/frontend/enums/StakingProgramStatus.ts b/frontend/enums/StakingProgramStatus.ts index d161434b..7e7364d0 100644 --- a/frontend/enums/StakingProgramStatus.ts +++ b/frontend/enums/StakingProgramStatus.ts @@ -1,3 +1,4 @@ export enum StakingProgramStatus { - Selected = 'current', + Active = 'current', + Default = 'default', } diff --git a/frontend/hooks/useStakingContractInfo.ts b/frontend/hooks/useStakingContractInfo.ts index 96860360..e8de2168 100644 --- a/frontend/hooks/useStakingContractInfo.ts +++ b/frontend/hooks/useStakingContractInfo.ts @@ -2,10 +2,9 @@ import { isNil } from 'lodash'; import { useContext } from 'react'; import { StakingContractInfoContext } from '@/context/StakingContractInfoProvider'; +import { StakingProgramId } from '@/enums/StakingProgram'; -import { useServices } from './useServices'; - -export const useStakingContractInfo = () => { +export const useStakingContractContext = () => { const { activeStakingContractInfo, isPaused, @@ -14,17 +13,20 @@ export const useStakingContractInfo = () => { updateActiveStakingContractInfo, setIsPaused, } = useContext(StakingContractInfoContext); + return { + activeStakingContractInfo, + isPaused, + isStakingContractInfoLoaded, + stakingContractInfoRecord, + updateActiveStakingContractInfo, + setIsPaused, + }; +}; - const { service } = useServices(); +export const useStakingContractInfo = (stakingProgramId: StakingProgramId) => { + const { stakingContractInfoRecord } = useStakingContractContext(); - // TODO: find a better way to handle this, currently stops react lifecycle hooks being implemented below it - if (!service || !activeStakingContractInfo) - return { - stakingContractInfoRecord, - updateActiveStakingContractInfo, - setIsPaused, - isPaused, - }; + const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; const { serviceStakingState, @@ -33,7 +35,7 @@ export const useStakingContractInfo = () => { maxNumServices, minimumStakingDuration, availableRewards, - } = activeStakingContractInfo; + } = stakingContractInfo ?? {}; const isRewardsAvailable = availableRewards ?? 0 > 0; @@ -81,18 +83,13 @@ export const useStakingContractInfo = () => { (serviceStakingStartTime ?? 0) + (minimumStakingDuration ?? 0); return { - activeStakingContractInfo, hasEnoughServiceSlots, isAgentEvicted, evictionExpiresAt, isEligibleForStaking, - isPaused, isRewardsAvailable, isServiceStakedForMinimumDuration, isServiceStaked, - isStakingContractInfoLoaded, - stakingContractInfoRecord, - updateActiveStakingContractInfo, - setIsPaused, + stakingContractInfo, }; }; diff --git a/frontend/hooks/useStakingProgram.ts b/frontend/hooks/useStakingProgram.ts index da4f3e8c..13dc2168 100644 --- a/frontend/hooks/useStakingProgram.ts +++ b/frontend/hooks/useStakingProgram.ts @@ -14,6 +14,7 @@ export const useStakingProgram = () => { activeStakingProgramId, defaultStakingProgramId, updateActiveStakingProgramId, + setDefaultStakingProgramId, } = useContext(StakingProgramContext); const isActiveStakingProgramLoaded = activeStakingProgramId !== undefined; @@ -54,5 +55,6 @@ export const useStakingProgram = () => { defaultStakingProgramMeta, isActiveStakingProgramLoaded, updateActiveStakingProgramId, + setDefaultStakingProgramId, }; }; diff --git a/frontend/utils/service.ts b/frontend/utils/service.ts index 53ec7fc7..3b3fe45b 100644 --- a/frontend/utils/service.ts +++ b/frontend/utils/service.ts @@ -1,11 +1,11 @@ import { ServiceTemplate } from '@/client'; -import { DEFAULT_STAKING_PROGRAM_ID } from '@/context/StakingProgramProvider'; +import { INITIAL_DEFAULT_STAKING_PROGRAM_ID } from '@/context/StakingProgramProvider'; import { StakingProgramId } from '@/enums/StakingProgram'; /** TODO: update from hardcoded, workaround for quick release */ export const getMinimumStakedAmountRequired = ( serviceTemplate?: ServiceTemplate, //TODO: remove, as unused - stakingProgramId: StakingProgramId = DEFAULT_STAKING_PROGRAM_ID, + stakingProgramId: StakingProgramId = INITIAL_DEFAULT_STAKING_PROGRAM_ID, ): number | undefined => { if (stakingProgramId === StakingProgramId.Alpha) { return 20; From 15be025e1a1fb67507aa6f283eac9c3e87c3d5ff Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:18:16 +0000 Subject: [PATCH 020/146] fix: backup wallet alert wait for owners to be loaded --- .../MainPage/sections/AlertSections/AddBackupWalletAlert.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx b/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx index 23952138..7ee41c49 100644 --- a/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx +++ b/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx @@ -1,4 +1,5 @@ import { Flex, Typography } from 'antd'; +import { isNil } from 'lodash'; import { Pages } from '@/enums/PageState'; import { useMasterSafe } from '@/hooks/useMasterSafe'; @@ -10,8 +11,9 @@ const { Text } = Typography; export const AddBackupWalletAlert = () => { const { goto } = usePageState(); - const { backupSafeAddress } = useMasterSafe(); + const { backupSafeAddress, masterSafeOwners } = useMasterSafe(); + if (isNil(masterSafeOwners)) return null; if (backupSafeAddress) return null; return ( From c8ff7df2b4414c5e0dc4dcd390fa015fe2993886 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:18:38 +0000 Subject: [PATCH 021/146] fix: support both xdai and olas gas requirements on first deploy --- .../CantMigrateAlert.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx index d75f6a6a..80741fdb 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx @@ -2,8 +2,10 @@ import { Flex, Typography } from 'antd'; import { isNil } from 'lodash'; import { CustomAlert } from '@/components/Alert'; +import { LOW_MASTER_SAFE_BALANCE } from '@/constants/thresholds'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useBalance } from '@/hooks/useBalance'; +import { useNeedsFunds } from '@/hooks/useNeedsFunds'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; import { useStakingContractContext, @@ -25,6 +27,7 @@ const AlertInsufficientMigrationFunds = ({ const { isServiceStaked } = useStakingContractInfo(stakingProgramId); const { masterSafeBalance: safeBalance, totalOlasStakedBalance } = useBalance(); + const { serviceFundRequirements, isInitialFunded } = useNeedsFunds(); const totalOlasRequiredForStaking = getMinimumStakedAmountRequired( serviceTemplate, @@ -40,18 +43,24 @@ const AlertInsufficientMigrationFunds = ({ ? totalOlasRequiredForStaking - (totalOlasStakedBalance + safeBalance.OLAS) // when staked : totalOlasRequiredForStaking - safeBalance.OLAS; // when not staked + const requiredXdaiDeposit = isInitialFunded + ? LOW_MASTER_SAFE_BALANCE - safeBalance.ETH // is already funded allow minimal maintenance + : serviceFundRequirements.eth - safeBalance.ETH; // otherwise require full initial funding requirements + return ( - - An additional {requiredOlasDeposit} OLAS is required to switch - + Additional funds required - Add {requiredOlasDeposit} OLAS to your account to - meet the contract requirements and switch. +
    + {requiredOlasDeposit > 0 &&
  • {requiredOlasDeposit} OLAS
  • } + {requiredXdaiDeposit > 0 &&
  • {requiredXdaiDeposit} XDAI
  • } +
+ Add the required funds to your account to meet the staking + requirements.
} @@ -107,7 +116,10 @@ export const CantMigrateAlert = ({ return ; } - if (cantMigrateReason === CantMigrateReason.InsufficientOlasToMigrate) { + if ( + cantMigrateReason === CantMigrateReason.InsufficientOlasToMigrate || + cantMigrateReason === CantMigrateReason.InsufficientGasToMigrate + ) { return ( ); From 85f0fc9fcc964250b867523e2875cb45dc85748b Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:18:55 +0000 Subject: [PATCH 022/146] fix: support both gas and olas checks --- .../ManageStakingPage/StakingContractSection/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx index ce45bdc7..410b934d 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/index.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/index.tsx @@ -66,7 +66,9 @@ export const StakingContractSection = ({ const showFundingButton = useMemo(() => { if (migrateValidation.canMigrate) return false; return ( - migrateValidation.reason === CantMigrateReason.InsufficientOlasToMigrate + migrateValidation.reason === + CantMigrateReason.InsufficientOlasToMigrate || + migrateValidation.reason === CantMigrateReason.InsufficientGasToMigrate ); }, [migrateValidation]); From 63c37ef7591dc59111e9a92507c50b3383d4c300 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:19:10 +0000 Subject: [PATCH 023/146] feat: add insufficient gas enum --- .../StakingContractSection/useMigrate.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx index 736df82e..87e7e8e3 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx @@ -19,6 +19,7 @@ export enum CantMigrateReason { LoadingBalance = 'Loading balance...', LoadingStakingContractInfo = 'Loading staking contract information...', InsufficientOlasToMigrate = 'Insufficient OLAS to switch', + InsufficientGasToMigrate = 'Insufficient XDAI to switch', // TODO: make chain agnostic MigrationNotSupported = 'Switching to this program is not currently supported', NoAvailableRewards = 'This program has no rewards available', NoAvailableStakingSlots = 'The program has no more available slots', @@ -62,6 +63,8 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { const { hasInitialLoaded: isServicesLoaded } = useServices(); + const { hasEnoughEthForInitialFunding } = useNeedsFunds(); + const minimumOlasRequiredToMigrate = useMemo( () => getMinimumStakedAmountRequired(serviceTemplate, stakingProgramId), [serviceTemplate, stakingProgramId], @@ -206,6 +209,10 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { ]); const firstDeployValidation = useMemo(() => { + /** + * @todo fix temporary check for xDai balance on first deploy (same as initial funding requirement) + */ + if (!isServicesLoaded) { return { canMigrate: false, reason: CantMigrateReason.LoadingServices }; } @@ -270,8 +277,6 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } - // fund requirements - if (!hasEnoughOlasForFirstRun) { return { canMigrate: false, @@ -279,14 +284,23 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { }; } + if (!hasEnoughEthForInitialFunding) { + return { + canMigrate: false, + reason: CantMigrateReason.InsufficientGasToMigrate, + }; + } + return { canMigrate: true }; }, [ isServicesLoaded, isBalanceLoaded, + hasEnoughEthForInitialFunding, isStakingContractInfoLoaded, stakingContractInfoRecord, stakingProgramId, hasEnoughOlasForFirstRun, + serviceStatus, ]); const canUpdateStakingContract = useMemo(() => { From 77459c39b1b16016b0b20134f8cb43b3940540bc Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:19:35 +0000 Subject: [PATCH 024/146] chore: move isInitialFunded higher --- frontend/hooks/useNeedsFunds.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/hooks/useNeedsFunds.ts b/frontend/hooks/useNeedsFunds.ts index 78117fda..76886062 100644 --- a/frontend/hooks/useNeedsFunds.ts +++ b/frontend/hooks/useNeedsFunds.ts @@ -17,6 +17,8 @@ export const useNeedsFunds = () => { ); const { storeState } = useStore(); + const isInitialFunded = storeState?.isInitialFunded; + const { isBalanceLoaded, masterSafeBalance: safeBalance, @@ -51,7 +53,6 @@ export const useNeedsFunds = () => { serviceFundRequirements?.olas, ]); - const isInitialFunded = storeState?.isInitialFunded; const needsInitialFunding: boolean = useMemo(() => { if (isInitialFunded) return false; if (!isBalanceLoaded) return false; From 37988d9aaf96632f7215519861ae8141eea21971 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 14:30:48 +0000 Subject: [PATCH 025/146] bump: rc187 --- package.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b9dbb657..205bf10e 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,5 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.1.0-rc186" + "version": "0.1.0-rc187" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f5109942..e5f356ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.1.0-rc186" +version = "0.1.0-rc187" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" From d1d23a3a1e1bc39aa8d7a775753b67ccd02c04c7 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 15:16:17 +0000 Subject: [PATCH 026/146] fix: fallback to activeStakingProgramInfo if activeStakingProgramId exists --- .../NoAvailableSlotsOnTheContract.tsx | 3 ++- .../context/StakingContractInfoProvider.tsx | 23 ++++++++++++++----- frontend/hooks/useStakingContractInfo.ts | 14 ++++++++--- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx index 5174f22e..8975155e 100644 --- a/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx +++ b/frontend/components/MainPage/sections/AlertSections/NoAvailableSlotsOnTheContract.tsx @@ -24,7 +24,7 @@ export const NoAvailableSlotsOnTheContract = () => { } = useStakingProgram(); const { isStakingContractInfoLoaded } = useStakingContractContext(); - const { hasEnoughServiceSlots } = useStakingContractInfo( + const { hasEnoughServiceSlots, isServiceStaked } = useStakingContractInfo( activeStakingProgramId ?? defaultStakingProgramId, ); @@ -43,6 +43,7 @@ export const NoAvailableSlotsOnTheContract = () => { if (!isStakingContractInfoLoaded) return null; if (hasEnoughServiceSlots) return null; + if (isServiceStaked) return null; return ( ; + isActiveStakingContractInfoLoaded: boolean; isPaused: boolean; - isStakingContractInfoLoaded: boolean; stakingContractInfoRecord?: Record< StakingProgramId, Partial >; + isStakingContractInfoRecordLoaded: boolean; updateActiveStakingContractInfo: () => Promise; setIsPaused: Dispatch>; }; @@ -36,7 +37,8 @@ export const StakingContractInfoContext = createContext({ activeStakingContractInfo: undefined, isPaused: false, - isStakingContractInfoLoaded: false, + isStakingContractInfoRecordLoaded: false, + isActiveStakingContractInfoLoaded: false, stakingContractInfoRecord: undefined, updateActiveStakingContractInfo: async () => {}, setIsPaused: () => {}, @@ -49,8 +51,14 @@ export const StakingContractInfoProvider = ({ const { activeStakingProgramId } = useContext(StakingProgramContext); const [isPaused, setIsPaused] = useState(false); - const [isStakingContractInfoLoaded, setIsStakingContractInfoLoaded] = - useState(false); + const [ + isStakingContractInfoRecordLoaded, + setIsStakingContractInfoRecordLoaded, + ] = useState(false); + const [ + isActiveStakingContractInfoLoaded, + setIsActiveStakingContractInfoLoaded, + ] = useState(false); const [activeStakingContractInfo, setActiveStakingContractInfo] = useState>(); @@ -72,6 +80,8 @@ export const StakingContractInfoProvider = ({ serviceId, activeStakingProgramId, ).then(setActiveStakingContractInfo); + + setIsActiveStakingContractInfoLoaded(true); }, [activeStakingProgramId, serviceId]); /** Updates general staking contract information, not user or service specific */ @@ -98,7 +108,7 @@ export const StakingContractInfoProvider = ({ ); setStakingContractInfoRecord(stakingContractInfoRecord); - setIsStakingContractInfoLoaded(true); + setIsStakingContractInfoRecordLoaded(true); } catch (e) { console.error({ e }); } @@ -120,7 +130,8 @@ export const StakingContractInfoProvider = ({ { const { activeStakingContractInfo, isPaused, - isStakingContractInfoLoaded, + isStakingContractInfoRecordLoaded: isStakingContractInfoLoaded, stakingContractInfoRecord, updateActiveStakingContractInfo, setIsPaused, @@ -24,9 +26,15 @@ export const useStakingContractContext = () => { }; export const useStakingContractInfo = (stakingProgramId: StakingProgramId) => { - const { stakingContractInfoRecord } = useStakingContractContext(); + const { activeStakingProgramId } = useStakingProgram(); + + const { stakingContractInfoRecord, activeStakingContractInfo } = + useStakingContractContext(); - const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; + const stakingContractInfo = + activeStakingProgramId === stakingProgramId + ? stakingContractInfoRecord?.[stakingProgramId] + : activeStakingContractInfo; const { serviceStakingState, From 40d72aa08311f6994414c8bfa5ee8f71c375e4a0 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 6 Nov 2024 16:10:37 +0000 Subject: [PATCH 027/146] refactor: add useActiveStakingContractInfo, this record is updated seperately with active service id, where as other contract info records are not --- .../MainPage/header/AgentButton.tsx | 32 ++++---- .../header/CannotStartAgentPopover.tsx | 28 +++---- frontend/components/MainPage/index.tsx | 4 +- .../NoAvailableSlotsOnTheContract.tsx | 12 +-- .../sections/StakingContractUpdate.tsx | 6 +- .../CantMigrateAlert.tsx | 8 +- .../CountdownUntilMigration.tsx | 8 +- .../StakingContractSection/MigrateButton.tsx | 31 +++++-- .../StakingContractDetails.tsx | 8 +- .../StakingContractSection/useMigrate.tsx | 23 ++++-- frontend/hooks/useStakingContractInfo.ts | 82 +++++++++++-------- 11 files changed, 135 insertions(+), 107 deletions(-) diff --git a/frontend/components/MainPage/header/AgentButton.tsx b/frontend/components/MainPage/header/AgentButton.tsx index af3d1098..f0846bb6 100644 --- a/frontend/components/MainPage/header/AgentButton.tsx +++ b/frontend/components/MainPage/header/AgentButton.tsx @@ -11,6 +11,7 @@ import { useReward } from '@/hooks/useReward'; import { useServices } from '@/hooks/useServices'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; import { + useActiveStakingContractInfo, useStakingContractContext, useStakingContractInfo, } from '@/hooks/useStakingContractInfo'; @@ -145,7 +146,7 @@ const AgentNotRunningButton = () => { const { storeState } = useStore(); const { - isStakingContractInfoLoaded, + isStakingContractInfoRecordLoaded, setIsPaused: setIsStakingContractInfoPollingPaused, updateActiveStakingContractInfo, } = useStakingContractContext(); @@ -153,12 +154,12 @@ const AgentNotRunningButton = () => { const { activeStakingProgramId, defaultStakingProgramId } = useStakingProgram(); - const { - isEligibleForStaking, - isAgentEvicted, - hasEnoughServiceSlots, - isServiceStaked, - } = useStakingContractInfo(activeStakingProgramId ?? defaultStakingProgramId); + const { isEligibleForStaking, isAgentEvicted, isServiceStaked } = + useActiveStakingContractInfo(); + + const { hasEnoughServiceSlots } = useStakingContractInfo( + activeStakingProgramId ?? defaultStakingProgramId, + ); // const minStakingDeposit = // stakingContractInfoRecord?.[activeStakingProgram ?? defaultStakingProgram] @@ -276,7 +277,7 @@ const AgentNotRunningButton = () => { ]); const isDeployable = useMemo(() => { - if (!isStakingContractInfoLoaded) return false; + if (!isStakingContractInfoRecordLoaded) return false; // if the agent is NOT running and the balance is too low, // user should not be able to start the agent @@ -311,7 +312,7 @@ const AgentNotRunningButton = () => { return hasEnoughOlas && hasEnoughEth; }, [ - isStakingContractInfoLoaded, + isStakingContractInfoRecordLoaded, serviceStatus, isLowBalance, requiredOlas, @@ -343,15 +344,12 @@ export const AgentButton = () => { serviceStatus, hasInitialLoaded: isServicesLoaded, } = useServices(); - const { activeStakingProgramId, defaultStakingProgramId } = - useStakingProgram(); - const { isStakingContractInfoLoaded } = useStakingContractContext(); - const { isEligibleForStaking, isAgentEvicted } = useStakingContractInfo( - activeStakingProgramId ?? defaultStakingProgramId, - ); + const { isStakingContractInfoRecordLoaded } = useStakingContractContext(); + const { isEligibleForStaking, isAgentEvicted } = + useActiveStakingContractInfo(); return useMemo(() => { - if (!isServicesLoaded || !isStakingContractInfoLoaded) { + if (!isServicesLoaded || !isStakingContractInfoRecordLoaded) { return -
-); - -const AgentStoppingButton = () => ( - -); - -const AgentRunningButton = () => { - const { showNotification } = useElectronApi(); - const { isEligibleForRewards } = useReward(); - const { service, setIsServicePollingPaused, setServiceStatus } = - useServices(); - - const handlePause = useCallback(async () => { - if (!service) return; - // Paused to stop overlapping service poll while waiting for response - setIsServicePollingPaused(true); - - // Optimistically update service status - setServiceStatus(MiddlewareDeploymentStatus.STOPPING); - try { - await ServicesService.stopDeployment(service.hash); - } catch (error) { - console.error(error); - showNotification?.('Error while stopping agent'); - } finally { - // Resume polling, will update to correct status regardless of success - setIsServicePollingPaused(false); - } - }, [service, setIsServicePollingPaused, setServiceStatus, showNotification]); - - return ( - - - - - {isEligibleForRewards ? ( - - Agent is idle  - - - ) : ( - - Agent is working - - )} - - - - ); -}; +import { requiredGas } from '../constants'; /** Button used to start / deploy the agent */ -const AgentNotRunningButton = () => { +export const AgentNotRunningButton = () => { const { wallets, masterSafeAddress } = useWallet(); + const { - service, - serviceStatus, - setServiceStatus, - setIsServicePollingPaused, - updateServicesState, + selectedService, + setPaused: setIsServicePollingPaused, + isLoaded, + refetch: updateServicesState, } = useServices(); + + const { service, deploymentStatus, setDeploymentStatus } = useService({ + serviceConfigId: + isLoaded && selectedService ? selectedService?.service_config_id : '', + }); + const { serviceTemplate } = useServiceTemplates(); const { showNotification } = useElectronApi(); const { @@ -146,16 +53,15 @@ const AgentNotRunningButton = () => { setIsPaused: setIsStakingContractInfoPollingPaused, updateActiveStakingContractInfo, } = useStakingContractInfo(); + const { activeStakingProgramId } = useStakingProgram(); // const minStakingDeposit = // stakingContractInfoRecord?.[activeStakingProgram ?? defaultStakingProgram] // ?.minStakingDeposit; - const requiredOlas = getMinimumStakedAmountRequired( - serviceTemplate, - activeStakingProgramId ?? DEFAULT_STAKING_PROGRAM_ID, - ); + const requiredOlas = + STAKING_PROGRAMS[activeStakingProgramId]?.minStakingDeposit; // TODO: fix activeStakingProgramId const safeOlasBalance = safeBalance?.OLAS; const safeOlasBalanceWithStaked = @@ -177,7 +83,7 @@ const AgentNotRunningButton = () => { setIsStakingContractInfoPollingPaused(true); // Mock "DEPLOYING" status (service polling will update this once resumed) - setServiceStatus(MiddlewareDeploymentStatus.DEPLOYING); + setDeploymentStatus(MiddlewareDeploymentStatus.DEPLOYING); // Get the active staking program id; default id if there's no agent yet const stakingProgramId: StakingProgramId = @@ -190,7 +96,7 @@ const AgentNotRunningButton = () => { } } catch (error) { console.error(error); - setServiceStatus(undefined); + setDeploymentStatus(undefined); showNotification?.('Error while creating safe'); setIsStakingContractInfoPollingPaused(false); setIsServicePollingPaused(false); @@ -208,7 +114,7 @@ const AgentNotRunningButton = () => { }); } catch (error) { console.error(error); - setServiceStatus(undefined); + setDeploymentStatus(undefined); showNotification?.('Error while deploying service'); setIsServicePollingPaused(false); setIsBalancePollingPaused(false); @@ -225,7 +131,7 @@ const AgentNotRunningButton = () => { } // Can assume successful deployment - setServiceStatus(MiddlewareDeploymentStatus.DEPLOYED); + setDeploymentStatus(MiddlewareDeploymentStatus.DEPLOYED); // TODO: remove this workaround, middleware should respond when agent is staked & confirmed running after `createService` call await delayInSeconds(5); @@ -233,7 +139,7 @@ const AgentNotRunningButton = () => { // update provider states sequentially // service id is required before activeStakingContractInfo & balances can be updated try { - await updateServicesState(); // reload the available services + await updateServicesState?.(); // reload the available services await updateActiveStakingContractInfo(); // reload active staking contract with new service await updateBalances(); // reload the balances } catch (error) { @@ -249,7 +155,7 @@ const AgentNotRunningButton = () => { setIsServicePollingPaused, setIsBalancePollingPaused, setIsStakingContractInfoPollingPaused, - setServiceStatus, + setDeploymentStatus, masterSafeAddress, showNotification, activeStakingProgramId, @@ -263,15 +169,15 @@ const AgentNotRunningButton = () => { // if the agent is NOT running and the balance is too low, // user should not be able to start the agent const isServiceInactive = - serviceStatus === MiddlewareDeploymentStatus.BUILT || - serviceStatus === MiddlewareDeploymentStatus.STOPPED; + deploymentStatus === MiddlewareDeploymentStatus.BUILT || + deploymentStatus === MiddlewareDeploymentStatus.STOPPED; if (isServiceInactive && isLowBalance) { return false; } - if (serviceStatus === MiddlewareDeploymentStatus.DEPLOYED) return false; - if (serviceStatus === MiddlewareDeploymentStatus.DEPLOYING) return false; - if (serviceStatus === MiddlewareDeploymentStatus.STOPPING) return false; + if (deploymentStatus === MiddlewareDeploymentStatus.DEPLOYED) return false; + if (deploymentStatus === MiddlewareDeploymentStatus.DEPLOYING) return false; + if (deploymentStatus === MiddlewareDeploymentStatus.STOPPING) return false; if (!requiredOlas) return false; @@ -290,7 +196,7 @@ const AgentNotRunningButton = () => { return hasEnoughOlas && hasEnoughEth; }, [ - serviceStatus, + deploymentStatus, service, storeState?.isInitialFunded, isEligibleForStaking, @@ -312,47 +218,3 @@ const AgentNotRunningButton = () => { return ; }; - -export const AgentButton = () => { - const { service, serviceStatus, hasInitialLoaded } = useServices(); - const { isEligibleForStaking, isAgentEvicted } = useStakingContractInfo(); - - return useMemo(() => { - if (!hasInitialLoaded) { - return + + + {isEligibleForRewards ? ( + + Agent is idle  + + + ) : ( + + Agent is working + + )} + + + + ); +}; diff --git a/frontend/components/MainPage/header/AgentButton/AgentStartingButton.tsx b/frontend/components/MainPage/header/AgentButton/AgentStartingButton.tsx new file mode 100644 index 00000000..6503f6cf --- /dev/null +++ b/frontend/components/MainPage/header/AgentButton/AgentStartingButton.tsx @@ -0,0 +1,27 @@ +import { InfoCircleOutlined } from '@ant-design/icons'; +import { Button, Flex, Popover, Typography } from 'antd'; + +import { COLOR } from '@/constants/colors'; + +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."; + +export const AgentStartingButton = () => ( + + + + + {LOADING_MESSAGE} + + } + > + + +); diff --git a/frontend/components/MainPage/header/AgentButton/AgentStoppingButton.tsx b/frontend/components/MainPage/header/AgentButton/AgentStoppingButton.tsx new file mode 100644 index 00000000..8fdea7f3 --- /dev/null +++ b/frontend/components/MainPage/header/AgentButton/AgentStoppingButton.tsx @@ -0,0 +1,7 @@ +import { Button } from 'antd'; + +export const AgentStoppingButton = () => ( + +); From 372c0fcf645e0ad8205a90f4ba46dbafd80f2179 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:23:57 +0000 Subject: [PATCH 116/146] refactor: support updated services in useSetupTrayIcon --- frontend/components/MainPage/header/index.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/components/MainPage/header/index.tsx b/frontend/components/MainPage/header/index.tsx index e5948b57..786af663 100644 --- a/frontend/components/MainPage/header/index.tsx +++ b/frontend/components/MainPage/header/index.tsx @@ -4,28 +4,32 @@ import { useCallback, useEffect, useState } from 'react'; import { MiddlewareDeploymentStatus } from '@/client'; import { useBalance } from '@/hooks/useBalance'; import { useElectronApi } from '@/hooks/useElectronApi'; +import { useService } from '@/hooks/useService'; import { useServices } from '@/hooks/useServices'; import { FirstRunModal } from '../modals/FirstRunModal'; -import { AgentButton } from './AgentButton'; +import { AgentButton } from './AgentButton/AgentButton'; import { AgentHead } from './AgentHead'; const useSetupTrayIcon = () => { const { isLowBalance } = useBalance(); - const { serviceStatus } = useServices(); + const { selectedService } = useServices(); + const { deploymentStatus } = useService({ + serviceConfigId: selectedService?.service_config_id, + }); const { setTrayIcon } = useElectronApi(); useEffect(() => { if (isLowBalance) { setTrayIcon?.('low-gas'); - } else if (serviceStatus === MiddlewareDeploymentStatus.DEPLOYED) { + } else if (deploymentStatus === MiddlewareDeploymentStatus.DEPLOYED) { setTrayIcon?.('running'); - } else if (serviceStatus === MiddlewareDeploymentStatus.STOPPED) { + } else if (deploymentStatus === MiddlewareDeploymentStatus.STOPPED) { setTrayIcon?.('paused'); - } else if (serviceStatus === MiddlewareDeploymentStatus.BUILT) { + } else if (deploymentStatus === MiddlewareDeploymentStatus.BUILT) { setTrayIcon?.('logged-out'); } - }, [isLowBalance, serviceStatus, setTrayIcon]); + }, [isLowBalance, deploymentStatus, setTrayIcon]); return null; }; From cc51de1df17e9cb6a38f2967d1b5d4ecb8acbf8d Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:29:06 +0000 Subject: [PATCH 117/146] release: rc195 --- package.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8b9d99e0..51757886 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,5 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.1.0-rc194" + "version": "0.1.0-rc195" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b9f6ca24..8d66f2f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.1.0-rc194" +version = "0.1.0-rc195" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" From ded9a09d92a91072a3a66ce657f4600327a7cb74 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:35:50 +0000 Subject: [PATCH 118/146] refactor: migrate button to support new service hooks --- .../StakingContractSection/MigrateButton.tsx | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx b/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx index f2589d32..4aa288b4 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx @@ -8,6 +8,7 @@ import { StakingProgramId } from '@/enums/StakingProgram'; import { useBalance } from '@/hooks/useBalance'; import { useModals } from '@/hooks/useModals'; import { usePageState } from '@/hooks/usePageState'; +import { useService } from '@/hooks/useService'; import { useServices } from '@/hooks/useServices'; import { useServiceTemplates } from '@/hooks/useServiceTemplates'; import { useStakingContractInfo } from '@/hooks/useStakingContractInfo'; @@ -20,32 +21,41 @@ import { CantMigrateReason, useMigrate } from './useMigrate'; type MigrateButtonProps = { stakingProgramId: StakingProgramId; }; -export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { +export const MigrateButton = ({ + stakingProgramId: stakingProgramIdToMigrateTo, +}: MigrateButtonProps) => { const { goto } = usePageState(); const { serviceTemplate } = useServiceTemplates(); const { - setIsServicePollingPaused, - setServiceStatus, - updateServiceStatus, - hasInitialLoaded: isServicesLoaded, - service, + setPaused: setIsServicePollingPaused, + isLoaded: isServicesLoaded, + selectedService, } = useServices(); + + const { setDeploymentStatus } = useService({ + serviceConfigId: + isServicesLoaded && selectedService + ? selectedService.service_config_id + : '', + }); + const { setIsPaused: setIsBalancePollingPaused } = useBalance(); const { updateActiveStakingProgramId: updateStakingProgram } = useStakingProgram(); const { activeStakingContractInfo } = useStakingContractInfo(); const { setMigrationModalOpen } = useModals(); - const { migrateValidation, firstDeployValidation } = - useMigrate(stakingProgramId); + const { migrateValidation, firstDeployValidation } = useMigrate( + stakingProgramIdToMigrateTo, + ); // if false, user is migrating, not running for first time const isFirstDeploy = useMemo(() => { if (!isServicesLoaded) return false; - if (service) return false; + if (selectedService) return false; return true; - }, [isServicesLoaded, service]); + }, [isServicesLoaded, selectedService]); const validation = isFirstDeploy ? firstDeployValidation : migrateValidation; @@ -77,17 +87,19 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { setIsBalancePollingPaused(true); try { - setServiceStatus(MiddlewareDeploymentStatus.DEPLOYING); + setDeploymentStatus(MiddlewareDeploymentStatus.DEPLOYING); goto(Pages.Main); - await ServicesService.createService({ - stakingProgramId, + // TODO: create type for this response, we need the service_config_id to update the relevant service + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const createServiceResponse = await ServicesService.createService({ + stakingProgramId: stakingProgramIdToMigrateTo, serviceTemplate, deploy: true, useMechMarketplace: false, }); - await updateStakingProgram(); + await updateStakingProgram(); // TODO: refactor to support single staking program & multi staking programs, this on longer works setMigrationModalOpen(true); } catch (error) { @@ -95,7 +107,7 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { } finally { setIsServicePollingPaused(false); setIsBalancePollingPaused(false); - updateServiceStatus(); + // updateServiceStatus(); // TODO: update service status } }} > From 2870ba67fa11d19e79e2271aff3be6572ef0a0ec Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:36:26 +0000 Subject: [PATCH 119/146] chore: add todo --- .../ManageStakingPage/StakingContractSection/useMigrate.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx index 2f4dde07..33c6507a 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/useMigrate.tsx @@ -53,10 +53,10 @@ export const useMigrate = (stakingProgramId: StakingProgramId) => { const stakingContractInfo = stakingContractInfoRecord?.[stakingProgramId]; - const { hasInitialLoaded: isServicesLoaded } = useServices(); + const { isLoaded: isServicesLoaded } = useServices(); const minimumOlasRequiredToMigrate = useMemo( - () => getMinimumStakedAmountRequired(serviceTemplate, stakingProgramId), + () => getMinimumStakedAmountRequired(serviceTemplate, stakingProgramId), // TODO: refactor, can no longer use service template, must use config for funding requirements [serviceTemplate, stakingProgramId], ); From 75c039d7378fea7500b8b5211421290baa8549db Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:37:42 +0000 Subject: [PATCH 120/146] refactor: comment optimus, add todos --- frontend/config/agents.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/frontend/config/agents.ts b/frontend/config/agents.ts index d387c577..db97cbf2 100644 --- a/frontend/config/agents.ts +++ b/frontend/config/agents.ts @@ -1,21 +1,26 @@ import { AgentType } from '@/enums/Agent'; import { ChainId } from '@/enums/Chain'; -import { OptimusService } from '@/service/agents/Optimus'; import { PredictTraderService } from '@/service/agents/PredictTrader'; +// TODO: complete this config +// TODO: add funding requirements + export const AGENT_CONFIG = { [AgentType.PredictTrader]: { name: 'Predict Trader', homeChainId: ChainId.Gnosis, requiresAgentSafesOn: [ChainId.Gnosis], + agentSafeFundingRequirements: { + [ChainId.Gnosis]: 100000000000000000, + }, requiresMasterSafesOn: [ChainId.Gnosis], serviceApi: PredictTraderService, }, - [AgentType.Optimus]: { - name: 'Optimus', - homeChainId: ChainId.Optimism, - requiresAgentSafesOn: [ChainId.Optimism, ChainId.Ethereum, ChainId.Base], - requiresMasterSafesOn: [ChainId.Optimism, ChainId.Ethereum, ChainId.Base], - serviceApi: OptimusService, - }, + // [AgentType.Optimus]: { + // name: 'Optimus', + // homeChainId: ChainId.Optimism, + // requiresAgentSafesOn: [ChainId.Optimism, ChainId.Ethereum, ChainId.Base], + // requiresMasterSafesOn: [ChainId.Optimism, ChainId.Ethereum, ChainId.Base], + // serviceApi: OptimusService, + // }, }; From 68ee3dc256d6a67cb94c0eefbf5f2f93df36431c Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:46:29 +0000 Subject: [PATCH 121/146] refactor: add todos for future improvements and streamline balance fetching logic --- frontend/context/BalanceProvider.tsx | 59 +++++++++++++--------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/frontend/context/BalanceProvider.tsx b/frontend/context/BalanceProvider.tsx index eae56b74..c3fd0da4 100644 --- a/frontend/context/BalanceProvider.tsx +++ b/frontend/context/BalanceProvider.tsx @@ -1,6 +1,7 @@ +// TODO: large refactor needed, provider is way too big, needs to support multiple chains, multiple services, and multiple tokens import { message } from 'antd'; import { isAddress } from 'ethers/lib/utils'; -import { isNumber } from 'lodash'; +import { isNil, isNumber } from 'lodash'; import { ValueOf } from 'next/dist/shared/lib/constants'; import { createContext, @@ -22,9 +23,10 @@ import { LOW_AGENT_SAFE_BALANCE, LOW_MASTER_SAFE_BALANCE, } from '@/constants/thresholds'; +import { ChainId } from '@/enums/Chain'; import { ServiceRegistryL2ServiceState } from '@/enums/ServiceRegistryL2ServiceState'; import { TokenSymbol } from '@/enums/Token'; -import { AutonolasService } from '@/service/Autonolas'; +import { StakedAgentService } from '@/service/agents/StakedAgentService'; import { EthersService } from '@/service/Ethers'; import MulticallService from '@/service/Multicall'; import { Address } from '@/types/Address'; @@ -97,10 +99,8 @@ export const BalanceProvider = ({ children }: PropsWithChildren) => { const [isBalanceLoaded, setIsBalanceLoaded] = useState(false); const [walletBalances, setWalletBalances] = useState({}); - const [baseBalance, setBaseBalance] = useState(); - const [ethereumBalance, setEthereumBalance] = useState(); - const [optimismBalance, setOptimismBalance] = useState(); + // TODO: refactor to support multiple chains, and gas tokens from config const totalEthBalance: number | undefined = useMemo(() => { if (!isLoaded) return; return Object.values(walletBalances).reduce( @@ -152,28 +152,16 @@ export const BalanceProvider = ({ children }: PropsWithChildren) => { } // fetch balances for other chains - try { - const baseBalanceTemp = - EthersService.getBaseBalance(masterEoaAddress).then(setBaseBalance); - - const ethereumBalanceTemp = - EthersService.getEthereumBalance(masterEoaAddress).then( - setEthereumBalance, - ); - - const optimismBalanceTemp = - EthersService.getOptimismBalance(masterEoaAddress).then( - setOptimismBalance, - ); - - await Promise.allSettled([ - baseBalanceTemp, - ethereumBalanceTemp, - optimismBalanceTemp, - ]); - } catch (error) { - console.error(error); - } + // TODO: refactor to dynamically fetch balances for all chains + // try { + // await Promise.allSettled([ + // baseBalanceTemp, + // ethereumBalanceTemp, + // optimismBalanceTemp, + // ]); + // } catch (error) { + // console.error(error); + // } try { const walletBalances = await getWalletBalances(walletAddresses); @@ -181,6 +169,7 @@ export const BalanceProvider = ({ children }: PropsWithChildren) => { setWalletBalances(walletBalances); + // TODO: refactor to use ChainId enum, service from useService(), const serviceId = services?.[0]?.chain_configs[CHAIN_CONFIG.OPTIMISM.chainId].chain_data .token; @@ -191,11 +180,16 @@ export const BalanceProvider = ({ children }: PropsWithChildren) => { return; } - if (isAddress(`${masterSafeAddress}`) && serviceId > 0) { + if ( + !isNil(masterSafeAddress) && + isAddress(masterSafeAddress) && + serviceId > 0 + ) { const { depositValue, bondValue, serviceState } = - await AutonolasService.getServiceRegistryInfo( - masterSafeAddress as Address, + await StakedAgentService.getServiceRegistryInfo( + masterSafeAddress, serviceId, + ChainId.Gnosis, // TODO: refactor to get chain id from service ); switch (serviceState) { @@ -234,7 +228,7 @@ export const BalanceProvider = ({ children }: PropsWithChildren) => { message.error('Unable to retrieve wallet balances'); setIsBalanceLoaded(true); } - }, [masterEoaAddress, masterSafeAddress, serviceAddresses, services]); + }, [masterEoaAddress, masterSafeAddress, services]); const agentEoaAddress = useMemo( () => @@ -242,14 +236,17 @@ export const BalanceProvider = ({ children }: PropsWithChildren) => { ?.instances?.[0], [services], ); + const masterEoaBalance = useMemo( () => masterEoaAddress && walletBalances[masterEoaAddress], [masterEoaAddress, walletBalances], ); + const masterSafeBalance = useMemo( () => masterSafeAddress && walletBalances[masterSafeAddress], [masterSafeAddress, walletBalances], ); + const agentSafeBalance = useMemo( () => services?.[0]?.chain_configs[CHAIN_CONFIG.OPTIMISM.chainId].chain_data From d9800bc24ba9feda94bc149d702e521d5230db60 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:49:28 +0000 Subject: [PATCH 122/146] refactor: rename selectedServiceUuid to selectedServiceConfigId and update related logic --- frontend/context/ServicesProvider.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/frontend/context/ServicesProvider.tsx b/frontend/context/ServicesProvider.tsx index 8eec40b9..38a3cafc 100644 --- a/frontend/context/ServicesProvider.tsx +++ b/frontend/context/ServicesProvider.tsx @@ -41,7 +41,8 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => { const { paused, setPaused, togglePaused } = usePause(); // user selected service identifier - const [selectedServiceUuid, setSelectedServiceUuid] = useState(); + const [selectedServiceConfigId, setSelectedServiceConfigId] = + useState(); const { data: services, @@ -59,17 +60,23 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => { const selectedService = useMemo(() => { if (!services) return; - return services.find((service) => service.hash === selectedServiceUuid); // TODO: use uuid instead of hash once middleware refactored - }, [selectedServiceUuid, services]); + return services.find((service) => service.hash === selectedServiceConfigId); // TODO: use uuid instead of hash once middleware refactored + }, [selectedServiceConfigId, services]); const selectService = useCallback((serviceUuid: string) => { - setSelectedServiceUuid(serviceUuid); + setSelectedServiceConfigId(serviceUuid); }, []); + /** + * Select the first service by default + */ useEffect(() => { if (!services) return; - setSelectedServiceUuid(services[0]?.hash); // TODO: use uuid instead of hash once middleware refactored - }, [services]); + if (selectedServiceConfigId) return; + // only select a service by default if services are fetched, but there has been no selection yet + if (isFetched && services.length > 0 && !selectedServiceConfigId) + setSelectedServiceConfigId(services[0].service_config_id); // TODO: use uuid instead of hash once middleware refactored + }, [isFetched, selectedServiceConfigId, services]); // const serviceAddresses = useMemo( // () => From bc9c0db541b2814b9e8ec7912031a4b362495418 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:50:28 +0000 Subject: [PATCH 123/146] feat: add wallet enums, easier to distinguish between what a wallet is, what entity owns it, and enforce types to stop conflicts i.e. running safe functions on an eoa --- frontend/enums/Wallet.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 frontend/enums/Wallet.ts diff --git a/frontend/enums/Wallet.ts b/frontend/enums/Wallet.ts new file mode 100644 index 00000000..f6496c1d --- /dev/null +++ b/frontend/enums/Wallet.ts @@ -0,0 +1,9 @@ +export enum WalletType { + Safe = 'multisig', + EOA = 'eoa', +} + +export enum WalletOwner { + Master = 'master', // user + Agent = 'agent', +} From a777e7c74f3e8d9795e39bbe1c9dc4d2b9510fc4 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:56:30 +0000 Subject: [PATCH 124/146] refactor: support multiple services & statuses in useLogs --- frontend/hooks/useLogs.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/hooks/useLogs.ts b/frontend/hooks/useLogs.ts index c024fc43..45e1b93f 100644 --- a/frontend/hooks/useLogs.ts +++ b/frontend/hooks/useLogs.ts @@ -1,6 +1,7 @@ +import { useQueryClient } from '@tanstack/react-query'; import { useMemo } from 'react'; -import { MiddlewareDeploymentStatus } from '@/client'; +import { REACT_QUERY_KEYS } from '@/constants/react-query-keys'; import { useBalance } from './useBalance'; import { useMasterSafe } from './useMasterSafe'; @@ -46,19 +47,24 @@ const useBalancesLogs = () => { }; }; +// TODO: refactor to support logs for multiple services const useServicesLogs = () => { - const { serviceStatus, services, hasInitialLoaded } = useServices(); + const { services, isLoaded } = useServices(); + const { getQueryData } = useQueryClient(); return { - isLoaded: hasInitialLoaded, + isLoaded: isLoaded, data: { - serviceStatus: serviceStatus - ? MiddlewareDeploymentStatus[serviceStatus] - : 'undefined', services: services?.map((item) => ({ ...item, keys: item.keys.map((key) => key.address), + deploymentStatus: getQueryData([ + REACT_QUERY_KEYS.SERVICE_DEPLOYMENT_STATUS_KEY( + item.service_config_id, + ), + item.service_config_id, + ]), })) ?? 'undefined', }, }; From 2345c457ed7175bdbe8a35c8b9f36bad5d656077 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:56:47 +0000 Subject: [PATCH 125/146] chore: rename keys to have KEY suffix, easier to understand --- frontend/constants/react-query-keys.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/constants/react-query-keys.ts b/frontend/constants/react-query-keys.ts index 8c7cb031..1d509171 100644 --- a/frontend/constants/react-query-keys.ts +++ b/frontend/constants/react-query-keys.ts @@ -1,4 +1,5 @@ export const REACT_QUERY_KEYS = { - SERVICES: ['services'] as const, - SERVICE_STATUS: (uuid: string) => ['serviceStatus', uuid] as const, + SERVICES_KEY: ['services'] as const, + SERVICE_DEPLOYMENT_STATUS_KEY: (serviceConfigId: string) => + ['serviceStatus', serviceConfigId] as const, } as const; From 98a17432d4911db0eb25a1b7ec71425537b6e00e Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:57:01 +0000 Subject: [PATCH 126/146] chore: use renamed react query key --- frontend/context/ServicesProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/context/ServicesProvider.tsx b/frontend/context/ServicesProvider.tsx index 38a3cafc..8ccdb3e4 100644 --- a/frontend/context/ServicesProvider.tsx +++ b/frontend/context/ServicesProvider.tsx @@ -52,7 +52,7 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => { isFetching, refetch, } = useQuery({ - queryKey: REACT_QUERY_KEYS.SERVICES, + queryKey: REACT_QUERY_KEYS.SERVICES_KEY, queryFn: ServicesService.getServices, enabled: isOnline && !paused, refetchInterval: FIVE_SECONDS_INTERVAL, From 902a3b2f7939538637b907da2566cf4f0a40a6a2 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:57:30 +0000 Subject: [PATCH 127/146] feat: support overwriting service deployment status in reactquery cache --- frontend/hooks/useService.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/frontend/hooks/useService.ts b/frontend/hooks/useService.ts index 1ab6b48d..b02fcae7 100644 --- a/frontend/hooks/useService.ts +++ b/frontend/hooks/useService.ts @@ -1,6 +1,8 @@ +import { useQueryClient } from '@tanstack/react-query'; import { useMemo } from 'react'; import { MiddlewareDeploymentStatus } from '@/client'; +import { REACT_QUERY_KEYS } from '@/constants/react-query-keys'; import { Address } from '@/types/Address'; import { useServices } from './useServices'; @@ -16,11 +18,12 @@ type ServiceChainIdAddressRecord = { * Hook for interacting with a single service. */ export const useService = ({ - serviceConfigId, + serviceConfigId = '', }: { - serviceConfigId: string; + serviceConfigId?: string; }) => { const { services, isLoaded } = useServices(); + const queryClient = useQueryClient(); const service = useMemo(() => { return services?.find( @@ -55,10 +58,25 @@ export const useService = ({ return addressesByChainId; }, [service]); + /** + * Overrides the deployment status of the service in the cache. + * @note Overwrite is only temporary if ServicesContext is polling + */ + const setDeploymentStatus = (deploymentStatus?: MiddlewareDeploymentStatus) => + queryClient.setQueryData( + REACT_QUERY_KEYS.SERVICE_DEPLOYMENT_STATUS_KEY(serviceConfigId), + deploymentStatus, + ); + + const deploymentStatus = queryClient.getQueryData< + MiddlewareDeploymentStatus | undefined + >(REACT_QUERY_KEYS.SERVICE_DEPLOYMENT_STATUS_KEY(serviceConfigId)); + return { service, addresses, - serviceStatus: MiddlewareDeploymentStatus.DEPLOYED, // TODO support other statuses isLoaded, + deploymentStatus, // TODO support other statuses + setDeploymentStatus, }; }; From ec7ea8e5a6a4939cb34527d745862b7dd67ef7a9 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 10:57:42 +0000 Subject: [PATCH 128/146] chore: export services context refetch --- frontend/hooks/useServices.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/hooks/useServices.ts b/frontend/hooks/useServices.ts index 7c36c562..92967577 100644 --- a/frontend/hooks/useServices.ts +++ b/frontend/hooks/useServices.ts @@ -86,6 +86,7 @@ export const useServices = () => { setPaused: setPaused, selectedService, selectService, + refetch, } = useContext(ServicesContext); const servicesByChain = useMemo(() => { @@ -115,5 +116,6 @@ export const useServices = () => { paused, selectedService, selectService, + refetch, }; }; From dd9c8d0efb6f47fe0c8d3333f74db38d0d6ed5ef Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 11:19:29 +0000 Subject: [PATCH 129/146] chore: import Address type from local types --- frontend/service/agents/StakedAgentService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/service/agents/StakedAgentService.ts b/frontend/service/agents/StakedAgentService.ts index 837df5d2..a10a34cf 100644 --- a/frontend/service/agents/StakedAgentService.ts +++ b/frontend/service/agents/StakedAgentService.ts @@ -7,7 +7,6 @@ * @note `noop` functions should be replaced with actual implementations in the extending classes. * @warning DO NOT STORE STATE IN THESE CLASSES. THEY ARE SINGLETONS AND WILL BE SHARED ACROSS THE APPLICATION. */ -import { Address } from 'cluster'; import { ethers } from 'ethers'; import { Contract as MulticallContract } from 'ethers-multicall'; @@ -18,6 +17,7 @@ import { ChainId } from '@/enums/Chain'; import { ContractType } from '@/enums/Contract'; import { ServiceRegistryL2ServiceState } from '@/enums/ServiceRegistryL2ServiceState'; import { StakingProgramId } from '@/enums/StakingProgram'; +import { Address } from '@/types/Address'; export const ONE_YEAR = 1 * 24 * 60 * 60 * 365; From 2eddecc67366a7f8dd8686ba2a5a409ef159ff98 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 11:41:51 +0000 Subject: [PATCH 130/146] chore: remove todo comment for useLogs refactor --- frontend/hooks/useLogs.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/hooks/useLogs.ts b/frontend/hooks/useLogs.ts index 45e1b93f..a6155dab 100644 --- a/frontend/hooks/useLogs.ts +++ b/frontend/hooks/useLogs.ts @@ -47,7 +47,6 @@ const useBalancesLogs = () => { }; }; -// TODO: refactor to support logs for multiple services const useServicesLogs = () => { const { services, isLoaded } = useServices(); const { getQueryData } = useQueryClient(); From c32d9c5212b4b2e4a1e504765e2ea9643aaa8543 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 11:49:28 +0000 Subject: [PATCH 131/146] refactor: improve number formatting utility functions and enhance documentation --- frontend/utils/numberFormatters.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/utils/numberFormatters.ts b/frontend/utils/numberFormatters.ts index 1fe86ca2..ceb28dd9 100644 --- a/frontend/utils/numberFormatters.ts +++ b/frontend/utils/numberFormatters.ts @@ -17,7 +17,8 @@ export const balanceFormat = ( /** * Formats larger numbers into small numbers - * i.e. wei to ether `formatUnits('1000000000000000000', 18)` => '1.0' + * @note **divides** the input by 10^decimals + * @example `formatUnits('1000000000000000000', 18)` => '1.0' */ export const formatUnits = (value: BigNumberish, decimals = 18): string => { return ethers.utils.formatUnits(value, decimals); @@ -31,16 +32,20 @@ export const formatEther = (wei: BigNumberish): string => { }; /** - * Parse converts smaller numbers into larger numbers + * Parse converts small numbers into larger numbers + * @note **multiplies** the input by `10 ** decimals` * @example parseUnits('1.0', 18) => '1000000000000000000' */ -export const parseUnits = (value: string, decimals: 18): string => { - return ethers.utils.parseUnits(value, decimals).toString(); +export const parseUnits = ( + value: BigNumberish, + decimals: number = 18, +): string => { + return ethers.utils.parseUnits(`${value}`, decimals).toString(); }; /** * Assumes the input is in ether and converts it to wei */ -export const parseEther = (ether: string | number | bigint): string => { +export const parseEther = (ether: BigNumberish): string => { return ethers.utils.parseEther(`${ether}`).toString(); }; From 0967f32742bf1bdd7dd8262baf1fb936a96397c7 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 11:49:39 +0000 Subject: [PATCH 132/146] chore: remove todo comment for deploymentStatus in useService hook --- frontend/hooks/useService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/hooks/useService.ts b/frontend/hooks/useService.ts index b02fcae7..e4a82708 100644 --- a/frontend/hooks/useService.ts +++ b/frontend/hooks/useService.ts @@ -76,7 +76,7 @@ export const useService = ({ service, addresses, isLoaded, - deploymentStatus, // TODO support other statuses + deploymentStatus, setDeploymentStatus, }; }; From 9ef2eec340a3909aaf6b93e8d1ced437237d249d Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:53:31 +0000 Subject: [PATCH 133/146] chore: remove todo --- frontend/context/ServicesProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/context/ServicesProvider.tsx b/frontend/context/ServicesProvider.tsx index 8ccdb3e4..0f2ea99b 100644 --- a/frontend/context/ServicesProvider.tsx +++ b/frontend/context/ServicesProvider.tsx @@ -75,7 +75,7 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => { if (selectedServiceConfigId) return; // only select a service by default if services are fetched, but there has been no selection yet if (isFetched && services.length > 0 && !selectedServiceConfigId) - setSelectedServiceConfigId(services[0].service_config_id); // TODO: use uuid instead of hash once middleware refactored + setSelectedServiceConfigId(services[0].service_config_id); }, [isFetched, selectedServiceConfigId, services]); // const serviceAddresses = useMemo( From f88c3543a42ea3f866a725bd51fbb801bf4acdfb Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 17:24:33 +0000 Subject: [PATCH 134/146] refactor: process killing, logging, var names for clarity --- electron/main.js | 81 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/electron/main.js b/electron/main.js index 763e4f98..7afb26cc 100644 --- a/electron/main.js +++ b/electron/main.js @@ -70,11 +70,19 @@ let splashWindow = null; /** @type {Electron.Tray | null} */ let tray = null; -let operateDaemon, operateDaemonPid, nextAppProcess, nextAppProcessPid; +// Used in production and development +let operateDaemon; +let operateDaemonPid; +// Child processes for running next app are only used in development +// required for hot reloads and other dev features +let devNextApp; +let devNextAppPid; + +// Next.js app instance for production // @ts-ignore - Workaround for the missing type definitions const nextApp = next({ - dev: false, // this instance is only used for production + dev: false, // DO NOT SET TO TRUE dir: path.join(__dirname), }); @@ -85,28 +93,73 @@ function showNotification(title, body) { } async function beforeQuit() { - if (operateDaemonPid) { + // destroy all ui components for immediate feedback + tray?.destroy(); + splashWindow?.destroy(); + mainWindow?.destroy(); + + if (operateDaemon || operateDaemonPid) { + // gracefully stop running services try { await fetch( `http://localhost:${appConfig.ports.prod.operate}/stop_all_services`, ); - await killProcesses(operateDaemonPid); } catch (e) { - logger.electron(e); + logger.electron("Couldn't stop_all_services gracefully:"); + logger.electron(JSON.stringify(e, null, 2)); + } + + // clean-up via pid first* + // may have dangling subprocesses + try { + operateDaemonPid && (await killProcesses(operateDaemonPid)); + } catch (e) { + logger.electron("Couldn't kill daemon processes via pid:"); + logger.electron(JSON.stringify(e, null, 2)); + } + + // attempt to kill the daemon process via kill + // if the pid-based cleanup fails + try { + const dead = operateDaemon?.kill(); + if (!dead) { + logger.electron('Daemon process still alive after kill'); + } + } catch (e) { + logger.electron("Couldn't kill operate daemon process via kill:"); + logger.electron(JSON.stringify(e, null, 2)); } } - if (nextAppProcessPid) { + if (devNextApp || devNextAppPid) { + // attempt graceful kill first with next app + try { + const dead = devNextApp?.kill(); + if (!dead) { + logger.electron('Dev NextApp process still alive after kill'); + } + } catch (e) { + logger.electron("Couldn't kill devNextApp process via kill:"); + logger.electron(JSON.stringify(e, null, 2)); + } + + // attempt to kill the dev next app process via pid try { - await killProcesses(nextAppProcessPid); + devNextAppPid && (await killProcesses(devNextAppPid)); } catch (e) { - logger.electron(e); + logger.electron("Couldn't kill devNextApp processes via pid:"); + logger.electron(JSON.stringify(e, null, 2)); } } - tray?.destroy(); - splashWindow?.destroy(); - mainWindow?.destroy(); + if (nextApp) { + // attempt graceful close of prod next app + await nextApp.close().catch((e) => { + logger.electron("Couldn't close NextApp gracefully:"); + logger.electron(JSON.stringify(e, null, 2)); + }); + // electron will kill next service on exit + } } const APP_WIDTH = 460; @@ -340,7 +393,7 @@ async function launchNextApp() { async function launchNextAppDev() { await new Promise(function (resolve, _reject) { process.env.NEXT_PUBLIC_BACKEND_PORT = appConfig.ports.dev.operate; // must set next env var to connect to backend - nextAppProcess = spawn( + devNextApp = spawn( 'yarn', ['dev:frontend', '--port', appConfig.ports.dev.next], { @@ -352,8 +405,8 @@ async function launchNextAppDev() { }, }, ); - nextAppProcessPid = nextAppProcess.pid; - nextAppProcess.stdout.on('data', (data) => { + devNextAppPid = devNextApp.pid; + devNextApp.stdout.on('data', (data) => { logger.next(data.toString().trim()); resolve(); }); From 7f3ef6286946872bbc3437bbf8b789c3f64f4a3e Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 17:25:05 +0000 Subject: [PATCH 135/146] feat: add electron logging to killProcesses --- electron/processes.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/electron/processes.js b/electron/processes.js index faaa9266..e65e2ed2 100644 --- a/electron/processes.js +++ b/electron/processes.js @@ -16,15 +16,16 @@ function killProcesses(pid) { // Array of PIDs to kill, starting with the children const pidsToKill = children.map((p) => p.PID); - logger.info("Pids to kill " + JSON.stringify(pidsToKill)); + logger.electron('Pids to kill ' + JSON.stringify(pidsToKill)); const killCommand = isWindows ? windowsKillCommand : unixKillCommand; let errors = []; - for (const ppid of pidsToKill) { - logger.info("kill: " + ppid); - exec(`${killCommand} ${ppid}`, (err) => { - logger.error("Pids to kill error:" + err); + for (const pid of pidsToKill) { + logger.electron('killing: ' + pid); + exec(`${killCommand} ${pid}`, (err) => { + logger.electron(`error killing pid ${pid}`); + logger.electron(JSON.stringify(err, null, 2)); if ( err?.message?.includes(isWindows ? 'not found' : 'No such process') ) { @@ -36,9 +37,7 @@ function killProcesses(pid) { if (errors.length === 0) { reject(errors); - - - } else resolve(); + } else resolve(); }); }); } From 119b6985b7667ad206b9077aa5e1da0490f21625 Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:38:23 +0000 Subject: [PATCH 136/146] enforce err --- electron/processes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electron/processes.js b/electron/processes.js index e65e2ed2..5a1e8ba0 100644 --- a/electron/processes.js +++ b/electron/processes.js @@ -24,8 +24,8 @@ function killProcesses(pid) { for (const pid of pidsToKill) { logger.electron('killing: ' + pid); exec(`${killCommand} ${pid}`, (err) => { - logger.electron(`error killing pid ${pid}`); - logger.electron(JSON.stringify(err, null, 2)); + err && logger.electron(`error killing pid ${pid}`); + err && logger.electron(JSON.stringify(err, null, 2)); if ( err?.message?.includes(isWindows ? 'not found' : 'No such process') ) { From 3de2a136f666287f79b2ee308b468d38f1b8cd92 Mon Sep 17 00:00:00 2001 From: truemiller Date: Tue, 12 Nov 2024 17:40:26 +0000 Subject: [PATCH 137/146] bump: rc196 --- package.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 51757886..16dbf2dd 100644 --- a/package.json +++ b/package.json @@ -63,5 +63,5 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.1.0-rc195" + "version": "0.1.0-rc196" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8d66f2f0..440429ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.1.0-rc195" +version = "0.1.0-rc196" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" From a845e62dc882fb3a8598485500ace00c96428ecb Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:44:37 +0000 Subject: [PATCH 138/146] Update electron/main.js --- electron/main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/electron/main.js b/electron/main.js index 7afb26cc..6c453b6a 100644 --- a/electron/main.js +++ b/electron/main.js @@ -80,6 +80,7 @@ let devNextApp; let devNextAppPid; // Next.js app instance for production +// requires http server wrap to work; assign port, receive requests, deliver responses // @ts-ignore - Workaround for the missing type definitions const nextApp = next({ dev: false, // DO NOT SET TO TRUE From af620d18ffe48bfdd8d69128101411748859f9c1 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 13 Nov 2024 00:21:09 +0000 Subject: [PATCH 139/146] chore: rename balance of fragment --- frontend/abis/erc20.ts | 2 +- frontend/components/MainPage/sections/AddFundsSection.tsx | 6 +++--- frontend/service/Multicall.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/abis/erc20.ts b/frontend/abis/erc20.ts index aafcd708..2c32e58c 100644 --- a/frontend/abis/erc20.ts +++ b/frontend/abis/erc20.ts @@ -1,5 +1,5 @@ import { Abi } from '@/types/Abi'; -export const ERC20_BALANCEOF_STRING_FRAGMENT: Abi = [ +export const ERC20_BALANCE_OF_STRING_FRAGMENT: Abi = [ 'function balanceOf(address owner) view returns (uint256)', ]; diff --git a/frontend/components/MainPage/sections/AddFundsSection.tsx b/frontend/components/MainPage/sections/AddFundsSection.tsx index bd37ea48..44454622 100644 --- a/frontend/components/MainPage/sections/AddFundsSection.tsx +++ b/frontend/components/MainPage/sections/AddFundsSection.tsx @@ -17,7 +17,7 @@ import { forwardRef, useCallback, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { useInterval } from 'usehooks-ts'; -import { ERC20_BALANCEOF_STRING_FRAGMENT } from '@/abis/erc20'; +import { ERC20_BALANCE_OF_STRING_FRAGMENT } from '@/abis/erc20'; import { CHAIN_CONFIG } from '@/config/chains'; import { TOKEN_CONFIG } from '@/config/tokens'; import { UNICODE_SYMBOLS } from '@/constants/symbols'; @@ -111,7 +111,7 @@ export const OpenAddFundsSection = forwardRef((_, ref) => { //USDC balance new Contract( TOKEN_CONFIG[CHAIN_CONFIG.ETHEREUM.chainId]['USDC'].address, - ERC20_BALANCEOF_STRING_FRAGMENT, + ERC20_BALANCE_OF_STRING_FRAGMENT, ETHEREUM_PROVIDER, ) .balanceOf(masterSafeAddress) @@ -124,7 +124,7 @@ export const OpenAddFundsSection = forwardRef((_, ref) => { .then(setopEth), new Contract( TOKEN_CONFIG[CHAIN_CONFIG.OPTIMISM.chainId]['OLAS'].address, - ERC20_BALANCEOF_STRING_FRAGMENT, + ERC20_BALANCE_OF_STRING_FRAGMENT, OPTIMISM_PROVIDER, ) .balanceOf(masterSafeAddress) diff --git a/frontend/service/Multicall.ts b/frontend/service/Multicall.ts index 6ba2f64c..668247f9 100644 --- a/frontend/service/Multicall.ts +++ b/frontend/service/Multicall.ts @@ -1,7 +1,7 @@ import { ethers } from 'ethers'; import { Contract as MulticallContract, ContractCall } from 'ethers-multicall'; -import { ERC20_BALANCEOF_STRING_FRAGMENT } from '@/abis/erc20'; +import { ERC20_BALANCE_OF_STRING_FRAGMENT } from '@/abis/erc20'; import { Erc20TokenConfig } from '@/config/tokens'; import { PROVIDERS } from '@/constants/providers'; import { ChainId } from '@/enums/Chain'; @@ -59,7 +59,7 @@ const getErc20Balances = async ( const callData: ContractCall[] = addresses.map((address: Address) => new MulticallContract( erc20TokenConfig.address, - ERC20_BALANCEOF_STRING_FRAGMENT, + ERC20_BALANCE_OF_STRING_FRAGMENT, ) .balanceOf(address) .then((balance: bigint) => From 6240763eb6973242fed7becdcaf34add44b87cb0 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 13 Nov 2024 00:22:35 +0000 Subject: [PATCH 140/146] chore: extend commented hide top bar --- frontend/components/MainPage/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/MainPage/index.tsx b/frontend/components/MainPage/index.tsx index dc196cfa..c425294b 100644 --- a/frontend/components/MainPage/index.tsx +++ b/frontend/components/MainPage/index.tsx @@ -36,7 +36,7 @@ export const Main = () => { // const hideMainOlasBalanceTopBorder = [ // !backupSafeAddress, - // currentStakingProgram === StakingProgramId.Alpha, + // currentStakingProgram === StakingProgramId.PearlAlpha, // ].some((condition) => !!condition); return ( From 3a6c155369defb2fa2983d9cadc9df568642c86d Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:14:53 +0000 Subject: [PATCH 141/146] Update frontend/components/MainPage/header/AgentButton/AgentButton.tsx Co-authored-by: Mohan --- frontend/components/MainPage/header/AgentButton/AgentButton.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/components/MainPage/header/AgentButton/AgentButton.tsx b/frontend/components/MainPage/header/AgentButton/AgentButton.tsx index 11b3a4cf..d5cf98f2 100644 --- a/frontend/components/MainPage/header/AgentButton/AgentButton.tsx +++ b/frontend/components/MainPage/header/AgentButton/AgentButton.tsx @@ -17,13 +17,11 @@ import { AgentStoppingButton } from './AgentStoppingButton'; export const AgentButton = () => { const { selectedService } = useServices(); - const { service, deploymentStatus: serviceStatus, isLoaded, } = useService({ serviceConfigId: selectedService?.service_config_id }); - const { isEligibleForStaking, isAgentEvicted } = useStakingContractInfo(); return useMemo(() => { From d52558d9c3b5c192c656c6ec288284ed40e3b350 Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:33:13 +0000 Subject: [PATCH 142/146] Update frontend/utils/numberFormatters.ts Co-authored-by: Mohan --- frontend/utils/numberFormatters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/utils/numberFormatters.ts b/frontend/utils/numberFormatters.ts index ceb28dd9..88411c85 100644 --- a/frontend/utils/numberFormatters.ts +++ b/frontend/utils/numberFormatters.ts @@ -32,7 +32,7 @@ export const formatEther = (wei: BigNumberish): string => { }; /** - * Parse converts small numbers into larger numbers + * Converts small numbers into larger numbers * @note **multiplies** the input by `10 ** decimals` * @example parseUnits('1.0', 18) => '1000000000000000000' */ From bb4c251bb3bd239a043c19ab7c10259ee22506e9 Mon Sep 17 00:00:00 2001 From: Mohan Date: Wed, 13 Nov 2024 17:36:40 +0530 Subject: [PATCH 143/146] feat: integrate v2 API endpoints (#440) * feat: add BACKEND_URL_V2 for versioned API endpoint * feat: implement support for versioned API and chain-specific service configurations * chore: remove commented-out deployment methods from Services.ts * feat: update service fetch URLs to use BACKEND_URL_V2 for consistency * feat: refactor service handling to use service_config_id instead of hash * feat: update types for MiddlewareServiceResponse and ServiceTemplate to include new fields and improve structure * refactor: remove unused types and update service_config_id references in ServiceTemplate * Update frontend/constants/serviceTemplates.ts Co-authored-by: Josh Miller <31908788+truemiller@users.noreply.github.com> --------- Co-authored-by: Josh Miller <31908788+truemiller@users.noreply.github.com> --- frontend/client/types.ts | 69 +++---- .../MainPage/header/AgentButton.tsx | 6 +- .../StakingContractSection/MigrateButton.tsx | 11 +- frontend/constants/urls.ts | 2 + frontend/context/ServicesProvider.tsx | 8 +- frontend/hooks/useServiceTemplates.ts | 8 +- frontend/hooks/useServices.ts | 9 +- frontend/service/Services.ts | 194 +++++++++--------- 8 files changed, 156 insertions(+), 151 deletions(-) diff --git a/frontend/client/types.ts b/frontend/client/types.ts index e461348d..e97b01b4 100644 --- a/frontend/client/types.ts +++ b/frontend/client/types.ts @@ -9,18 +9,18 @@ import { export type ServiceHash = string; -export type LedgerConfig = { - rpc: string; - type: MiddlewareLedger; - chain: MiddlewareChain; -}; - export type ServiceKeys = { address: Address; private_key: string; ledger: MiddlewareChain; }; +export type LedgerConfig = { + rpc: string; + type: MiddlewareLedger; + chain: MiddlewareChain; +}; + export type ChainData = { instances?: Address[]; token?: number; @@ -28,6 +28,7 @@ export type ChainData = { on_chain_state: number; staked: boolean; user_params: { + agent_id: number; cost_of_bond: number; fund_requirements: { agent: number; @@ -36,15 +37,23 @@ export type ChainData = { nft: string; staking_program_id: StakingProgramId; threshold: number; + use_mech_marketplace: true; use_staking: true; }; }; export type MiddlewareServiceResponse = { - name: string; + description: string; hash: string; + hash_history: { + [block: string]: string; + }; + home_chain_id: number; keys: ServiceKeys[]; - readme?: string; + name: string; + service_path?: string; + service_config_id: string; + version: string; chain_configs: { [chainId: number]: { ledger_config: LedgerConfig; @@ -56,18 +65,27 @@ export type MiddlewareServiceResponse = { export type ServiceTemplate = { name: string; hash: string; - image: string; description: string; + image: string; service_version: string; home_chain_id: string; configurations: { [key: string]: ConfigurationTemplate }; deploy?: boolean; + service_env_variables?: { + [key: string]: { + name: string; + env_variable_name: string; + description: string; + value: string; + provision_type: string; + }; + }; }; export type ConfigurationTemplate = { - rpc?: string; // added on deployment staking_program_id?: StakingProgramId; // added on deployment nft: string; + rpc?: string; // added on deployment agent_id: number; threshold: number; use_staking: boolean; @@ -92,37 +110,6 @@ export type Deployment = { nodes: DeployedNodes; }; -export type EmptyPayload = Record; - -export type EmptyResponse = Record; - -export type HttpResponse = { - error?: string; - data?: string; -}; - -export type ClientResponse = { - error?: string; - data?: ResponseType; -}; - -export type StopDeployment = { - delete: boolean /* Delete deployment*/; -}; - -export type UpdateServicePayload = { - old: ServiceHash; - new: ServiceTemplate; -}; - -export type DeleteServicesPayload = { - hashes: ServiceHash[]; -}; - -export type DeleteServicesResponse = { - hashes: ServiceHash[]; -}; - export type AppInfo = { account?: { key: Address; diff --git a/frontend/components/MainPage/header/AgentButton.tsx b/frontend/components/MainPage/header/AgentButton.tsx index d09f31fe..523669ed 100644 --- a/frontend/components/MainPage/header/AgentButton.tsx +++ b/frontend/components/MainPage/header/AgentButton.tsx @@ -5,6 +5,7 @@ import { useCallback, useMemo } from 'react'; import { MiddlewareChain, MiddlewareDeploymentStatus } from '@/client'; import { COLOR } from '@/constants/colors'; import { DEFAULT_STAKING_PROGRAM_ID } from '@/context/StakingProgramProvider'; +import { ChainId } from '@/enums/Chain'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useBalance } from '@/hooks/useBalance'; import { useElectronApi } from '@/hooks/useElectronApi'; @@ -86,7 +87,7 @@ const AgentRunningButton = () => { // Optimistically update service status setServiceStatus(MiddlewareDeploymentStatus.STOPPING); try { - await ServicesService.stopDeployment(service.hash); + await ServicesService.stopDeployment(service.service_config_id); } catch (error) { console.error(error); showNotification?.('Error while stopping agent'); @@ -205,7 +206,10 @@ const AgentNotRunningButton = () => { serviceTemplate, deploy: true, useMechMarketplace: false, + chainId: ChainId.Gnosis, // TODO: Add support for other chains }); + + await ServicesService.startService(serviceTemplate.service_config_id); } catch (error) { console.error(error); setServiceStatus(undefined); diff --git a/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx b/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx index f2589d32..59694560 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/MigrateButton.tsx @@ -3,6 +3,7 @@ import { isNil } from 'lodash'; import { useMemo } from 'react'; import { MiddlewareDeploymentStatus } from '@/client'; +import { ChainId } from '@/enums/Chain'; import { Pages } from '@/enums/Pages'; import { StakingProgramId } from '@/enums/StakingProgram'; import { useBalance } from '@/hooks/useBalance'; @@ -80,13 +81,21 @@ export const MigrateButton = ({ stakingProgramId }: MigrateButtonProps) => { setServiceStatus(MiddlewareDeploymentStatus.DEPLOYING); goto(Pages.Main); - await ServicesService.createService({ + // update service + await ServicesService.updateService({ stakingProgramId, serviceTemplate, + serviceUuid: serviceTemplate.service_config_id, deploy: true, useMechMarketplace: false, + chainId: ChainId.Gnosis, // TODO: Add support for other chains }); + // start service after updating + await ServicesService.startService( + serviceTemplate.service_config_id, + ); + await updateStakingProgram(); setMigrationModalOpen(true); diff --git a/frontend/constants/urls.ts b/frontend/constants/urls.ts index 97afa4d6..392a37a6 100644 --- a/frontend/constants/urls.ts +++ b/frontend/constants/urls.ts @@ -2,6 +2,8 @@ import { MiddlewareChain } from '@/client'; export const BACKEND_URL: string = `http://localhost:${process.env.NODE_ENV === 'production' ? 8765 : 8000}/api`; +export const BACKEND_URL_V2: string = `http://localhost:${process.env.NODE_ENV === 'production' ? 8765 : 8000}/api/v2`; + export const COW_SWAP_GNOSIS_XDAI_OLAS_URL: string = 'https://swap.cow.fi/#/100/swap/WXDAI/OLAS'; diff --git a/frontend/context/ServicesProvider.tsx b/frontend/context/ServicesProvider.tsx index 8eec40b9..6f209dc1 100644 --- a/frontend/context/ServicesProvider.tsx +++ b/frontend/context/ServicesProvider.tsx @@ -59,7 +59,9 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => { const selectedService = useMemo(() => { if (!services) return; - return services.find((service) => service.hash === selectedServiceUuid); // TODO: use uuid instead of hash once middleware refactored + return services.find( + (service) => service.service_config_id === selectedServiceUuid, + ); }, [selectedServiceUuid, services]); const selectService = useCallback((serviceUuid: string) => { @@ -68,7 +70,7 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => { useEffect(() => { if (!services) return; - setSelectedServiceUuid(services[0]?.hash); // TODO: use uuid instead of hash once middleware refactored + setSelectedServiceUuid(services[0]?.service_config_id); }, [services]); // const serviceAddresses = useMemo( @@ -107,7 +109,7 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => { // const updateServiceStatus = useCallback(async () => { // if (!services?.[0]) return; - // const serviceStatus = await ServicesService.getDeployment(services[0].hash); + // const serviceStatus = await ServicesService.getDeployment(services[0].service_config_id); // setServiceStatuses(serviceStatus.status); // }, [services]); diff --git a/frontend/hooks/useServiceTemplates.ts b/frontend/hooks/useServiceTemplates.ts index 7e4e733b..0a6e39d8 100644 --- a/frontend/hooks/useServiceTemplates.ts +++ b/frontend/hooks/useServiceTemplates.ts @@ -3,8 +3,12 @@ import { SERVICE_TEMPLATES } from '@/constants/serviceTemplates'; export const useServiceTemplates = () => { const getServiceTemplates = (): ServiceTemplate[] => SERVICE_TEMPLATES; - const getServiceTemplate = (hash: string): ServiceTemplate | undefined => - SERVICE_TEMPLATES.find((template) => template.hash === hash); + const getServiceTemplate = ( + serviceUuid: string, + ): ServiceTemplate | undefined => + SERVICE_TEMPLATES.find( + (template) => template.service_config_id === serviceUuid, + ); return { getServiceTemplate, diff --git a/frontend/hooks/useServices.ts b/frontend/hooks/useServices.ts index 1da28a06..957f9ea4 100644 --- a/frontend/hooks/useServices.ts +++ b/frontend/hooks/useServices.ts @@ -52,15 +52,18 @@ export const useServices = () => { const { services, isFetched: hasInitialLoaded } = useContext(ServicesContext); const serviceId = - services?.[0]?.chain_configs[CHAIN_CONFIG.OPTIMISM.chainId].chain_data?.token; + services?.[0]?.chain_configs[CHAIN_CONFIG.OPTIMISM.chainId].chain_data + ?.token; // STATE METHODS const getServiceFromState = ( - serviceHash: ServiceHash, + serviceUuid: ServiceHash, ): MiddlewareServiceResponse | undefined => { if (!hasInitialLoaded) return; if (!services) return; - return services.find((service) => service.hash === serviceHash); + return services.find( + (service) => service.service_config_id === serviceUuid, + ); }; return { diff --git a/frontend/service/Services.ts b/frontend/service/Services.ts index 596e7273..a80189c7 100644 --- a/frontend/service/Services.ts +++ b/frontend/service/Services.ts @@ -4,8 +4,9 @@ import { ServiceHash, ServiceTemplate, } from '@/client'; +import { CHAIN_CONFIG } from '@/config/chains'; import { CONTENT_TYPE_JSON_UTF8 } from '@/constants/headers'; -import { BACKEND_URL } from '@/constants/urls'; +import { BACKEND_URL_V2 } from '@/constants/urls'; import { ChainId } from '@/enums/Chain'; import { StakingProgramId } from '@/enums/StakingProgram'; @@ -15,18 +16,16 @@ import { StakingProgramId } from '@/enums/StakingProgram'; * @returns */ const getService = async ( - serviceHash: ServiceHash, + serviceUuid: ServiceHash, ): Promise => - fetch(`${BACKEND_URL}/services/${serviceHash}`, { + fetch(`${BACKEND_URL_V2}/service/${serviceUuid}`, { method: 'GET', - headers: { - ...CONTENT_TYPE_JSON_UTF8, - }, + headers: { ...CONTENT_TYPE_JSON_UTF8 }, }).then((response) => { if (response.ok) { return response.json(); } - throw new Error(`Failed to fetch service ${serviceHash}`); + throw new Error(`Failed to fetch service ${serviceUuid}`); }); /** @@ -34,11 +33,9 @@ const getService = async ( * @returns An array of services */ const getServices = async (): Promise => - fetch(`${BACKEND_URL}/services`, { + fetch(`${BACKEND_URL_V2}/services`, { method: 'GET', - headers: { - ...CONTENT_TYPE_JSON_UTF8, - }, + headers: { ...CONTENT_TYPE_JSON_UTF8 }, }).then((response) => { if (response.ok) { return response.json(); @@ -56,83 +53,100 @@ const createService = async ({ serviceTemplate, stakingProgramId, useMechMarketplace = false, + chainId, }: { deploy: boolean; serviceTemplate: ServiceTemplate; stakingProgramId: StakingProgramId; useMechMarketplace?: boolean; + chainId: ChainId; }): Promise => - new Promise((resolve, reject) => - fetch(`${BACKEND_URL}/services`, { - method: 'POST', - body: JSON.stringify({ - ...serviceTemplate, - deploy, - configurations: { - [ChainId.Optimism]: { - ...serviceTemplate.configurations[ChainId.Optimism], - staking_program_id: stakingProgramId, - rpc: `${process.env.OPTIMISM_RPC}`, - use_mech_marketplace: useMechMarketplace, - }, + fetch(`${BACKEND_URL_V2}/service`, { + method: 'POST', + body: JSON.stringify({ + ...serviceTemplate, + deploy, + configurations: { + [chainId]: { + ...serviceTemplate.configurations[ChainId.Optimism], + staking_program_id: stakingProgramId, + rpc: CHAIN_CONFIG[chainId].rpc, + use_mech_marketplace: useMechMarketplace, }, - }), - headers: { - ...CONTENT_TYPE_JSON_UTF8, }, - }).then((response) => { - if (response.ok) { - resolve(response.json()); - } - reject(response); }), - ); - -// const deployOnChain = async (serviceHash: ServiceHash): Promise => -// fetch(`${BACKEND_URL}/services/${serviceHash}/onchain/deploy`, { -// method: 'POST', -// headers: { -// ...CONTENT_TYPE_JSON_UTF8, -// }, -// }).then((response) => { -// if (response.ok) { -// return response.json(); -// } -// throw new Error('Failed to deploy service on chain'); -// }); + headers: { ...CONTENT_TYPE_JSON_UTF8 }, + }).then((response) => { + if (response.ok) { + return response.json(); + } + throw new Error('Failed to create service'); + }); -// const buildDeployment = async (serviceHash: ServiceHash): Promise => -// fetch(`${BACKEND_URL}/services/${serviceHash}/deployment/build`, { -// method: 'POST', -// headers: { -// ...CONTENT_TYPE_JSON_UTF8, -// }, -// }).then((response) => { -// if (response.ok) { -// return response.json(); -// } -// throw new Error('Failed to build deployment'); -// }); +/** + * Updates a service + * @param serviceTemplate + * @returns Promise + */ +const updateService = async ({ + deploy, + serviceTemplate, + serviceUuid, + stakingProgramId, + useMechMarketplace = false, + chainId, +}: { + deploy: boolean; + serviceTemplate: ServiceTemplate; + serviceUuid: ServiceHash; + stakingProgramId: StakingProgramId; + useMechMarketplace?: boolean; + chainId: ChainId; +}): Promise => + fetch(`${BACKEND_URL_V2}/service/${serviceUuid}`, { + method: 'PUT', + body: JSON.stringify({ + ...serviceTemplate, + deploy, + configurations: { + [chainId]: { + ...serviceTemplate.configurations[ChainId.Optimism], + staking_program_id: stakingProgramId, + rpc: CHAIN_CONFIG[chainId].rpc, + use_mech_marketplace: useMechMarketplace, + }, + }, + }), + headers: { ...CONTENT_TYPE_JSON_UTF8 }, + }).then((response) => { + if (response.ok) { + return response.json(); + } + throw new Error('Failed to update service'); + }); -// const startDeployment = async (serviceHash: ServiceHash): Promise => -// fetch(`${BACKEND_URL}/services/${serviceHash}/deployment/start`, { -// method: 'POST', -// headers: { -// ...CONTENT_TYPE_JSON_UTF8, -// }, -// }).then((response) => { -// if (response.ok) { -// return response.json(); -// } -// throw new Error('Failed to start deployment'); -// }); +/** + * Starts a service + * @param serviceTemplate + * @returns Promise + */ +const startService = async ( + serviceUuid: ServiceHash, +): Promise => + fetch(`${BACKEND_URL_V2}/service/${serviceUuid}`, { + method: 'POST', + headers: { ...CONTENT_TYPE_JSON_UTF8 }, + }).then((response) => { + if (response.ok) { + return response.json(); + } + throw new Error('Failed to start the service'); + }); -const stopDeployment = async (serviceHash: ServiceHash): Promise => - fetch(`${BACKEND_URL}/services/${serviceHash}/deployment/stop`, { +const stopDeployment = async (serviceUuid: ServiceHash): Promise => + fetch(`${BACKEND_URL_V2}/service/${serviceUuid}/deployment/stop`, { method: 'POST', - headers: { - ...CONTENT_TYPE_JSON_UTF8, - }, + headers: { ...CONTENT_TYPE_JSON_UTF8 }, }).then((response) => { if (response.ok) { return response.json(); @@ -140,27 +154,10 @@ const stopDeployment = async (serviceHash: ServiceHash): Promise => throw new Error('Failed to stop deployment'); }); -// const deleteDeployment = async ( -// serviceHash: ServiceHash, -// ): Promise => -// fetch(`${BACKEND_URL}/services/${serviceHash}/deployment/delete`, { -// method: 'POST', -// headers: { -// ...CONTENT_TYPE_JSON_UTF8, -// }, -// }).then((response) => { -// if (response.ok) { -// return response.json(); -// } -// throw new Error('Failed to delete deployment'); -// }); - -const getDeployment = async (serviceHash: ServiceHash): Promise => - fetch(`${BACKEND_URL}/services/${serviceHash}/deployment`, { +const getDeployment = async (serviceUuid: ServiceHash): Promise => + fetch(`${BACKEND_URL_V2}/service/${serviceUuid}/deployment`, { method: 'GET', - headers: { - ...CONTENT_TYPE_JSON_UTF8, - }, + headers: { ...CONTENT_TYPE_JSON_UTF8 }, }).then((response) => { if (response.ok) { return response.json(); @@ -172,11 +169,8 @@ export const ServicesService = { getService, getServices, getDeployment, + startService, createService, - // deployOnChain, - // stopOnChain, - // buildDeployment, - // startDeployment, + updateService, stopDeployment, - // deleteDeployment, }; From d1853c5da3b21d43b0064f360f0273fc70c8e274 Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:57:05 +0000 Subject: [PATCH 144/146] chore: use gnosis in template --- frontend/constants/serviceTemplates.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/constants/serviceTemplates.ts b/frontend/constants/serviceTemplates.ts index 77017ee7..660f575e 100644 --- a/frontend/constants/serviceTemplates.ts +++ b/frontend/constants/serviceTemplates.ts @@ -10,7 +10,7 @@ export const SERVICE_TEMPLATES: ServiceTemplate[] = [ image: 'https://operate.olas.network/_next/image?url=%2Fimages%2Fprediction-agent.png&w=3840&q=75', service_version: 'v0.18.4', - home_chain_id: ChainId.Optimism.toString(), + home_chain_id: ChainId.Gnosis.toString(), configurations: { [ChainId.Optimism]: { staking_program_id: StakingProgramId.OptimusAlpha, // default, may be overwritten From 2b2ba5a66d479181e2d3d1fb11ee14ee2156e074 Mon Sep 17 00:00:00 2001 From: truemiller Date: Wed, 13 Nov 2024 14:15:47 +0000 Subject: [PATCH 145/146] refactor: remove unused constants from MainPage header --- frontend/components/MainPage/header/constants.ts | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 frontend/components/MainPage/header/constants.ts diff --git a/frontend/components/MainPage/header/constants.ts b/frontend/components/MainPage/header/constants.ts deleted file mode 100644 index 27f36a98..00000000 --- a/frontend/components/MainPage/header/constants.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { formatUnits } from 'ethers/lib/utils'; - -import { CHAIN_CONFIG } from '@/config/chains'; -import { SERVICE_TEMPLATES } from '@/constants/serviceTemplates'; - -export const requiredGas = Number( - formatUnits( - `${SERVICE_TEMPLATES[0].configurations[CHAIN_CONFIG.OPTIMISM.chainId].monthly_gas_estimate}`, - 18, - ), -); From f17c0bddbed2186c3b5bdbd19806eedf4616dafc Mon Sep 17 00:00:00 2001 From: Mohan Date: Thu, 14 Nov 2024 15:48:01 +0530 Subject: [PATCH 146/146] feat update RewardProvider and related hooks (#448) * refactor: update staking rewards info retrieval to use AGENT_CONFIG * refactor: enhance RewardProvider to utilize react-query for rewards data fetching * refactor: update RewardProvider to use dynamic agent and chain selection for rewards fetching * refactor: update RewardProvider and react-query keys for dynamic rewards fetching * refactor: improve RewardProvider logic for storing first staking reward and refreshing rewards data * refactor: update StakingRewardsInfo to use Zod schema for validation and enhance query logic --- frontend/constants/react-query-keys.ts | 28 ++++ frontend/context/RewardProvider.tsx | 180 +++++++++++++++---------- frontend/types/Autonolas.ts | 26 ++-- 3 files changed, 155 insertions(+), 79 deletions(-) diff --git a/frontend/constants/react-query-keys.ts b/frontend/constants/react-query-keys.ts index 1d509171..e2772581 100644 --- a/frontend/constants/react-query-keys.ts +++ b/frontend/constants/react-query-keys.ts @@ -2,4 +2,32 @@ export const REACT_QUERY_KEYS = { SERVICES_KEY: ['services'] as const, SERVICE_DEPLOYMENT_STATUS_KEY: (serviceConfigId: string) => ['serviceStatus', serviceConfigId] as const, + REWARDS_KEY: ( + chainId: number, + serviceUuid: string, + stakingProgramId: string, + multisig: string, + token: number, + ) => + [ + 'rewards', + chainId, + serviceUuid, + stakingProgramId, + multisig, + token, + ] as const, + AVAILABLE_REWARDS_FOR_EPOCH_KEY: ( + currentChainId: number, + serviceConfigId: string, + stakingProgramId: string, + chainId: number, + ) => + [ + 'availableRewardsForEpoch', + currentChainId, + serviceConfigId, + stakingProgramId, + chainId, + ] as const, } as const; diff --git a/frontend/context/RewardProvider.tsx b/frontend/context/RewardProvider.tsx index d409ae03..d3327ddd 100644 --- a/frontend/context/RewardProvider.tsx +++ b/frontend/context/RewardProvider.tsx @@ -1,4 +1,5 @@ -import { ethers } from 'ethers'; +import { useQuery } from '@tanstack/react-query'; +import { formatUnits } from 'ethers/lib/utils'; import { createContext, PropsWithChildren, @@ -6,18 +7,19 @@ import { useContext, useEffect, useMemo, - useState, } from 'react'; -import { useInterval } from 'usehooks-ts'; -import { CHAIN_CONFIG } from '@/config/chains'; +import { AGENT_CONFIG } from '@/config/agents'; +import { GNOSIS_CHAIN_CONFIG } from '@/config/chains'; import { FIVE_SECONDS_INTERVAL } from '@/constants/intervals'; +import { REACT_QUERY_KEYS } from '@/constants/react-query-keys'; import { useElectronApi } from '@/hooks/useElectronApi'; +import { useService } from '@/hooks/useService'; +import { useServices } from '@/hooks/useServices'; import { useStore } from '@/hooks/useStore'; -import { AutonolasService } from '@/service/Autonolas'; +import { StakingRewardsInfoSchema } from '@/types/Autonolas'; import { OnlineStatusContext } from './OnlineStatusProvider'; -import { ServicesContext } from './ServicesProvider'; import { StakingProgramContext } from './StakingProgramProvider'; export const RewardContext = createContext<{ @@ -38,88 +40,130 @@ export const RewardContext = createContext<{ updateRewards: async () => {}, }); -export const RewardProvider = ({ children }: PropsWithChildren) => { +const currentAgent = AGENT_CONFIG.trader; // TODO: replace with dynamic agent selection +const currentChainId = GNOSIS_CHAIN_CONFIG.chainId; // TODO: replace with dynamic chain selection + +/** + * hook to fetch staking rewards details + */ +const useStakingRewardsDetails = () => { + const { isOnline } = useContext(OnlineStatusContext); + const { activeStakingProgramId } = useContext(StakingProgramContext); + + const { selectedService, isLoaded } = useServices(); + const serviceConfigId = + isLoaded && selectedService ? selectedService?.service_config_id : ''; + const { service } = useService({ serviceConfigId }); + + // fetch chain data from the selected service + const chainData = service?.chain_configs[currentChainId].chain_data; + const multisig = chainData?.multisig; + const token = chainData?.token; + + return useQuery({ + queryKey: REACT_QUERY_KEYS.REWARDS_KEY( + currentChainId, + serviceConfigId, + activeStakingProgramId!, + multisig!, + token!, + ), + queryFn: async () => { + const response = await currentAgent.serviceApi.getAgentStakingRewardsInfo( + { + agentMultisigAddress: multisig!, + serviceId: token!, + stakingProgramId: activeStakingProgramId!, + chainId: currentChainId, + }, + ); + return StakingRewardsInfoSchema.parse(response); + }, + enabled: !!isOnline && !!activeStakingProgramId && !!multisig && !!token, + refetchInterval: isOnline ? FIVE_SECONDS_INTERVAL : false, + refetchOnWindowFocus: false, + }); +}; + +/** + * hook to fetch available rewards for the current epoch + */ +const useAvailableRewardsForEpoch = () => { const { isOnline } = useContext(OnlineStatusContext); - const { services } = useContext(ServicesContext); - const service = useMemo(() => services?.[0], [services]); - const { storeState } = useStore(); - const electronApi = useElectronApi(); const { activeStakingProgramId, defaultStakingProgramId } = useContext( StakingProgramContext, ); + const { selectedService, isLoaded } = useServices(); + const serviceConfigId = + isLoaded && selectedService ? selectedService?.service_config_id : ''; + + return useQuery({ + queryKey: REACT_QUERY_KEYS.AVAILABLE_REWARDS_FOR_EPOCH_KEY( + currentChainId, + serviceConfigId, + activeStakingProgramId!, + currentChainId, + ), + queryFn: async () => { + return await currentAgent.serviceApi.getAvailableRewardsForEpoch( + activeStakingProgramId ?? defaultStakingProgramId, + currentChainId, + ); + }, + enabled: + !!isOnline && !!activeStakingProgramId && !!defaultStakingProgramId, + refetchInterval: isOnline ? FIVE_SECONDS_INTERVAL : false, + refetchOnWindowFocus: false, + }); +}; - const [accruedServiceStakingRewards, setAccruedServiceStakingRewards] = - useState(); - const [availableRewardsForEpoch, setAvailableRewardsForEpoch] = - useState(); - const [isEligibleForRewards, setIsEligibleForRewards] = useState(); +/** + * Provider to manage rewards context + */ +export const RewardProvider = ({ children }: PropsWithChildren) => { + const { storeState } = useStore(); + const electronApi = useElectronApi(); - const availableRewardsForEpochEth = useMemo(() => { - if (!availableRewardsForEpoch) return; + const { data: stakingRewardsDetails, refetch: refetchStakingRewardsDetails } = + useStakingRewardsDetails(); + const { + data: availableRewardsForEpoch, + refetch: refetchAvailableRewardsForEpoch, + } = useAvailableRewardsForEpoch(); - const formatRewardsEth = parseFloat( - ethers.utils.formatUnits(`${availableRewardsForEpoch}`, 18), - ); + const isEligibleForRewards = stakingRewardsDetails?.isEligibleForRewards; + const accruedServiceStakingRewards = + stakingRewardsDetails?.accruedServiceStakingRewards; - return formatRewardsEth; + // available rewards for the current epoch in ETH + const availableRewardsForEpochEth = useMemo(() => { + if (!availableRewardsForEpoch) return; + return parseFloat(formatUnits(`${availableRewardsForEpoch}`)); }, [availableRewardsForEpoch]); + // optimistic rewards earned for the current epoch in ETH const optimisticRewardsEarnedForEpoch = useMemo(() => { - if (isEligibleForRewards && availableRewardsForEpochEth) { - return availableRewardsForEpochEth; - } - return; + if (!isEligibleForRewards) return; + if (!availableRewardsForEpochEth) return; + return availableRewardsForEpochEth; }, [availableRewardsForEpochEth, isEligibleForRewards]); - const updateRewards = useCallback(async (): Promise => { - let stakingRewardsInfoPromise; - - // only check for rewards if there's a currentStakingProgram active - if ( - activeStakingProgramId && - service?.chain_configs[CHAIN_CONFIG.OPTIMISM.chainId].chain_data?.multisig && - service?.chain_configs[CHAIN_CONFIG.OPTIMISM.chainId].chain_data?.token - ) { - stakingRewardsInfoPromise = AutonolasService.getAgentStakingRewardsInfo({ - agentMultisigAddress: - service.chain_configs[CHAIN_CONFIG.OPTIMISM.chainId].chain_data.multisig!, - serviceId: - service.chain_configs[CHAIN_CONFIG.OPTIMISM.chainId].chain_data.token!, - stakingProgram: activeStakingProgramId, - }); - } - - // can fallback to default staking program if no current staking program is active - const epochRewardsPromise = AutonolasService.getAvailableRewardsForEpoch( - activeStakingProgramId ?? defaultStakingProgramId, - ); - - const [stakingRewardsInfo, rewards] = await Promise.all([ - stakingRewardsInfoPromise, - epochRewardsPromise, - ]); - - setIsEligibleForRewards(stakingRewardsInfo?.isEligibleForRewards); - setAccruedServiceStakingRewards( - stakingRewardsInfo?.accruedServiceStakingRewards, - ); - setAvailableRewardsForEpoch(rewards); - }, [activeStakingProgramId, defaultStakingProgramId, service]); - + // store the first staking reward achieved in the store for notification useEffect(() => { - if (isEligibleForRewards && !storeState?.firstStakingRewardAchieved) { - electronApi.store?.set?.('firstStakingRewardAchieved', true); - } + if (!isEligibleForRewards) return; + if (storeState?.firstStakingRewardAchieved) return; + electronApi.store?.set?.('firstStakingRewardAchieved', true); }, [ electronApi.store, isEligibleForRewards, storeState?.firstStakingRewardAchieved, ]); - useInterval( - async () => updateRewards(), - isOnline ? FIVE_SECONDS_INTERVAL : null, - ); + // refresh rewards data + const updateRewards = useCallback(async () => { + await refetchStakingRewardsDetails(); + await refetchAvailableRewardsForEpoch(); + }, [refetchStakingRewardsDetails, refetchAvailableRewardsForEpoch]); return ( ; export type StakingContractInfo = { availableRewards: number;