@@ -63,12 +82,20 @@ export const FundsToActivate = ({
staking.
)}
- {otherFundsRequired && (
+ {nativeFundsRequired && (
{UNICODE_SYMBOLS.BULLET} {nativeTokenRequired} -
{` ${FUNDS_REQUIRED_FOR_BY_AGENT_TYPE[selectedAgentType]}.`}
)}
+
+ {additionalFundsRequired &&
+ additionalTokensRequired.map((additionalToken) => (
+
+ {UNICODE_SYMBOLS.BULLET} {additionalToken} -
+ {` ${FUNDS_REQUIRED_FOR_BY_AGENT_TYPE[selectedAgentType]}.`}
+
+ ))}
{masterSafeAddress && (
diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx
index d0fb2525..744e4db3 100644
--- a/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx
+++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/LowFunds.tsx
@@ -1,6 +1,8 @@
import { round } from 'lodash';
import { useMemo } from 'react';
+import { getNativeTokenSymbol } from '@/config/tokens';
+import { TokenSymbol } from '@/enums/Token';
import { WalletType } from '@/enums/Wallet';
import { useMasterBalances } from '@/hooks/useBalanceContext';
import { useNeedsFunds } from '@/hooks/useNeedsFunds';
@@ -22,8 +24,9 @@ export const LowFunds = () => {
masterSafeNativeGasBalance,
} = useMasterBalances();
- const { nativeBalancesByChain, olasBalancesByChain, isInitialFunded } =
- useNeedsFunds(selectedStakingProgramId);
+ const { balancesByChain, isInitialFunded } = useNeedsFunds(
+ selectedStakingProgramId,
+ );
const { tokenSymbol, masterThresholds } = useLowFundsDetails();
const chainId = selectedAgentConfig.evmHomeChainId;
@@ -50,15 +53,14 @@ export const LowFunds = () => {
// Show the empty funds alert if the agent is not funded
const isEmptyFundsVisible = useMemo(() => {
if (!isBalanceLoaded) return false;
- if (!olasBalancesByChain) return false;
- if (!nativeBalancesByChain) return false;
+ if (!balancesByChain) return false;
// If the agent is not funded, will be displayed
if (!isInitialFunded) return false;
if (
- round(nativeBalancesByChain[chainId], 2) === 0 &&
- round(olasBalancesByChain[chainId], 2) === 0 &&
+ round(balancesByChain[chainId][getNativeTokenSymbol(chainId)], 2) === 0 &&
+ round(balancesByChain[chainId][TokenSymbol.OLAS], 2) === 0 &&
isSafeSignerBalanceLow
) {
return true;
@@ -69,8 +71,7 @@ export const LowFunds = () => {
isBalanceLoaded,
isInitialFunded,
chainId,
- nativeBalancesByChain,
- olasBalancesByChain,
+ balancesByChain,
isSafeSignerBalanceLow,
]);
diff --git a/frontend/components/MainPage/sections/AlertSections/LowFunds/MainNeedsFunds.tsx b/frontend/components/MainPage/sections/AlertSections/LowFunds/MainNeedsFunds.tsx
index 60a7169e..8b4d744c 100644
--- a/frontend/components/MainPage/sections/AlertSections/LowFunds/MainNeedsFunds.tsx
+++ b/frontend/components/MainPage/sections/AlertSections/LowFunds/MainNeedsFunds.tsx
@@ -18,6 +18,7 @@ export const MainNeedsFunds = () => {
const {
hasEnoughEthForInitialFunding,
hasEnoughOlasForInitialFunding,
+ hasEnoughAdditionalTokensForInitialFunding,
isInitialFunded,
needsInitialFunding,
} = useNeedsFunds(selectedStakingProgramId);
@@ -54,7 +55,10 @@ export const MainNeedsFunds = () => {
}
diff --git a/frontend/components/MainPage/sections/AlertSections/NewStakingProgramAlert.tsx b/frontend/components/MainPage/sections/AlertSections/NewStakingProgramAlert.tsx
index cecb4105..4fe4a430 100644
--- a/frontend/components/MainPage/sections/AlertSections/NewStakingProgramAlert.tsx
+++ b/frontend/components/MainPage/sections/AlertSections/NewStakingProgramAlert.tsx
@@ -1,25 +1,25 @@
import { Button, Flex, Typography } from 'antd';
import { Pages } from '@/enums/Pages';
-import { StakingProgramId } from '@/enums/StakingProgram';
import { usePageState } from '@/hooks/usePageState';
-import { useStakingProgram } from '@/hooks/useStakingProgram';
import { CustomAlert } from '../../../Alert';
const { Text } = Typography;
+// TODO: need to figure out how to understand if there are new staking contracts
+// To show this alert; also need to hide it, when a use clicks "review"
export const NewStakingProgramAlert = () => {
const { goto } = usePageState();
- const { activeStakingProgramId, isActiveStakingProgramLoaded } =
- useStakingProgram();
+ // const { activeStakingProgramId, isActiveStakingProgramLoaded } =
+ // useStakingProgram();
- // TODO: remove single staking program check
- if (
- !isActiveStakingProgramLoaded ||
- activeStakingProgramId !== StakingProgramId.OptimusAlpha
- )
- return null;
+ // // TODO: remove single staking program check
+ // if (
+ // !isActiveStakingProgramLoaded ||
+ // activeStakingProgramId !== StakingProgramId.OptimusAlpha
+ // )
+ // return null;
return (
{
{isBackupViaSafeEnabled && }
-
+ {/* */}
diff --git a/frontend/components/SetupPage/Create/SetupEoaFunding.tsx b/frontend/components/SetupPage/Create/SetupEoaFunding.tsx
index a9b73b55..90007709 100644
--- a/frontend/components/SetupPage/Create/SetupEoaFunding.tsx
+++ b/frontend/components/SetupPage/Create/SetupEoaFunding.tsx
@@ -18,7 +18,6 @@ import { useSetup } from '@/hooks/useSetup';
import { useMasterWalletContext } from '@/hooks/useWallet';
import { copyToClipboard } from '@/utils/copyToClipboard';
import { delayInSeconds } from '@/utils/delay';
-import { formatEther } from '@/utils/numberFormatters';
import { SetupCreateHeader } from './SetupCreateHeader';
@@ -192,9 +191,7 @@ export const SetupEoaFunding = () => {
return (
diff --git a/frontend/config/agents.ts b/frontend/config/agents.ts
index dca34322..c0f5b7f0 100644
--- a/frontend/config/agents.ts
+++ b/frontend/config/agents.ts
@@ -97,23 +97,28 @@ export const AGENT_CONFIG: {
middlewareHomeChainId: MiddlewareChain.MODE,
requiresAgentSafesOn: [EvmChainId.Mode],
agentSafeFundingRequirements: {
- [EvmChainId.Mode]: 5260000000000000, // 0.00526 eth
+ [EvmChainId.Mode]: 0.0005,
+ },
+ additionalRequirements: {
+ [EvmChainId.Mode]: {
+ [TokenSymbol.USDC]: 16,
+ },
},
operatingThresholds: {
[WalletOwnerType.Master]: {
[WalletType.EOA]: {
- [TokenSymbol.ETH]: 0.0001, // TODO: ensure this is correct
+ [TokenSymbol.ETH]: 0.0002,
},
[WalletType.Safe]: {
- [TokenSymbol.ETH]: 0.0001, // TODO: ensure this is correct
+ [TokenSymbol.ETH]: 0.001,
},
},
[WalletOwnerType.Agent]: {
[WalletType.EOA]: {
- [TokenSymbol.ETH]: 0.0001, // TODO: ensure this is correct
+ [TokenSymbol.ETH]: 0.00005,
},
[WalletType.Safe]: {
- [TokenSymbol.ETH]: 0.0001, // TODO: ensure this is correct
+ [TokenSymbol.ETH]: 0.0005,
},
},
},
diff --git a/frontend/config/tokens.ts b/frontend/config/tokens.ts
index f09d3269..08fcf071 100644
--- a/frontend/config/tokens.ts
+++ b/frontend/config/tokens.ts
@@ -110,6 +110,17 @@ export const MODE_TOKEN_CONFIG: ChainTokenConfig = {
tokenType: TokenType.Erc20,
symbol: TokenSymbol.OLAS,
},
+ /**
+ * @warning USDC is a special case, it has 6 decimals, not 18.
+ * https://explorer.mode.network/address/0xd988097fb8612cc24eeC14542bC03424c656005f?tab=read_contract#313ce567
+ * @note When parsing or formatting units, use `decimals` (6) instead of the standard `ether` sizing (10^18).
+ */
+ [TokenSymbol.USDC]: {
+ address: '0xd988097fb8612cc24eeC14542bC03424c656005f',
+ decimals: 6,
+ tokenType: TokenType.Erc20,
+ symbol: TokenSymbol.USDC,
+ },
};
export const TOKEN_CONFIG = {
diff --git a/frontend/constants/serviceTemplates.ts b/frontend/constants/serviceTemplates.ts
index 5ba80157..5292d723 100644
--- a/frontend/constants/serviceTemplates.ts
+++ b/frontend/constants/serviceTemplates.ts
@@ -280,10 +280,10 @@ export const SERVICE_TEMPLATES: ServiceTemplate[] = [
agent_id: 40,
threshold: 1,
use_staking: true,
- cost_of_bond: 20000000000000000000,
- monthly_gas_estimate: 5260000000000000,
+ cost_of_bond: +parseEther(20),
+ monthly_gas_estimate: +parseEther(0.00516),
fund_requirements: {
- agent: 5000000000000000,
+ agent: +parseEther(0.001),
safe: 0,
},
},
diff --git a/frontend/context/BalanceProvider.tsx b/frontend/context/BalanceProvider.tsx
index c28716cd..e377c2ad 100644
--- a/frontend/context/BalanceProvider.tsx
+++ b/frontend/context/BalanceProvider.tsx
@@ -28,7 +28,7 @@ import { StakedAgentService } from '@/service/agents/StakedAgentService';
import { Address } from '@/types/Address';
import { Maybe } from '@/types/Util';
import { asEvmChainId } from '@/utils/middlewareHelpers';
-import { formatEther } from '@/utils/numberFormatters';
+import { formatUnits } from '@/utils/numberFormatters';
import { MasterWalletContext } from './MasterWalletProvider';
import { OnlineStatusContext } from './OnlineStatusProvider';
@@ -228,6 +228,7 @@ const getCrossChainWalletBalances = async (
tokenType,
symbol: tokenSymbol,
address: tokenAddress,
+ decimals,
} of Object.values(tokensOnChain)) {
const isNative = tokenType === TokenType.NativeGas;
const isErc20 = tokenType === TokenType.Erc20;
@@ -258,7 +259,7 @@ const getCrossChainWalletBalances = async (
evmChainId: providerEvmChainId,
symbol: tokenSymbol,
isNative: true,
- balance: Number(formatEther(balance)),
+ balance: Number(formatUnits(balance)),
}),
);
}
@@ -287,7 +288,7 @@ const getCrossChainWalletBalances = async (
evmChainId: providerEvmChainId,
symbol: tokenSymbol,
isNative: false,
- balance: Number(formatEther(erc20Balances[index])),
+ balance: Number(formatUnits(erc20Balances[index], decimals)),
}),
) as WalletBalanceResult[];
diff --git a/frontend/enums/Agent.ts b/frontend/enums/Agent.ts
index 77c0f95a..62ed4538 100644
--- a/frontend/enums/Agent.ts
+++ b/frontend/enums/Agent.ts
@@ -2,7 +2,7 @@ export const AgentType = {
PredictTrader: 'trader',
// Optimus: 'optimus',
Memeooorr: 'memeooorr',
- Modius: 'Modius',
+ Modius: 'modius',
} as const;
export type AgentType = (typeof AgentType)[keyof typeof AgentType];
diff --git a/frontend/hooks/useNeedsFunds.ts b/frontend/hooks/useNeedsFunds.ts
index 52e3deae..50804082 100644
--- a/frontend/hooks/useNeedsFunds.ts
+++ b/frontend/hooks/useNeedsFunds.ts
@@ -18,10 +18,11 @@ import { useStore } from './useStore';
export const useNeedsFunds = (stakingProgramId: Maybe) => {
const { storeState } = useStore();
- const { selectedAgentType } = useServices();
+ const { selectedAgentType, selectedAgentConfig } = useServices();
const serviceTemplate = SERVICE_TEMPLATES.find(
(template) => template.agentType === selectedAgentType,
);
+
const { selectedStakingProgramId } = useStakingProgram();
const { isLoaded: isBalanceLoaded } = useBalanceContext();
@@ -52,38 +53,48 @@ export const useNeedsFunds = (stakingProgramId: Maybe) => {
if (!resolvedStakingProgramId) return;
+ // Gas requirements
const gasEstimate = config.monthly_gas_estimate;
const monthlyGasEstimate = Number(formatUnits(`${gasEstimate}`, 18));
+ const nativeTokenSymbol = getNativeTokenSymbol(evmChainId);
+
+ // OLAS staking requirements
const minimumStakedAmountRequired =
STAKING_PROGRAMS[evmChainId]?.[resolvedStakingProgramId]
?.stakingRequirements?.[TokenSymbol.OLAS] || 0;
- const nativeTokenSymbol = getNativeTokenSymbol(evmChainId);
+ // Additional tokens requirements
+ const additionalRequirements =
+ selectedAgentConfig.additionalRequirements?.[evmChainId] ?? {};
results[evmChainId] = {
[TokenSymbol.OLAS]: minimumStakedAmountRequired,
[nativeTokenSymbol]: monthlyGasEstimate,
- // TODO: extend with any further erc20s..
+ ...additionalRequirements,
};
},
);
return results;
- }, [selectedStakingProgramId, serviceTemplate, stakingProgramId]);
+ }, [
+ selectedAgentConfig.additionalRequirements,
+ selectedStakingProgramId,
+ serviceTemplate,
+ stakingProgramId,
+ ]);
/**
- * Native balances by chain
+ * Balances by chain
*/
- const nativeBalancesByChain = useMemo(() => {
+ const balancesByChain = useMemo(() => {
if (isNil(masterSafeBalances)) return;
return masterSafeBalances.reduce<{
- [chainId: number]: number;
+ [chainId: number]: { [symbol: string]: number };
}>((acc, { symbol, balance, evmChainId }) => {
- if (getNativeTokenSymbol(evmChainId) !== symbol) return acc;
-
- if (!acc[evmChainId]) acc[evmChainId] = 0;
- acc[evmChainId] += balance;
+ if (!acc[evmChainId]) acc[evmChainId] = { [symbol]: 0 };
+ if (!acc[evmChainId][symbol]) acc[evmChainId][symbol] = 0;
+ acc[evmChainId][symbol] += balance;
return acc;
}, {});
@@ -95,53 +106,69 @@ export const useNeedsFunds = (stakingProgramId: Maybe) => {
const hasEnoughEthForInitialFunding = useMemo(() => {
if (isNil(serviceFundRequirements)) return;
if (isNil(masterSafeBalances)) return;
- if (!nativeBalancesByChain) return;
+ if (!balancesByChain) return;
const chainIds = Object.keys(serviceFundRequirements).map(Number);
return chainIds.every((chainId) => {
const nativeTokenSymbol = getNativeTokenSymbol(chainId);
- const nativeTokenBalance = nativeBalancesByChain[chainId] || 0;
+ const nativeTokenBalance =
+ balancesByChain[chainId][nativeTokenSymbol] || 0;
const nativeTokenRequired =
serviceFundRequirements[chainId]?.[nativeTokenSymbol] || 0;
return nativeTokenBalance >= nativeTokenRequired;
});
- }, [serviceFundRequirements, nativeBalancesByChain, masterSafeBalances]);
+ }, [serviceFundRequirements, balancesByChain, masterSafeBalances]);
/**
- * OLAS balances by chain
+ * Check if the agent has enough OLAS for initial funding
*/
- const olasBalancesByChain = useMemo(() => {
- if (!masterSafeBalances) return;
-
- return masterSafeBalances.reduce<{
- [chainId: number]: number;
- }>((acc, { symbol, balance, evmChainId }) => {
- if (TokenSymbol.OLAS !== symbol) return acc;
-
- if (!acc[evmChainId]) acc[evmChainId] = 0;
- acc[evmChainId] += balance;
-
- return acc;
- }, {});
- }, [masterSafeBalances]);
-
const hasEnoughOlasForInitialFunding = useMemo(() => {
if (!serviceFundRequirements) return;
if (!masterSafeBalances) return;
- if (!olasBalancesByChain) return;
+ if (!balancesByChain) return;
const chainIds = Object.keys(serviceFundRequirements).map(Number);
return chainIds.every((chainId) => {
- const olasBalance = olasBalancesByChain[chainId] || 0;
+ const olasBalance = balancesByChain[chainId][TokenSymbol.OLAS] || 0;
const olasRequired =
serviceFundRequirements[chainId]?.[TokenSymbol.OLAS] || 0;
return olasBalance >= olasRequired;
});
- }, [masterSafeBalances, olasBalancesByChain, serviceFundRequirements]);
+ }, [masterSafeBalances, balancesByChain, serviceFundRequirements]);
+
+ /**
+ * Check if the agent requires additional tokens and has enough for initial funding
+ */
+ const hasEnoughAdditionalTokensForInitialFunding = useMemo(() => {
+ if (isNil(serviceFundRequirements)) return;
+ if (isNil(masterSafeBalances)) return;
+ if (!balancesByChain) return;
+
+ const chainIds = Object.keys(serviceFundRequirements).map(Number);
+
+ return chainIds.every((chainId) => {
+ const nativeTokenSymbol = getNativeTokenSymbol(chainId);
+ const additionalTokens = Object.keys(
+ serviceFundRequirements[chainId],
+ ).filter(
+ (token) => token !== TokenSymbol.OLAS && token !== nativeTokenSymbol,
+ );
+
+ if (additionalTokens.length === 0) return true;
+
+ return additionalTokens.every((tokenSymbol) => {
+ const tokenBalance = balancesByChain[chainId][tokenSymbol] || 0;
+ const tokenRequired =
+ serviceFundRequirements[chainId]?.[tokenSymbol] || 0;
+
+ return tokenBalance >= tokenRequired;
+ });
+ });
+ }, [serviceFundRequirements, masterSafeBalances, balancesByChain]);
/**
* Check if the agent needs initial funding (both eth and olas)
@@ -149,10 +176,15 @@ export const useNeedsFunds = (stakingProgramId: Maybe) => {
const needsInitialFunding: boolean = useMemo(() => {
if (isInitialFunded) return false;
if (!isBalanceLoaded) return false;
- if (hasEnoughEthForInitialFunding && hasEnoughOlasForInitialFunding)
+ if (
+ hasEnoughEthForInitialFunding &&
+ hasEnoughOlasForInitialFunding &&
+ hasEnoughAdditionalTokensForInitialFunding
+ )
return false;
return true;
}, [
+ hasEnoughAdditionalTokensForInitialFunding,
hasEnoughEthForInitialFunding,
hasEnoughOlasForInitialFunding,
isBalanceLoaded,
@@ -162,8 +194,8 @@ export const useNeedsFunds = (stakingProgramId: Maybe) => {
return {
hasEnoughEthForInitialFunding,
hasEnoughOlasForInitialFunding,
- nativeBalancesByChain,
- olasBalancesByChain,
+ hasEnoughAdditionalTokensForInitialFunding,
+ balancesByChain,
serviceFundRequirements,
isInitialFunded,
needsInitialFunding,
diff --git a/frontend/types/Agent.ts b/frontend/types/Agent.ts
index 2ccafb19..624ce201 100644
--- a/frontend/types/Agent.ts
+++ b/frontend/types/Agent.ts
@@ -12,6 +12,9 @@ export type AgentConfig = {
requiresAgentSafesOn: EvmChainId[];
agentSafeFundingRequirements: Record;
requiresMasterSafesOn: EvmChainId[];
+ additionalRequirements?: Partial<
+ Record>>
+ >;
serviceApi: typeof PredictTraderService;
displayName: string;
description: string;
diff --git a/frontend/types/ElectronApi.ts b/frontend/types/ElectronApi.ts
index a15c18c3..712ecfed 100644
--- a/frontend/types/ElectronApi.ts
+++ b/frontend/types/ElectronApi.ts
@@ -4,6 +4,7 @@ export type ElectronStore = {
environmentName?: string;
isInitialFunded_trader?: boolean;
isInitialFunded_memeooorr?: boolean;
+ isInitialFunded_modius?: boolean;
firstStakingRewardAchieved?: boolean;
firstRewardNotificationShown?: boolean;
agentEvictionAlertShown?: boolean;