From dd2f9e6995c14e640ae356d94f74fae4765f5df4 Mon Sep 17 00:00:00 2001 From: Mohan Date: Mon, 2 Dec 2024 17:22:53 +0530 Subject: [PATCH 01/79] feat: add feature flags for agents (#523) * feat: add feature flag hook for balance breakdown functionality * feat: conditionally render balance breakdown link based on feature flag * feat: support array of feature flags in useFeatureFlag hook * feat: implement conditional rendering for balance breakdown based on feature flag * refactor: simplify feature flag usage for balance breakdown * refactor: enhance feature flag handling with Zod validation and improved type safety * refactor: update error alert type and improve feature flag handling in useFeatureFlag hook * feat: add FeatureNotEnabled component for better error handling in YourWalletPage --- frontend/components/FeatureNotEnabled.tsx | 11 ++++ .../MainPage/sections/OlasBalanceSection.tsx | 20 +++++--- frontend/components/YourWalletPage/index.tsx | 40 ++++++++------- frontend/enums/Agent.ts | 1 + frontend/hooks/useFeatureFlag.ts | 50 +++++++++++++++++++ 5 files changed, 96 insertions(+), 26 deletions(-) create mode 100644 frontend/components/FeatureNotEnabled.tsx create mode 100644 frontend/hooks/useFeatureFlag.ts diff --git a/frontend/components/FeatureNotEnabled.tsx b/frontend/components/FeatureNotEnabled.tsx new file mode 100644 index 000000000..a8d32fe15 --- /dev/null +++ b/frontend/components/FeatureNotEnabled.tsx @@ -0,0 +1,11 @@ +import { Alert } from 'antd'; + +export const FeatureNotEnabled = () => ( + +); diff --git a/frontend/components/MainPage/sections/OlasBalanceSection.tsx b/frontend/components/MainPage/sections/OlasBalanceSection.tsx index cb034bae9..365f694ef 100644 --- a/frontend/components/MainPage/sections/OlasBalanceSection.tsx +++ b/frontend/components/MainPage/sections/OlasBalanceSection.tsx @@ -12,6 +12,7 @@ import { useMasterBalances, useServiceBalances, } from '@/hooks/useBalanceContext'; +import { useFeatureFlag } from '@/hooks/useFeatureFlag'; import { usePageState } from '@/hooks/usePageState'; import { useServices } from '@/hooks/useServices'; import { balanceFormat } from '@/utils/numberFormatters'; @@ -36,6 +37,7 @@ export const MainOlasBalance = ({ selectedService?.service_config_id, ); const { goto } = usePageState(); + const isBalanceBreakdownEnabled = useFeatureFlag('balance-breakdown'); const displayedBalance = useMemo(() => { // olas across master wallets, safes and eoa @@ -94,14 +96,16 @@ export const MainOlasBalance = ({ OLAS - goto(Pages.YourWalletBreakdown)} - > - See breakdown - - + {isBalanceBreakdownEnabled && ( + goto(Pages.YourWalletBreakdown)} + > + See breakdown + + + )} ) : ( diff --git a/frontend/components/YourWalletPage/index.tsx b/frontend/components/YourWalletPage/index.tsx index 7fc36de1e..a23bc613b 100644 --- a/frontend/components/YourWalletPage/index.tsx +++ b/frontend/components/YourWalletPage/index.tsx @@ -22,6 +22,7 @@ import { useBalanceContext, useMasterBalances, } from '@/hooks/useBalanceContext'; +import { useFeatureFlag } from '@/hooks/useFeatureFlag'; import { usePageState } from '@/hooks/usePageState'; import { useServices } from '@/hooks/useServices'; import { useMasterWalletContext } from '@/hooks/useWallet'; @@ -29,6 +30,7 @@ import { type Address } from '@/types/Address'; import { Optional } from '@/types/Util'; import { balanceFormat } from '@/utils/numberFormatters'; +import { FeatureNotEnabled } from '../FeatureNotEnabled'; import { Container, infoBreakdownParentStyle } from './styles'; import { SignerTitle } from './Titles'; import { YourAgentWallet } from './YourAgent'; @@ -41,8 +43,6 @@ const yourWalletTheme: ThemeConfig = { }, }; -const YourWalletTitle = () => ; - const Address = () => { const { masterSafes } = useMasterWalletContext(); @@ -190,15 +190,15 @@ const MasterEoaSignerNativeBalance = () => { }; export const YourWalletPage = () => { - const { goto } = usePageState(); - + const isBalanceBreakdownEnabled = useFeatureFlag('balance-breakdown'); const { services } = useServices(); + const { goto } = usePageState(); return ( } + title={} extra={ + )} + + + + + {agentConfig.displayName} + + + {agentConfig.description} + + ); +}; + +/** + * + * Component to select the agent type + */ +export const SelectYourAgent = () => ( + + + Select your agent + + {entries(AGENT_CONFIG).map(([agentType, agentConfig]) => { + return ( + + ); + })} + +); diff --git a/frontend/config/agents.ts b/frontend/config/agents.ts index b3b446982..017d61a1a 100644 --- a/frontend/config/agents.ts +++ b/frontend/config/agents.ts @@ -21,6 +21,8 @@ export const AGENT_CONFIG: { }, requiresMasterSafesOn: [EvmChainId.Gnosis], serviceApi: PredictTraderService, + displayName: 'Prediction agent', + description: 'Participates in prediction markets.', }, // TODO: check optimus config // [AgentType.Optimus]: { @@ -35,4 +37,11 @@ export const AGENT_CONFIG: { // }, // serviceApi: OptimusService, // }, + // [AgentType.Memeooorr]: { + // name: 'Memeooorr agent', + // // homeChainId: ChainId.Base, + // displayName: 'Memeooorr agent', + // description: + // 'Autonomously post to Twitter, create and trade memecoins, and interact with other agents.', + // }, }; diff --git a/frontend/enums/Pages.ts b/frontend/enums/Pages.ts index 3b856aa8c..e33320e6d 100644 --- a/frontend/enums/Pages.ts +++ b/frontend/enums/Pages.ts @@ -9,4 +9,5 @@ export enum Pages { YourWalletBreakdown, RewardsHistory, AddBackupWalletViaSafe, + SetupYourAgent, } diff --git a/frontend/enums/SetupScreen.ts b/frontend/enums/SetupScreen.ts index 9003425c8..33fcea030 100644 --- a/frontend/enums/SetupScreen.ts +++ b/frontend/enums/SetupScreen.ts @@ -4,6 +4,7 @@ export enum SetupScreen { SetupPassword, SetupSeedPhrase, SetupBackupSigner, + SelectYourAgent, SetupEoaFunding, SetupEoaFundingIncomplete, SetupCreateSafe, diff --git a/frontend/public/agent-memeooorr-icon.png b/frontend/public/agent-memeooorr-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fb2f72fade00860144592221d6c824ba16dece20 GIT binary patch literal 41121 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�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 literal 0 HcmV?d00001 diff --git a/frontend/public/agent-trader-icon.png b/frontend/public/agent-trader-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5d59f472a22ff6c7f42e87929a24dc772378e2aa GIT binary patch literal 14594 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 jqW|kdJjla1YIwT;pq3Fs literal 0 HcmV?d00001 diff --git a/frontend/types/Agent.ts b/frontend/types/Agent.ts index db7079a2b..d13a252db 100644 --- a/frontend/types/Agent.ts +++ b/frontend/types/Agent.ts @@ -11,4 +11,6 @@ export type AgentConfig = { agentSafeFundingRequirements: Record; requiresMasterSafesOn: EvmChainId[]; serviceApi: typeof PredictTraderService; + displayName: string; + description: string; }; From 8686b0920d59b9df94dad7b3f621e06852af05a9 Mon Sep 17 00:00:00 2001 From: Atatakai Date: Mon, 2 Dec 2024 17:08:15 +0400 Subject: [PATCH 03/79] chore: extend configs with memeooor --- frontend/abis/memeActivityChecker.ts | 44 ++++ frontend/config/activityCheckers.ts | 11 +- frontend/config/agents.ts | 22 +- frontend/config/olasContracts.ts | 12 ++ frontend/config/stakingPrograms/base.ts | 38 ++++ frontend/config/stakingPrograms/index.ts | 21 +- frontend/config/tokens.ts | 2 +- frontend/enums/Agent.ts | 2 +- frontend/enums/StakingProgram.ts | 1 + frontend/service/agents/Memeooor.ts | 257 +++++++++++++++++++++++ frontend/service/agents/PredictTrader.ts | 16 +- 11 files changed, 401 insertions(+), 25 deletions(-) create mode 100644 frontend/abis/memeActivityChecker.ts create mode 100644 frontend/config/stakingPrograms/base.ts create mode 100644 frontend/service/agents/Memeooor.ts diff --git a/frontend/abis/memeActivityChecker.ts b/frontend/abis/memeActivityChecker.ts new file mode 100644 index 000000000..859316b57 --- /dev/null +++ b/frontend/abis/memeActivityChecker.ts @@ -0,0 +1,44 @@ +export const MEME_ACTIVITY_CHECKER_ABI = [ + { + inputs: [ + { internalType: 'address', name: '_memeFactory', type: 'address' }, + { internalType: 'uint256', name: '_livenessRatio', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { inputs: [], name: 'ZeroAddress', type: 'error' }, + { inputs: [], name: 'ZeroValue', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'multisig', type: 'address' }], + name: 'getMultisigNonces', + outputs: [{ internalType: 'uint256[]', name: 'nonces', type: 'uint256[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint256[]', name: 'curNonces', type: 'uint256[]' }, + { internalType: 'uint256[]', name: 'lastNonces', type: 'uint256[]' }, + { internalType: 'uint256', name: 'ts', type: 'uint256' }, + ], + name: 'isRatioPass', + outputs: [{ internalType: 'bool', name: 'ratioPass', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'livenessRatio', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'memeFactory', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +]; diff --git a/frontend/config/activityCheckers.ts b/frontend/config/activityCheckers.ts index 5207f61c6..374ffe23b 100644 --- a/frontend/config/activityCheckers.ts +++ b/frontend/config/activityCheckers.ts @@ -1,15 +1,17 @@ import { Contract as MulticallContract } from 'ethers-multicall'; import { MECH_ACTIVITY_CHECKER_ABI } from '@/abis/mechActivityChecker'; +import { MEME_ACTIVITY_CHECKER_ABI } from '@/abis/memeActivityChecker'; import { REQUESTER_ACTIVITY_CHECKER_ABI } from '@/abis/requesterActivityChecker'; import { EvmChainId } from '@/enums/Chain'; import { MechType } from './mechs'; -enum ActivityCheckerType { +export enum ActivityCheckerType { MechActivityChecker = MechType.Agent, RequesterActivityChecker = MechType.Marketplace, Staking = 'StakingActivityChecker', + MemeActivityChecker = 'MemeActivityChecker', } type ActivityCheckers = { @@ -29,6 +31,12 @@ export const GNOSIS_ACTIVITY_CHECKERS: ActivityCheckers = { export const OPTIMISM_ACTIVITY_CHECKERS: ActivityCheckers = {}; +export const BASE_ACTIVITY_CHECKERS: ActivityCheckers = { + [ActivityCheckerType.MemeActivityChecker]: new MulticallContract( + '0xAe2f766506F6BDF740Cc348a90139EF317Fa7Faf', + MEME_ACTIVITY_CHECKER_ABI, + ), +}; export const ACTIVITY_CHECKERS: { [chainId: number]: { [activityCheckerType: string]: MulticallContract; @@ -36,4 +44,5 @@ export const ACTIVITY_CHECKERS: { } = { [EvmChainId.Gnosis]: GNOSIS_ACTIVITY_CHECKERS, [EvmChainId.Optimism]: OPTIMISM_ACTIVITY_CHECKERS, + [EvmChainId.Base]: BASE_ACTIVITY_CHECKERS, } as const; diff --git a/frontend/config/agents.ts b/frontend/config/agents.ts index 017d61a1a..b0e9ec06c 100644 --- a/frontend/config/agents.ts +++ b/frontend/config/agents.ts @@ -1,6 +1,7 @@ import { MiddlewareChain } from '@/client'; import { AgentType } from '@/enums/Agent'; import { EvmChainId } from '@/enums/Chain'; +import { MemeooorBaseService } from '@/service/agents/Memeooor'; import { PredictTraderService } from '@/service/agents/PredictTrader'; // import { OptimusService } from '@/service/agents/Optimus'; import { AgentConfig } from '@/types/Agent'; @@ -37,11 +38,18 @@ export const AGENT_CONFIG: { // }, // serviceApi: OptimusService, // }, - // [AgentType.Memeooorr]: { - // name: 'Memeooorr agent', - // // homeChainId: ChainId.Base, - // displayName: 'Memeooorr agent', - // description: - // 'Autonomously post to Twitter, create and trade memecoins, and interact with other agents.', - // }, + [AgentType.Memeooorr]: { + name: 'Memeooorr agent', + evmHomeChainId: EvmChainId.Base, + middlewareHomeChainId: MiddlewareChain.BASE, + requiresAgentSafesOn: [EvmChainId.Base], + agentSafeFundingRequirements: { + [EvmChainId.Base]: 1000000000000000, // 0.001 eth + }, + requiresMasterSafesOn: [EvmChainId.Base], + serviceApi: MemeooorBaseService, + displayName: 'Memeooorr agent', + description: + 'Autonomously post to Twitter, create and trade memecoins, and interact with other agents.', + }, }; diff --git a/frontend/config/olasContracts.ts b/frontend/config/olasContracts.ts index 7586d202c..a3179cc9d 100644 --- a/frontend/config/olasContracts.ts +++ b/frontend/config/olasContracts.ts @@ -40,9 +40,21 @@ export const GNOSIS_OLAS_CONTRACTS: ContractsByType = { ), }; +export const BASE_OLAS_CONTRACTS: ContractsByType = { + [ContractType.ServiceRegistryL2]: new MulticallContract( + '0x3C1fF68f5aa342D296d4DEe4Bb1cACCA912D95fE', + SERVICE_REGISTRY_L2_ABI, + ), + [ContractType.ServiceRegistryTokenUtility]: new MulticallContract( + '0x34C895f302D0b5cf52ec0Edd3945321EB0f83dd5', + SERVICE_REGISTRY_TOKEN_UTILITY_ABI, + ), +}; + export const OLAS_CONTRACTS: { [chainId: number]: ContractsByType; } = { [EvmChainId.Gnosis]: GNOSIS_OLAS_CONTRACTS, [EvmChainId.Optimism]: OPTIMISM_OLAS_CONTRACTS, + [EvmChainId.Base]: BASE_OLAS_CONTRACTS, }; diff --git a/frontend/config/stakingPrograms/base.ts b/frontend/config/stakingPrograms/base.ts new file mode 100644 index 000000000..e96277090 --- /dev/null +++ b/frontend/config/stakingPrograms/base.ts @@ -0,0 +1,38 @@ +import { Contract as MulticallContract } from 'ethers-multicall'; + +import { STAKING_TOKEN_PROXY_ABI } from '@/abis/stakingTokenProxy'; +import { AgentType } from '@/enums/Agent'; +import { EvmChainId } from '@/enums/Chain'; +import { StakingProgramId } from '@/enums/StakingProgram'; +import { TokenSymbol } from '@/enums/Token'; +import { Address } from '@/types/Address'; + +import { ACTIVITY_CHECKERS, ActivityCheckerType } from '../activityCheckers'; +import { StakingProgramMap } from '.'; + +export const BASE_STAKING_PROGRAMS_CONTRACT_ADDRESSES: Record = + { + [StakingProgramId.MemeBaseAlpha]: + '0x06702a05312091013fdb50c8b60b98ca30762931', + }; + +export const BASE_STAKING_PROGRAMS: StakingProgramMap = { + [StakingProgramId.MemeBaseAlpha]: { + chainId: EvmChainId.Base, + name: 'MemeBase Alpha', + agentsSupported: [AgentType.Memeooorr], + stakingRequirements: { + [TokenSymbol.OLAS]: 100, + }, + // TODO: find out how the activity is tracked and provide mech if through it + // mech: MECHS[EvmChainId.Base][??].contract, + activityChecker: + ACTIVITY_CHECKERS[EvmChainId.Base][ + ActivityCheckerType.MemeActivityChecker + ], + contract: new MulticallContract( + BASE_STAKING_PROGRAMS_CONTRACT_ADDRESSES[StakingProgramId.MemeBaseAlpha], + STAKING_TOKEN_PROXY_ABI, + ), + }, +}; diff --git a/frontend/config/stakingPrograms/index.ts b/frontend/config/stakingPrograms/index.ts index c19b6f682..9af54d9db 100644 --- a/frontend/config/stakingPrograms/index.ts +++ b/frontend/config/stakingPrograms/index.ts @@ -6,14 +6,18 @@ import { StakingProgramId } from '@/enums/StakingProgram'; import { Address } from '@/types/Address'; import { MechType } from '../mechs'; -import { - GNOSIS_STAKING_PROGRAMS, - GNOSIS_STAKING_PROGRAMS_CONTRACT_ADDRESSES, -} from './gnosis'; // import { // OPTIMISM_STAKING_PROGRAMS, // OPTIMISM_STAKING_PROGRAMS_CONTRACT_ADDRESSES, // } from './optimism'; +import { + BASE_STAKING_PROGRAMS, + BASE_STAKING_PROGRAMS_CONTRACT_ADDRESSES, +} from './base'; +import { + GNOSIS_STAKING_PROGRAMS, + GNOSIS_STAKING_PROGRAMS_CONTRACT_ADDRESSES, +} from './gnosis'; /** * Single non-chain specific staking program configuration @@ -40,19 +44,22 @@ export const STAKING_PROGRAMS: { [chainId: number | EvmChainId]: StakingProgramMap; } = { [EvmChainId.Gnosis]: GNOSIS_STAKING_PROGRAMS, - // [ChainId.Optimism]: OPTIMISM_STAKING_PROGRAMS, + // [EvmChainId.Optimism]: OPTIMISM_STAKING_PROGRAMS, + [EvmChainId.Base]: BASE_STAKING_PROGRAMS, }; export const STAKING_PROGRAM_ADDRESS: { [chainId: number | EvmChainId]: Record; } = { [EvmChainId.Gnosis]: GNOSIS_STAKING_PROGRAMS_CONTRACT_ADDRESSES, - // [ChainId.Optimism]: OPTIMISM_STAKING_PROGRAMS_CONTRACT_ADDRESSES, + // [EvmChainId.Optimism]: OPTIMISM_STAKING_PROGRAMS_CONTRACT_ADDRESSES, + [EvmChainId.Base]: BASE_STAKING_PROGRAMS_CONTRACT_ADDRESSES, }; export const DEFAULT_STAKING_PROGRAM_IDS: { [chainId: number | EvmChainId]: StakingProgramId; } = { [EvmChainId.Gnosis]: StakingProgramId.PearlBeta, - // [ChainId.Optimism]: StakingProgramId.OptimusAlpha, + // [EvmChainId.Optimism]: StakingProgramId.OptimusAlpha, + [EvmChainId.Base]: StakingProgramId.MemeBaseAlpha, }; diff --git a/frontend/config/tokens.ts b/frontend/config/tokens.ts index 7cabcf7d8..b9909f9ad 100644 --- a/frontend/config/tokens.ts +++ b/frontend/config/tokens.ts @@ -91,7 +91,7 @@ export const BASE_TOKEN_CONFIG: ChainTokenConfig = { symbol: TokenSymbol.ETH, }, [TokenSymbol.OLAS]: { - address: '0x4B1a99467a284CC690e3237bc69105956816F762', + address: '0x54330d28ca3357F294334BDC454a032e7f353416', decimals: 18, tokenType: TokenType.Erc20, symbol: TokenSymbol.OLAS, diff --git a/frontend/enums/Agent.ts b/frontend/enums/Agent.ts index e534321ac..fb85c56b4 100644 --- a/frontend/enums/Agent.ts +++ b/frontend/enums/Agent.ts @@ -1,7 +1,7 @@ export const AgentType = { PredictTrader: 'trader', // Optimus: 'optimus', - // Memeooorr: 'memeooorr', + Memeooorr: 'memeooorr', } as const; export type AgentType = (typeof AgentType)[keyof typeof AgentType]; diff --git a/frontend/enums/StakingProgram.ts b/frontend/enums/StakingProgram.ts index 07c36876c..6fc75c1d7 100644 --- a/frontend/enums/StakingProgram.ts +++ b/frontend/enums/StakingProgram.ts @@ -7,4 +7,5 @@ export enum StakingProgramId { PearlBeta5 = 'pearl_beta_5', PearlBetaMechMarketplace = 'pearl_beta_mech_marketplace', OptimusAlpha = 'optimus_alpha', + MemeBaseAlpha = 'meme_base_alpha', } diff --git a/frontend/service/agents/Memeooor.ts b/frontend/service/agents/Memeooor.ts new file mode 100644 index 000000000..71528c521 --- /dev/null +++ b/frontend/service/agents/Memeooor.ts @@ -0,0 +1,257 @@ +import { ethers } from 'ethers'; +import { formatEther } from 'ethers/lib/utils'; + +import { STAKING_PROGRAMS } from '@/config/stakingPrograms'; +import { PROVIDERS } from '@/constants/providers'; +import { EvmChainId } from '@/enums/Chain'; +import { StakingProgramId } from '@/enums/StakingProgram'; +import { Address } from '@/types/Address'; +import { + ServiceStakingDetails, + StakingContractDetails, + StakingRewardsInfo, +} from '@/types/Autonolas'; + +import { ONE_YEAR, StakedAgentService } from './StakedAgentService'; + +export abstract class MemeooorBaseService extends StakedAgentService { + static getAgentStakingRewardsInfo = async ({ + agentMultisigAddress, + serviceId, + stakingProgramId, + chainId = EvmChainId.Base, + }: { + agentMultisigAddress: Address; + serviceId: number; + stakingProgramId: StakingProgramId; + chainId?: EvmChainId; + }): Promise => { + if (!agentMultisigAddress) return; + if (!serviceId) return; + + const stakingProgramConfig = STAKING_PROGRAMS[chainId][stakingProgramId]; + + if (!stakingProgramConfig) throw new Error('Staking program not found'); + + const { activityChecker, contract: stakingTokenProxyContract } = + stakingProgramConfig; + + const provider = PROVIDERS[chainId].multicallProvider; + + // TODO: discuss how to calculate the activity + // const mechContract = + // MECHS[chainId][stakingProgramConfig.mechType!].contract; + + const contractCalls = [ + // mechContract.getRequestsCount(agentMultisigAddress), + stakingTokenProxyContract.getServiceInfo(serviceId), + stakingTokenProxyContract.livenessPeriod(), + activityChecker.livenessRatio(), + stakingTokenProxyContract.rewardsPerSecond(), + stakingTokenProxyContract.calculateStakingReward(serviceId), + stakingTokenProxyContract.minStakingDeposit(), + stakingTokenProxyContract.tsCheckpoint(), + ]; + const multicallResponse = await provider.all(contractCalls); + + const [ + // mechRequestCount, + serviceInfo, + livenessPeriod, + livenessRatio, + rewardsPerSecond, + accruedStakingReward, + minStakingDeposit, + tsCheckpoint, + ] = multicallResponse; + + /** + * struct ServiceInfo { + // Service multisig address + address multisig; + // Service owner + address owner; + // Service multisig nonces + uint256[] nonces; <-- (we use this in the rewards eligibility check) + // Staking start time + uint256 tsStart; + // Accumulated service staking reward + uint256 reward; + // Accumulated inactivity that might lead to the service eviction + uint256 inactivity;} + */ + + const nowInSeconds = Math.floor(Date.now() / 1000); + + // const requiredMechRequests = + // (Math.ceil(Math.max(livenessPeriod, nowInSeconds - tsCheckpoint)) * + // livenessRatio) / + // 1e18 + + // MECH_REQUESTS_SAFETY_MARGIN; + + // const mechRequestCountOnLastCheckpoint = serviceInfo[2][1]; + // const eligibleRequests = + // mechRequestCount - mechRequestCountOnLastCheckpoint; + + // const isEligibleForRewards = eligibleRequests >= requiredMechRequests; + + const availableRewardsForEpoch = Math.max( + rewardsPerSecond * livenessPeriod, // expected rewards for the epoch + rewardsPerSecond * (nowInSeconds - tsCheckpoint), // incase of late checkpoint + ); + + // Minimum staked amount is double the minimum staking deposit + // (all the bonds must be the same as deposit) + const minimumStakedAmount = + parseFloat(ethers.utils.formatEther(`${minStakingDeposit}`)) * 2; + + return { + serviceInfo, + livenessPeriod, + livenessRatio, + rewardsPerSecond, + isEligibleForRewards: false, + availableRewardsForEpoch, + accruedServiceStakingRewards: parseFloat( + ethers.utils.formatEther(`${accruedStakingReward}`), + ), + minimumStakedAmount, + } as StakingRewardsInfo; + }; + + static getAvailableRewardsForEpoch = async ( + stakingProgramId: StakingProgramId, + chainId: EvmChainId = EvmChainId.Base, + ): Promise => { + const { contract: stakingTokenProxy } = + STAKING_PROGRAMS[chainId][stakingProgramId]; + const { multicallProvider } = PROVIDERS[chainId]; + + const contractCalls = [ + stakingTokenProxy.rewardsPerSecond(), + stakingTokenProxy.livenessPeriod(), // epoch length + stakingTokenProxy.tsCheckpoint(), // last checkpoint timestamp + ]; + + const multicallResponse = await multicallProvider.all(contractCalls); + const [rewardsPerSecond, livenessPeriod, tsCheckpoint] = multicallResponse; + const nowInSeconds = Math.floor(Date.now() / 1000); + + return Math.max( + rewardsPerSecond * livenessPeriod, // expected rewards + rewardsPerSecond * (nowInSeconds - tsCheckpoint), // incase of late checkpoint + ); + }; + + /** + * Get service details by it's NftTokenId on a provided staking contract + */ + + static getServiceStakingDetails = async ( + serviceNftTokenId: number, + stakingProgramId: StakingProgramId, + chainId: EvmChainId = EvmChainId.Base, + ): Promise => { + const { multicallProvider } = PROVIDERS[chainId]; + + const { contract: stakingTokenProxy } = + STAKING_PROGRAMS[chainId][stakingProgramId]; + + const contractCalls = [ + stakingTokenProxy.getServiceInfo(serviceNftTokenId), + stakingTokenProxy.getStakingState(serviceNftTokenId), + ]; + + const multicallResponse = await multicallProvider.all(contractCalls); + const [serviceInfo, serviceStakingState] = multicallResponse; + + return { + serviceStakingStartTime: serviceInfo.tsStart.toNumber(), + serviceStakingState, + }; + }; + + /** + * Get staking contract info by staking program name + * eg. Alpha, Beta, Beta2 + */ + static getStakingContractDetails = async ( + stakingProgramId: StakingProgramId, + chainId: EvmChainId, + ): Promise => { + const { multicallProvider } = PROVIDERS[chainId]; + + const { contract: stakingTokenProxy } = + STAKING_PROGRAMS[chainId][stakingProgramId]; + + const contractCalls = [ + stakingTokenProxy.availableRewards(), + stakingTokenProxy.maxNumServices(), + stakingTokenProxy.getServiceIds(), + stakingTokenProxy.minStakingDuration(), + stakingTokenProxy.minStakingDeposit(), + stakingTokenProxy.rewardsPerSecond(), + stakingTokenProxy.numAgentInstances(), + stakingTokenProxy.livenessPeriod(), + stakingTokenProxy.epochCounter(), + ]; + + const multicallResponse = await multicallProvider.all(contractCalls); + + const [ + availableRewardsInBN, + maxNumServicesInBN, + getServiceIdsInBN, + minStakingDurationInBN, + minStakingDeposit, + rewardsPerSecond, + numAgentInstances, + livenessPeriod, + epochCounter, + ] = multicallResponse; + + const availableRewards = parseFloat( + ethers.utils.formatUnits(availableRewardsInBN, 18), + ); + + const serviceIds = getServiceIdsInBN.map((id: bigint) => id); + const maxNumServices = maxNumServicesInBN.toNumber(); + + // APY + const rewardsPerYear = rewardsPerSecond.mul(ONE_YEAR); + + let apy = 0; + + if (rewardsPerSecond.gt(0) && minStakingDeposit.gt(0)) { + apy = + Number(rewardsPerYear.mul(100).div(minStakingDeposit)) / + (1 + numAgentInstances.toNumber()); + } + + // Amount of OLAS required for Stake + const stakeRequiredInWei = minStakingDeposit.add( + minStakingDeposit.mul(numAgentInstances), + ); + + const olasStakeRequired = Number(formatEther(stakeRequiredInWei)); + + // Rewards per work period + const rewardsPerWorkPeriod = + Number(formatEther(rewardsPerSecond as bigint)) * + livenessPeriod.toNumber(); + + return { + availableRewards, + maxNumServices, + serviceIds, + minimumStakingDuration: minStakingDurationInBN.toNumber(), + minStakingDeposit: parseFloat( + ethers.utils.formatEther(minStakingDeposit), + ), + apy, + olasStakeRequired, + rewardsPerWorkPeriod, + epochCounter: epochCounter.toNumber(), + }; + }; +} diff --git a/frontend/service/agents/PredictTrader.ts b/frontend/service/agents/PredictTrader.ts index bb7315244..d4759bac6 100644 --- a/frontend/service/agents/PredictTrader.ts +++ b/frontend/service/agents/PredictTrader.ts @@ -1,7 +1,6 @@ import { ethers } from 'ethers'; import { formatEther } from 'ethers/lib/utils'; -import { MECHS } from '@/config/mechs'; import { STAKING_PROGRAMS } from '@/config/stakingPrograms'; import { PROVIDERS } from '@/constants/providers'; import { EvmChainId } from '@/enums/Chain'; @@ -32,18 +31,19 @@ export abstract class PredictTraderService extends StakedAgentService { if (!agentMultisigAddress) return; if (!serviceId) return; - const stakingProgramConfig = - STAKING_PROGRAMS[EvmChainId.Gnosis][stakingProgramId]; + const stakingProgramConfig = STAKING_PROGRAMS[chainId][stakingProgramId]; if (!stakingProgramConfig) throw new Error('Staking program not found'); - const { activityChecker, contract: stakingTokenProxyContract } = - stakingProgramConfig; + const { + activityChecker, + contract: stakingTokenProxyContract, + mech: mechContract, + } = stakingProgramConfig; - const provider = PROVIDERS[chainId].multicallProvider; + if (!mechContract) throw new Error('Mech contract is not defined'); - const mechContract = - MECHS[chainId][stakingProgramConfig.mechType!].contract; + const provider = PROVIDERS[chainId].multicallProvider; const contractCalls = [ mechContract.getRequestsCount(agentMultisigAddress), From 1d733333ace23c72d4348ebd00942f05f086019e Mon Sep 17 00:00:00 2001 From: Mohan Date: Mon, 2 Dec 2024 21:52:56 +0530 Subject: [PATCH 04/79] feat: agent switch header (#528) * feat: add Select Your Agent screen to setup flow * feat: remove unused FirstRunModal component from MainHeader * feat: restructure Main component layout with CardSection * feat: enhance MainHeader with help and settings buttons in CardSection * feat: add SwitchAgentSection to MainPage for agent switching functionality * feat: rename SelectYourAgent component to AgentSelection and update related references * feat: enhance SwitchAgentSection with loading and service state checks for enabling switch button * feat: refactor AgentSelection component and improve SwitchAgentSection button logic * feat: make onSelect and onNext props required in AgentSelection component --- ...SelectYourAgent.tsx => AgentSelection.tsx} | 33 ++++++---- .../MainPage/MainHeader/FirstRunModal.tsx | 56 ---------------- frontend/components/MainPage/header/index.tsx | 35 ++++++++-- frontend/components/MainPage/index.tsx | 33 ++-------- .../MainPage/sections/SwitchAgentSection.tsx | 66 +++++++++++++++++++ .../SetupPage/Create/SetupCreateHeader.tsx | 22 ++++--- frontend/components/SetupPage/index.tsx | 15 ++++- frontend/enums/Pages.ts | 2 +- frontend/enums/SetupScreen.ts | 2 +- frontend/pages/index.tsx | 12 +++- 10 files changed, 159 insertions(+), 117 deletions(-) rename frontend/components/{SetupPage/SelectYourAgent.tsx => AgentSelection.tsx} (75%) delete mode 100644 frontend/components/MainPage/MainHeader/FirstRunModal.tsx create mode 100644 frontend/components/MainPage/sections/SwitchAgentSection.tsx diff --git a/frontend/components/SetupPage/SelectYourAgent.tsx b/frontend/components/AgentSelection.tsx similarity index 75% rename from frontend/components/SetupPage/SelectYourAgent.tsx rename to frontend/components/AgentSelection.tsx index d41b8fc60..e7556471c 100644 --- a/frontend/components/SetupPage/SelectYourAgent.tsx +++ b/frontend/components/AgentSelection.tsx @@ -6,29 +6,29 @@ import { useCallback } from 'react'; import { AGENT_CONFIG } from '@/config/agents'; import { COLOR } from '@/constants/colors'; import { AgentType } from '@/enums/Agent'; -import { Pages } from '@/enums/Pages'; -import { SetupScreen } from '@/enums/SetupScreen'; -import { usePageState } from '@/hooks/usePageState'; import { useServices } from '@/hooks/useServices'; import { AgentConfig } from '@/types/Agent'; -import { CardFlex } from '../styled/CardFlex'; -import { SetupCreateHeader } from './Create/SetupCreateHeader'; +import { SetupCreateHeader } from './SetupPage/Create/SetupCreateHeader'; +import { CardFlex } from './styled/CardFlex'; const { Title, Text } = Typography; -type EachAgentProps = { agentType: AgentType; agentConfig: AgentConfig }; +type EachAgentProps = { + agentType: AgentType; + agentConfig: AgentConfig; + onSelect: () => void; +}; -const EachAgent = ({ agentType, agentConfig }: EachAgentProps) => { +const EachAgent = ({ agentType, agentConfig, onSelect }: EachAgentProps) => { const { selectedAgentType, updateAgentType } = useServices(); - const { goto } = usePageState(); const isCurrentAgent = selectedAgentType === agentType; const handleSelectAgent = useCallback(() => { updateAgentType(agentType); - goto(Pages.SetupYourAgent); // TODO: page to be added - }, [agentType, updateAgentType, goto]); + onSelect(); + }, [agentType, updateAgentType, onSelect]); return ( { ); }; +type AgentSelectionProps = { + onPrev: () => void; + onNext: () => void; +}; + /** - * - * Component to select the agent type + * Component to select the agent type. */ -export const SelectYourAgent = () => ( +export const AgentSelection = ({ onPrev, onNext }: AgentSelectionProps) => ( - + Select your agent {entries(AGENT_CONFIG).map(([agentType, agentConfig]) => { @@ -86,6 +90,7 @@ export const SelectYourAgent = () => ( key={agentType} agentType={agentType as AgentType} agentConfig={agentConfig} + onSelect={onNext} /> ); })} diff --git a/frontend/components/MainPage/MainHeader/FirstRunModal.tsx b/frontend/components/MainPage/MainHeader/FirstRunModal.tsx deleted file mode 100644 index f52543f53..000000000 --- a/frontend/components/MainPage/MainHeader/FirstRunModal.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Button, Flex, Modal, Typography } from 'antd'; -import Image from 'next/image'; -import { FC } from 'react'; - -import { MODAL_WIDTH } from '@/constants/width'; -import { TokenSymbol } from '@/enums/Token'; -import { useStakingProgram } from '@/hooks/useStakingProgram'; - -type FirstRunModalProps = { open: boolean; onClose: () => void }; - -export const FirstRunModal: FC = ({ open, onClose }) => { - const { activeStakingProgramMeta } = useStakingProgram(); - - const minimumStakedAmountRequired = - activeStakingProgramMeta?.stakingRequirements?.[TokenSymbol.OLAS]; - - return ( - - Got it - , - ]} - > - - OLAS logo - - - {`Your agent is running and you've staked ${minimumStakedAmountRequired} OLAS!`} - - - Your agent is working towards earning rewards. - - - Pearl is designed to make it easy for you to earn staking rewards every - day. Simply leave the app and agent running in the background for ~1hr a - day. - - - ); -}; diff --git a/frontend/components/MainPage/header/index.tsx b/frontend/components/MainPage/header/index.tsx index 9ce1e696d..4298a0e0a 100644 --- a/frontend/components/MainPage/header/index.tsx +++ b/frontend/components/MainPage/header/index.tsx @@ -1,9 +1,13 @@ -import { Flex } from 'antd'; +import { QuestionCircleOutlined, SettingOutlined } from '@ant-design/icons'; +import { Button, Flex } from 'antd'; import { useCallback, useEffect, useState } from 'react'; import { MiddlewareDeploymentStatus } from '@/client'; +import { CardSection } from '@/components/styled/CardSection'; +import { Pages } from '@/enums/Pages'; import { useBalanceContext } from '@/hooks/useBalanceContext'; import { useElectronApi } from '@/hooks/useElectronApi'; +import { usePageState } from '@/hooks/usePageState'; import { useService } from '@/hooks/useService'; import { useServices } from '@/hooks/useServices'; @@ -33,6 +37,8 @@ const useSetupTrayIcon = () => { }; export const MainHeader = () => { + const { goto } = usePageState(); + const [isFirstRunModalOpen, setIsFirstRunModalOpen] = useState(false); const handleModalClose = useCallback(() => setIsFirstRunModalOpen(false), []); @@ -40,10 +46,27 @@ export const MainHeader = () => { // TODO: support loading state return ( - - - - - + + + + + + + + + + + ); +}; diff --git a/frontend/components/SetupPage/Create/SetupCreateHeader.tsx b/frontend/components/SetupPage/Create/SetupCreateHeader.tsx index d4f7bc670..d331bd9a4 100644 --- a/frontend/components/SetupPage/Create/SetupCreateHeader.tsx +++ b/frontend/components/SetupPage/Create/SetupCreateHeader.tsx @@ -1,20 +1,26 @@ import { ArrowLeftOutlined } from '@ant-design/icons'; import { Button, Col, Flex, Row } from 'antd'; +import { isFunction } from 'lodash'; import Image from 'next/image'; -import { memo } from 'react'; +import { useCallback } from 'react'; import { SetupScreen } from '@/enums/SetupScreen'; import { useSetup } from '@/hooks/useSetup'; -export const SetupCreateHeader = memo(function SetupCreateHeader({ +type SetupCreateHeaderProps = { + prev: SetupScreen | (() => void); + disabled?: boolean; +}; + +export const SetupCreateHeader = ({ prev, disabled = false, -}: { - prev: SetupScreen; - disabled?: boolean; -}) { +}: SetupCreateHeaderProps) => { const { goto } = useSetup(); - const handleBack = () => goto(prev); + const handleBack = useCallback(() => { + isFunction(prev) ? prev() : goto(prev); + }, [goto, prev]); + return ( @@ -39,4 +45,4 @@ export const SetupCreateHeader = memo(function SetupCreateHeader({ ); -}); +}; diff --git a/frontend/components/SetupPage/index.tsx b/frontend/components/SetupPage/index.tsx index 935ac2810..fb6da1e86 100644 --- a/frontend/components/SetupPage/index.tsx +++ b/frontend/components/SetupPage/index.tsx @@ -2,7 +2,9 @@ import { useContext, useMemo } from 'react'; import { SetupContext } from '@/context/SetupProvider'; import { SetupScreen } from '@/enums/SetupScreen'; +import { useSetup } from '@/hooks/useSetup'; +import { AgentSelection } from '../AgentSelection'; import { SetupBackupSigner } from './Create/SetupBackupSigner'; import { SetupCreateSafe } from './Create/SetupCreateSafe'; import { SetupEoaFunding } from './Create/SetupEoaFunding'; @@ -22,10 +24,13 @@ const UnexpectedError = () => ( export const Setup = () => { const { setupObject } = useContext(SetupContext); + const { goto } = useSetup(); + const setupScreen = useMemo(() => { switch (setupObject.state) { case SetupScreen.Welcome: return ; + // Create account case SetupScreen.SetupPassword: return ; @@ -39,6 +44,14 @@ export const Setup = () => { return ; case SetupScreen.SetupCreateSafe: return ; + case SetupScreen.AgentSelection: + return ( + goto(SetupScreen.SetupBackupSigner)} + onNext={() => goto(SetupScreen.SetupEoaFunding)} + /> + ); + // Restore account case SetupScreen.Restore: return ; @@ -51,7 +64,7 @@ export const Setup = () => { default: return ; } - }, [setupObject.state]); + }, [setupObject.state, goto]); return setupScreen; }; diff --git a/frontend/enums/Pages.ts b/frontend/enums/Pages.ts index e33320e6d..986b01413 100644 --- a/frontend/enums/Pages.ts +++ b/frontend/enums/Pages.ts @@ -9,5 +9,5 @@ export enum Pages { YourWalletBreakdown, RewardsHistory, AddBackupWalletViaSafe, - SetupYourAgent, + SwitchAgent, } diff --git a/frontend/enums/SetupScreen.ts b/frontend/enums/SetupScreen.ts index 33fcea030..c6c950386 100644 --- a/frontend/enums/SetupScreen.ts +++ b/frontend/enums/SetupScreen.ts @@ -4,7 +4,7 @@ export enum SetupScreen { SetupPassword, SetupSeedPhrase, SetupBackupSigner, - SelectYourAgent, + AgentSelection, SetupEoaFunding, SetupEoaFundingIncomplete, SetupCreateSafe, diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index a2096d367..813366acf 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -1,5 +1,6 @@ import { useEffect, useMemo } from 'react'; +import { AgentSelection } from '@/components/AgentSelection'; import { Main } from '@/components/MainPage'; import { ManageStakingPage } from '@/components/ManageStakingPage'; import { AddBackupWalletViaSafePage } from '@/components/Pages/AddBackupWalletViaSafePage'; @@ -15,7 +16,7 @@ import { usePageState } from '@/hooks/usePageState'; const DEFAULT_APP_HEIGHT = 700; export default function Home() { - const { pageState } = usePageState(); + const { pageState, goto } = usePageState(); const electronApi = useElectronApi(); useEffect(() => { @@ -46,6 +47,13 @@ export default function Home() { return ; case Pages.Main: return
; + case Pages.SwitchAgent: + return ( + goto(Pages.Main)} + onNext={() => goto(Pages.Main)} + /> + ); case Pages.Settings: return ; case Pages.HelpAndSupport: @@ -61,7 +69,7 @@ export default function Home() { default: return
; } - }, [pageState]); + }, [pageState, goto]); return page; } From 3d1cf1745806b2ac3cac0ea84b4f27b55d52d863 Mon Sep 17 00:00:00 2001 From: Mohan Date: Tue, 3 Dec 2024 20:53:59 +0530 Subject: [PATCH 05/79] feat: add SetupYourAgent screen (#529) * feat: add SetupYourAgent screen to setup flow * feat: implement SetupYourAgent form for agent configuration * feat: enhance SetupYourAgent form with X account credentials and validation messages * feat: update SetupYourAgent form to include X account credentials and validation logic * feat: implement validation logic for Gemini API key and Twitter credentials in SetupYourAgent form * Update frontend/components/SetupPage/SetupYourAgent/index.tsx Co-authored-by: Josh Miller <31908788+truemiller@users.noreply.github.com> * feat: validate Gemini API key (#530) * feat: enhance Gemini API key validation with real-time API request and error handling * feat: improve SetupYourAgent form with dynamic submit button text and error handling for API validation --------- Co-authored-by: Josh Miller <31908788+truemiller@users.noreply.github.com> --- .../SetupPage/Create/SetupPassword.tsx | 9 +- .../SetupPage/SetupYourAgent/index.tsx | 240 ++++++++++++++++++ .../SetupPage/SetupYourAgent/validation.ts | 35 +++ frontend/components/SetupPage/index.tsx | 3 + frontend/enums/SetupScreen.ts | 1 + frontend/styles/globals.scss | 4 + 6 files changed, 284 insertions(+), 8 deletions(-) create mode 100644 frontend/components/SetupPage/SetupYourAgent/index.tsx create mode 100644 frontend/components/SetupPage/SetupYourAgent/validation.ts diff --git a/frontend/components/SetupPage/Create/SetupPassword.tsx b/frontend/components/SetupPage/Create/SetupPassword.tsx index 4a9b7d2f1..f43dd68b9 100644 --- a/frontend/components/SetupPage/Create/SetupPassword.tsx +++ b/frontend/components/SetupPage/Create/SetupPassword.tsx @@ -37,14 +37,7 @@ export const SetupPassword = () => { }; return ( - + Create password Come up with a strong password. diff --git a/frontend/components/SetupPage/SetupYourAgent/index.tsx b/frontend/components/SetupPage/SetupYourAgent/index.tsx new file mode 100644 index 000000000..641dbacd4 --- /dev/null +++ b/frontend/components/SetupPage/SetupYourAgent/index.tsx @@ -0,0 +1,240 @@ +import { EyeInvisibleOutlined, EyeTwoTone } from '@ant-design/icons'; +import { + Button, + ConfigProvider, + Divider, + Flex, + Form, + Input, + message, + Typography, +} from 'antd'; +import React, { useMemo, useState } from 'react'; +import { useUnmount } from 'usehooks-ts'; + +import { CustomAlert } from '@/components/Alert'; +import { CardFlex } from '@/components/styled/CardFlex'; +import { SetupScreen } from '@/enums/SetupScreen'; +import { useSetup } from '@/hooks/useSetup'; + +import { SetupCreateHeader } from '../Create/SetupCreateHeader'; +import { + onAgentSetupComplete, + validateGeminiApiKey, + validateTwitterCredentials, +} from './validation'; + +const { Title, Text } = Typography; + +// TODO: consolidate theme into mainTheme +const LOCAL_THEME = { components: { Input: { fontSize: 16 } } }; + +type FieldValues = { + personaDescription: string; + geminiApiKey: string; + xEmail: string; + xUsername: string; + xPassword: string; +}; +type ValidationStatus = 'valid' | 'invalid' | 'unknown'; + +const requiredRules = [{ required: true, message: 'Field is required' }]; +const validateMessages = { + required: 'Field is required', + types: { email: 'Enter a valid email' }, +}; + +const XAccountCredentials = () => ( + + + + X account credentials + + + Create a new account for your agent at{' '} + + x.com + {' '} + and enter the login details. This enables your agent to view X and + interact with other agents. + + +); + +const InvalidGeminiApiCredentials = () => ( + API key is invalid} + className="mb-8" + /> +); + +const InvalidXCredentials = () => ( + X account credentials are invalid or 2FA is enabled.} + className="mb-16" + /> +); + +// Agent setup form +const SetupYourAgentForm = () => { + const { goto } = useSetup(); + + const [form] = Form.useForm(); + const [isSubmitting, setIsSubmitting] = useState(false); + const [submitButtonText, setSubmitButtonText] = useState('Continue'); + const [geminiApiKeyValidationStatus, setGeminiApiKeyValidationStatus] = + useState('unknown'); + const [ + twitterCredentialsValidationStatus, + setTwitterCredentialsValidationStatus, + ] = useState('unknown'); + + const onFinish = async (values: Record) => { + try { + setIsSubmitting(true); + + // validate the gemini API + setSubmitButtonText('Validating API key...'); + const isGeminiApiValid = await validateGeminiApiKey(values.geminiApiKey); + setGeminiApiKeyValidationStatus(isGeminiApiValid ? 'valid' : 'invalid'); + if (!isGeminiApiValid) return; + + // validate the twitter credentials + setSubmitButtonText('Validating Twitter credentials...'); + const isTwitterCredentialsValid = await validateTwitterCredentials( + values.xEmail, + values.xUsername, + values.xPassword, + ); + setTwitterCredentialsValidationStatus( + isTwitterCredentialsValid ? 'valid' : 'invalid', + ); + if (!isTwitterCredentialsValid) return; + + // wait for agent setup to complete + setSubmitButtonText('Setting up agent...'); + await onAgentSetupComplete(); + + // move to next page + goto(SetupScreen.SetupEoaFunding); + } catch (error) { + message.error('Something went wrong. Please try again.'); + console.error(error); + } finally { + setIsSubmitting(false); + setSubmitButtonText('Continue'); + } + }; + + // Clean up + useUnmount(async () => { + setIsSubmitting(false); + setGeminiApiKeyValidationStatus('unknown'); + setTwitterCredentialsValidationStatus('unknown'); + setSubmitButtonText('Continue'); + }); + + const commonFieldProps = useMemo( + () => ({ rules: requiredRules, hasFeedback: true }), + [], + ); + + return ( + + form={form} + name="setup-your-agent" + layout="vertical" + onFinish={onFinish} + validateMessages={validateMessages} + disabled={isSubmitting} + > + + + + + + + visible ? : + } + /> + + {geminiApiKeyValidationStatus === 'invalid' && ( + + )} + + {/* X */} + + {twitterCredentialsValidationStatus === 'invalid' && ( + + )} + + + + + + + + + + + + visible ? : + } + /> + + + + + + + ); +}; + +export const SetupYourAgent = () => { + return ( + + + + Set up your agent + + Provide your agent with a persona, access to an LLM and an X account. + + + + + + + You won’t be able to update your agent’s configuration after this + step. + + + + ); +}; diff --git a/frontend/components/SetupPage/SetupYourAgent/validation.ts b/frontend/components/SetupPage/SetupYourAgent/validation.ts new file mode 100644 index 000000000..e278c96b5 --- /dev/null +++ b/frontend/components/SetupPage/SetupYourAgent/validation.ts @@ -0,0 +1,35 @@ +import { delayInSeconds } from '@/utils/delay'; + +export const validateGeminiApiKey = async (apiKey: string) => { + if (!apiKey) return false; + + try { + // sample request to fetch the models + const apiUrl = + 'https://generativelanguage.googleapis.com/v1/models?key=' + apiKey; + const response = await fetch(apiUrl); + + return response.ok; + } catch (error) { + console.error('Error validating Gemini API key:', error); + return false; + } +}; + +export const validateTwitterCredentials = async ( + email: string, + username: string, + password: string, +) => { + if (!email || !username || !password) return false; + + // TODO: validate the twitter credentials and remove the delay + await delayInSeconds(2); + + return false; +}; + +export const onAgentSetupComplete = async () => { + // TODO: send to backend and remove the delay + await delayInSeconds(2); +}; diff --git a/frontend/components/SetupPage/index.tsx b/frontend/components/SetupPage/index.tsx index fb6da1e86..6199070fe 100644 --- a/frontend/components/SetupPage/index.tsx +++ b/frontend/components/SetupPage/index.tsx @@ -17,6 +17,7 @@ import { SetupRestoreViaSeed, } from './SetupRestore'; import { SetupWelcome } from './SetupWelcome'; +import { SetupYourAgent } from './SetupYourAgent'; const UnexpectedError = () => (
Something went wrong!
@@ -51,6 +52,8 @@ export const Setup = () => { onNext={() => goto(SetupScreen.SetupEoaFunding)} /> ); + case SetupScreen.SetupYourAgent: + return ; // Restore account case SetupScreen.Restore: diff --git a/frontend/enums/SetupScreen.ts b/frontend/enums/SetupScreen.ts index c6c950386..7f68619bc 100644 --- a/frontend/enums/SetupScreen.ts +++ b/frontend/enums/SetupScreen.ts @@ -5,6 +5,7 @@ export enum SetupScreen { SetupSeedPhrase, SetupBackupSigner, AgentSelection, + SetupYourAgent, SetupEoaFunding, SetupEoaFundingIncomplete, SetupCreateSafe, diff --git a/frontend/styles/globals.scss b/frontend/styles/globals.scss index ee408e2ba..d5c6f5776 100644 --- a/frontend/styles/globals.scss +++ b/frontend/styles/globals.scss @@ -134,6 +134,10 @@ textarea, margin: 0 !important; } +.mt-0 { + margin-top: 0 !important; +} + .mb-4 { margin-bottom: 4px !important; } From 6ac8d4d37a2a2053c26d4864e939681a6a8e81f5 Mon Sep 17 00:00:00 2001 From: Atatakai Date: Tue, 3 Dec 2024 23:22:05 +0400 Subject: [PATCH 06/79] feat: support base during onboarding (#531) * feat: support base during onboarding * feat: update service config template * fix: reset hardcoded agent type --- frontend/client/types.ts | 2 +- frontend/components/AgentSelection.tsx | 18 +- .../SetupPage/Create/SetupBackupSigner.tsx | 2 +- .../SetupPage/Create/SetupCreateSafe.tsx | 170 +++++---------- .../SetupPage/Create/SetupEoaFunding.tsx | 204 ++++++++---------- .../components/SetupPage/SetupWelcome.tsx | 24 ++- frontend/components/SetupPage/index.tsx | 1 + frontend/components/styled/CardSection.tsx | 2 +- frontend/config/chains.ts | 4 +- frontend/constants/providers.ts | 4 + frontend/constants/serviceTemplates.ts | 58 +++++ frontend/hooks/useFeatureFlag.ts | 4 + frontend/pages/_app.tsx | 4 - 13 files changed, 257 insertions(+), 240 deletions(-) diff --git a/frontend/client/types.ts b/frontend/client/types.ts index 208584595..7c8113167 100644 --- a/frontend/client/types.ts +++ b/frontend/client/types.ts @@ -90,7 +90,7 @@ export type ConfigurationTemplate = { agent_id: number; threshold: number; use_staking: boolean; - use_mech_marketplace: boolean; + use_mech_marketplace?: boolean; cost_of_bond: number; monthly_gas_estimate: number; fund_requirements: FundRequirementsTemplate; diff --git a/frontend/components/AgentSelection.tsx b/frontend/components/AgentSelection.tsx index e7556471c..65fb1d473 100644 --- a/frontend/components/AgentSelection.tsx +++ b/frontend/components/AgentSelection.tsx @@ -15,15 +15,21 @@ import { CardFlex } from './styled/CardFlex'; const { Title, Text } = Typography; type EachAgentProps = { + showSelected: boolean; agentType: AgentType; agentConfig: AgentConfig; onSelect: () => void; }; -const EachAgent = ({ agentType, agentConfig, onSelect }: EachAgentProps) => { +const EachAgent = ({ + showSelected, + agentType, + agentConfig, + onSelect, +}: EachAgentProps) => { const { selectedAgentType, updateAgentType } = useServices(); - const isCurrentAgent = selectedAgentType === agentType; + const isCurrentAgent = showSelected ? selectedAgentType === agentType : false; const handleSelectAgent = useCallback(() => { updateAgentType(agentType); @@ -72,6 +78,7 @@ const EachAgent = ({ agentType, agentConfig, onSelect }: EachAgentProps) => { }; type AgentSelectionProps = { + showSelected?: boolean; onPrev: () => void; onNext: () => void; }; @@ -79,7 +86,11 @@ type AgentSelectionProps = { /** * Component to select the agent type. */ -export const AgentSelection = ({ onPrev, onNext }: AgentSelectionProps) => ( +export const AgentSelection = ({ + showSelected = true, + onPrev, + onNext, +}: AgentSelectionProps) => ( Select your agent @@ -88,6 +99,7 @@ export const AgentSelection = ({ onPrev, onNext }: AgentSelectionProps) => ( return ( { } setBackupSigner(checksummedAddress); - goto(SetupScreen.SetupEoaFunding); + goto(SetupScreen.AgentSelection); }; return ( diff --git a/frontend/components/SetupPage/Create/SetupCreateSafe.tsx b/frontend/components/SetupPage/Create/SetupCreateSafe.tsx index 7dd921bc8..c5ddd616b 100644 --- a/frontend/components/SetupPage/Create/SetupCreateSafe.tsx +++ b/frontend/components/SetupPage/Create/SetupCreateSafe.tsx @@ -4,27 +4,21 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { MiddlewareChain } from '@/client'; import { CardSection } from '@/components/styled/CardSection'; +import { SERVICE_TEMPLATES } from '@/constants/serviceTemplates'; import { UNICODE_SYMBOLS } from '@/constants/symbols'; import { SUPPORT_URL } from '@/constants/urls'; +import { EvmChainName } from '@/enums/Chain'; import { Pages } from '@/enums/Pages'; import { usePageState } from '@/hooks/usePageState'; +import { useServices } from '@/hooks/useServices'; import { useSetup } from '@/hooks/useSetup'; import { useMasterWalletContext } from '@/hooks/useWallet'; import { WalletService } from '@/service/Wallet'; import { delayInSeconds } from '@/utils/delay'; +import { asEvmChainId } from '@/utils/middlewareHelpers'; const { Text } = Typography; -const capitalizedMiddlewareChainNames: { [key in MiddlewareChain]: string } = { - [MiddlewareChain.ETHEREUM]: 'Ethereum', - [MiddlewareChain.BASE]: 'Base', - [MiddlewareChain.OPTIMISM]: 'Optimism', - [MiddlewareChain.GOERLI]: 'Goerli', - [MiddlewareChain.GNOSIS]: 'Gnosis', - [MiddlewareChain.SOLANA]: 'Solana', - [MiddlewareChain.MODE]: 'Mode', -}; - const YouWillBeRedirected = ({ text }: { text: string }) => ( <> logo @@ -59,31 +53,33 @@ const CreationError = () => ( export const SetupCreateSafe = () => { const { goto } = usePageState(); - const { masterSafes, refetch: updateWallets } = useMasterWalletContext(); - // const { updateMasterSafeOwners } = useMultisig(); + + const { selectedAgentType } = useServices(); + const serviceTemplate = SERVICE_TEMPLATES.find( + (template) => template.agentType === selectedAgentType, + ); + + const { + masterSafes, + refetch: updateWallets, + isFetched: isWalletsFetched, + } = useMasterWalletContext(); const { backupSigner } = useSetup(); const masterSafeAddress = useMemo(() => { if (!masterSafes) return; - return masterSafes[0]?.address; - }, [masterSafes]); + return masterSafes.find( + (safe) => safe.evmChainId === asEvmChainId(serviceTemplate?.home_chain), + ); + }, [masterSafes, serviceTemplate?.home_chain]); const [isCreatingSafe, setIsCreatingSafe] = useState(false); - const [gnosisFailed, setGnosisFailed] = useState(false); - // const [optimismFailed, setOptimismFailed] = useState(false); - // const [ethereumFailed, setEthereumFailed] = useState(false); - // const [baseFailed, setBaseFailed] = useState(false); - - const [isGnosisSuccess, setIsGnosisSuccess] = useState(false); - // const [isOptimismSuccess, setIsOptimismSuccess] = useState(false); - // const [isEthereumSuccess, setIsEthereumSuccess] = useState(false); - // const [isBaseSuccess, setIsBaseSuccess] = useState(false); + const [isFailed, setIsFailed] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); const createSafeWithRetries = useCallback( async (middlewareChain: MiddlewareChain, retries: number) => { - setIsCreatingSafe(true); - for (let attempt = retries; attempt > 0; attempt--) { try { // Attempt to create the safe @@ -91,37 +87,15 @@ export const SetupCreateSafe = () => { // Update wallets and handle successful creation await updateWallets?.(); - setIsCreatingSafe(false); - setGnosisFailed(false); + setIsFailed(false); + setIsSuccess(true); break; // Exit the loop once successful } catch (e) { console.error(e); if (attempt === 1) { - // Final failure case after all retries - // If we have retried too many times, set failed - // if (middlewareChain === MiddlewareChain.OPTIMISM) { - // setOptimismFailed(true); - // setIsOptimismSuccess(false); - // throw new Error('Failed to create safe on Optimism'); - // } - // if (middlewareChain === MiddlewareChain.ETHEREUM) { - // setEthereumFailed(true); - // setIsEthereumSuccess(false); - // throw new Error('Failed to create safe on Ethereum'); - // } - // if (middlewareChain === MiddlewareChain.BASE) { - // setBaseFailed(true); - // setIsBaseSuccess(false); - // throw new Error('Failed to create safe on Base'); - // } - - if (middlewareChain === MiddlewareChain.GNOSIS) { - setGnosisFailed(true); - setIsGnosisSuccess(false); - throw new Error('Failed to create safe on Gnosis'); - } - - throw new Error('Failed to create safe as chain is not supported'); + setIsFailed(true); + setIsSuccess(false); + throw new Error(`Failed to create safe on ${middlewareChain}`); } else { // Retry delay message.error( @@ -137,75 +111,49 @@ export const SetupCreateSafe = () => { const creationStatusText = useMemo(() => { if (isCreatingSafe) return 'Creating accounts'; - if (masterSafeAddress) return 'Account created'; + if (isSuccess) return 'Account created'; return 'Account creation in progress'; - }, [isCreatingSafe, masterSafeAddress]); + }, [isCreatingSafe, isSuccess]); useEffect(() => { if ( /** * Avoid creating safes if any of the following conditions are met: */ - [ - // optimismFailed, baseFailed, ethereumFailed - gnosisFailed, - ].some((x) => x) || // any of the chains failed - isCreatingSafe //|| // already creating a safe - // [isBaseSuccess, isEthereumSuccess, isOptimismSuccess].some((x) => !x) // any of the chains are not successful + isFailed || // creation failed - it's retried in background + isCreatingSafe || // already creating a safe + !isWalletsFetched // wallets are not loaded yet ) return; - const chainsToCreateSafesFor = { - // [MiddlewareChain.OPTIMISM]: masterSafeAddressKeyExistsForChain( - // MiddlewareChain.OPTIMISM, - // ), - // [MiddlewareChain.ETHEREUM]: masterSafeAddressKeyExistsForChain( - // MiddlewareChain.ETHEREUM, - // ), - // [MiddlewareChain.BASE]: masterSafeAddressKeyExistsForChain( - // MiddlewareChain.BASE, - // ), - [MiddlewareChain.GNOSIS]: !!masterSafeAddress, - }; - - const safeCreationsRequired = Object.entries(chainsToCreateSafesFor).reduce( - (acc, [chain, safeAddressAlreadyExists]) => { - const middlewareChain = chain as MiddlewareChain; - if (safeAddressAlreadyExists) { - // switch (middlewareChain) { - // case MiddlewareChain.OPTIMISM: - // setIsOptimismSuccess(true); - // break; - // case MiddlewareChain.ETHEREUM: - // setIsEthereumSuccess(true); - // break; - // case MiddlewareChain.BASE: - // setIsBaseSuccess(true); - // break; - // } - - switch (middlewareChain) { - case MiddlewareChain.GNOSIS: - setIsGnosisSuccess(true); - break; + const chainsToCreateSafesFor = serviceTemplate + ? Object.keys(serviceTemplate.configurations) + : null; + + const safeCreationsRequired = chainsToCreateSafesFor + ? chainsToCreateSafesFor.reduce((acc, chain) => { + const safeAddressAlreadyExists = masterSafes?.find( + (safe) => safe.evmChainId === asEvmChainId(chain), + ); + if (!safeAddressAlreadyExists) { + const middlewareChain = chain as MiddlewareChain; + acc.push(middlewareChain); } return acc; - } - return [...acc, middlewareChain]; - }, - [] as MiddlewareChain[], - ); + }, [] as MiddlewareChain[]) + : []; (async () => { for (const middlewareChain of safeCreationsRequired) { + setIsCreatingSafe(true); try { await createSafeWithRetries(middlewareChain, 3); message.success( - `${capitalizedMiddlewareChainNames[middlewareChain]} account created`, + `${EvmChainName[asEvmChainId(middlewareChain)]} account created`, ); } catch (e) { message.warning( - `Failed to create ${capitalizedMiddlewareChainNames[middlewareChain]} account`, + `Failed to create ${EvmChainName[asEvmChainId(middlewareChain)]} account`, ); console.error(e); } @@ -214,23 +162,21 @@ export const SetupCreateSafe = () => { setIsCreatingSafe(false); }); }, [ - backupSigner, createSafeWithRetries, - masterSafeAddress, isCreatingSafe, - // optimismFailed, - // isBaseSuccess, - // isEthereumSuccess, - // isOptimismSuccess, - // baseFailed, - // ethereumFailed, - isGnosisSuccess, - gnosisFailed, + isFailed, + isWalletsFetched, + masterSafes, + serviceTemplate, ]); useEffect(() => { // Only progress is the safe is created and accessible via context (updates on interval) - if (masterSafeAddress) goto(Pages.Main); + if (masterSafeAddress) { + delayInSeconds(2).then(() => { + goto(Pages.Main); + }); + } }, [goto, masterSafeAddress]); return ( @@ -242,7 +188,7 @@ export const SetupCreateSafe = () => { padding="80px 24px" gap={12} > - {gnosisFailed ? ( + {isFailed ? ( ) : ( diff --git a/frontend/components/SetupPage/Create/SetupEoaFunding.tsx b/frontend/components/SetupPage/Create/SetupEoaFunding.tsx index 5f8d6492b..2e519812b 100644 --- a/frontend/components/SetupPage/Create/SetupEoaFunding.tsx +++ b/frontend/components/SetupPage/Create/SetupEoaFunding.tsx @@ -1,24 +1,23 @@ import { CopyOutlined } from '@ant-design/icons'; import { Flex, message, Tooltip, Typography } from 'antd'; -import { BigNumber, ethers } from 'ethers'; +import { ethers } from 'ethers'; import { useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { useInterval } from 'usehooks-ts'; -import { MiddlewareChain } from '@/client'; import { CustomAlert } from '@/components/Alert'; import { CardFlex } from '@/components/styled/CardFlex'; import { CardSection } from '@/components/styled/CardSection'; -import { CHAIN_CONFIG } from '@/config/chains'; +import { CHAIN_CONFIG, ChainConfig } from '@/config/chains'; import { PROVIDERS } from '@/constants/providers'; import { NA } from '@/constants/symbols'; import { MIN_ETH_BALANCE_THRESHOLDS } from '@/constants/thresholds'; +import { AgentType } from '@/enums/Agent'; import { EvmChainId } from '@/enums/Chain'; import { SetupScreen } from '@/enums/SetupScreen'; import { useMasterBalances } from '@/hooks/useBalanceContext'; +import { useServices } from '@/hooks/useServices'; import { useSetup } from '@/hooks/useSetup'; import { useMasterWalletContext } from '@/hooks/useWallet'; -import { Address } from '@/types/Address'; import { copyToClipboard } from '@/utils/copyToClipboard'; import { delayInSeconds } from '@/utils/delay'; @@ -47,8 +46,11 @@ const statusMessage = (isFunded?: boolean) => { } }; -type SetupEoaFundingWaitingProps = { chainName: string }; -const SetupEoaFundingWaiting = ({ chainName }: SetupEoaFundingWaitingProps) => { +type SetupEoaFundingWaitingProps = { chainName: string; currency: string }; +const SetupEoaFundingWaiting = ({ + chainName, + currency, +}: SetupEoaFundingWaitingProps) => { const { masterEoa } = useMasterWalletContext(); const masterEoaAddress = masterEoa?.address; @@ -88,7 +90,7 @@ const SetupEoaFundingWaiting = ({ chainName }: SetupEoaFundingWaitingProps) => { - {`XDAI: ${masterEoaAddress || NA}`} + {`${currency}: ${masterEoaAddress || NA}`} {/* void; }; export const SetupEoaFundingForChain = ({ isFunded, minRequiredBalance, currency, chainName, - onFunded, }: SetupEoaFundingProps) => { - const { goto } = useSetup(); - - useEffect(() => { - message.success(`${chainName} funds have been received!`); - - // Wait for a second before moving to the next step - delayInSeconds(1).then(onFunded); - }, [chainName, goto, isFunded, onFunded]); - return ( @@ -135,135 +126,128 @@ export const SetupEoaFundingForChain = ({ The app needs these funds to create your account on-chain. - + Status: {statusMessage(isFunded)} - {!isFunded && } + {!isFunded && ( + + )} ); }; +type EoaFundingMapParams = { + provider: ethers.providers.JsonRpcProvider; + chainConfig: ChainConfig; + requiredEth: number; +}; + // TODO: chain independent -const eoaFundingMap = { - // [MiddlewareChain.OPTIMISM]: { - // provider: OPTIMISM_PROVIDER, - // chainConfig: CHAIN_CONFIG.OPTIMISM, - // requiredEth: - // MIN_ETH_BALANCE_THRESHOLDS[MiddlewareChain.OPTIMISM].safeCreation, +// use SERVICE_TEMPLATES[].configurations instead? +const EOA_FUNDING_MAP: Record< + AgentType, + Partial> +> = { + [AgentType.PredictTrader]: { + [EvmChainId.Gnosis]: { + provider: PROVIDERS[EvmChainId.Gnosis].provider, + chainConfig: CHAIN_CONFIG[EvmChainId.Gnosis], + requiredEth: MIN_ETH_BALANCE_THRESHOLDS[EvmChainId.Gnosis].safeCreation, + }, + }, + [AgentType.Memeooorr]: { + [EvmChainId.Base]: { + provider: PROVIDERS[EvmChainId.Base].provider, + chainConfig: CHAIN_CONFIG[EvmChainId.Base], + requiredEth: MIN_ETH_BALANCE_THRESHOLDS[EvmChainId.Base].safeCreation, + }, + }, + // [AgentType.Optimus]: { + // [EvmChainId.Optimism]: { + // provider: PROVIDERS[EvmChainId.Optimism].provider, + // chainConfig: CHAIN_CONFIG[EvmChainId.Optimism], + // requiredEth: MIN_ETH_BALANCE_THRESHOLDS[EvmChainId.Optimism].safeCreation, // }, - // [MiddlewareChain.ETHEREUM]: { - // provider: ETHEREUM_PROVIDER, - // chainConfig: CHAIN_CONFIG.ETHEREUM, - // requiredEth: - // MIN_ETH_BALANCE_THRESHOLDS[MiddlewareChain.ETHEREUM].safeCreation, + // [EvmChainId.Ethereum]: { + // provider: PROVIDERS[EvmChainId.Ethereum].provider, + // chainConfig: CHAIN_CONFIG[EvmChainId.Ethereum], + // requiredEth: MIN_ETH_BALANCE_THRESHOLDS[EvmChainId.Ethereum].safeCreation, // }, - // [MiddlewareChain.BASE]: { - // provider: BASE_PROVIDER, - // chainConfig: CHAIN_CONFIG.BASE, - // requiredEth: MIN_ETH_BALANCE_THRESHOLDS[MiddlewareChain.BASE].safeCreation, + // [EvmChainId.Base]: { + // provider: PROVIDERS[EvmChainId.Base].provider, + // chainConfig: CHAIN_CONFIG[EvmChainId.Base], + // requiredEth: MIN_ETH_BALANCE_THRESHOLDS[EvmChainId.Base].safeCreation, // }, - [MiddlewareChain.GNOSIS]: { - provider: PROVIDERS[EvmChainId.Gnosis].provider, - chainConfig: CHAIN_CONFIG[EvmChainId.Gnosis], - requiredEth: MIN_ETH_BALANCE_THRESHOLDS[EvmChainId.Gnosis].safeCreation, - }, + // } }; export const SetupEoaFunding = () => { const { goto } = useSetup(); + const { selectedAgentType, selectedAgentConfig } = useServices(); const { masterEoa } = useMasterWalletContext(); const { masterWalletBalances } = useMasterBalances(); const masterEoaAddress = masterEoa?.address; - const [currentChain, setCurrentChain] = useState( - MiddlewareChain.GNOSIS, + const [currentChain, setCurrentChain] = useState( + selectedAgentConfig.evmHomeChainId, ); - const currentFundingMapObject = eoaFundingMap[MiddlewareChain.GNOSIS]; + const currentFundingMapObject = + EOA_FUNDING_MAP[selectedAgentType][currentChain]; - const getIsCurrentChainFunded = useCallback( - async ( - currentFundingMapObject: (typeof eoaFundingMap)[keyof typeof eoaFundingMap], - masterEoaAddress: Address, - ) => { - const { provider, requiredEth } = currentFundingMapObject; - - return provider - .getBalance(masterEoaAddress) - .then( - (balance: BigNumber) => - parseFloat(ethers.utils.formatEther(balance)) >= requiredEth, - ); - }, - [], + const eoaBalance = masterWalletBalances?.find( + (balance) => + balance.walletAddress === masterEoaAddress && + balance.evmChainId === currentChain, ); - useInterval(async () => { - if (!masterEoaAddress) return; + const isFunded = + eoaBalance?.evmChainId === currentChain && + eoaBalance.balance >= MIN_ETH_BALANCE_THRESHOLDS[currentChain].safeCreation; - const currentChainIsFunded = await getIsCurrentChainFunded( - currentFundingMapObject, - masterEoaAddress, + const handleFunded = useCallback(async () => { + message.success( + `${currentFundingMapObject?.chainConfig.name} funds have been received!`, ); - if (!currentChainIsFunded) return; + await delayInSeconds(1); - message.success( - `${currentFundingMapObject.chainConfig.name} funds have been received!`, - ); + const chains = Object.keys(EOA_FUNDING_MAP[selectedAgentType]); + const indexOfCurrentChain = chains.indexOf(currentChain.toString()); + const nextChainExists = chains.length > indexOfCurrentChain + 1; - const indexOfCurrentChain = Object.keys(eoaFundingMap).indexOf( - currentChain.toString(), - ); - const nextChainExists = - Object.keys(eoaFundingMap).length > indexOfCurrentChain + 1; if (nextChainExists) { // goto next chain - setCurrentChain( - Object.keys(eoaFundingMap)[indexOfCurrentChain + 1] as MiddlewareChain, - ); + setCurrentChain(chains[indexOfCurrentChain + 1] as unknown as EvmChainId); return; } + goto(SetupScreen.SetupCreateSafe); - }, 5000); + }, [ + currentChain, + currentFundingMapObject?.chainConfig.name, + goto, + selectedAgentType, + ]); - const eoaBalance = masterWalletBalances?.find( - (balance) => balance.walletAddress === masterEoaAddress, - ); - const isFunded = - eoaBalance?.evmChainId === EvmChainId.Gnosis && - eoaBalance.balance >= - MIN_ETH_BALANCE_THRESHOLDS[EvmChainId.Gnosis].safeCreation; + useEffect(() => { + if (!currentFundingMapObject) return; + if (!masterEoaAddress) return; + if (!isFunded) return; - return ( - - - - {`Deposit ${currentFundingMapObject.requiredEth} ${currentFundingMapObject.chainConfig.nativeToken.symbol} on ${currentFundingMapObject.chainConfig.name}`} - - - The app needs these funds to create your account on-chain. - + handleFunded(); + }, [currentFundingMapObject, handleFunded, isFunded, masterEoaAddress]); - - - Status: {statusMessage(isFunded)} - - + if (!currentFundingMapObject) return null; - - + return ( + ); }; diff --git a/frontend/components/SetupPage/SetupWelcome.tsx b/frontend/components/SetupPage/SetupWelcome.tsx index c39009b63..30774d246 100644 --- a/frontend/components/SetupPage/SetupWelcome.tsx +++ b/frontend/components/SetupPage/SetupWelcome.tsx @@ -12,6 +12,7 @@ import Image from 'next/image'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { MiddlewareAccountIsSetup } from '@/client'; +import { MIN_ETH_BALANCE_THRESHOLDS } from '@/constants/thresholds'; import { Pages } from '@/enums/Pages'; import { SetupScreen } from '@/enums/SetupScreen'; import { @@ -134,7 +135,7 @@ export const SetupWelcomeLogin = () => { const { goto } = useSetup(); const { goto: gotoPage } = usePageState(); - const { selectedService } = useServices(); + const { selectedService, selectedAgentConfig } = useServices(); const { masterSafes, masterWallets: wallets, @@ -143,16 +144,22 @@ export const SetupWelcomeLogin = () => { const { isLoaded: isBalanceLoaded, updateBalances } = useBalanceContext(); const { masterWalletBalances } = useMasterBalances(); + const selectedServiceOrAgentChainId = selectedService?.home_chain + ? asEvmChainId(selectedService?.home_chain) + : selectedAgentConfig.evmHomeChainId; + const masterSafe = masterSafes?.find( (safe) => - selectedService?.home_chain && - safe.evmChainId === asEvmChainId(selectedService?.home_chain), + selectedServiceOrAgentChainId && + safe.evmChainId === selectedServiceOrAgentChainId, ) ?? null; const eoaBalanceEth = masterWalletBalances?.find( - (balance) => balance.walletAddress === masterEoa?.address, - ); + (balance) => + balance.walletAddress === masterEoa?.address && + balance.evmChainId === selectedServiceOrAgentChainId, + )?.balance; const [isLoggingIn, setIsLoggingIn] = useState(false); const [canNavigate, setCanNavigate] = useState(false); @@ -184,7 +191,11 @@ export const SetupWelcomeLogin = () => { // TODO: fix wallet and balance loads if (canNavigate) { setIsLoggingIn(false); - if (!eoaBalanceEth) { + if ( + !eoaBalanceEth || + eoaBalanceEth < + MIN_ETH_BALANCE_THRESHOLDS[selectedServiceOrAgentChainId].safeCreation + ) { goto(SetupScreen.SetupEoaFundingIncomplete); } else if (!masterSafe?.address) { goto(SetupScreen.SetupCreateSafe); @@ -199,6 +210,7 @@ export const SetupWelcomeLogin = () => { gotoPage, isBalanceLoaded, masterSafe?.address, + selectedServiceOrAgentChainId, wallets?.length, ]); diff --git a/frontend/components/SetupPage/index.tsx b/frontend/components/SetupPage/index.tsx index 6199070fe..04e122c65 100644 --- a/frontend/components/SetupPage/index.tsx +++ b/frontend/components/SetupPage/index.tsx @@ -48,6 +48,7 @@ export const Setup = () => { case SetupScreen.AgentSelection: return ( goto(SetupScreen.SetupBackupSigner)} onNext={() => goto(SetupScreen.SetupEoaFunding)} /> diff --git a/frontend/components/styled/CardSection.tsx b/frontend/components/styled/CardSection.tsx index 40521858b..5e28ceb45 100644 --- a/frontend/components/styled/CardSection.tsx +++ b/frontend/components/styled/CardSection.tsx @@ -27,7 +27,7 @@ export const CardSection = styled(Flex)` const borderBottomStyle = borderbottom === 'true' ? `border-bottom: 1px solid ${COLOR.BORDER_GRAY};` - : 'border-top: none;'; + : 'border-bottom: none;'; const verticalStyle = props.vertical ? 'flex-direction: column;' : ''; diff --git a/frontend/config/chains.ts b/frontend/config/chains.ts index 2968cfe1d..9dd7127c1 100644 --- a/frontend/config/chains.ts +++ b/frontend/config/chains.ts @@ -10,7 +10,7 @@ import { TOKEN_CONFIG, TokenConfig } from './tokens'; type HttpUrl = `http${'s' | ''}://${string}`; -type ChainConfig = { +export type ChainConfig = { name: string; nativeToken: TokenConfig; evmChainId: number; @@ -53,7 +53,7 @@ export const ETHEREUM_CHAIN_CONFIG: ChainConfig = { export const CHAIN_CONFIG: { [evmChainId: number]: ChainConfig; } = { - // [EvmChainId.Base]: BASE_CHAIN_CONFIG, + [EvmChainId.Base]: BASE_CHAIN_CONFIG, // [EvmChainId.Ethereum]: ETHEREUM_CHAIN_CONFIG, [EvmChainId.Gnosis]: GNOSIS_CHAIN_CONFIG, // [EvmChainId.Optimism]: OPTIMISM_CHAIN_CONFIG, diff --git a/frontend/constants/providers.ts b/frontend/constants/providers.ts index 028adc630..c5997446a 100644 --- a/frontend/constants/providers.ts +++ b/frontend/constants/providers.ts @@ -2,6 +2,7 @@ import { ethers } from 'ethers'; import { Provider as MulticallProvider } from 'ethers-multicall'; import { EvmChainId } from '@/enums/Chain'; +import { setupMulticallAddresses } from '@/utils/setupMulticall'; import { CHAIN_CONFIG } from '../config/chains'; @@ -12,6 +13,9 @@ type Providers = { }; }; +// Setup multicall addresses +setupMulticallAddresses(); + export const PROVIDERS: Providers = Object.entries(CHAIN_CONFIG).reduce( (acc, [, { rpc, name, evmChainId }]) => { const provider = new ethers.providers.StaticJsonRpcProvider(rpc, { diff --git a/frontend/constants/serviceTemplates.ts b/frontend/constants/serviceTemplates.ts index 41fc45e99..02474563d 100644 --- a/frontend/constants/serviceTemplates.ts +++ b/frontend/constants/serviceTemplates.ts @@ -155,6 +155,64 @@ export const SERVICE_TEMPLATES: ServiceTemplate[] = [ // }, // }, // }, + { + agentType: AgentType.Memeooorr, + name: 'Memeooorr', + hash: '', // TODO: fix value + description: 'Memeooorr @twitter_handle', // should be overwritten with twitter username + image: + 'https://gateway.autonolas.tech/ipfs/QmQYDGMg8m91QQkTWSSmANs5tZwKrmvUCawXZfXVVWQPcu', + service_version: 'v0.0.1', + home_chain: MiddlewareChain.BASE, + configurations: { + [MiddlewareChain.BASE]: { + staking_program_id: StakingProgramId.MemeBaseAlpha, // default, may be overwritten + nft: 'bafybeiaakdeconw7j5z76fgghfdjmsr6tzejotxcwnvmp3nroaw3glgyve', + rpc: 'http://localhost:8545', // overwritten + agent_id: 43, + threshold: 1, + use_staking: true, + cost_of_bond: 50000000000000000000, + monthly_gas_estimate: 50000000000000000, // 0.05 + fund_requirements: { + agent: 1000000000000000, // 0.001 + safe: 2000000000000000, // 0.002 + }, + }, + }, + env_variables: { + TWIKIT_USERNAME: { + name: 'Twitter username', + description: '', + value: '', + provision_type: EnvProvisionType.USER, + }, + TWIKIT_EMAIL: { + name: 'Twitter email', + description: '', + value: '', + provision_type: EnvProvisionType.USER, + }, + TWIKIT_PASSWORD: { + name: 'Twitter password', + description: '', + value: '', + provision_type: EnvProvisionType.USER, + }, + GENAI_API_KEY: { + name: 'Gemini api key', + description: '', + value: '', + provision_type: EnvProvisionType.USER, + }, + PERSONA: { + name: 'Persona description', + description: '', + value: '', + provision_type: EnvProvisionType.USER, + }, + }, + }, ]; export const getServiceTemplates = (): ServiceTemplate[] => SERVICE_TEMPLATES; diff --git a/frontend/hooks/useFeatureFlag.ts b/frontend/hooks/useFeatureFlag.ts index 6e5d101a0..2c0471fb2 100644 --- a/frontend/hooks/useFeatureFlag.ts +++ b/frontend/hooks/useFeatureFlag.ts @@ -16,6 +16,10 @@ const FeaturesConfigSchema = z.record( const FEATURES_CONFIG = FeaturesConfigSchema.parse({ [AgentType.PredictTrader]: { 'balance-breakdown': true, + 'last-transactions': true, + }, + [AgentType.Memeooorr]: { + 'balance-breakdown': false, 'last-transactions': false, }, }); diff --git a/frontend/pages/_app.tsx b/frontend/pages/_app.tsx index 63ecbdf74..937f6cb2b 100644 --- a/frontend/pages/_app.tsx +++ b/frontend/pages/_app.tsx @@ -21,10 +21,6 @@ import { StakingProgramProvider } from '@/context/StakingProgramProvider'; import { StoreProvider } from '@/context/StoreProvider'; import { SystemNotificationTriggers } from '@/context/SystemNotificationTriggers'; import { mainTheme } from '@/theme'; -import { setupMulticallAddresses } from '@/utils/setupMulticall'; - -// Setup multicall addresses -setupMulticallAddresses(); const queryClient = new QueryClient(); From ee50a7091e1fb28e4f42298758ebf703cc2d6b51 Mon Sep 17 00:00:00 2001 From: Mohan Date: Wed, 4 Dec 2024 15:37:36 +0530 Subject: [PATCH 07/79] feat: hide unwanted features for mememooorr (#535) * feat: enable last transaction feature flag in AgentRunningButton * feat: add feature flags for staking contract section and rewards streak --- .../header/AgentButton/AgentRunningButton.tsx | 6 +++- frontend/components/MainPage/index.tsx | 6 +++- .../MainPage/sections/OlasBalanceSection.tsx | 1 + .../sections/RewardsSection/index.tsx | 19 +++++++---- frontend/hooks/useFeatureFlag.ts | 33 +++++++++++++++---- 5 files changed, 50 insertions(+), 15 deletions(-) diff --git a/frontend/components/MainPage/header/AgentButton/AgentRunningButton.tsx b/frontend/components/MainPage/header/AgentButton/AgentRunningButton.tsx index 4ed533482..02ec57440 100644 --- a/frontend/components/MainPage/header/AgentButton/AgentRunningButton.tsx +++ b/frontend/components/MainPage/header/AgentButton/AgentRunningButton.tsx @@ -4,6 +4,7 @@ import { useCallback } from 'react'; import { MiddlewareDeploymentStatus } from '@/client'; import { useElectronApi } from '@/hooks/useElectronApi'; +import { useFeatureFlag } from '@/hooks/useFeatureFlag'; import { useReward } from '@/hooks/useReward'; import { useService } from '@/hooks/useService'; import { useServices } from '@/hooks/useServices'; @@ -29,6 +30,7 @@ const IdleTooltip = () => ( ); export const AgentRunningButton = () => { + const isLastTransactionEnabled = useFeatureFlag('last-transactions'); const { showNotification } = useElectronApi(); const { isEligibleForRewards } = useReward(); @@ -82,7 +84,9 @@ export const AgentRunningButton = () => { )} - + {isLastTransactionEnabled && ( + + )} ); diff --git a/frontend/components/MainPage/index.tsx b/frontend/components/MainPage/index.tsx index 227b4c1f0..34fda3d9f 100644 --- a/frontend/components/MainPage/index.tsx +++ b/frontend/components/MainPage/index.tsx @@ -1,6 +1,7 @@ import { Card, Flex } from 'antd'; import { StakingProgramId } from '@/enums/StakingProgram'; +import { useFeatureFlag } from '@/hooks/useFeatureFlag'; // import { StakingProgramId } from '@/enums/StakingProgram'; // import { useMasterSafe } from '@/hooks/useMasterSafe'; import { @@ -22,6 +23,9 @@ import { StakingContractSection } from './sections/StakingContractUpdate'; import { SwitchAgentSection } from './sections/SwitchAgentSection'; export const Main = () => { + const isStakingContractSectionEnabled = useFeatureFlag( + 'staking-contract-section', + ); // const { backupSafeAddress } = useMasterWalletContext(); // const { refetch: updateServicesState } = useServices(); // const { @@ -73,7 +77,7 @@ export const Main = () => { - + {isStakingContractSectionEnabled && } diff --git a/frontend/components/MainPage/sections/OlasBalanceSection.tsx b/frontend/components/MainPage/sections/OlasBalanceSection.tsx index 365f694ef..6dbcbd2de 100644 --- a/frontend/components/MainPage/sections/OlasBalanceSection.tsx +++ b/frontend/components/MainPage/sections/OlasBalanceSection.tsx @@ -27,6 +27,7 @@ const Balance = styled.span` `; type MainOlasBalanceProps = { isBorderTopVisible?: boolean }; + export const MainOlasBalance = ({ isBorderTopVisible = true, }: MainOlasBalanceProps) => { diff --git a/frontend/components/MainPage/sections/RewardsSection/index.tsx b/frontend/components/MainPage/sections/RewardsSection/index.tsx index 7a494865e..eedeac287 100644 --- a/frontend/components/MainPage/sections/RewardsSection/index.tsx +++ b/frontend/components/MainPage/sections/RewardsSection/index.tsx @@ -2,6 +2,7 @@ import { Flex, Skeleton, Tag, Typography } from 'antd'; import { NA } from '@/constants/symbols'; import { useBalanceContext } from '@/hooks/useBalanceContext'; +import { useFeatureFlag } from '@/hooks/useFeatureFlag'; import { useReward } from '@/hooks/useReward'; import { balanceFormat } from '@/utils/numberFormatters'; @@ -46,10 +47,14 @@ const DisplayRewards = () => { ); }; -export const RewardsSection = () => ( - <> - - - - -); +export const RewardsSection = () => { + const isRewardsStreakEnabled = useFeatureFlag('rewards-streak'); + + return ( + <> + + {isRewardsStreakEnabled && } + + + ); +}; diff --git a/frontend/hooks/useFeatureFlag.ts b/frontend/hooks/useFeatureFlag.ts index 2c0471fb2..043a7e78c 100644 --- a/frontend/hooks/useFeatureFlag.ts +++ b/frontend/hooks/useFeatureFlag.ts @@ -5,7 +5,12 @@ import { assertRequired } from '@/types/Util'; import { useServices } from './useServices'; -const FeatureFlagsSchema = z.enum(['last-transactions', 'balance-breakdown']); +const FeatureFlagsSchema = z.enum([ + 'last-transactions', + 'balance-breakdown', + 'rewards-streak', + 'staking-contract-section', +]); type FeatureFlags = z.infer; const FeaturesConfigSchema = z.record( @@ -13,24 +18,37 @@ const FeaturesConfigSchema = z.record( z.record(FeatureFlagsSchema, z.boolean()), ); +/** + * Feature flags configuration for each agent type + * If true - the feature is enabled + * if false - the feature is disabled + */ const FEATURES_CONFIG = FeaturesConfigSchema.parse({ [AgentType.PredictTrader]: { 'balance-breakdown': true, 'last-transactions': true, + 'rewards-streak': true, + 'staking-contract-section': true, }, [AgentType.Memeooorr]: { 'balance-breakdown': false, 'last-transactions': false, + 'rewards-streak': false, + 'staking-contract-section': false, }, }); +type FeatureFlagReturn = + T extends FeatureFlags[] ? boolean[] : boolean; + /** * Hook to check if a feature flag is enabled for the selected agent * @example const isFeatureEnabled = useFeatureFlag('feature-name'); */ -export const useFeatureFlag = (featureFlag: FeatureFlags | FeatureFlags[]) => { +export function useFeatureFlag( + featureFlag: T, +): FeatureFlagReturn { const { selectedAgentType } = useServices(); - // Ensure an agent is selected before using the feature flag assertRequired( selectedAgentType, @@ -46,9 +64,12 @@ export const useFeatureFlag = (featureFlag: FeatureFlags | FeatureFlags[]) => { // If the feature flag is an array, return an array of booleans if (Array.isArray(featureFlag)) { - return featureFlag.map((flag) => selectedAgentFeatures[flag] ?? false); + return featureFlag.map( + (flag) => selectedAgentFeatures[flag] ?? false, + ) as FeatureFlagReturn; } // Return the boolean value for the single feature flag - return selectedAgentFeatures[featureFlag] ?? false; -}; + return (selectedAgentFeatures[featureFlag as FeatureFlags] ?? + false) as FeatureFlagReturn; +} From 7339fe98171e602eac066ca4e60f9e479855bba3 Mon Sep 17 00:00:00 2001 From: Mohan Date: Wed, 4 Dec 2024 15:51:12 +0530 Subject: [PATCH 08/79] feat: validate twitter credentials (#533) * feat: add SetupYourAgent screen to setup flow * feat: implement SetupYourAgent form for agent configuration * feat: enhance SetupYourAgent form with X account credentials and validation messages * feat: update SetupYourAgent form to include X account credentials and validation logic * feat: implement validation logic for Gemini API key and Twitter credentials in SetupYourAgent form * Update frontend/components/SetupPage/SetupYourAgent/index.tsx Co-authored-by: Josh Miller <31908788+truemiller@users.noreply.github.com> * feat: validate Gemini API key (#530) * feat: enhance Gemini API key validation with real-time API request and error handling * feat: improve SetupYourAgent form with dynamic submit button text and error handling for API validation * feat: add agent-twitter-client dependency and implement Twitter credentials validation * feat: move twitter login to electron main process * feat: enhance Twitter login validation and refactor related components * feat: rename check-twitter-login to validate-twitter-login and update related components * feat: remove unused dependencies and clean up configuration --------- Co-authored-by: Josh Miller <31908788+truemiller@users.noreply.github.com> Co-authored-by: Atatakai --- electron/main.js | 20 +- electron/preload.js | 2 + .../{index.tsx => SetupYourAgent.tsx} | 85 ++++--- .../SetupPage/SetupYourAgent/validation.ts | 33 ++- frontend/components/SetupPage/index.tsx | 2 +- frontend/context/ElectronApiProvider.tsx | 11 + frontend/yarn.lock | 33 +-- package.json | 3 +- yarn.lock | 211 +++++++++++++++--- 9 files changed, 298 insertions(+), 102 deletions(-) rename frontend/components/SetupPage/SetupYourAgent/{index.tsx => SetupYourAgent.tsx} (76%) diff --git a/electron/main.js b/electron/main.js index 4157b081d..1040dc84a 100644 --- a/electron/main.js +++ b/electron/main.js @@ -14,7 +14,6 @@ const os = require('os'); const next = require('next/dist/server/next'); const http = require('http'); const AdmZip = require('adm-zip'); -const { validateEnv } = require('./utils/env-validation'); const { setupDarwin, setupUbuntu, setupWindows, Env } = require('./install'); @@ -26,6 +25,7 @@ const { setupStoreIpc } = require('./store'); const { logger } = require('./logger'); const { isDev } = require('./constants'); const { PearlTray } = require('./components/PearlTray'); +const { Scraper } = require('agent-twitter-client'); // Validates environment variables required for Pearl // kills the app/process if required environment variables are unavailable @@ -256,6 +256,24 @@ const createMainWindow = async () => { ipcMain.handle('app-version', () => app.getVersion()); + // Handle twitter login + ipcMain.handle('validate-twitter-login', async (_event, credentials) => { + const scraper = new Scraper(); + + const { username, password, email } = credentials; + if (!username || !password || !email) { + return { success: false, error: 'Missing credentials' }; + } + + try { + await scraper.login(username, password, email); + return { success: true }; + } catch (error) { + console.error('Twitter login error:', error); + return { success: false, error: error.message }; + } + }); + mainWindow.webContents.on('did-fail-load', () => { mainWindow.webContents.reloadIgnoringCache(); }); diff --git a/electron/preload.js b/electron/preload.js index f3143a1ad..93e1ff0cd 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -25,4 +25,6 @@ contextBridge.exposeInMainWorld('electronAPI', { saveLogs: (data) => ipcRenderer.invoke('save-logs', data), openPath: (filePath) => ipcRenderer.send('open-path', filePath), getAppVersion: () => ipcRenderer.invoke('app-version'), + validateTwitterLogin: (credentials) => + ipcRenderer.invoke('validate-twitter-login', credentials), }); diff --git a/frontend/components/SetupPage/SetupYourAgent/index.tsx b/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx similarity index 76% rename from frontend/components/SetupPage/SetupYourAgent/index.tsx rename to frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx index 641dbacd4..a58d1469b 100644 --- a/frontend/components/SetupPage/SetupYourAgent/index.tsx +++ b/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx @@ -9,12 +9,13 @@ import { message, Typography, } from 'antd'; -import React, { useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { useUnmount } from 'usehooks-ts'; import { CustomAlert } from '@/components/Alert'; import { CardFlex } from '@/components/styled/CardFlex'; import { SetupScreen } from '@/enums/SetupScreen'; +import { useElectronApi } from '@/hooks/useElectronApi'; import { useSetup } from '@/hooks/useSetup'; import { SetupCreateHeader } from '../Create/SetupCreateHeader'; @@ -93,42 +94,52 @@ const SetupYourAgentForm = () => { setTwitterCredentialsValidationStatus, ] = useState('unknown'); - const onFinish = async (values: Record) => { - try { - setIsSubmitting(true); - - // validate the gemini API - setSubmitButtonText('Validating API key...'); - const isGeminiApiValid = await validateGeminiApiKey(values.geminiApiKey); - setGeminiApiKeyValidationStatus(isGeminiApiValid ? 'valid' : 'invalid'); - if (!isGeminiApiValid) return; - - // validate the twitter credentials - setSubmitButtonText('Validating Twitter credentials...'); - const isTwitterCredentialsValid = await validateTwitterCredentials( - values.xEmail, - values.xUsername, - values.xPassword, - ); - setTwitterCredentialsValidationStatus( - isTwitterCredentialsValid ? 'valid' : 'invalid', - ); - if (!isTwitterCredentialsValid) return; - - // wait for agent setup to complete - setSubmitButtonText('Setting up agent...'); - await onAgentSetupComplete(); - - // move to next page - goto(SetupScreen.SetupEoaFunding); - } catch (error) { - message.error('Something went wrong. Please try again.'); - console.error(error); - } finally { - setIsSubmitting(false); - setSubmitButtonText('Continue'); - } - }; + const electronApi = useElectronApi(); + + const onFinish = useCallback( + async (values: Record) => { + try { + setIsSubmitting(true); + + // validate the gemini API + setSubmitButtonText('Validating Gemini API key...'); + const isGeminiApiValid = await validateGeminiApiKey( + values.geminiApiKey, + ); + setGeminiApiKeyValidationStatus(isGeminiApiValid ? 'valid' : 'invalid'); + if (!isGeminiApiValid) return; + + // validate the twitter credentials + setSubmitButtonText('Validating Twitter credentials...'); + const isTwitterCredentialsValid = electronApi?.validateTwitterLogin + ? await validateTwitterCredentials( + values.xEmail, + values.xUsername, + values.xPassword, + electronApi.validateTwitterLogin, + ) + : false; + setTwitterCredentialsValidationStatus( + isTwitterCredentialsValid ? 'valid' : 'invalid', + ); + if (!isTwitterCredentialsValid) return; + + // wait for agent setup to complete + setSubmitButtonText('Setting up agent...'); + await onAgentSetupComplete(); + + // move to next page + goto(SetupScreen.SetupEoaFunding); + } catch (error) { + message.error('Something went wrong. Please try again.'); + console.error(error); + } finally { + setIsSubmitting(false); + setSubmitButtonText('Continue'); + } + }, + [electronApi, goto], + ); // Clean up useUnmount(async () => { diff --git a/frontend/components/SetupPage/SetupYourAgent/validation.ts b/frontend/components/SetupPage/SetupYourAgent/validation.ts index e278c96b5..97db97e4e 100644 --- a/frontend/components/SetupPage/SetupYourAgent/validation.ts +++ b/frontend/components/SetupPage/SetupYourAgent/validation.ts @@ -1,5 +1,8 @@ import { delayInSeconds } from '@/utils/delay'; +/** + * Validate the Google Gemini API key + */ export const validateGeminiApiKey = async (apiKey: string) => { if (!apiKey) return false; @@ -16,17 +19,41 @@ export const validateGeminiApiKey = async (apiKey: string) => { } }; +/** + * Validate the Twitter credentials + */ export const validateTwitterCredentials = async ( email: string, username: string, password: string, + validateTwitterLogin: ({ + username, + password, + email, + }: { + email: string; + username: string; + password: string; + }) => Promise<{ success: boolean }>, ) => { if (!email || !username || !password) return false; - // TODO: validate the twitter credentials and remove the delay - await delayInSeconds(2); + try { + const isValidated = await validateTwitterLogin({ + username, + password, + email, + }); + if (isValidated.success) { + return true; + } - return false; + console.error('Error validating Twitter credentials:', isValidated); + return false; + } catch (error) { + console.error('Unexpected error validating Twitter credentials:', error); + return false; + } }; export const onAgentSetupComplete = async () => { diff --git a/frontend/components/SetupPage/index.tsx b/frontend/components/SetupPage/index.tsx index 04e122c65..7646c4f07 100644 --- a/frontend/components/SetupPage/index.tsx +++ b/frontend/components/SetupPage/index.tsx @@ -17,7 +17,7 @@ import { SetupRestoreViaSeed, } from './SetupRestore'; import { SetupWelcome } from './SetupWelcome'; -import { SetupYourAgent } from './SetupYourAgent'; +import { SetupYourAgent } from './SetupYourAgent/SetupYourAgent'; const UnexpectedError = () => (
Something went wrong!
diff --git a/frontend/context/ElectronApiProvider.tsx b/frontend/context/ElectronApiProvider.tsx index 304c9ff00..472d26022 100644 --- a/frontend/context/ElectronApiProvider.tsx +++ b/frontend/context/ElectronApiProvider.tsx @@ -32,6 +32,15 @@ type ElectronApiContextProps = { debugData?: Record; }) => Promise<{ success: true; dirPath: string } | { success?: false }>; openPath?: (filePath: string) => void; + validateTwitterLogin?: ({ + username, + password, + email, + }: { + username: string; + password: string; + email: string; + }) => Promise<{ success: boolean }>; }; export const ElectronApiContext = createContext({ @@ -55,6 +64,7 @@ export const ElectronApiContext = createContext({ setAppHeight: () => {}, saveLogs: async () => ({ success: false }), openPath: () => {}, + validateTwitterLogin: async () => ({ success: false }), }); export const ElectronApiProvider = ({ children }: PropsWithChildren) => { @@ -95,6 +105,7 @@ export const ElectronApiProvider = ({ children }: PropsWithChildren) => { showNotification: getElectronApiFunction('showNotification'), saveLogs: getElectronApiFunction('saveLogs'), openPath: getElectronApiFunction('openPath'), + validateTwitterLogin: getElectronApiFunction('validateTwitterLogin'), }} > {children} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ed10fce83..2502121b9 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -5690,16 +5690,8 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - 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: +"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 version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5763,14 +5755,7 @@ 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": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -6245,16 +6230,8 @@ word-wrap@^1.2.5: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - 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: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== diff --git a/package.json b/package.json index 28cc3617f..4f0860618 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@fontsource/inter": "^5.0.17", "@tanstack/react-query": "^5.29.0", "adm-zip": "^0.5.12", + "agent-twitter-client": "^0.0.16", "antd": "^5.14.0", "axios": "^1.7.7", "child_process": "^1.0.2", @@ -71,4 +72,4 @@ "npm": "please-use-yarn" }, "engineStrict": true -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 684a3fb4f..b7cc4e228 100644 --- a/yarn.lock +++ b/yarn.lock @@ -737,6 +737,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== +"@noble/hashes@1.5.0": + version "1.5.0" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -1143,6 +1148,11 @@ "@sentry/types" "5.30.0" tslib "^1.9.3" +"@sinclair/typebox@^0.32.20": + version "0.32.35" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.32.35.tgz#41c04473509478df9895800018a3d3ae7d40fb3c" + integrity sha512-Ul3YyOTU++to8cgNkttakC0dWvpERr6RYoHO2W47DLbFvrwBDJUY31B1sImH6JZSYc4Kt4PyHtoPNu+vL2r2dA== + "@sindresorhus/is@^4.0.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" @@ -1378,6 +1388,21 @@ agent-base@^7.0.2, agent-base@^7.1.0: dependencies: debug "^4.3.4" +agent-twitter-client@^0.0.16: + version "0.0.16" + resolved "https://registry.npmjs.org/agent-twitter-client/-/agent-twitter-client-0.0.16.tgz#c800047b668c111d4d07c7fdcad3237a2a34f29f" + integrity sha512-Clgb/N2LXoGMlId6GDUaaR05eJ0PqSifM6wikl/FiQ2+3+6I2ZhZB7KRulc8R4xvYFe6h0wNWe6FZiF48r124w== + dependencies: + "@sinclair/typebox" "^0.32.20" + headers-polyfill "^3.1.2" + json-stable-stringify "^1.0.2" + node-fetch "^3.3.2" + otpauth "^9.2.2" + set-cookie-parser "^2.6.0" + tough-cookie "^4.1.2" + tslib "^2.5.2" + twitter-api-v2 "^1.18.2" + agentkeepalive@^4.2.1: version "4.5.0" resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" @@ -1914,6 +1939,17 @@ cacheable-request@^7.0.2: normalize-url "^6.0.1" responselike "^2.0.0" +call-bind@^1.0.5: + version "1.0.7" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -2298,6 +2334,11 @@ csstype@3.1.3, csstype@^3.1.3: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + dayjs@^1.11.11: version "1.11.11" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" @@ -2346,7 +2387,7 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== -define-data-property@^1.0.1: +define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== @@ -2950,6 +2991,14 @@ fecha@^4.2.0: resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3039,6 +3088,13 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + fp-ts@1.19.3: version "1.19.3" resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.19.3.tgz#261a60d1088fbff01f91256f91d21d0caaaaa96f" @@ -3374,7 +3430,7 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== @@ -3425,6 +3481,11 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +headers-polyfill@^3.1.2: + version "3.3.0" + resolved "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.3.0.tgz#67c6ef7b72d4c8cac832ad5936f5b3a56e7b705a" + integrity sha512-5e57etwBpNcDc0b6KCVWEh/Ro063OxPvzVimUdM0/tsYM/T7Hfy3kknIGj78SFTOhNd8AZY41U8mOHoO4LzmIQ== + hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -3679,6 +3740,11 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isbinaryfile@^4.0.8: version "4.0.10" resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" @@ -3765,6 +3831,16 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stable-stringify@^1.0.2: + version "1.1.1" + resolved "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz#52d4361b47d49168bcc4e564189a42e5a7439454" + integrity sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg== + dependencies: + call-bind "^1.0.5" + isarray "^2.0.5" + jsonify "^0.0.1" + object-keys "^1.1.1" + json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -3805,6 +3881,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== + keccak@^3.0.0, keccak@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d" @@ -4308,6 +4389,11 @@ node-api-version@^0.2.0: dependencies: semver "^7.3.5" +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + node-fetch@^2.6.12: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -4315,6 +4401,15 @@ node-fetch@^2.6.12: dependencies: whatwg-url "^5.0.0" +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: version "4.8.1" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.1.tgz#976d3ad905e71b76086f4f0b0d3637fe79b6cda5" @@ -4427,6 +4522,13 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== +otpauth@^9.2.2: + version "9.3.5" + resolved "https://registry.npmjs.org/otpauth/-/otpauth-9.3.5.tgz#0a15e453549f521ecc93b67f5e57aa95fe3de991" + integrity sha512-jQyqOuQExeIl4YIiOUz4TdEcamgAgPX6UYeeS9Iit4lkvs7bwHb0JNDqchGRccbRfvWHV6oRwH36tOsVmc+7hQ== + dependencies: + "@noble/hashes" "1.5.0" + p-cancelable@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" @@ -4660,6 +4762,13 @@ ps-tree@^1.2.0: dependencies: event-stream "=3.3.4" +psl@^1.1.33: + version "1.15.0" + resolved "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" + pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -4668,11 +4777,16 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2, queue-microtask@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -5109,6 +5223,11 @@ require-from-string@^2.0.0, require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + resedit@^1.7.0: version "1.7.2" resolved "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz#b1041170b99811710c13f949c7d225871de4cc78" @@ -5336,6 +5455,23 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-cookie-parser@^2.6.0: + version "2.7.1" + resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz#3016f150072202dfbe90fadee053573cc89d2943" + integrity sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -5517,16 +5653,7 @@ 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": - 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@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", 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== @@ -5551,14 +5678,7 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"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: +"strip-ansi-cjs@npm:strip-ansi@^6.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== @@ -5733,6 +5853,16 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +tough-cookie@^4.1.2: + version "4.1.4" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" + integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -5765,6 +5895,11 @@ tslib@^2.4.0, tslib@^2.6.2: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.5.2: + version "2.8.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tsort@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786" @@ -5780,6 +5915,11 @@ tweetnacl@^1.0.3: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== +twitter-api-v2@^1.18.2: + version "1.18.2" + resolved "https://registry.npmjs.org/twitter-api-v2/-/twitter-api-v2-1.18.2.tgz#fd03d0ac4c7ed9f0d8f76786d5908d7aced3af78" + integrity sha512-ggImmoAeVgETYqrWeZy+nWnDpwgTP+IvFEc03Pitt1HcgMX+Yw17rP38Fb5FFTinuyNvS07EPtAfZ184uIyB0A== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -5853,6 +5993,11 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -5870,6 +6015,14 @@ uri-js@^4.2.2, uri-js@^4.4.1: dependencies: punycode "^2.1.0" +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + usehooks-ts@^2.14.0: version "2.16.0" resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-2.16.0.tgz#31deaa2f1147f65666aae925bd890b54e63b0d3f" @@ -5908,6 +6061,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -5971,16 +6129,7 @@ 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": - 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: +"wrap-ansi-cjs@npm:wrap-ansi@^7.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 6c164d2049e055014331eed72f6ec1989c4f0af4 Mon Sep 17 00:00:00 2001 From: Mohan Date: Wed, 4 Dec 2024 21:29:30 +0530 Subject: [PATCH 09/79] feat: Override memeooorr service template for service creation (#536) * feat: enhance agent setup with service template integration and validation * feat: add dynamic description for service template in SetupYourAgent --- .../SetupYourAgent/SetupYourAgent.tsx | 59 +++++++++++++++++-- .../SetupPage/SetupYourAgent/validation.ts | 5 +- frontend/constants/serviceTemplates.ts | 2 +- frontend/context/RewardProvider.tsx | 28 ++++----- frontend/context/ServicesProvider.tsx | 6 +- .../StakingContractDetailsProvider.tsx | 2 +- 6 files changed, 76 insertions(+), 26 deletions(-) diff --git a/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx b/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx index a58d1469b..8a2be2f94 100644 --- a/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx +++ b/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx @@ -12,10 +12,13 @@ import { import React, { useCallback, useMemo, useState } from 'react'; import { useUnmount } from 'usehooks-ts'; +import { ServiceTemplate } from '@/client'; import { CustomAlert } from '@/components/Alert'; import { CardFlex } from '@/components/styled/CardFlex'; +import { SERVICE_TEMPLATES } from '@/constants/serviceTemplates'; import { SetupScreen } from '@/enums/SetupScreen'; import { useElectronApi } from '@/hooks/useElectronApi'; +import { useServices } from '@/hooks/useServices'; import { useSetup } from '@/hooks/useSetup'; import { SetupCreateHeader } from '../Create/SetupCreateHeader'; @@ -80,8 +83,9 @@ const InvalidXCredentials = () => ( /> ); +type SetupYourAgentFormProps = { serviceTemplate: ServiceTemplate }; // Agent setup form -const SetupYourAgentForm = () => { +const SetupYourAgentForm = ({ serviceTemplate }: SetupYourAgentFormProps) => { const { goto } = useSetup(); const [form] = Form.useForm(); @@ -97,7 +101,7 @@ const SetupYourAgentForm = () => { const electronApi = useElectronApi(); const onFinish = useCallback( - async (values: Record) => { + async (values: Record) => { try { setIsSubmitting(true); @@ -126,7 +130,36 @@ const SetupYourAgentForm = () => { // wait for agent setup to complete setSubmitButtonText('Setting up agent...'); - await onAgentSetupComplete(); + + const overriddenServiceConfig: ServiceTemplate = { + ...serviceTemplate, + description: `Memeooorr @${values.xUsername}`, + env_variables: { + ...serviceTemplate.env_variables, + TWIKIT_USERNAME: { + ...serviceTemplate.env_variables.TWIKIT_USERNAME, + value: values.xUsername, + }, + TWIKIT_EMAIL: { + ...serviceTemplate.env_variables.TWIKIT_EMAIL, + value: values.xEmail, + }, + TWIKIT_PASSWORD: { + ...serviceTemplate.env_variables.TWIKIT_PASSWORD, + value: values.xPassword, + }, + GENAI_API_KEY: { + ...serviceTemplate.env_variables.GENAI_API_KEY, + value: values.geminiApiKey, + }, + PERSONA: { + ...serviceTemplate.env_variables.PERSONA, + value: values.personaDescription, + }, + }, + }; + + await onAgentSetupComplete(overriddenServiceConfig); // move to next page goto(SetupScreen.SetupEoaFunding); @@ -138,7 +171,7 @@ const SetupYourAgentForm = () => { setSubmitButtonText('Continue'); } }, - [electronApi, goto], + [electronApi, goto, serviceTemplate], ); // Clean up @@ -229,6 +262,22 @@ const SetupYourAgentForm = () => { }; export const SetupYourAgent = () => { + const { selectedAgentType } = useServices(); + const serviceTemplate = SERVICE_TEMPLATES.find( + (template) => template.agentType === selectedAgentType, + ); + + if (!serviceTemplate) { + return ( + Please select an agent type first!} + className="mb-8" + /> + ); + } + return ( @@ -239,7 +288,7 @@ export const SetupYourAgent = () => { - + You won’t be able to update your agent’s configuration after this diff --git a/frontend/components/SetupPage/SetupYourAgent/validation.ts b/frontend/components/SetupPage/SetupYourAgent/validation.ts index 97db97e4e..fc06fa314 100644 --- a/frontend/components/SetupPage/SetupYourAgent/validation.ts +++ b/frontend/components/SetupPage/SetupYourAgent/validation.ts @@ -1,3 +1,4 @@ +import { ServiceTemplate } from '@/client'; import { delayInSeconds } from '@/utils/delay'; /** @@ -56,7 +57,9 @@ export const validateTwitterCredentials = async ( } }; -export const onAgentSetupComplete = async () => { +export const onAgentSetupComplete = async (serviceConfig: ServiceTemplate) => { + window.console.log('Agent setup complete:', serviceConfig); + // TODO: send to backend and remove the delay await delayInSeconds(2); }; diff --git a/frontend/constants/serviceTemplates.ts b/frontend/constants/serviceTemplates.ts index 02474563d..be41adb74 100644 --- a/frontend/constants/serviceTemplates.ts +++ b/frontend/constants/serviceTemplates.ts @@ -213,7 +213,7 @@ export const SERVICE_TEMPLATES: ServiceTemplate[] = [ }, }, }, -]; +] as const; export const getServiceTemplates = (): ServiceTemplate[] => SERVICE_TEMPLATES; diff --git a/frontend/context/RewardProvider.tsx b/frontend/context/RewardProvider.tsx index 8d4010fdc..6456a8f97 100644 --- a/frontend/context/RewardProvider.tsx +++ b/frontend/context/RewardProvider.tsx @@ -10,8 +10,6 @@ import { useMemo, } from 'react'; -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'; @@ -35,9 +33,6 @@ export const RewardContext = createContext<{ updateRewards: async () => {}, }); -const currentAgent = AGENT_CONFIG.trader; // TODO: replace with dynamic agent selection -const currentChainId = GNOSIS_CHAIN_CONFIG.evmChainId; // TODO: replace with selectedAgentConfig.chainId - /** * hook to fetch staking rewards details */ @@ -45,15 +40,14 @@ const useStakingRewardsDetails = () => { const { isOnline } = useContext(OnlineStatusContext); const { selectedStakingProgramId } = useContext(StakingProgramContext); - const { selectedService } = useServices(); - // const { service } = useService(selectedService?.service_config_id); - + const { selectedService, selectedAgentConfig } = useServices(); const serviceConfigId = selectedService?.service_config_id; + const currentChainId = selectedAgentConfig.evmHomeChainId; // fetch chain data from the selected service const chainData = !isNil(selectedService?.chain_configs) ? selectedService?.chain_configs?.[asMiddlewareChain(currentChainId)] - .chain_data + ?.chain_data : null; const multisig = chainData?.multisig; const token = chainData?.token; @@ -67,14 +61,13 @@ const useStakingRewardsDetails = () => { token!, ), queryFn: async () => { - const response = await currentAgent.serviceApi.getAgentStakingRewardsInfo( - { + const response = + await selectedAgentConfig.serviceApi.getAgentStakingRewardsInfo({ agentMultisigAddress: multisig!, serviceId: token!, stakingProgramId: selectedStakingProgramId!, chainId: currentChainId, - }, - ); + }); return StakingRewardsInfoSchema.parse(response); }, enabled: @@ -95,9 +88,14 @@ const useAvailableRewardsForEpoch = () => { const { isOnline } = useContext(OnlineStatusContext); const { selectedStakingProgramId } = useContext(StakingProgramContext); - const { selectedService, isFetched: isLoaded } = useServices(); + const { + selectedService, + isFetched: isLoaded, + selectedAgentConfig, + } = useServices(); const serviceConfigId = isLoaded && selectedService ? selectedService?.service_config_id : ''; + const currentChainId = selectedAgentConfig.evmHomeChainId; return useQuery({ queryKey: REACT_QUERY_KEYS.AVAILABLE_REWARDS_FOR_EPOCH_KEY( @@ -107,7 +105,7 @@ const useAvailableRewardsForEpoch = () => { currentChainId, ), queryFn: async () => { - return await currentAgent.serviceApi.getAvailableRewardsForEpoch( + return await selectedAgentConfig.serviceApi.getAvailableRewardsForEpoch( selectedStakingProgramId!, currentChainId, ); diff --git a/frontend/context/ServicesProvider.tsx b/frontend/context/ServicesProvider.tsx index f535d87b8..0f11be2df 100644 --- a/frontend/context/ServicesProvider.tsx +++ b/frontend/context/ServicesProvider.tsx @@ -59,8 +59,8 @@ export const ServicesContext = createContext({ selectService: noop, isSelectedServiceStatusFetched: false, refetchSelectedServiceStatus: noop, - selectedAgentConfig: AGENT_CONFIG[AgentType.PredictTrader], - selectedAgentType: AgentType.PredictTrader, + selectedAgentConfig: AGENT_CONFIG[AgentType.Memeooorr], // TODO: from storage + selectedAgentType: AgentType.Memeooorr, // TODO: from storage updateAgentType: noop, overrideSelectedServiceStatus: noop, }); @@ -74,7 +74,7 @@ export const ServicesProvider = ({ children }: PropsWithChildren) => { // selected agent type const [selectedAgentType, setAgentType] = useState( - AgentType.PredictTrader, + AgentType.Memeooorr, // TODO: from storage ); // user selected service identifier diff --git a/frontend/context/StakingContractDetailsProvider.tsx b/frontend/context/StakingContractDetailsProvider.tsx index 776ff8adc..b491e153a 100644 --- a/frontend/context/StakingContractDetailsProvider.tsx +++ b/frontend/context/StakingContractDetailsProvider.tsx @@ -179,7 +179,7 @@ export const StakingContractDetailsProvider = ({ serviceNftTokenId: !isNil(selectedService?.service_config_id) ? selectedService?.chain_configs?.[ asMiddlewareChain(selectedAgentConfig.evmHomeChainId) - ].chain_data.token + ]?.chain_data?.token : null, stakingProgramId: selectedStakingProgramId, }); From d3dfe91f967c2d8ebcdf8ab1feb2a21d9a53d64a Mon Sep 17 00:00:00 2001 From: Atatakai Date: Wed, 4 Dec 2024 21:00:20 +0400 Subject: [PATCH 10/79] feat: navigation fixes --- frontend/components/AgentSelection.tsx | 42 ++++++++++++++++++++----- frontend/components/SetupPage/index.tsx | 1 - frontend/pages/index.tsx | 7 +---- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/frontend/components/AgentSelection.tsx b/frontend/components/AgentSelection.tsx index 65fb1d473..48e977d29 100644 --- a/frontend/components/AgentSelection.tsx +++ b/frontend/components/AgentSelection.tsx @@ -6,7 +6,12 @@ import { useCallback } from 'react'; import { AGENT_CONFIG } from '@/config/agents'; import { COLOR } from '@/constants/colors'; import { AgentType } from '@/enums/Agent'; +import { Pages } from '@/enums/Pages'; +import { SetupScreen } from '@/enums/SetupScreen'; +import { usePageState } from '@/hooks/usePageState'; import { useServices } from '@/hooks/useServices'; +import { useSetup } from '@/hooks/useSetup'; +import { useMasterWalletContext } from '@/hooks/useWallet'; import { AgentConfig } from '@/types/Agent'; import { SetupCreateHeader } from './SetupPage/Create/SetupCreateHeader'; @@ -18,23 +23,43 @@ type EachAgentProps = { showSelected: boolean; agentType: AgentType; agentConfig: AgentConfig; - onSelect: () => void; }; const EachAgent = ({ showSelected, agentType, agentConfig, - onSelect, }: EachAgentProps) => { + const { goto: gotoSetup } = useSetup(); + const { goto: gotoPage } = usePageState(); const { selectedAgentType, updateAgentType } = useServices(); + const { masterSafes, isLoading } = useMasterWalletContext(); const isCurrentAgent = showSelected ? selectedAgentType === agentType : false; const handleSelectAgent = useCallback(() => { updateAgentType(agentType); - onSelect(); - }, [agentType, updateAgentType, onSelect]); + + // If a safe was not created for the selected agent type + // Need to navigate to onboarding page + if ( + !masterSafes?.find( + (masterSafe) => + masterSafe.evmChainId === AGENT_CONFIG[agentType].evmHomeChainId, + ) + ) { + if (agentType === AgentType.Memeooorr) { + // if the selected type is Memeooorr - should set up the agent first + gotoPage(Pages.Setup); + gotoSetup(SetupScreen.SetupYourAgent); + } else { + gotoSetup(SetupScreen.SetupEoaFunding); + } + } else { + // If the safe is created for the selected agent, navigate to main page + gotoPage(Pages.Main); + } + }, [agentType, gotoPage, gotoSetup, masterSafes, updateAgentType]); return ( Selected Agent ) : ( - )} @@ -80,7 +109,6 @@ const EachAgent = ({ type AgentSelectionProps = { showSelected?: boolean; onPrev: () => void; - onNext: () => void; }; /** @@ -89,7 +117,6 @@ type AgentSelectionProps = { export const AgentSelection = ({ showSelected = true, onPrev, - onNext, }: AgentSelectionProps) => ( @@ -102,7 +129,6 @@ export const AgentSelection = ({ showSelected={showSelected} agentType={agentType as AgentType} agentConfig={agentConfig} - onSelect={onNext} /> ); })} diff --git a/frontend/components/SetupPage/index.tsx b/frontend/components/SetupPage/index.tsx index 7646c4f07..3397334f7 100644 --- a/frontend/components/SetupPage/index.tsx +++ b/frontend/components/SetupPage/index.tsx @@ -50,7 +50,6 @@ export const Setup = () => { goto(SetupScreen.SetupBackupSigner)} - onNext={() => goto(SetupScreen.SetupEoaFunding)} /> ); case SetupScreen.SetupYourAgent: diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index 813366acf..8f725642a 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -48,12 +48,7 @@ export default function Home() { case Pages.Main: return
; case Pages.SwitchAgent: - return ( - goto(Pages.Main)} - onNext={() => goto(Pages.Main)} - /> - ); + return goto(Pages.Main)} />; case Pages.Settings: return ; case Pages.HelpAndSupport: From b00fdbb373b743b03ca75e04e0c240f8a65d12b1 Mon Sep 17 00:00:00 2001 From: Atatakai Date: Wed, 4 Dec 2024 21:15:39 +0400 Subject: [PATCH 11/79] feat: disable back button for select agent during onboarding --- frontend/components/AgentSelection.tsx | 4 +++- frontend/components/SetupPage/index.tsx | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/components/AgentSelection.tsx b/frontend/components/AgentSelection.tsx index 48e977d29..f35f5ce40 100644 --- a/frontend/components/AgentSelection.tsx +++ b/frontend/components/AgentSelection.tsx @@ -108,6 +108,7 @@ const EachAgent = ({ type AgentSelectionProps = { showSelected?: boolean; + canGoBack?: boolean; onPrev: () => void; }; @@ -116,10 +117,11 @@ type AgentSelectionProps = { */ export const AgentSelection = ({ showSelected = true, + canGoBack = true, onPrev, }: AgentSelectionProps) => ( - + Select your agent {entries(AGENT_CONFIG).map(([agentType, agentConfig]) => { diff --git a/frontend/components/SetupPage/index.tsx b/frontend/components/SetupPage/index.tsx index 3397334f7..a22c81ef2 100644 --- a/frontend/components/SetupPage/index.tsx +++ b/frontend/components/SetupPage/index.tsx @@ -2,7 +2,6 @@ import { useContext, useMemo } from 'react'; import { SetupContext } from '@/context/SetupProvider'; import { SetupScreen } from '@/enums/SetupScreen'; -import { useSetup } from '@/hooks/useSetup'; import { AgentSelection } from '../AgentSelection'; import { SetupBackupSigner } from './Create/SetupBackupSigner'; @@ -25,7 +24,6 @@ const UnexpectedError = () => ( export const Setup = () => { const { setupObject } = useContext(SetupContext); - const { goto } = useSetup(); const setupScreen = useMemo(() => { switch (setupObject.state) { @@ -49,7 +47,8 @@ export const Setup = () => { return ( goto(SetupScreen.SetupBackupSigner)} + onPrev={() => {}} + canGoBack={false} /> ); case SetupScreen.SetupYourAgent: @@ -67,7 +66,7 @@ export const Setup = () => { default: return ; } - }, [setupObject.state, goto]); + }, [setupObject.state]); return setupScreen; }; From 46f4be72b349ca90ef94b647defe6088d68a4a3b Mon Sep 17 00:00:00 2001 From: Mohan Date: Thu, 5 Dec 2024 01:09:30 +0530 Subject: [PATCH 12/79] feat: create dummy service (#541) * feat: enhance agent setup process with staking program integration and UI improvements * feat: update hash value for Memeooorr service template * feat: simplify MainHeader component by removing unused selectedAgentType and update navigation in SetupPage --- .../MainPage/sections/OlasBalanceSection.tsx | 1 + .../SetupYourAgent/SetupYourAgent.tsx | 23 ++++++++++++++----- .../SetupPage/SetupYourAgent/validation.ts | 18 ++++++++++----- frontend/constants/serviceTemplates.ts | 2 +- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/frontend/components/MainPage/sections/OlasBalanceSection.tsx b/frontend/components/MainPage/sections/OlasBalanceSection.tsx index 6dbcbd2de..c413d6d35 100644 --- a/frontend/components/MainPage/sections/OlasBalanceSection.tsx +++ b/frontend/components/MainPage/sections/OlasBalanceSection.tsx @@ -87,6 +87,7 @@ export const MainOlasBalance = ({ bordertop={isBorderTopVisible ? 'true' : 'false'} borderbottom="true" padding="16px 24px" + align="center" > {isBalanceLoaded ? ( diff --git a/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx b/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx index 8a2be2f94..95b226345 100644 --- a/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx +++ b/frontend/components/SetupPage/SetupYourAgent/SetupYourAgent.tsx @@ -20,10 +20,11 @@ import { SetupScreen } from '@/enums/SetupScreen'; import { useElectronApi } from '@/hooks/useElectronApi'; import { useServices } from '@/hooks/useServices'; import { useSetup } from '@/hooks/useSetup'; +import { useStakingProgram } from '@/hooks/useStakingProgram'; import { SetupCreateHeader } from '../Create/SetupCreateHeader'; import { - onAgentSetupComplete, + onDummyServiceCreation, validateGeminiApiKey, validateTwitterCredentials, } from './validation'; @@ -86,7 +87,9 @@ const InvalidXCredentials = () => ( type SetupYourAgentFormProps = { serviceTemplate: ServiceTemplate }; // Agent setup form const SetupYourAgentForm = ({ serviceTemplate }: SetupYourAgentFormProps) => { + const electronApi = useElectronApi(); const { goto } = useSetup(); + const { defaultStakingProgramId } = useStakingProgram(); const [form] = Form.useForm(); const [isSubmitting, setIsSubmitting] = useState(false); @@ -98,10 +101,10 @@ const SetupYourAgentForm = ({ serviceTemplate }: SetupYourAgentFormProps) => { setTwitterCredentialsValidationStatus, ] = useState('unknown'); - const electronApi = useElectronApi(); - const onFinish = useCallback( async (values: Record) => { + if (!defaultStakingProgramId) return; + try { setIsSubmitting(true); @@ -159,7 +162,12 @@ const SetupYourAgentForm = ({ serviceTemplate }: SetupYourAgentFormProps) => { }, }; - await onAgentSetupComplete(overriddenServiceConfig); + await onDummyServiceCreation( + defaultStakingProgramId, + overriddenServiceConfig, + ); + + message.success('Agent setup complete'); // move to next page goto(SetupScreen.SetupEoaFunding); @@ -171,7 +179,7 @@ const SetupYourAgentForm = ({ serviceTemplate }: SetupYourAgentFormProps) => { setSubmitButtonText('Continue'); } }, - [electronApi, goto, serviceTemplate], + [electronApi, defaultStakingProgramId, serviceTemplate, goto], ); // Clean up @@ -187,6 +195,8 @@ const SetupYourAgentForm = ({ serviceTemplate }: SetupYourAgentFormProps) => { [], ); + const canSubmitForm = isSubmitting || !defaultStakingProgramId; + return ( form={form} @@ -194,7 +204,7 @@ const SetupYourAgentForm = ({ serviceTemplate }: SetupYourAgentFormProps) => { layout="vertical" onFinish={onFinish} validateMessages={validateMessages} - disabled={isSubmitting} + disabled={canSubmitForm} > { size="large" block loading={isSubmitting} + disabled={canSubmitForm} > {submitButtonText} diff --git a/frontend/components/SetupPage/SetupYourAgent/validation.ts b/frontend/components/SetupPage/SetupYourAgent/validation.ts index fc06fa314..542baf0ae 100644 --- a/frontend/components/SetupPage/SetupYourAgent/validation.ts +++ b/frontend/components/SetupPage/SetupYourAgent/validation.ts @@ -1,5 +1,6 @@ import { ServiceTemplate } from '@/client'; -import { delayInSeconds } from '@/utils/delay'; +import { StakingProgramId } from '@/enums/StakingProgram'; +import { ServicesService } from '@/service/Services'; /** * Validate the Google Gemini API key @@ -57,9 +58,14 @@ export const validateTwitterCredentials = async ( } }; -export const onAgentSetupComplete = async (serviceConfig: ServiceTemplate) => { - window.console.log('Agent setup complete:', serviceConfig); - - // TODO: send to backend and remove the delay - await delayInSeconds(2); +export const onDummyServiceCreation = async ( + stakingProgramId: StakingProgramId, + serviceTemplateConfig: ServiceTemplate, +) => { + await ServicesService.createService({ + serviceTemplate: serviceTemplateConfig, + deploy: true, + useMechMarketplace: true, + stakingProgramId, + }); }; diff --git a/frontend/constants/serviceTemplates.ts b/frontend/constants/serviceTemplates.ts index be41adb74..b142e6aa9 100644 --- a/frontend/constants/serviceTemplates.ts +++ b/frontend/constants/serviceTemplates.ts @@ -158,7 +158,7 @@ export const SERVICE_TEMPLATES: ServiceTemplate[] = [ { agentType: AgentType.Memeooorr, name: 'Memeooorr', - hash: '', // TODO: fix value + hash: 'bafybeidvfwl7mrrxh7rjq2ytjihr6xc4ac4hniszawsrc4oc7p6ubklloy', description: 'Memeooorr @twitter_handle', // should be overwritten with twitter username image: 'https://gateway.autonolas.tech/ipfs/QmQYDGMg8m91QQkTWSSmANs5tZwKrmvUCawXZfXVVWQPcu', From d558de80c1c2c997c4047ba18333c0abfaf40adc Mon Sep 17 00:00:00 2001 From: Atatakai Date: Thu, 5 Dec 2024 12:23:10 +0400 Subject: [PATCH 13/79] chore: review fixes --- frontend/components/AgentSelection.tsx | 19 ++++++++----------- .../SetupPage/Create/SetupCreateHeader.tsx | 11 +++++------ .../SetupPage/Create/SetupEoaFunding.tsx | 2 +- .../SetupPage/Create/SetupSeedPhrase.tsx | 2 +- frontend/components/SetupPage/index.tsx | 8 +------- 5 files changed, 16 insertions(+), 26 deletions(-) diff --git a/frontend/components/AgentSelection.tsx b/frontend/components/AgentSelection.tsx index f35f5ce40..567629839 100644 --- a/frontend/components/AgentSelection.tsx +++ b/frontend/components/AgentSelection.tsx @@ -40,14 +40,14 @@ const EachAgent = ({ const handleSelectAgent = useCallback(() => { updateAgentType(agentType); - // If a safe was not created for the selected agent type - // Need to navigate to onboarding page - if ( - !masterSafes?.find( - (masterSafe) => - masterSafe.evmChainId === AGENT_CONFIG[agentType].evmHomeChainId, - ) - ) { + const isSafeCreated = masterSafes?.find( + (masterSafe) => + masterSafe.evmChainId === AGENT_CONFIG[agentType].evmHomeChainId, + ); + + if (isSafeCreated) { + gotoPage(Pages.Main); + } else { if (agentType === AgentType.Memeooorr) { // if the selected type is Memeooorr - should set up the agent first gotoPage(Pages.Setup); @@ -55,9 +55,6 @@ const EachAgent = ({ } else { gotoSetup(SetupScreen.SetupEoaFunding); } - } else { - // If the safe is created for the selected agent, navigate to main page - gotoPage(Pages.Main); } }, [agentType, gotoPage, gotoSetup, masterSafes, updateAgentType]); diff --git a/frontend/components/SetupPage/Create/SetupCreateHeader.tsx b/frontend/components/SetupPage/Create/SetupCreateHeader.tsx index d331bd9a4..e41af3bf1 100644 --- a/frontend/components/SetupPage/Create/SetupCreateHeader.tsx +++ b/frontend/components/SetupPage/Create/SetupCreateHeader.tsx @@ -8,16 +8,15 @@ import { SetupScreen } from '@/enums/SetupScreen'; import { useSetup } from '@/hooks/useSetup'; type SetupCreateHeaderProps = { - prev: SetupScreen | (() => void); + prev?: SetupScreen | (() => void); disabled?: boolean; }; -export const SetupCreateHeader = ({ - prev, - disabled = false, -}: SetupCreateHeaderProps) => { +export const SetupCreateHeader = ({ prev }: SetupCreateHeaderProps) => { const { goto } = useSetup(); const handleBack = useCallback(() => { + if (!prev) return; + isFunction(prev) ? prev() : goto(prev); }, [goto, prev]); @@ -26,7 +25,7 @@ export const SetupCreateHeader = ({ + )} + + {UNICODE_SYMBOLS.OLAS} {displayedBalance} OLAS - - {isBalanceBreakdownEnabled && ( - goto(Pages.YourWalletBreakdown)} - > - See breakdown - - - )} ) : ( diff --git a/frontend/components/YourWalletPage/index.tsx b/frontend/components/YourWalletPage/index.tsx index cd0329e18..003bcc3f2 100644 --- a/frontend/components/YourWalletPage/index.tsx +++ b/frontend/components/YourWalletPage/index.tsx @@ -204,7 +204,7 @@ const MasterEoaSignerNativeBalance = () => { }; export const YourWalletPage = () => { - const isBalanceBreakdownEnabled = useFeatureFlag('balance-breakdown'); + const isBalanceBreakdownEnabled = useFeatureFlag('manage-wallet'); const { services } = useServices(); const { goto } = usePageState(); diff --git a/frontend/enums/Pages.ts b/frontend/enums/Pages.ts index 986b01413..63742cd30 100644 --- a/frontend/enums/Pages.ts +++ b/frontend/enums/Pages.ts @@ -6,7 +6,7 @@ export enum Pages { Receive, Send, ManageStaking, - YourWalletBreakdown, + ManageWallet, RewardsHistory, AddBackupWalletViaSafe, SwitchAgent, diff --git a/frontend/hooks/useFeatureFlag.ts b/frontend/hooks/useFeatureFlag.ts index 043a7e78c..c2d09dcf9 100644 --- a/frontend/hooks/useFeatureFlag.ts +++ b/frontend/hooks/useFeatureFlag.ts @@ -7,7 +7,7 @@ import { useServices } from './useServices'; const FeatureFlagsSchema = z.enum([ 'last-transactions', - 'balance-breakdown', + 'manage-wallet', 'rewards-streak', 'staking-contract-section', ]); @@ -25,13 +25,13 @@ const FeaturesConfigSchema = z.record( */ const FEATURES_CONFIG = FeaturesConfigSchema.parse({ [AgentType.PredictTrader]: { - 'balance-breakdown': true, + 'manage-wallet': true, 'last-transactions': true, 'rewards-streak': true, 'staking-contract-section': true, }, [AgentType.Memeooorr]: { - 'balance-breakdown': false, + 'manage-wallet': false, 'last-transactions': false, 'rewards-streak': false, 'staking-contract-section': false, diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index 8f725642a..a4b08ef87 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -55,7 +55,7 @@ export default function Home() { return ; case Pages.ManageStaking: return ; - case Pages.YourWalletBreakdown: + case Pages.ManageWallet: return ; case Pages.RewardsHistory: return ; diff --git a/frontend/styles/globals.scss b/frontend/styles/globals.scss index d5c6f5776..982801a0c 100644 --- a/frontend/styles/globals.scss +++ b/frontend/styles/globals.scss @@ -170,13 +170,6 @@ textarea, .p-0 { padding: 0 !important; } -.pl-16 { - padding-left: 16px !important; -} -.pr-16 { - padding-right: 16px !important; -} - .pl-16 { padding-left: 16px !important; From d173f032051bca364694fb2f7c03b5a499e9c4d5 Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Wed, 11 Dec 2024 03:28:44 +0530 Subject: [PATCH 33/79] refactor: simplify backup owners check in AddBackupWalletAlert component --- .../AlertSections/AddBackupWalletAlert.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx b/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx index 2374d7029..e777648f6 100644 --- a/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx +++ b/frontend/components/MainPage/sections/AlertSections/AddBackupWalletAlert.tsx @@ -25,14 +25,13 @@ export const AddBackupWalletAlert = () => { if (!masterSafeOwnersIsFetched) return null; if (isNil(backupOwners)) return null; - if ( - isEmpty( - backupOwners.filter( - (owner) => !isNil(masterEoa) && owner === masterEoa.address, - ), - ) - ) - return null; + + const hasNoBackupOwners = isEmpty( + backupOwners.filter( + (owner) => !isNil(masterEoa) && owner === masterEoa.address, + ), + ); + if (hasNoBackupOwners) return null; return ( Date: Wed, 11 Dec 2024 03:34:24 +0530 Subject: [PATCH 34/79] feat: add LowFunds component and rename LowTradingBalanceAlert --- .../MainPage/sections/AlertSections/LowFunds/LowFunds.tsx | 7 +++++++ .../LowOperatingBalanceAlert.tsx} | 2 +- .../components/MainPage/sections/AlertSections/index.tsx | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx rename frontend/components/MainPage/sections/AlertSections/{LowTradingBalanceAlert.tsx => LowFunds/LowOperatingBalanceAlert.tsx} (95%) diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx new file mode 100644 index 000000000..24ef0870b --- /dev/null +++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx @@ -0,0 +1,7 @@ +import { LowOperatingBalanceAlert } from './LowOperatingBalanceAlert'; + +export const LowFunds = () => ( + <> + + +); diff --git a/frontend/components/MainPage/sections/AlertSections/LowTradingBalanceAlert.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx similarity index 95% rename from frontend/components/MainPage/sections/AlertSections/LowTradingBalanceAlert.tsx rename to frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx index 1993859ac..b8b822a04 100644 --- a/frontend/components/MainPage/sections/AlertSections/LowTradingBalanceAlert.tsx +++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx @@ -7,7 +7,7 @@ import { useStore } from '@/hooks/useStore'; const { Text, Title } = Typography; -export const LowTradingBalanceAlert = () => { +export const LowOperatingBalanceAlert = () => { const { isLoaded: isBalanceLoaded, isLowBalance } = useBalanceContext(); const { storeState } = useStore(); diff --git a/frontend/components/MainPage/sections/AlertSections/index.tsx b/frontend/components/MainPage/sections/AlertSections/index.tsx index a8c70417c..d9ed1af87 100644 --- a/frontend/components/MainPage/sections/AlertSections/index.tsx +++ b/frontend/components/MainPage/sections/AlertSections/index.tsx @@ -2,7 +2,7 @@ import { CardSection } from '@/components/styled/CardSection'; import { AddBackupWalletAlert } from './AddBackupWalletAlert'; import { AvoidSuspensionAlert } from './AvoidSuspensionAlert'; -import { LowTradingBalanceAlert } from './LowTradingBalanceAlert'; +import { LowFunds } from './LowFunds/LowFunds'; import { NewStakingProgramAlert } from './NewStakingProgramAlert'; import { NoAvailableSlotsOnTheContract } from './NoAvailableSlotsOnTheContract'; import { UpdateAvailableAlert } from './UpdateAvailableAlert'; @@ -14,7 +14,7 @@ export const AlertSections = () => { - + ); From 17743484fbd764e086a25164deef50c6ef4b9ea9 Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Wed, 11 Dec 2024 04:21:48 +0530 Subject: [PATCH 35/79] feat: add InlineBanner component and update LowOperatingBalanceAlert to display safe address --- .../AlertSections/LowFunds/InlineBanner.tsx | 46 +++++++++++++++++++ .../LowFunds/LowOperatingBalanceAlert.tsx | 40 ++++++++++++++-- frontend/hooks/useChainDetails.ts | 16 +++++++ frontend/styles/globals.scss | 22 ++++----- 4 files changed, 107 insertions(+), 17 deletions(-) create mode 100644 frontend/components/MainPage/sections/AlertSections/LowFunds/InlineBanner.tsx create mode 100644 frontend/hooks/useChainDetails.ts diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/InlineBanner.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/InlineBanner.tsx new file mode 100644 index 000000000..fd0088942 --- /dev/null +++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/InlineBanner.tsx @@ -0,0 +1,46 @@ +import { CopyOutlined } from '@ant-design/icons'; +import { Button, Typography } from 'antd'; +import styled from 'styled-components'; + +import { COLOR } from '@/constants/colors'; +import { Address } from '@/types/Address'; +import { copyToClipboard } from '@/utils/copyToClipboard'; +import { truncateAddress } from '@/utils/truncate'; + +const { Text } = Typography; + +const InlineBannerContainer = styled.div` + display: flex; + padding: 8px 8px 8px 12px; + justify-content: space-between; + align-items: center; + align-self: stretch; + background-color: ${COLOR.WHITE}; + box-shadow: + 0px 1px 2px 0px rgba(0, 0, 0, 0.03), + 0px 1px 6px -1px rgba(0, 0, 0, 0.02), + 0px 2px 4px 0px rgba(0, 0, 0, 0.02); + color: ${COLOR.TEXT}; + border-radius: 8px; + margin-top: 8px; +`; + +type InlineBannerProps = { text: string; address: Address }; + +export const InlineBanner = ({ text, address }: InlineBannerProps) => { + return ( + +
{text}
+
+ {truncateAddress(address)} + +
+
+ ); +}; diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx index b8b822a04..a9746f030 100644 --- a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx +++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx @@ -1,19 +1,40 @@ import { Flex, Typography } from 'antd'; +import { useMemo } from 'react'; import { CustomAlert } from '@/components/Alert'; import { LOW_MASTER_SAFE_BALANCE } from '@/constants/thresholds'; import { useBalanceContext } from '@/hooks/useBalanceContext'; +import { useChainDetails } from '@/hooks/useChainDetails'; +import { useServices } from '@/hooks/useServices'; import { useStore } from '@/hooks/useStore'; +import { useMasterWalletContext } from '@/hooks/useWallet'; + +import { InlineBanner } from './InlineBanner'; const { Text, Title } = Typography; export const LowOperatingBalanceAlert = () => { + const { selectedAgentConfig } = useServices(); + const { evmHomeChainId: homeChainId } = selectedAgentConfig; + + const { masterSafes } = useMasterWalletContext(); const { isLoaded: isBalanceLoaded, isLowBalance } = useBalanceContext(); const { storeState } = useStore(); - if (!isBalanceLoaded) return null; - if (!storeState?.isInitialFunded) return; - if (!isLowBalance) return null; + const { name, symbol } = useChainDetails(homeChainId); + + const selectedMasterSafe = useMemo(() => { + if (!masterSafes) return; + if (!homeChainId) return; + + return masterSafes.find( + (masterSafe) => masterSafe.evmChainId === homeChainId, + ); + }, [masterSafes, homeChainId]); + + // if (!isBalanceLoaded) return null; + // if (!storeState?.isInitialFunded) return; + // if (!isLowBalance) return null; return ( { message={ - Trading balance is too low + Operating balance is too low - {`To run your agent, add at least $${LOW_MASTER_SAFE_BALANCE} XDAI to your account.`} + To run your agent, add at least + {` ${LOW_MASTER_SAFE_BALANCE} ${symbol} `} + on {name} chain to your safe. Your agent is at risk of missing its targets, which would result in several days' suspension. + + {selectedMasterSafe?.address && ( + + )} } /> diff --git a/frontend/hooks/useChainDetails.ts b/frontend/hooks/useChainDetails.ts new file mode 100644 index 000000000..40b8feb5c --- /dev/null +++ b/frontend/hooks/useChainDetails.ts @@ -0,0 +1,16 @@ +import { CHAIN_CONFIG } from '@/config/chains'; +import { EvmChainId } from '@/enums/Chain'; + +export const useChainDetails = (chainId: EvmChainId) => { + if (!chainId) throw new Error('Chain ID is required'); + + const details = CHAIN_CONFIG[chainId]; + if (!details) { + throw new Error(`Chain details not found for chain ID: ${chainId}`); + } + + return { + name: details.name, + symbol: details.nativeToken.symbol, + }; +}; diff --git a/frontend/styles/globals.scss b/frontend/styles/globals.scss index d5c6f5776..1ca48f426 100644 --- a/frontend/styles/globals.scss +++ b/frontend/styles/globals.scss @@ -150,12 +150,20 @@ textarea, margin-bottom: 16px !important; } +.mb-auto { + margin-bottom: auto !important; +} + .ml-auto { margin-left: auto !important; } -.mb-auto { - margin-bottom: auto !important; +.ml-8 { + margin-left: 8px !important; +} + +.ml-12 { + margin-left: 12px !important; } .mt-8 { @@ -177,7 +185,6 @@ textarea, padding-right: 16px !important; } - .pl-16 { padding-left: 16px !important; } @@ -186,7 +193,6 @@ textarea, padding-right: 16px !important; } - .mx-auto { margin-left: auto !important; margin-right: auto !important; @@ -227,14 +233,6 @@ textarea, } // font color -.text-light { - color: #4D596A !important; -} - -.text-light { - color: #4D596A !important; -} - .text-primary { color: #7E22CE !important; } From 52d4ca585aa1ee2f5f266a4a369720c0004cd10c Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Wed, 11 Dec 2024 04:30:22 +0530 Subject: [PATCH 36/79] feat: enhance InlineBanner layout and improve LowOperatingBalanceAlert logic --- .../AlertSections/LowFunds/InlineBanner.tsx | 20 +++++++++---------- .../LowFunds/LowOperatingBalanceAlert.tsx | 11 +++++----- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/InlineBanner.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/InlineBanner.tsx index fd0088942..34a6b3ca9 100644 --- a/frontend/components/MainPage/sections/AlertSections/LowFunds/InlineBanner.tsx +++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/InlineBanner.tsx @@ -1,5 +1,5 @@ import { CopyOutlined } from '@ant-design/icons'; -import { Button, Typography } from 'antd'; +import { Button, Flex, Typography } from 'antd'; import styled from 'styled-components'; import { COLOR } from '@/constants/colors'; @@ -9,30 +9,28 @@ import { truncateAddress } from '@/utils/truncate'; const { Text } = Typography; -const InlineBannerContainer = styled.div` - display: flex; +const InlineBannerContainer = styled(Flex)` + width: 100%; + margin-top: 8px; padding: 8px 8px 8px 12px; - justify-content: space-between; - align-items: center; - align-self: stretch; background-color: ${COLOR.WHITE}; + color: ${COLOR.TEXT}; + border-radius: 8px; + box-sizing: border-box; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.03), 0px 1px 6px -1px rgba(0, 0, 0, 0.02), 0px 2px 4px 0px rgba(0, 0, 0, 0.02); - color: ${COLOR.TEXT}; - border-radius: 8px; - margin-top: 8px; `; type InlineBannerProps = { text: string; address: Address }; export const InlineBanner = ({ text, address }: InlineBannerProps) => { return ( - +
{text}
- {truncateAddress(address)} + {truncateAddress(address)} -
+
); }; From 45758b7df10cf536ecd4731d498cf9ef2db40c33 Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Wed, 11 Dec 2024 15:49:37 +0530 Subject: [PATCH 42/79] feat: enhance balance comparison logic in useMasterBalances and add formatEther utility function --- frontend/hooks/useBalanceContext.ts | 6 +++++- frontend/utils/numberFormatters.ts | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/hooks/useBalanceContext.ts b/frontend/hooks/useBalanceContext.ts index 640fc316b..6e1c93d88 100644 --- a/frontend/hooks/useBalanceContext.ts +++ b/frontend/hooks/useBalanceContext.ts @@ -3,6 +3,7 @@ import { useContext, useMemo } from 'react'; import { CHAIN_CONFIG } from '@/config/chains'; import { BalanceContext, WalletBalanceResult } from '@/context/BalanceProvider'; import { Optional } from '@/types/Util'; +import { formatEther } from '@/utils/numberFormatters'; import { useService } from './useService'; import { useServices } from './useServices'; @@ -151,7 +152,10 @@ export const useMasterBalances = () => { homeChainNativeToken.symbol ]; - return masterSafeNative.balance < agentNativeGasRequirement; + return ( + masterSafeNative.balance < + parseFloat(formatEther(agentNativeGasRequirement)) + ); }, [ masterSafeNative, selectedAgentConfig.agentSafeFundingRequirements, diff --git a/frontend/utils/numberFormatters.ts b/frontend/utils/numberFormatters.ts index 88411c851..06a2ba047 100644 --- a/frontend/utils/numberFormatters.ts +++ b/frontend/utils/numberFormatters.ts @@ -26,6 +26,7 @@ export const formatUnits = (value: BigNumberish, decimals = 18): string => { /** * Assumes the input is in wei and converts it to ether + * @example `formatEther('1000000000000000000')` => '1.0' */ export const formatEther = (wei: BigNumberish): string => { return ethers.utils.formatEther(wei); From 01a20ef774bdac0e317edef87274a60669167b2a Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Wed, 11 Dec 2024 16:58:05 +0530 Subject: [PATCH 43/79] fix: correct agent native gas requirement check in useMasterBalances and improve dependency array --- frontend/hooks/useBalanceContext.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/frontend/hooks/useBalanceContext.ts b/frontend/hooks/useBalanceContext.ts index 6e1c93d88..20d6437bd 100644 --- a/frontend/hooks/useBalanceContext.ts +++ b/frontend/hooks/useBalanceContext.ts @@ -149,18 +149,16 @@ export const useMasterBalances = () => { const agentNativeGasRequirement = selectedAgentConfig.agentSafeFundingRequirements?.[ - homeChainNativeToken.symbol + selectedAgentConfig.evmHomeChainId ]; + if (!agentNativeGasRequirement) return; + return ( masterSafeNative.balance < - parseFloat(formatEther(agentNativeGasRequirement)) + parseFloat(formatEther(`${agentNativeGasRequirement}`)) ); - }, [ - masterSafeNative, - selectedAgentConfig.agentSafeFundingRequirements, - homeChainNativeToken?.symbol, - ]); + }, [masterSafeNative, homeChainNativeToken, selectedAgentConfig]); const masterEoaNative = useMemo(() => { if (!masterEoaBalances) return; From ead9d0878112cf4dac41db65845a151ad37b46f8 Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Wed, 11 Dec 2024 17:03:14 +0530 Subject: [PATCH 44/79] refactor: clean up imports and remove unused QR code functionality in AddFundsSection --- .../MainPage/sections/AddFundsSection.tsx | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/frontend/components/MainPage/sections/AddFundsSection.tsx b/frontend/components/MainPage/sections/AddFundsSection.tsx index 9a689cd84..d5db6f6b7 100644 --- a/frontend/components/MainPage/sections/AddFundsSection.tsx +++ b/frontend/components/MainPage/sections/AddFundsSection.tsx @@ -1,16 +1,5 @@ -import { - CopyOutlined, - // QrcodeOutlined, -} from '@ant-design/icons'; -import { - Button, - Flex, - message, - Popover, - // QRCode, - Tooltip, - Typography, -} from 'antd'; +import { CopyOutlined } from '@ant-design/icons'; +import { Button, Flex, message, Popover, Tooltip, Typography } from 'antd'; import Link from 'next/link'; import { forwardRef, useCallback, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; @@ -174,17 +163,5 @@ const AddFundsAddressSection = ({ - )} + }, [agentType, gotoPage, gotoSetup, masterSafes, updateAgentType]); + + return ( + + + + {agentConfig.displayName} + {isCurrentAgent ? ( + Selected Agent + ) : ( + + )} + - - - {agentConfig.displayName} - + + {agentConfig.displayName} + - {agentConfig.description} - - ); -}; + {agentConfig.description} + + ); + }, +); + +EachAgent.displayName = 'EachAgent'; type AgentSelectionProps = { showSelected?: boolean; diff --git a/frontend/components/SetupPage/Create/SetupEoaFunding.tsx b/frontend/components/SetupPage/Create/SetupEoaFunding.tsx index b14556574..dbe6ff0c7 100644 --- a/frontend/components/SetupPage/Create/SetupEoaFunding.tsx +++ b/frontend/components/SetupPage/Create/SetupEoaFunding.tsx @@ -181,7 +181,7 @@ const EOA_FUNDING_MAP: Record< // requiredEth: MIN_ETH_BALANCE_THRESHOLDS[EvmChainId.Base].safeCreation, // }, // } -}; +} as const; /** * EOA funding setup screen @@ -221,8 +221,8 @@ export const SetupEoaFunding = () => { const indexOfCurrentChain = chains.indexOf(currentChain.toString()); const nextChainExists = chains.length > indexOfCurrentChain + 1; + // goto next chain if (nextChainExists) { - // goto next chain setCurrentChain(chains[indexOfCurrentChain + 1] as unknown as EvmChainId); return; } diff --git a/frontend/config/agents.ts b/frontend/config/agents.ts index b0e9ec06c..858508804 100644 --- a/frontend/config/agents.ts +++ b/frontend/config/agents.ts @@ -52,4 +52,4 @@ export const AGENT_CONFIG: { description: 'Autonomously post to Twitter, create and trade memecoins, and interact with other agents.', }, -}; +} as const; From f38dec1ec3fd79a833c2bda1c8e60382ab3cb33d Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Fri, 13 Dec 2024 18:09:56 +0530 Subject: [PATCH 61/79] fix: update comment for clarity and remove unnecessary console logs in EachAgent component --- frontend/components/AgentSelection.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/frontend/components/AgentSelection.tsx b/frontend/components/AgentSelection.tsx index 52fe7312c..e874d9f2b 100644 --- a/frontend/components/AgentSelection.tsx +++ b/frontend/components/AgentSelection.tsx @@ -41,7 +41,8 @@ const EachAgent = memo( updateAgentType(agentType); // DO NOTE REMOVE THIS DELAY - // NOTE: the delay is added so that agentType is update in electron store + // NOTE: the delay is added so that agentType is updated in electron store + // and re-rendered with the updated agentType await delayInSeconds(0.5); const isSafeCreated = masterSafes?.find( @@ -49,12 +50,7 @@ const EachAgent = memo( masterSafe.evmChainId === AGENT_CONFIG[agentType].evmHomeChainId, ); - console.log({ - isSafeCreated, - masterSafes, - agentType, - }); - + // If safe is created for the agent type, then go to main page if (isSafeCreated) { gotoPage(Pages.Main); return; From a3391e97bbd691014f0f5293a83159a8d027dc6a Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Fri, 13 Dec 2024 18:27:29 +0530 Subject: [PATCH 62/79] fix: handle empty staking information with an alert message --- .../StakingContractDetails.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx index 17506754e..634ee165a 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/StakingContractDetails.tsx @@ -62,7 +62,17 @@ export const StakingContractDetails = ({ ); } + if (!list || list.length === 0) { + return ( + + ); + } + return ( - + ); }; From c0816bd405e145b348edca45dac934274c35566f Mon Sep 17 00:00:00 2001 From: Atatakai Date: Fri, 13 Dec 2024 17:34:12 +0400 Subject: [PATCH 63/79] feat: update service template --- templates/memeooorr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/memeooorr.yaml b/templates/memeooorr.yaml index e69b9ddba..84029d463 100644 --- a/templates/memeooorr.yaml +++ b/templates/memeooorr.yaml @@ -6,7 +6,7 @@ service_version: v0.0.1 home_chain: "base" configurations: base: - staking_program_id: meme_base_alpha + staking_program_id: meme_base_alpha_2 nft: bafybeiaakdeconw7j5z76fgghfdjmsr6tzejotxcwnvmp3nroaw3glgyve rpc: http://localhost:8545 # User provided threshold: 1 # TODO: Move to service component From e7ecf6a7fa661682cc4177b677d33b823658137c Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Fri, 13 Dec 2024 20:17:52 +0530 Subject: [PATCH 64/79] feat: add low-funds feature flag to conditionally render LowFunds alert --- .../components/MainPage/sections/AlertSections/index.tsx | 5 ++++- frontend/hooks/useFeatureFlag.ts | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/components/MainPage/sections/AlertSections/index.tsx b/frontend/components/MainPage/sections/AlertSections/index.tsx index d9ed1af87..4799f03b9 100644 --- a/frontend/components/MainPage/sections/AlertSections/index.tsx +++ b/frontend/components/MainPage/sections/AlertSections/index.tsx @@ -1,4 +1,5 @@ import { CardSection } from '@/components/styled/CardSection'; +import { useFeatureFlag } from '@/hooks/useFeatureFlag'; import { AddBackupWalletAlert } from './AddBackupWalletAlert'; import { AvoidSuspensionAlert } from './AvoidSuspensionAlert'; @@ -8,13 +9,15 @@ import { NoAvailableSlotsOnTheContract } from './NoAvailableSlotsOnTheContract'; import { UpdateAvailableAlert } from './UpdateAvailableAlert'; export const AlertSections = () => { + const isLowFundsEnabled = useFeatureFlag('low-funds'); + return ( - + {isLowFundsEnabled && } ); diff --git a/frontend/hooks/useFeatureFlag.ts b/frontend/hooks/useFeatureFlag.ts index c2d09dcf9..bfd9ec18d 100644 --- a/frontend/hooks/useFeatureFlag.ts +++ b/frontend/hooks/useFeatureFlag.ts @@ -10,6 +10,7 @@ const FeatureFlagsSchema = z.enum([ 'manage-wallet', 'rewards-streak', 'staking-contract-section', + 'low-funds', ]); type FeatureFlags = z.infer; @@ -29,12 +30,14 @@ const FEATURES_CONFIG = FeaturesConfigSchema.parse({ 'last-transactions': true, 'rewards-streak': true, 'staking-contract-section': true, + 'low-funds': true, }, [AgentType.Memeooorr]: { 'manage-wallet': false, 'last-transactions': false, 'rewards-streak': false, 'staking-contract-section': false, + 'low-funds': false, }, }); From a80a22c8907d4ec1e6ad2f9c05c3ee2a894b2968 Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 15:59:25 +0000 Subject: [PATCH 65/79] feat: implement operating thresholds for wallet types in agent configuration --- .../AgentButton/AgentNotRunningButton.tsx | 14 ++++-- .../AlertSections/LowFunds/EmptyFunds.tsx | 10 ++++- .../AlertSections/LowFunds/LowFunds.tsx | 18 ++++++-- .../LowFunds/LowOperatingBalanceAlert.tsx | 27 ++++++++++-- .../LowFunds/LowSafeSignerBalanceAlert.tsx | 8 +++- .../CantMigrateAlert.tsx | 8 +++- frontend/config/agents.ts | 43 ++++++++++++++++++- frontend/constants/thresholds.ts | 1 - frontend/types/Agent.ts | 9 ++++ 9 files changed, 119 insertions(+), 19 deletions(-) diff --git a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx index a4e2f4253..03c8554dd 100644 --- a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx +++ b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx @@ -3,11 +3,12 @@ import { isNil, sum } from 'lodash'; import { useCallback, useMemo } from 'react'; import { MiddlewareDeploymentStatus } from '@/client'; +import { CHAIN_CONFIG } from '@/config/chains'; import { MechType } from '@/config/mechs'; import { STAKING_PROGRAMS } from '@/config/stakingPrograms'; import { SERVICE_TEMPLATES } from '@/constants/serviceTemplates'; -import { LOW_MASTER_SAFE_BALANCE } from '@/constants/thresholds'; import { TokenSymbol } from '@/enums/Token'; +import { WalletOwnerType, WalletType } from '@/enums/Wallet'; import { useBalanceContext, useMasterBalances, @@ -123,13 +124,19 @@ export const AgentNotRunningButton = () => { const hasEnoughOlas = (serviceSafeOlasWithStaked ?? 0) >= requiredStakedOlas; const hasEnoughNativeGas = - (masterSafeNativeGasBalance ?? 0) > LOW_MASTER_SAFE_BALANCE; + (masterSafeNativeGasBalance ?? 0) > + selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][ + WalletType.Safe + ][CHAIN_CONFIG[selectedAgentConfig.evmHomeChainId].nativeToken.symbol]; return hasEnoughOlas && hasEnoughNativeGas; } const hasEnoughForInitialDeployment = (masterSafeOlasBalance ?? 0) >= requiredStakedOlas && - (masterSafeNativeGasBalance ?? 0) >= LOW_MASTER_SAFE_BALANCE; + (masterSafeNativeGasBalance ?? 0) >= + selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][ + WalletType.Safe + ][TokenSymbol.XDAI]; return hasEnoughForInitialDeployment; }, [ @@ -145,6 +152,7 @@ export const AgentNotRunningButton = () => { isEligibleForStaking, isAgentEvicted, masterSafeNativeGasBalance, + selectedAgentConfig.operatingThresholds, selectedAgentConfig.evmHomeChainId, serviceTotalStakedOlas, serviceSafeOlasWithStaked, diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/EmptyFunds.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/EmptyFunds.tsx index 188e1a5a3..9f8fb4d6a 100644 --- a/frontend/components/MainPage/sections/AlertSections/LowFunds/EmptyFunds.tsx +++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/EmptyFunds.tsx @@ -2,7 +2,8 @@ import { Divider, Flex, Typography } from 'antd'; import { CustomAlert } from '@/components/Alert'; import { COLOR } from '@/constants/colors'; -import { LOW_AGENT_SAFE_BALANCE } from '@/constants/thresholds'; +import { WalletOwnerType, WalletType } from '@/enums/Wallet'; +import { useServices } from '@/hooks/useServices'; import { FundsToActivate } from './FundsToActivate'; import { InlineBanner } from './InlineBanner'; @@ -16,6 +17,7 @@ const PurpleDivider = () => ( export const EmptyFunds = () => { const { chainName, tokenSymbol, masterEoaAddress } = useLowFundsDetails(); + const { selectedAgentConfig } = useServices(); return ( { To keep your agent operational, add - {` ${LOW_AGENT_SAFE_BALANCE} ${tokenSymbol} `} + {` ${ + selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][ + WalletType.EOA + ][tokenSymbol] + } ${tokenSymbol} `} on {chainName} chain to the safe signer. diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx index bc22e5225..b29a5f542 100644 --- a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx +++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx @@ -1,7 +1,8 @@ import { round } from 'lodash'; import { useMemo } from 'react'; -import { LOW_AGENT_SAFE_BALANCE } from '@/constants/thresholds'; +import { CHAIN_CONFIG } from '@/config/chains'; +import { WalletOwnerType, WalletType } from '@/enums/Wallet'; import { useMasterBalances } from '@/hooks/useBalanceContext'; import { useNeedsFunds } from '@/hooks/useNeedsFunds'; import { useServices } from '@/hooks/useServices'; @@ -32,8 +33,19 @@ export const LowFunds = () => { if (!masterEoaNativeGasBalance) return false; if (!storeState?.isInitialFunded) return false; - return masterEoaNativeGasBalance < LOW_AGENT_SAFE_BALANCE; - }, [isBalanceLoaded, masterEoaNativeGasBalance, storeState]); + return ( + masterEoaNativeGasBalance < + selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][ + WalletType.EOA + ][CHAIN_CONFIG[selectedAgentConfig.evmHomeChainId].nativeToken.symbol] + ); + }, [ + isBalanceLoaded, + masterEoaNativeGasBalance, + selectedAgentConfig.evmHomeChainId, + selectedAgentConfig.operatingThresholds, + storeState?.isInitialFunded, + ]); // Show the empty funds alert if the agent is not funded const isEmptyFundsVisible = useMemo(() => { diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx index cb020adbd..de16700ad 100644 --- a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx +++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowOperatingBalanceAlert.tsx @@ -2,8 +2,10 @@ import { Flex, Typography } from 'antd'; import { useMemo } from 'react'; import { CustomAlert } from '@/components/Alert'; -import { LOW_MASTER_SAFE_BALANCE } from '@/constants/thresholds'; +import { CHAIN_CONFIG } from '@/config/chains'; +import { WalletOwnerType, WalletType } from '@/enums/Wallet'; import { useMasterBalances } from '@/hooks/useBalanceContext'; +import { useServices } from '@/hooks/useServices'; import { useStore } from '@/hooks/useStore'; import { InlineBanner } from './InlineBanner'; @@ -13,6 +15,7 @@ const { Text, Title } = Typography; export const LowOperatingBalanceAlert = () => { const { storeState } = useStore(); + const { selectedAgentConfig } = useServices(); const { isLoaded: isBalanceLoaded, masterSafeNativeGasBalance } = useMasterBalances(); @@ -20,8 +23,17 @@ export const LowOperatingBalanceAlert = () => { const isLowBalance = useMemo(() => { if (!masterSafeNativeGasBalance) return false; - return masterSafeNativeGasBalance < LOW_MASTER_SAFE_BALANCE; - }, [masterSafeNativeGasBalance]); + return ( + masterSafeNativeGasBalance < + selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][ + WalletType.Safe + ][CHAIN_CONFIG[selectedAgentConfig.evmHomeChainId].nativeToken.symbol] + ); + }, [ + masterSafeNativeGasBalance, + selectedAgentConfig.evmHomeChainId, + selectedAgentConfig.operatingThresholds, + ]); if (!isBalanceLoaded) return null; if (!storeState?.isInitialFunded) return; @@ -39,7 +51,14 @@ export const LowOperatingBalanceAlert = () => { To run your agent, add at least - {` ${LOW_MASTER_SAFE_BALANCE} ${tokenSymbol} `} + {` ${ + selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][ + WalletType.Safe + ][ + CHAIN_CONFIG[selectedAgentConfig.evmHomeChainId].nativeToken + .symbol + ] + } ${tokenSymbol} `} on {chainName} chain to your safe. diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowSafeSignerBalanceAlert.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowSafeSignerBalanceAlert.tsx index d69381383..96a5122a7 100644 --- a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowSafeSignerBalanceAlert.tsx +++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowSafeSignerBalanceAlert.tsx @@ -1,7 +1,8 @@ import { Flex, Typography } from 'antd'; import { CustomAlert } from '@/components/Alert'; -import { LOW_AGENT_SAFE_BALANCE } from '@/constants/thresholds'; +import { WalletOwnerType, WalletType } from '@/enums/Wallet'; +import { useServices } from '@/hooks/useServices'; import { InlineBanner } from './InlineBanner'; import { useLowFundsDetails } from './useLowFunds'; @@ -10,6 +11,7 @@ const { Text, Title } = Typography; export const LowSafeSignerBalanceAlert = () => { const { chainName, tokenSymbol, masterEoaAddress } = useLowFundsDetails(); + const { selectedAgentConfig } = useServices(); return ( { To keep your agent operational, add - {` ${LOW_AGENT_SAFE_BALANCE} ${tokenSymbol} `} + {` ${selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][WalletType.EOA][tokenSymbol]} ${tokenSymbol} `} on {chainName} chain to the safe signer. diff --git a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx index e45283ac3..c504e44e3 100644 --- a/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx +++ b/frontend/components/ManageStakingPage/StakingContractSection/CantMigrateAlert.tsx @@ -3,10 +3,11 @@ import { isEmpty, isNil } from 'lodash'; import { useMemo } from 'react'; import { CustomAlert } from '@/components/Alert'; +import { CHAIN_CONFIG } from '@/config/chains'; import { getNativeTokenSymbol } from '@/config/tokens'; -import { LOW_MASTER_SAFE_BALANCE } from '@/constants/thresholds'; import { StakingProgramId } from '@/enums/StakingProgram'; import { TokenSymbol } from '@/enums/Token'; +import { WalletOwnerType, WalletType } from '@/enums/Wallet'; import { useBalanceContext, useMasterBalances, @@ -72,7 +73,10 @@ const AlertInsufficientMigrationFunds = ({ const homeChainId = selectedAgentConfig.evmHomeChainId; const nativeTokenSymbol = getNativeTokenSymbol(homeChainId); const requiredNativeTokenDeposit = isInitialFunded - ? LOW_MASTER_SAFE_BALANCE - (safeBalance[nativeTokenSymbol] || 0) // is already funded allow minimal maintenance + ? selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][ + WalletType.Safe + ][CHAIN_CONFIG[selectedAgentConfig.evmHomeChainId].nativeToken.symbol] - + (safeBalance[nativeTokenSymbol] || 0) // is already funded allow minimal maintenance : (serviceFundRequirements[homeChainId]?.[nativeTokenSymbol] || 0) - (safeBalance[nativeTokenSymbol] || 0); // otherwise require full initial funding requirements diff --git a/frontend/config/agents.ts b/frontend/config/agents.ts index 858508804..379bd277c 100644 --- a/frontend/config/agents.ts +++ b/frontend/config/agents.ts @@ -1,10 +1,13 @@ import { MiddlewareChain } from '@/client'; import { AgentType } from '@/enums/Agent'; import { EvmChainId } from '@/enums/Chain'; +import { TokenSymbol } from '@/enums/Token'; +import { WalletOwnerType, WalletType } from '@/enums/Wallet'; import { MemeooorBaseService } from '@/service/agents/Memeooor'; import { PredictTraderService } from '@/service/agents/PredictTrader'; // import { OptimusService } from '@/service/agents/Optimus'; import { AgentConfig } from '@/types/Agent'; +import { formatEther } from '@/utils/numberFormatters'; // TODO: complete this config // TODO: add funding requirements @@ -18,7 +21,25 @@ export const AGENT_CONFIG: { middlewareHomeChainId: MiddlewareChain.GNOSIS, requiresAgentSafesOn: [EvmChainId.Gnosis], agentSafeFundingRequirements: { - [EvmChainId.Gnosis]: 100000000000000000, + [EvmChainId.Gnosis]: +formatEther(0.1), + }, + operatingThresholds: { + [WalletOwnerType.Master]: { + [WalletType.EOA]: { + [TokenSymbol.XDAI]: +formatEther(1.5), + }, + [WalletType.Safe]: { + [TokenSymbol.XDAI]: +formatEther(2), + }, + }, + [WalletOwnerType.Agent]: { + [WalletType.EOA]: { + [TokenSymbol.XDAI]: +formatEther(0.1), + }, + [WalletType.Safe]: { + [TokenSymbol.XDAI]: +formatEther(0.1), + }, + }, }, requiresMasterSafesOn: [EvmChainId.Gnosis], serviceApi: PredictTraderService, @@ -44,7 +65,25 @@ export const AGENT_CONFIG: { middlewareHomeChainId: MiddlewareChain.BASE, requiresAgentSafesOn: [EvmChainId.Base], agentSafeFundingRequirements: { - [EvmChainId.Base]: 1000000000000000, // 0.001 eth + [EvmChainId.Base]: +formatEther(0.03), + }, + operatingThresholds: { + [WalletOwnerType.Master]: { + [WalletType.EOA]: { + [TokenSymbol.ETH]: +formatEther(0.0001), + }, + [WalletType.Safe]: { + [TokenSymbol.ETH]: +formatEther(0.0001), + }, + }, + [WalletOwnerType.Agent]: { + [WalletType.EOA]: { + [TokenSymbol.ETH]: +formatEther(0.0001), + }, + [WalletType.Safe]: { + [TokenSymbol.ETH]: +formatEther(0.0001), + }, + }, }, requiresMasterSafesOn: [EvmChainId.Base], serviceApi: MemeooorBaseService, diff --git a/frontend/constants/thresholds.ts b/frontend/constants/thresholds.ts index 21a06f724..5f532629a 100644 --- a/frontend/constants/thresholds.ts +++ b/frontend/constants/thresholds.ts @@ -28,4 +28,3 @@ export const MIN_ETH_BALANCE_THRESHOLDS: Record< // TODO: update to support multi-chain, very poor implementation export const LOW_AGENT_SAFE_BALANCE = 1.5; -export const LOW_MASTER_SAFE_BALANCE = 2; diff --git a/frontend/types/Agent.ts b/frontend/types/Agent.ts index d13a252db..071b87043 100644 --- a/frontend/types/Agent.ts +++ b/frontend/types/Agent.ts @@ -1,5 +1,7 @@ import { MiddlewareChain } from '@/client'; import { EvmChainId } from '@/enums/Chain'; +import { TokenSymbol } from '@/enums/Token'; +import { WalletOwnerType } from '@/enums/Wallet'; import { PredictTraderService } from '@/service/agents/PredictTrader'; export type StakedAgentServiceInstance = PredictTraderService; @@ -13,4 +15,11 @@ export type AgentConfig = { serviceApi: typeof PredictTraderService; displayName: string; description: string; + operatingThresholds: { + [owner: string | WalletOwnerType]: { + [walletType: string | WalletOwnerType]: { + [tokenSymbol: string | TokenSymbol]: number; + }; + }; + }; }; From 4aea6c2435f1aee92fccc3620df795e59e51c914 Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 16:01:41 +0000 Subject: [PATCH 66/79] fix: update token symbol reference for operating thresholds in AgentNotRunningButton --- .../MainPage/header/AgentButton/AgentNotRunningButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx index 03c8554dd..504d5a8af 100644 --- a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx +++ b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx @@ -136,7 +136,7 @@ export const AgentNotRunningButton = () => { (masterSafeNativeGasBalance ?? 0) >= selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][ WalletType.Safe - ][TokenSymbol.XDAI]; + ][CHAIN_CONFIG[selectedAgentConfig.evmHomeChainId].nativeToken.symbol]; return hasEnoughForInitialDeployment; }, [ From 3ef4e0ede09da76e63e3320da366a3f22ce1ff1c Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 16:03:07 +0000 Subject: [PATCH 67/79] refactor: remove outdated comment and clean up LOW_AGENT_SAFE_BALANCE definition --- frontend/constants/thresholds.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/constants/thresholds.ts b/frontend/constants/thresholds.ts index 5f532629a..513da17b5 100644 --- a/frontend/constants/thresholds.ts +++ b/frontend/constants/thresholds.ts @@ -25,6 +25,3 @@ export const MIN_ETH_BALANCE_THRESHOLDS: Record< safeAddSigner: 0.005, }, }; - -// TODO: update to support multi-chain, very poor implementation -export const LOW_AGENT_SAFE_BALANCE = 1.5; From 617787b582eed86b91ef5eea5fe223771d7d3811 Mon Sep 17 00:00:00 2001 From: mohandast52 Date: Fri, 13 Dec 2024 21:44:01 +0530 Subject: [PATCH 68/79] refactor: simplify SetupEoaFundingWaiting component by removing currency prop --- .../components/SetupPage/Create/SetupEoaFunding.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/frontend/components/SetupPage/Create/SetupEoaFunding.tsx b/frontend/components/SetupPage/Create/SetupEoaFunding.tsx index dbe6ff0c7..79f5e478b 100644 --- a/frontend/components/SetupPage/Create/SetupEoaFunding.tsx +++ b/frontend/components/SetupPage/Create/SetupEoaFunding.tsx @@ -46,11 +46,8 @@ const statusMessage = (isFunded?: boolean) => { } }; -type SetupEoaFundingWaitingProps = { chainName: string; currency: string }; -const SetupEoaFundingWaiting = ({ - chainName, - currency, -}: SetupEoaFundingWaitingProps) => { +type SetupEoaFundingWaitingProps = { chainName: string }; +const SetupEoaFundingWaiting = ({ chainName }: SetupEoaFundingWaitingProps) => { const { masterEoa } = useMasterWalletContext(); const masterEoaAddress = masterEoa?.address; @@ -90,7 +87,7 @@ const SetupEoaFundingWaiting = ({ - {`${currency}: ${masterEoaAddress || NA}`} + {`${masterEoaAddress || NA}`} {/* - {!isFunded && ( - - )} + {!isFunded && }
); }; From 22ded040a25651be4494cc7d7031b7b109167feb Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 16:41:41 +0000 Subject: [PATCH 69/79] fix: update service if hash is different in AgentNotRunningButton component --- .../AgentButton/AgentNotRunningButton.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx index 504d5a8af..02aafa706 100644 --- a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx +++ b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx @@ -230,6 +230,23 @@ export const AgentNotRunningButton = () => { if (isNil(service) && isNil(middlewareServiceResponse)) throw new Error('Service not found'); + // Update the service if the hash is different + if (!middlewareServiceResponse && service) { + if (service.hash !== serviceTemplate.hash) { + return ServicesService.updateService({ + serviceConfigId: service!.service_config_id, + stakingProgramId: selectedStakingProgramId, + // chainId: selectedAgentConfig.evmHomeChainId, + serviceTemplate, + deploy: false, // TODO: deprecated will remove + useMechMarketplace: + STAKING_PROGRAMS[selectedAgentConfig.evmHomeChainId][ + selectedStakingProgramId + ].mechType === MechType.Marketplace, + }); + } + } + // Start the service try { const serviceToStart = service ?? middlewareServiceResponse; From 557bc78802df8c7c1ebf35fb757cf3d0387a7c72 Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 16:46:25 +0000 Subject: [PATCH 70/79] fix: replace formatEther with parseEther for accurate token value parsing --- frontend/config/agents.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/config/agents.ts b/frontend/config/agents.ts index 379bd277c..e9b508230 100644 --- a/frontend/config/agents.ts +++ b/frontend/config/agents.ts @@ -7,7 +7,7 @@ import { MemeooorBaseService } from '@/service/agents/Memeooor'; import { PredictTraderService } from '@/service/agents/PredictTrader'; // import { OptimusService } from '@/service/agents/Optimus'; import { AgentConfig } from '@/types/Agent'; -import { formatEther } from '@/utils/numberFormatters'; +import { parseEther } from '@/utils/numberFormatters'; // TODO: complete this config // TODO: add funding requirements @@ -21,23 +21,23 @@ export const AGENT_CONFIG: { middlewareHomeChainId: MiddlewareChain.GNOSIS, requiresAgentSafesOn: [EvmChainId.Gnosis], agentSafeFundingRequirements: { - [EvmChainId.Gnosis]: +formatEther(0.1), + [EvmChainId.Gnosis]: +parseEther(0.1), }, operatingThresholds: { [WalletOwnerType.Master]: { [WalletType.EOA]: { - [TokenSymbol.XDAI]: +formatEther(1.5), + [TokenSymbol.XDAI]: +parseEther(1.5), }, [WalletType.Safe]: { - [TokenSymbol.XDAI]: +formatEther(2), + [TokenSymbol.XDAI]: +parseEther(2), }, }, [WalletOwnerType.Agent]: { [WalletType.EOA]: { - [TokenSymbol.XDAI]: +formatEther(0.1), + [TokenSymbol.XDAI]: +parseEther(0.1), }, [WalletType.Safe]: { - [TokenSymbol.XDAI]: +formatEther(0.1), + [TokenSymbol.XDAI]: +parseEther(0.1), }, }, }, @@ -65,23 +65,23 @@ export const AGENT_CONFIG: { middlewareHomeChainId: MiddlewareChain.BASE, requiresAgentSafesOn: [EvmChainId.Base], agentSafeFundingRequirements: { - [EvmChainId.Base]: +formatEther(0.03), + [EvmChainId.Base]: +parseEther(0.03), }, operatingThresholds: { [WalletOwnerType.Master]: { [WalletType.EOA]: { - [TokenSymbol.ETH]: +formatEther(0.0001), + [TokenSymbol.ETH]: +parseEther(0.0001), }, [WalletType.Safe]: { - [TokenSymbol.ETH]: +formatEther(0.0001), + [TokenSymbol.ETH]: +parseEther(0.0001), }, }, [WalletOwnerType.Agent]: { [WalletType.EOA]: { - [TokenSymbol.ETH]: +formatEther(0.0001), + [TokenSymbol.ETH]: +parseEther(0.0001), }, [WalletType.Safe]: { - [TokenSymbol.ETH]: +formatEther(0.0001), + [TokenSymbol.ETH]: +parseEther(0.0001), }, }, }, From 5156a79b37452bda0866b536c17e755931080eec Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 16:53:50 +0000 Subject: [PATCH 71/79] refactor: remove parseEther usage for static values in agent configuration --- frontend/config/agents.ts | 21 ++++++++++----------- frontend/hooks/useBalanceContext.ts | 6 +----- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/frontend/config/agents.ts b/frontend/config/agents.ts index e9b508230..c99e7a517 100644 --- a/frontend/config/agents.ts +++ b/frontend/config/agents.ts @@ -7,7 +7,6 @@ import { MemeooorBaseService } from '@/service/agents/Memeooor'; import { PredictTraderService } from '@/service/agents/PredictTrader'; // import { OptimusService } from '@/service/agents/Optimus'; import { AgentConfig } from '@/types/Agent'; -import { parseEther } from '@/utils/numberFormatters'; // TODO: complete this config // TODO: add funding requirements @@ -21,23 +20,23 @@ export const AGENT_CONFIG: { middlewareHomeChainId: MiddlewareChain.GNOSIS, requiresAgentSafesOn: [EvmChainId.Gnosis], agentSafeFundingRequirements: { - [EvmChainId.Gnosis]: +parseEther(0.1), + [EvmChainId.Gnosis]: 0.1, }, operatingThresholds: { [WalletOwnerType.Master]: { [WalletType.EOA]: { - [TokenSymbol.XDAI]: +parseEther(1.5), + [TokenSymbol.XDAI]: 1.5, }, [WalletType.Safe]: { - [TokenSymbol.XDAI]: +parseEther(2), + [TokenSymbol.XDAI]: 2, }, }, [WalletOwnerType.Agent]: { [WalletType.EOA]: { - [TokenSymbol.XDAI]: +parseEther(0.1), + [TokenSymbol.XDAI]: 0.1, }, [WalletType.Safe]: { - [TokenSymbol.XDAI]: +parseEther(0.1), + [TokenSymbol.XDAI]: 0.1, }, }, }, @@ -65,23 +64,23 @@ export const AGENT_CONFIG: { middlewareHomeChainId: MiddlewareChain.BASE, requiresAgentSafesOn: [EvmChainId.Base], agentSafeFundingRequirements: { - [EvmChainId.Base]: +parseEther(0.03), + [EvmChainId.Base]: 0.03, }, operatingThresholds: { [WalletOwnerType.Master]: { [WalletType.EOA]: { - [TokenSymbol.ETH]: +parseEther(0.0001), + [TokenSymbol.ETH]: 0.0001, }, [WalletType.Safe]: { - [TokenSymbol.ETH]: +parseEther(0.0001), + [TokenSymbol.ETH]: 0.0001, }, }, [WalletOwnerType.Agent]: { [WalletType.EOA]: { - [TokenSymbol.ETH]: +parseEther(0.0001), + [TokenSymbol.ETH]: 0.0001, }, [WalletType.Safe]: { - [TokenSymbol.ETH]: +parseEther(0.0001), + [TokenSymbol.ETH]: 0.0001, }, }, }, diff --git a/frontend/hooks/useBalanceContext.ts b/frontend/hooks/useBalanceContext.ts index 20d6437bd..05a9b3a73 100644 --- a/frontend/hooks/useBalanceContext.ts +++ b/frontend/hooks/useBalanceContext.ts @@ -3,7 +3,6 @@ import { useContext, useMemo } from 'react'; import { CHAIN_CONFIG } from '@/config/chains'; import { BalanceContext, WalletBalanceResult } from '@/context/BalanceProvider'; import { Optional } from '@/types/Util'; -import { formatEther } from '@/utils/numberFormatters'; import { useService } from './useService'; import { useServices } from './useServices'; @@ -154,10 +153,7 @@ export const useMasterBalances = () => { if (!agentNativeGasRequirement) return; - return ( - masterSafeNative.balance < - parseFloat(formatEther(`${agentNativeGasRequirement}`)) - ); + return masterSafeNative.balance < agentNativeGasRequirement; }, [masterSafeNative, homeChainNativeToken, selectedAgentConfig]); const masterEoaNative = useMemo(() => { From 7c6465b21d40b5e91618477378ef524bf66fc218 Mon Sep 17 00:00:00 2001 From: Josh Miller <31908788+truemiller@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:13:52 +0000 Subject: [PATCH 72/79] Update frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx Co-authored-by: Mohan --- .../MainPage/header/AgentButton/AgentNotRunningButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx index 02aafa706..259c0414a 100644 --- a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx +++ b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx @@ -234,7 +234,7 @@ export const AgentNotRunningButton = () => { if (!middlewareServiceResponse && service) { if (service.hash !== serviceTemplate.hash) { return ServicesService.updateService({ - serviceConfigId: service!.service_config_id, + serviceConfigId: service.service_config_id, stakingProgramId: selectedStakingProgramId, // chainId: selectedAgentConfig.evmHomeChainId, serviceTemplate, From 3c0941a164a752263be691cfe2188f89052971f1 Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 17:39:06 +0000 Subject: [PATCH 73/79] fix: update agent names to reflect branding changes --- frontend/config/agents.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/config/agents.ts b/frontend/config/agents.ts index c99e7a517..547f081e9 100644 --- a/frontend/config/agents.ts +++ b/frontend/config/agents.ts @@ -59,7 +59,7 @@ export const AGENT_CONFIG: { // serviceApi: OptimusService, // }, [AgentType.Memeooorr]: { - name: 'Memeooorr agent', + name: 'Agents.fun agent', evmHomeChainId: EvmChainId.Base, middlewareHomeChainId: MiddlewareChain.BASE, requiresAgentSafesOn: [EvmChainId.Base], @@ -86,7 +86,7 @@ export const AGENT_CONFIG: { }, requiresMasterSafesOn: [EvmChainId.Base], serviceApi: MemeooorBaseService, - displayName: 'Memeooorr agent', + displayName: 'Agents.fun agent', description: 'Autonomously post to Twitter, create and trade memecoins, and interact with other agents.', }, From f8eba60933359c455231bcd6e0d26d07911d93c5 Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 17:40:11 +0000 Subject: [PATCH 74/79] fix: update terminology from trading balance to operating balance in GasBalanceSection --- frontend/components/MainPage/sections/GasBalanceSection.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/MainPage/sections/GasBalanceSection.tsx b/frontend/components/MainPage/sections/GasBalanceSection.tsx index b1c58d767..be2f6489e 100644 --- a/frontend/components/MainPage/sections/GasBalanceSection.tsx +++ b/frontend/components/MainPage/sections/GasBalanceSection.tsx @@ -141,12 +141,12 @@ export const GasBalanceSection = () => { padding="16px 24px" > - Trading balance  + Operating balance  {masterSafe && ( - Your agent uses this balance to fund trading activity on-chain. + Your agent uses this balance to fund on-chain activity.
{activityLink} From 9c7a4ef5797c8096ca6ecaca4cd964429b1979d5 Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 17:54:29 +0000 Subject: [PATCH 75/79] fix: isMasterSafeNativeGas should check masterSafe, not agentSafe --- .../MainPage/sections/GasBalanceSection.tsx | 2 +- frontend/hooks/useBalanceContext.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/components/MainPage/sections/GasBalanceSection.tsx b/frontend/components/MainPage/sections/GasBalanceSection.tsx index be2f6489e..3428e0267 100644 --- a/frontend/components/MainPage/sections/GasBalanceSection.tsx +++ b/frontend/components/MainPage/sections/GasBalanceSection.tsx @@ -58,7 +58,7 @@ const BalanceStatus = () => { if (!storeState?.isInitialFunded) return; if (isMasterSafeLowOnNativeGas && !isLowBalanceNotificationShown) { - showNotification('Trading balance is too low.'); + showNotification('Operating balance is too low.'); setIsLowBalanceNotificationShown(true); } diff --git a/frontend/hooks/useBalanceContext.ts b/frontend/hooks/useBalanceContext.ts index 05a9b3a73..4b41996ae 100644 --- a/frontend/hooks/useBalanceContext.ts +++ b/frontend/hooks/useBalanceContext.ts @@ -1,7 +1,9 @@ +import { isNil } from 'lodash'; import { useContext, useMemo } from 'react'; import { CHAIN_CONFIG } from '@/config/chains'; import { BalanceContext, WalletBalanceResult } from '@/context/BalanceProvider'; +import { WalletOwnerType, WalletType } from '@/enums/Wallet'; import { Optional } from '@/types/Util'; import { useService } from './useService'; @@ -146,14 +148,14 @@ export const useMasterBalances = () => { if (!masterSafeNative) return; if (!homeChainNativeToken?.symbol) return; - const agentNativeGasRequirement = - selectedAgentConfig.agentSafeFundingRequirements?.[ - selectedAgentConfig.evmHomeChainId - ]; + const nativeGasRequirement = + selectedAgentConfig.operatingThresholds[WalletOwnerType.Master][ + WalletType.Safe + ][homeChainNativeToken.symbol]; - if (!agentNativeGasRequirement) return; + if (isNil(nativeGasRequirement)) return; - return masterSafeNative.balance < agentNativeGasRequirement; + return masterSafeNative.balance < nativeGasRequirement; }, [masterSafeNative, homeChainNativeToken, selectedAgentConfig]); const masterEoaNative = useMemo(() => { From d0549a9095c2044a36a3e86892128d415ac119ad Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 19:49:06 +0000 Subject: [PATCH 76/79] fix: serviceTemplate values for memeooorr --- frontend/constants/serviceTemplates.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/constants/serviceTemplates.ts b/frontend/constants/serviceTemplates.ts index ba68b4f04..a924d6cef 100644 --- a/frontend/constants/serviceTemplates.ts +++ b/frontend/constants/serviceTemplates.ts @@ -1,6 +1,7 @@ import { EnvProvisionType, MiddlewareChain, ServiceTemplate } from '@/client'; import { AgentType } from '@/enums/Agent'; import { StakingProgramId } from '@/enums/StakingProgram'; +import { parseEther } from '@/utils/numberFormatters'; export const SERVICE_TEMPLATES: ServiceTemplate[] = [ { @@ -172,11 +173,11 @@ export const SERVICE_TEMPLATES: ServiceTemplate[] = [ agent_id: 43, threshold: 1, use_staking: true, - cost_of_bond: 50000000000000000000, - monthly_gas_estimate: 50000000000000000, // 0.05 + cost_of_bond: +parseEther(50), + monthly_gas_estimate: +parseEther(0.03), fund_requirements: { - agent: 1000000000000000, // 0.001 - safe: 2000000000000000, // 0.002 + agent: +parseEther(0.001), + safe: +parseEther(0.001), }, }, }, From a4c6f6b1535d63850e215bf527cd8bdb2947fbf8 Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 19:52:59 +0000 Subject: [PATCH 77/79] fix: update hash value for Memeooorr service 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 a924d6cef..2eea95bf2 100644 --- a/frontend/constants/serviceTemplates.ts +++ b/frontend/constants/serviceTemplates.ts @@ -159,7 +159,7 @@ export const SERVICE_TEMPLATES: ServiceTemplate[] = [ { agentType: AgentType.Memeooorr, name: 'Memeooorr', - hash: 'bafybeidbgqxeh2yhzrxl3tib5s23hp4ihqjjw6melv7ks47afxc5gil5em', + hash: 'bafybeieslmmd2k4xzpypzr6a2jlkisxodrmt4lbuwjvsvtm3ury3yxv32m', description: 'Memeooorr @twitter_handle', // should be overwritten with twitter username image: 'https://gateway.autonolas.tech/ipfs/QmQYDGMg8m91QQkTWSSmANs5tZwKrmvUCawXZfXVVWQPcu', From 88a51d4e3b096f440db90ecba4dd0aeab110a8f6 Mon Sep 17 00:00:00 2001 From: truemiller Date: Fri, 13 Dec 2024 19:59:46 +0000 Subject: [PATCH 78/79] fix: update version to 0.2.0-rc23 in package.json and pyproject.toml --- package.json | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 1bc7185f8..66844d9c2 100644 --- a/package.json +++ b/package.json @@ -66,11 +66,11 @@ "download-binaries": "sh download_binaries.sh", "build:pearl": "sh build_pearl.sh" }, - "version": "0.2.0-rc9", + "version": "0.2.0-rc23", "engines": { "node": ">=20", "yarn": ">=1.22.0", "npm": "please-use-yarn" }, "engineStrict": true -} +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3cc32c215..ac8d86eab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "olas-operate-middleware" -version = "0.2.0-rc9" +version = "0.2.0-rc23" description = "" authors = ["David Vilela ", "Viraj Patel "] readme = "README.md" From 77312dd0cc686b6e1402c65b1abb4d3b899caace Mon Sep 17 00:00:00 2001 From: truemiller Date: Sat, 14 Dec 2024 01:36:11 +0000 Subject: [PATCH 79/79] fix: correct eligibility condition for staking in AgentNotRunningButton component --- .../MainPage/header/AgentButton/AgentNotRunningButton.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx index 259c0414a..947f4e2ce 100644 --- a/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx +++ b/frontend/components/MainPage/header/AgentButton/AgentNotRunningButton.tsx @@ -118,7 +118,7 @@ export const AgentNotRunningButton = () => { return (serviceTotalStakedOlas ?? 0) >= requiredStakedOlas; } - if (isEligibleForStaking && isAgentEvicted) return true; + if (isEligibleForStaking && !isAgentEvicted) return true; if (isServiceStaked) { const hasEnoughOlas = @@ -148,12 +148,11 @@ export const AgentNotRunningButton = () => { isServiceStaked, masterSafeBalances, service, - storeState?.isInitialFunded, + storeState, isEligibleForStaking, isAgentEvicted, masterSafeNativeGasBalance, - selectedAgentConfig.operatingThresholds, - selectedAgentConfig.evmHomeChainId, + selectedAgentConfig, serviceTotalStakedOlas, serviceSafeOlasWithStaked, ]);