diff --git a/.github/workflows/operate.yml b/.github/workflows/operate.yml
new file mode 100644
index 00000000..a77d4e1b
--- /dev/null
+++ b/.github/workflows/operate.yml
@@ -0,0 +1,30 @@
+# TODO: figure out how to trigger workflow from vercel for nx
+
+name: Operate
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - 'apps/operate/**'
+ pull_request:
+ branches:
+ - main
+ paths:
+ - 'apps/operate/**'
+
+jobs:
+ build:
+ continue-on-error: False
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20.x'
+ - name: Install modules
+ run: yarn
+ - name: Run ESLint
+ run: yarn nx lint operate
diff --git a/apps/govern/components/Contracts/EditVotes/index.tsx b/apps/govern/components/Contracts/EditVotes/index.tsx
index f04a278b..7ebf221b 100644
--- a/apps/govern/components/Contracts/EditVotes/index.tsx
+++ b/apps/govern/components/Contracts/EditVotes/index.tsx
@@ -7,9 +7,8 @@ import { Allocation } from 'types';
import { Address } from 'viem';
import { useAccount } from 'wagmi';
-import { CHAIN_NAMES } from 'libs/util-constants/src';
+import { CHAIN_NAMES, RETAINER_ADDRESS } from 'libs/util-constants/src';
-import { RETAINER_ADDRESS } from 'common-util/constants/addresses';
import { INVALIDATE_AFTER_UPDATE_KEYS } from 'common-util/constants/scopeKeys';
import { getBytes32FromAddress } from 'common-util/functions/addresses';
import { voteForNomineeWeights } from 'common-util/functions/requests';
diff --git a/apps/govern/components/Contracts/MyVotingWeight/Votes.tsx b/apps/govern/components/Contracts/MyVotingWeight/Votes.tsx
index 64fa841c..a8c60585 100644
--- a/apps/govern/components/Contracts/MyVotingWeight/Votes.tsx
+++ b/apps/govern/components/Contracts/MyVotingWeight/Votes.tsx
@@ -6,9 +6,8 @@ import styled from 'styled-components';
import { Allocation } from 'types';
import { COLOR } from 'libs/ui-theme/src';
-import { CHAIN_NAMES } from 'libs/util-constants/src';
+import { CHAIN_NAMES, RETAINER_ADDRESS } from 'libs/util-constants/src';
-import { RETAINER_ADDRESS } from 'common-util/constants/addresses';
import { getBytes32FromAddress } from 'common-util/functions/addresses';
import { NextWeekTooltip } from 'components/NextWeekTooltip';
import { useAppSelector } from 'store/index';
diff --git a/apps/govern/components/Layout/Menu.tsx b/apps/govern/components/Layout/Menu.tsx
index b070112c..6f4cb926 100644
--- a/apps/govern/components/Layout/Menu.tsx
+++ b/apps/govern/components/Layout/Menu.tsx
@@ -9,7 +9,7 @@ interface MenuItem {
}
const items: MenuItem[] = [
- { label: 'Staking Contracts', key: 'contracts', path: '/contracts' },
+ { label: 'Staking contracts', key: 'contracts', path: '/contracts' },
{ label: 'Proposals', key: 'proposals', path: '/proposals' },
{ label: 'veOLAS', key: 'veolas', path: '/veolas' },
{ label: 'Docs', key: 'docs', path: '/docs' },
diff --git a/apps/govern/hooks/useFetchStakingContractsList.ts b/apps/govern/hooks/useFetchStakingContractsList.ts
index 8c779ce0..b3254338 100644
--- a/apps/govern/hooks/useFetchStakingContractsList.ts
+++ b/apps/govern/hooks/useFetchStakingContractsList.ts
@@ -4,16 +4,15 @@ import { Address } from 'viem';
import { mainnet } from 'viem/chains';
import { useReadContract } from 'wagmi';
+import { useNominees, useNomineesMetadata } from 'libs/common-contract-functions/src';
+import { RETAINER_ADDRESS } from 'libs/util-constants/src';
import { VOTE_WEIGHTING } from 'libs/util-contracts/src/lib/abiAndAddresses';
-import { RETAINER_ADDRESS } from 'common-util/constants/addresses';
import { NEXT_RELATIVE_WEIGHTS_KEY, TIME_SUM_KEY } from 'common-util/constants/scopeKeys';
import { getBytes32FromAddress } from 'common-util/functions';
import { setStakingContracts } from 'store/govern';
import { useAppDispatch, useAppSelector } from 'store/index';
-import { useNominees } from './useNominees';
-import { useNomineesMetadata } from './useNomineesMetadata';
import { useNomineesWeights } from './useNomineesWeights';
const WEEK_IN_SECONDS = 604_800;
diff --git a/apps/govern/hooks/useFetchUserVotes.ts b/apps/govern/hooks/useFetchUserVotes.ts
index 39a5901d..d6a78bee 100644
--- a/apps/govern/hooks/useFetchUserVotes.ts
+++ b/apps/govern/hooks/useFetchUserVotes.ts
@@ -2,13 +2,14 @@ import { useEffect } from 'react';
import { UserVotes } from 'types';
import { useAccount, useBlock } from 'wagmi';
+import { useNominees } from 'libs/common-contract-functions/src';
+
import { LATEST_BLOCK_KEY, NEXT_USERS_SLOPES_KEY } from 'common-util/constants/scopeKeys';
import { getUnixWeekStartTimestamp } from 'common-util/functions/time';
import { setLastUserVote, setUserVotes } from 'store/govern';
import { useAppDispatch, useAppSelector } from 'store/index';
import { useLastUserVote } from './useLastUserVote';
-import { useNominees } from './useNominees';
import { useVoteUserPower } from './useVoteUserPower';
import { useVoteUserSlopes } from './useVoteUserSlopes';
diff --git a/apps/govern/jest.setup.js b/apps/govern/jest.setup.js
index ad1e31fe..82a0abdc 100644
--- a/apps/govern/jest.setup.js
+++ b/apps/govern/jest.setup.js
@@ -21,12 +21,14 @@ Object.defineProperty(window, 'matchMedia', {
})),
});
-
-const { gnosis, mainnet, polygon } = require('viem/chains');
+const { mainnet, optimism, gnosis, polygon, base, arbitrum, celo } = require('viem/chains');
jest.mock('wagmi/chains', () => ({
- gnosis,
mainnet,
+ optimism,
+ gnosis,
polygon,
+ base,
+ arbitrum,
+ celo,
}));
-
diff --git a/apps/launch/components/MyStakingContracts/FieldLabels.tsx b/apps/launch/components/MyStakingContracts/FieldLabels.tsx
index 75daac7e..3515163a 100644
--- a/apps/launch/components/MyStakingContracts/FieldLabels.tsx
+++ b/apps/launch/components/MyStakingContracts/FieldLabels.tsx
@@ -1,109 +1,89 @@
-import { InfoCircleOutlined } from '@ant-design/icons';
-import { Tooltip, Typography } from 'antd';
-import { ReactNode } from 'react';
+import { Typography } from 'antd';
-import { COLOR } from 'libs/ui-theme/src';
+import { LabelWithTooltip } from 'libs/ui-components/src';
import { FieldConfig } from './FieldConfig';
-const { Paragraph, Text } = Typography;
+const { Text } = Typography;
-const TextWithTooltip = ({
- text,
- description,
-}: {
- text: string;
- description?: string | ReactNode;
-}) => {
- if (!description) return {text} ;
+export const NameLabel = () => {FieldConfig.contractName.name} ;
- return (
- {description}}>
-
- {text}
-
-
- );
-};
-
-export const NameLabel = () => ;
-
-export const DescriptionLabel = () => ;
+export const DescriptionLabel = () => {FieldConfig.description.name} ;
export const MaximumStakedAgentsLabel = () => (
-
);
export const RewardsPerSecondLabel = () => (
-
);
export const TemplateInfo = () => (
-
+
);
export const MinimumStakingDepositLabel = () => (
-
);
export const MinimumStakingPeriodsLabel = () => (
-
);
export const MaximumInactivityPeriodsLabel = () => (
-
);
export const LivenessPeriodLabel = () => (
-
);
export const TimeForEmissionsLabel = () => (
-
);
export const AgentInstancesLabel = () => (
-
);
export const AgentIdsLabel = () => (
-
+
);
export const MultisigThresholdLabel = () => (
-
+
);
export const ServiceConfigHashLabel = () => (
-
+
);
export const ActivityCheckerAddressLabel = () => (
-
diff --git a/apps/launch/jest.setup.js b/apps/launch/jest.setup.js
index a4ee49c5..6f63434e 100644
--- a/apps/launch/jest.setup.js
+++ b/apps/launch/jest.setup.js
@@ -21,12 +21,16 @@ Object.defineProperty(window, 'matchMedia', {
})),
});
-const { gnosis, mainnet, polygon } = require('viem/chains');
+const { mainnet, optimism, gnosis, polygon, base, arbitrum, celo } = require('viem/chains');
jest.mock('wagmi/chains', () => ({
- gnosis,
mainnet,
+ optimism,
+ gnosis,
polygon,
+ base,
+ arbitrum,
+ celo,
}));
jest.mock('common-util/config/wagmi', () => ({
diff --git a/apps/operate/.babelrc b/apps/operate/.babelrc
new file mode 100644
index 00000000..4f049e2a
--- /dev/null
+++ b/apps/operate/.babelrc
@@ -0,0 +1,22 @@
+{
+ "presets": ["next/babel"],
+ "plugins": [
+ [
+ "module-resolver",
+ {
+ "extensions": [".js", ".jsx"],
+ "alias": {
+ "util": "./util",
+ "common-util": "./common-util",
+ "components": "./components",
+ "images": "./public/images",
+ "store": "./store",
+ "hooks": "./hooks",
+ "types": "./types",
+ "context": "./context"
+ }
+ }
+ ],
+ ["import", { "libraryName": "antd", "libraryDirectory": "lib", "style": true }]
+ ]
+}
diff --git a/apps/operate/.eslintrc.json b/apps/operate/.eslintrc.json
new file mode 100644
index 00000000..0ca4f7d6
--- /dev/null
+++ b/apps/operate/.eslintrc.json
@@ -0,0 +1,34 @@
+{
+ "extends": ["plugin:@nx/react-typescript", "next/core-web-vitals", "../../.eslintrc.json"],
+ "ignorePatterns": ["!**/*", ".next/**/*"],
+ "rules": {
+ "@nx/enforce-module-boundaries": [
+ "error",
+ {
+ "allow": ["libs*"]
+ }
+ ]
+ },
+ "overrides": [
+ {
+ "files": ["*.*"],
+ "rules": {
+ "@next/next/no-html-link-for-pages": "off"
+ }
+ },
+ {
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+ "rules": {
+ "@next/next/no-html-link-for-pages": ["error", "apps/operate/pages"]
+ }
+ },
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.js", "*.jsx"],
+ "rules": {}
+ }
+ ]
+}
diff --git a/apps/operate/common-util/config/wagmi.ts b/apps/operate/common-util/config/wagmi.ts
new file mode 100644
index 00000000..0ee86246
--- /dev/null
+++ b/apps/operate/common-util/config/wagmi.ts
@@ -0,0 +1,22 @@
+import { createConfig, http } from 'wagmi';
+import { Chain, arbitrum, base, celo, gnosis, mainnet, optimism, polygon } from 'wagmi/chains';
+
+import { RPC_URLS } from 'libs/util-constants/src';
+
+export const SUPPORTED_CHAINS: [Chain, ...Chain[]] = [
+ mainnet,
+ gnosis,
+ polygon,
+ optimism,
+ base,
+ arbitrum,
+ celo,
+];
+
+export const wagmiConfig = createConfig({
+ chains: SUPPORTED_CHAINS,
+ transports: SUPPORTED_CHAINS.reduce(
+ (acc, chain) => Object.assign(acc, { [chain.id]: http(RPC_URLS[chain.id]) }),
+ {},
+ ),
+});
diff --git a/apps/operate/common-util/functions/frontend-library.ts b/apps/operate/common-util/functions/frontend-library.ts
new file mode 100644
index 00000000..8eb8e380
--- /dev/null
+++ b/apps/operate/common-util/functions/frontend-library.ts
@@ -0,0 +1,26 @@
+import {
+ getChainId as getChainIdFn,
+ getProvider as getProviderFn,
+ notifyError,
+} from '@autonolas/frontend-library';
+
+import { RPC_URLS } from 'libs/util-constants/src';
+
+import { SUPPORTED_CHAINS } from 'common-util/config/wagmi';
+
+export const getProvider = () => {
+ const provider = getProviderFn(SUPPORTED_CHAINS, RPC_URLS);
+ // not connected, return fallback URL
+ if (typeof provider === 'string') return provider;
+ // coinbase injected multi wallet provider
+ if (provider?.selectedProvider) return provider.selectedProvider;
+ if (provider?.providerMap?.get('CoinbaseWallet'))
+ return provider.providerMap.get('CoinbaseWallet');
+ // standard provider
+ if (provider) return provider;
+ return notifyError('Provider not found');
+};
+
+export const getChainId = (chainId?: number) => {
+ return getChainIdFn(SUPPORTED_CHAINS, chainId || '');
+};
diff --git a/apps/operate/common-util/functions/index.ts b/apps/operate/common-util/functions/index.ts
new file mode 100644
index 00000000..fb4fb42a
--- /dev/null
+++ b/apps/operate/common-util/functions/index.ts
@@ -0,0 +1,2 @@
+export * from './frontend-library';
+export * from './web3';
diff --git a/apps/operate/common-util/functions/web3.ts b/apps/operate/common-util/functions/web3.ts
new file mode 100644
index 00000000..ab254ef8
--- /dev/null
+++ b/apps/operate/common-util/functions/web3.ts
@@ -0,0 +1,12 @@
+import Web3 from 'web3';
+
+import { getChainId, getProvider } from 'common-util/functions/frontend-library';
+
+/**
+ * returns the web3 details
+ */
+export const getWeb3Details = () => {
+ const chainId = getChainId();
+ const web3 = new Web3(getProvider());
+ return { web3, chainId };
+};
diff --git a/apps/operate/components/Agents.tsx b/apps/operate/components/Agents.tsx
new file mode 100644
index 00000000..01ba68a7
--- /dev/null
+++ b/apps/operate/components/Agents.tsx
@@ -0,0 +1,176 @@
+import { BulbFilled, PlayCircleOutlined, RobotOutlined } from '@ant-design/icons';
+import { Button, Card, Col, Row, Typography } from 'antd';
+import Image from 'next/image';
+import styled from 'styled-components';
+
+import { BREAK_POINT, COLOR } from 'libs/ui-theme/src';
+
+const { Title, Paragraph } = Typography;
+
+const StyledMain = styled.main`
+ display: flex;
+ flex-direction: column;
+ max-width: ${BREAK_POINT.md};
+ margin: 0 auto;
+`;
+
+const StyledCard = styled(Card)`
+ border-color: ${COLOR.BORDER_GREY};
+ width: 100%;
+ .ant-card-body {
+ padding: 0;
+ }
+`;
+
+const CardBody = styled.div`
+ padding: 16px;
+`;
+
+const StyledImage = styled(Image)`
+ border-bottom: 1px solid ${COLOR.BORDER_GREY};
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ display: block;
+ object-fit: cover;
+`;
+
+const StyledAddImage = styled(StyledImage)`
+ border-top-right-radius: 0px;
+ border-bottom-left-radius: 5px;
+ border-bottom: 0;
+ border-right: 1px solid ${COLOR.BORDER_GREY};
+`;
+
+type Agent = {
+ id: string;
+ name: string;
+ description: string;
+ comingSoon: boolean;
+ urls: Record;
+ imageFilename: string;
+};
+
+const agents: Agent[] = [
+ {
+ id: '582c485c-a2ba-4c53-8c58-8eb7b34ef87c',
+ name: 'Prediction Agent',
+ description: 'Participates in prediction markets according to your strategy.',
+ comingSoon: false,
+ urls: {
+ run: 'https://github.com/valory-xyz/trader-quickstart?tab=readme-ov-file#trader-quickstart',
+ learnMore: 'https://olas.network/services/prediction-agents',
+ gpt: 'https://chat.openai.com/g/g-6y88mEBzS-olas-trader-agent-guide',
+ },
+ imageFilename: 'prediction-agent.png',
+ },
+];
+
+const AgentCard = ({ agent }: { agent: Agent }) => {
+ const { id, name, description, imageFilename, urls, comingSoon } = agent;
+ const { run, learnMore, gpt } = urls;
+
+ return (
+
+
+
+
+
+ {name}
+
+
+ {comingSoon ? (
+
+ Coming soon
+
+ ) : (
+ <>
+ {run && (
+ }
+ href={run}
+ target="_blank"
+ className="mb-8"
+ >
+ Run Agent
+
+ )}
+
+ {learnMore && (
+
+ Learn More
+
+ )}
+ {gpt && (
+ } href={gpt} target="_blank">
+ GPT Guide
+
+ )}
+ >
+ )}
+
+
+
+ );
+};
+
+export const AgentsPage = () => {
+ return (
+
+
+ {agents.map((agent) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ Want people to run your agent?
+
+
+ Build an autonomous service using Open Autonomy. Then, simply submit a pull
+ request including the quickstart.
+
+ }
+ href="https://github.com/valory-xyz/autonolas-operate-frontend?tab=readme-ov-file#add-your-own-agent"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ Add your own
+
+
+
+
+
+
+
+ );
+};
diff --git a/apps/operate/components/Contracts/hooks.ts b/apps/operate/components/Contracts/hooks.ts
new file mode 100644
index 00000000..5ec63eed
--- /dev/null
+++ b/apps/operate/components/Contracts/hooks.ts
@@ -0,0 +1,148 @@
+import { useMemo } from 'react';
+import { StakingContract } from 'types';
+import { Abi, Address, formatEther } from 'viem';
+import { useReadContracts } from 'wagmi';
+
+import { useNominees, useNomineesMetadata } from 'libs/common-contract-functions/src';
+import { RETAINER_ADDRESS } from 'libs/util-constants/src';
+import { STAKING_TOKEN } from 'libs/util-contracts/src';
+import { getAddressFromBytes32, getBytes32FromAddress } from 'libs/util-functions/src';
+
+const ONE_YEAR = 1 * 24 * 60 * 60 * 365;
+
+const AVAILABLE_ON: Record = {
+ '0x000000000000000000000000ef44fb0842ddef59d37f85d61a1ef492bba6135d': 'pearl',
+ '0x000000000000000000000000389b46c259631acd6a69bde8b6cee218230bae8c': 'quickstart',
+ '0x0000000000000000000000005344b7dd311e5d3dddd46a4f71481bd7b05aaa3e': 'quickstart',
+};
+
+const getApy = (
+ rewardsPerSecond: bigint,
+ minStakingDeposit: bigint,
+ maxNumAgentInstances: bigint,
+) => {
+ const rewardsPerYear = rewardsPerSecond * BigInt(ONE_YEAR);
+ const apy = (rewardsPerYear * BigInt(100)) / minStakingDeposit;
+ return Number(apy) / (1 + Number(maxNumAgentInstances));
+};
+
+const getStakeRequired = (minStakingDeposit: bigint, numAgentInstances: bigint) => {
+ return formatEther(minStakingDeposit + minStakingDeposit * numAgentInstances);
+};
+
+const useContractDetails = (
+ nominees: { account: Address; chainId: number }[],
+ functionName: string,
+) => {
+ const contracts = nominees.map((nominee) => ({
+ address: getAddressFromBytes32(nominee.account),
+ abi: STAKING_TOKEN.abi as Abi,
+ chainId: Number(nominee.chainId),
+ functionName: functionName,
+ }));
+
+ const { data, isFetching } = useReadContracts({
+ contracts,
+ query: {
+ enabled: nominees.length > 0,
+ select: (data) => {
+ return data.map((item) => (item.status === 'success' ? item.result : null));
+ },
+ },
+ });
+
+ return { data, isFetching };
+};
+
+export const useStakingContractsList = () => {
+ // Get nominees list
+ const { data: nomineesData, isFetching: isNomineesLoading } = useNominees();
+ const nominees = (nomineesData || []).filter(
+ (nominee) => nominee.account !== getBytes32FromAddress(RETAINER_ADDRESS),
+ );
+
+ // Get contracts metadata
+ const { data: metadata, isLoading: isMetadataLoading } = useNomineesMetadata(nominees);
+ // Get maxNumServices
+ const { data: maxNumServicesList, isFetching: isMaxNumServicesLoading } = useContractDetails(
+ nominees,
+ 'maxNumServices',
+ );
+ // Get serviceIds
+ const { data: serviceIdsList, isFetching: isServiceIdsLoading } = useContractDetails(
+ nominees,
+ 'getServiceIds',
+ );
+ // Get rewardsPerSecond
+ const { data: rewardsPerSecondList, isFetching: isRewardsPerSecondLoading } = useContractDetails(
+ nominees,
+ 'rewardsPerSecond',
+ );
+ // Get minStakingDeposit
+ const { data: minStakingDepositList, isFetching: isMinStakingDepositLoading } =
+ useContractDetails(nominees, 'minStakingDeposit');
+ // Get numAgentInstances
+ const { data: numAgentInstancesList, isFetching: isNumAgentInstancesLoading } =
+ useContractDetails(nominees, 'numAgentInstances');
+
+ /**
+ * Sets staking contracts list to the store
+ **/
+ const contracts = useMemo(() => {
+ if (
+ // Check if all data is loaded
+ !!nominees &&
+ !!metadata &&
+ !!maxNumServicesList &&
+ !!serviceIdsList &&
+ !!rewardsPerSecondList &&
+ !!minStakingDepositList &&
+ !!numAgentInstancesList
+ ) {
+ return nominees.map((item, index) => {
+ const maxSlots = Number(maxNumServicesList[index]);
+ const servicesLength = ((serviceIdsList[index] as string[]) || []).length;
+ const availableSlots = maxSlots > 0 && servicesLength > 0 ? maxSlots - servicesLength : 0;
+ const rewardsPerSecond = rewardsPerSecondList[index] as bigint;
+ const minStakingDeposit = minStakingDepositList[index] as bigint;
+ const numAgentInstances = numAgentInstancesList[index] as bigint;
+
+ const apy = getApy(rewardsPerSecond, minStakingDeposit, numAgentInstances);
+ const stakeRequired = getStakeRequired(minStakingDeposit, numAgentInstances);
+
+ return {
+ key: item.account,
+ address: item.account,
+ chainId: Number(item.chainId),
+ metadata: metadata[item.account],
+ availableSlots,
+ maxSlots,
+ apy,
+ stakeRequired,
+ availableOn: AVAILABLE_ON[item.account] || null,
+ };
+ }) as StakingContract[];
+ }
+ return [];
+ }, [
+ nominees,
+ metadata,
+ maxNumServicesList,
+ serviceIdsList,
+ rewardsPerSecondList,
+ minStakingDepositList,
+ numAgentInstancesList,
+ ]);
+
+ return {
+ contracts,
+ isLoading:
+ isNomineesLoading ||
+ isMetadataLoading ||
+ isMaxNumServicesLoading ||
+ isServiceIdsLoading ||
+ isRewardsPerSecondLoading ||
+ isMinStakingDepositLoading ||
+ isNumAgentInstancesLoading,
+ };
+};
diff --git a/apps/operate/components/Contracts/index.tsx b/apps/operate/components/Contracts/index.tsx
new file mode 100644
index 00000000..15aaa64f
--- /dev/null
+++ b/apps/operate/components/Contracts/index.tsx
@@ -0,0 +1,159 @@
+import { DownOutlined, RightOutlined } from '@ant-design/icons';
+import { Button, Card, Flex, Table, Tag, Typography } from 'antd';
+import { ColumnsType } from 'antd/es/table';
+import Image from 'next/image';
+import styled from 'styled-components';
+import { StakingContract } from 'types';
+
+import { Caption, TextWithTooltip } from 'libs/ui-components/src';
+import { BREAK_POINT } from 'libs/ui-theme/src';
+import { CHAIN_NAMES, GOVERN_URL, NA, UNICODE_SYMBOLS } from 'libs/util-constants/src';
+
+import { useStakingContractsList } from './hooks';
+
+const StyledMain = styled.main`
+ display: flex;
+ flex-direction: column;
+ max-width: ${BREAK_POINT.xl};
+ margin: 0 auto;
+`;
+
+const { Title, Paragraph, Text } = Typography;
+
+const getAvailableOnData = (availableOn: StakingContract['availableOn']) => {
+ let icon;
+ let text;
+ let href;
+
+ switch (availableOn) {
+ case 'pearl':
+ icon = ;
+ text = 'Pearl';
+ href = 'https://olas.network/operate#download';
+ break;
+ case 'quickstart':
+ icon = ;
+ text = 'Quickstart';
+ href = 'https://github.com/valory-xyz/trader-quickstart';
+ break;
+ default:
+ text = 'Not available yet';
+ break;
+ }
+
+ return { icon, text, href };
+};
+
+const columns: ColumnsType = [
+ {
+ title: 'Contract',
+ dataIndex: 'metadata',
+ key: 'address',
+ render: (metadata) => {metadata.name || NA} ,
+ },
+ {
+ title: 'Chain',
+ dataIndex: 'chainId',
+ key: 'chainId',
+ render: (chainId) => {CHAIN_NAMES[chainId] || chainId} ,
+ },
+ {
+ title: 'Available slots',
+ dataIndex: 'availableSlots',
+ key: 'availableSlots',
+ render: (availableSlots, record) => {`${availableSlots} / ${record.maxSlots}`} ,
+ },
+ {
+ title: () => ,
+ dataIndex: 'apy',
+ key: 'apy',
+ render: (apy) => {`${apy}%`} ,
+ },
+ {
+ title: 'Stake required, OLAS',
+ dataIndex: 'stakeRequired',
+ key: 'stakeRequired',
+ render: (stakeRequired) => {stakeRequired} ,
+ },
+ {
+ title: () => (
+
+ Pearl - desktop app for non-technical users to run agents.
+
+ Quickstart - script for technical users to run agents with more
+ flexibility.
+ >
+ }
+ />
+ ),
+ dataIndex: 'availableOn',
+ key: 'availableOn',
+ render: (availableOn) => {
+ const { icon, text, href } = getAvailableOnData(availableOn);
+
+ if (href) {
+ return (
+
+
+ {icon} {text} {UNICODE_SYMBOLS.EXTERNAL_LINK}
+
+
+ );
+ }
+
+ return (
+
+ {text}
+
+ );
+ },
+ },
+];
+
+export const ContractsPage = () => {
+ const { contracts, isLoading } = useStakingContractsList();
+ return (
+
+
+
+ Staking contracts
+
+
+ Browse staking opportunities. Make a selection and start running them via Pearl or
+ Quickstart for the opportunity to earn OLAS rewards.
+
+ {
+ const Icon = expanded ? DownOutlined : RightOutlined;
+ return (
+ onExpand(record, e)}
+ />
+ );
+ },
+ expandedRowRender: (record) => (
+ <>
+ Contract description
+
+ {record.metadata ? record.metadata.description : NA}
+
+
+ View full contract details {UNICODE_SYMBOLS.EXTERNAL_LINK}
+
+ >
+ ),
+ }}
+ />
+
+
+ );
+};
diff --git a/apps/operate/components/Disclaimer.tsx b/apps/operate/components/Disclaimer.tsx
new file mode 100644
index 00000000..ff3436f3
--- /dev/null
+++ b/apps/operate/components/Disclaimer.tsx
@@ -0,0 +1,71 @@
+import { Typography } from 'antd';
+import styled from 'styled-components';
+
+const { Title, Paragraph } = Typography;
+
+const TypographyContainer = styled(Typography)`
+ max-width: 800px;
+ margin: 0 auto;
+ h1.ant-typography {
+ text-align: center;
+ }
+ ol.custom-list {
+ list-style-type: lower-alpha;
+ }
+`;
+
+export const DisclaimerPage = () => (
+
+ Disclaimer
+
+
+
+
+ This App is owned by the Autonolas DAO and operated by
+
+ Centrality Labs
+
+ . This App is for the Autonolas community to encourage Autonolas ecosystem contributors
+ and users to unlock Autonolas governance.
+
+
+
+ THIS APP IS PROVIDED "AS IS" AND "AS AVAILABLE," AT YOUR OWN RISK, AND
+ WITHOUT WARRANTIES OF ANY KIND. Neither Autonolas nor Centrality Labs will be liable for
+ any loss, whether such loss is direct, indirect, special or consequential, suffered by any
+ party as a result of their use of this app.
+
+
+
+ By accessing this app, you represent and warrant
+
+
+ that you are of legal age and that you will comply with any laws applicable to you and
+ not engage in any illegal activities;
+
+
+ that you are claiming OLAS tokens to participate in the Autonolas DAO governance
+ process and that they do not represent consideration for past or future services;
+
+
+ that you, the country you are a resident of and your wallet address is not on any
+ sanctions lists maintained by the United Nations, Switzerland, the EU, UK or the US;
+
+
+ that you are responsible for any tax obligations arising out of the interaction with
+ this app.
+
+
+
+
+
+ None of the information available on this app, or made otherwise available to you in
+ relation to its use, constitutes any legal, tax, financial or other advice. Where in doubt
+ as to the action you should take, please consult your own legal, financial, tax or other
+ professional advisors.
+
+
+
+
+
+);
diff --git a/apps/operate/components/Layout/Footer.tsx b/apps/operate/components/Layout/Footer.tsx
new file mode 100644
index 00000000..fa23a72e
--- /dev/null
+++ b/apps/operate/components/Layout/Footer.tsx
@@ -0,0 +1,24 @@
+import { Typography } from 'antd';
+import Link from 'next/link';
+
+import { Footer as CommonFooter } from 'libs/ui-components/src';
+import { OPERATE_REPO_URL } from 'libs/util-constants/src';
+
+const CenterContent = () => (
+
+ {`© Autonolas DAO ${new Date().getFullYear()} • `}
+ Disclaimer
+ {' • '}
+
+ DAO Constitution
+
+
+);
+
+export const Footer = () => (
+ } githubUrl={OPERATE_REPO_URL} />
+);
diff --git a/apps/operate/components/Layout/Logos.tsx b/apps/operate/components/Layout/Logos.tsx
new file mode 100644
index 00000000..3cf86b07
--- /dev/null
+++ b/apps/operate/components/Layout/Logos.tsx
@@ -0,0 +1,19 @@
+export const LogoSvg = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/apps/operate/components/Layout/Menu.tsx b/apps/operate/components/Layout/Menu.tsx
new file mode 100644
index 00000000..9a1396f3
--- /dev/null
+++ b/apps/operate/components/Layout/Menu.tsx
@@ -0,0 +1,39 @@
+import { Menu } from 'antd';
+import { MenuItemType } from 'antd/es/menu/hooks/useItems';
+import { useRouter } from 'next/router';
+import { FC, useEffect, useState } from 'react';
+
+
+const items: MenuItemType[] = [
+ { label: 'Live staking contracts', key: 'contracts' },
+ { label: 'Agents', key: 'agents' },
+];
+
+export const NavigationMenu: FC = () => {
+ const router = useRouter();
+ const [selectedMenu, setSelectedMenu] = useState('');
+ const { pathname } = router;
+
+ // to set default menu on first render
+ useEffect(() => {
+ if (pathname) {
+ setSelectedMenu(pathname);
+ }
+ }, [pathname]);
+
+ const handleMenuItemClick = ({ key }: MenuItemType) => {
+ const path = `/${key}`;
+ router.push(path);
+ setSelectedMenu(path);
+ };
+
+ return (
+
+ );
+};
diff --git a/apps/operate/components/Layout/index.tsx b/apps/operate/components/Layout/index.tsx
new file mode 100644
index 00000000..718dc442
--- /dev/null
+++ b/apps/operate/components/Layout/index.tsx
@@ -0,0 +1,32 @@
+import { Layout as AntdLayout } from 'antd';
+import { FC, ReactNode } from 'react';
+
+import { Footer } from './Footer';
+import { LogoSvg } from './Logos';
+import { CustomLayout, Logo, OlasHeader } from './styles';
+import { NavigationMenu } from './Menu';
+
+const { Content } = AntdLayout;
+
+interface LayoutProps {
+ children?: ReactNode;
+}
+
+export const Layout: FC = ({ children }) => {
+ return (
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+ );
+};
diff --git a/apps/operate/components/Layout/styles.tsx b/apps/operate/components/Layout/styles.tsx
new file mode 100644
index 00000000..6d457be0
--- /dev/null
+++ b/apps/operate/components/Layout/styles.tsx
@@ -0,0 +1,38 @@
+import { Layout } from 'antd';
+import Link from 'next/link';
+import styled from 'styled-components';
+
+import { COLOR } from 'libs/ui-theme/src';
+
+export const CustomLayout = styled(Layout)`
+ min-height: 100vh;
+
+ .site-layout {
+ padding: 0 24px;
+ margin-top: 64px;
+ }
+ .site-layout-background {
+ padding: 40px 0;
+ min-height: calc(100vh - 140px);
+ }
+`;
+
+export const Logo = styled(Link)`
+ font-weight: bold;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: left;
+ margin-right: 1rem;
+ > span {
+ margin-left: 0.5rem;
+ margin-right: 0.5rem;
+ }
+`;
+
+export const OlasHeader = styled(Layout.Header)`
+ padding: 0 40px;
+ border-bottom: 1px solid ${COLOR.BORDER_GREY_2};
+ display: flex;
+ align-items: center;
+`;
diff --git a/apps/operate/context/Web3ModalProvider.tsx b/apps/operate/context/Web3ModalProvider.tsx
new file mode 100644
index 00000000..d159de83
--- /dev/null
+++ b/apps/operate/context/Web3ModalProvider.tsx
@@ -0,0 +1,42 @@
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
+import { createWeb3Modal } from '@web3modal/wagmi';
+import { PropsWithChildren } from 'react';
+import { WagmiProvider } from 'wagmi';
+
+import { COLOR, W3M_BORDER_RADIUS } from 'libs/ui-theme/src';
+
+import { wagmiConfig } from 'common-util/config/wagmi';
+
+export const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: Infinity,
+ gcTime: Infinity,
+ },
+ },
+});
+
+createWeb3Modal({
+ wagmiConfig,
+ projectId: `${process.env.NEXT_PUBLIC_WALLET_PROJECT_ID}`,
+ themeMode: 'light',
+ themeVariables: {
+ '--w3m-font-family':
+ "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'",
+ '--w3m-accent': COLOR.PRIMARY,
+ '--w3m-border-radius-master': W3M_BORDER_RADIUS,
+ '--w3m-font-size-master': '11px',
+ },
+});
+
+export const Web3ModalProvider = ({ children }: PropsWithChildren) => {
+ return (
+
+
+ {children}
+
+
+
+ );
+};
diff --git a/apps/operate/index.d.ts b/apps/operate/index.d.ts
new file mode 100644
index 00000000..c6b2243a
--- /dev/null
+++ b/apps/operate/index.d.ts
@@ -0,0 +1,18 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+declare module '*.svg' {
+ const content: any;
+ export const ReactComponent: any;
+ export default content;
+}
+
+declare namespace JSX {
+ interface IntrinsicElements {
+ 'w3m-button': {
+ disabled?: boolean;
+ balance?: 'show' | 'hide';
+ size?: 'md' | 'sm';
+ label?: string;
+ loadingLabel?: string;
+ };
+ }
+}
diff --git a/apps/operate/jest.config.js b/apps/operate/jest.config.js
new file mode 100644
index 00000000..7dbaf9dd
--- /dev/null
+++ b/apps/operate/jest.config.js
@@ -0,0 +1,34 @@
+const { resolve } = require('path');
+const { pathsToModuleNameMapper } = require('ts-jest');
+const { compilerOptions } = require('../../tsconfig.base.json');
+
+module.exports = {
+ displayName: 'operate',
+ preset: '../../jest.preset.js',
+ testEnvironment: 'jsdom',
+
+ moduleNameMapper: {
+ ...pathsToModuleNameMapper(compilerOptions.paths, { prefix: resolve(__dirname, '../..') }),
+ '^util/(.*)$': '/util/$1',
+ '^common-util/(.*)$': '/common-util/$1',
+ '^components/(.*)$': '/components/$1',
+ '^images/(.*)$': '/public/images/$1',
+ '^store/(.*)$': '/store/$1',
+ '^hooks/(.*)$': '/hooks/$1',
+ '^types/(.*)$': '/types/$1',
+ '^context/(.*)$': '/context/$1',
+ },
+ setupFilesAfterEnv: ['/jest.setup.js'],
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
+ transform: {
+ '^.+\\.[tj]sx?$': [
+ 'ts-jest',
+ {
+ presets: ['@nx/next/babel'],
+ tsconfig: '/tsconfig.spec.json',
+ useESM: true,
+ },
+ ],
+ },
+ globals: { fetch },
+};
diff --git a/apps/operate/jest.setup.js b/apps/operate/jest.setup.js
new file mode 100644
index 00000000..ad1e31fe
--- /dev/null
+++ b/apps/operate/jest.setup.js
@@ -0,0 +1,32 @@
+import '@testing-library/jest-dom';
+import '@testing-library/jest-dom/jest-globals';
+
+const { TextEncoder, TextDecoder } = require('util');
+
+global.TextEncoder = TextEncoder;
+global.TextDecoder = TextDecoder;
+
+// https:// jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ })),
+});
+
+
+const { gnosis, mainnet, polygon } = require('viem/chains');
+
+jest.mock('wagmi/chains', () => ({
+ gnosis,
+ mainnet,
+ polygon,
+}));
+
diff --git a/apps/operate/middleware.ts b/apps/operate/middleware.ts
new file mode 100644
index 00000000..88188c61
--- /dev/null
+++ b/apps/operate/middleware.ts
@@ -0,0 +1,4 @@
+import { config, middleware } from 'libs/common-middleware/src';
+
+export default middleware;
+export { config };
diff --git a/apps/operate/next-env.d.ts b/apps/operate/next-env.d.ts
new file mode 100644
index 00000000..4f11a03d
--- /dev/null
+++ b/apps/operate/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/apps/operate/next.config.js b/apps/operate/next.config.js
new file mode 100644
index 00000000..06be0a2b
--- /dev/null
+++ b/apps/operate/next.config.js
@@ -0,0 +1,40 @@
+//@ts-check
+
+const objects = require('@nx/next');
+
+const { composePlugins, withNx } = objects;
+
+/**
+ * @type {import('@nx/next/plugins/with-nx').WithNxOptions}
+ **/
+const nextConfig = {
+ nx: {
+ // Set this to true if you would like to to use SVGR
+ // See: https://github.com/gregberge/svgr
+ svgr: false,
+ },
+ compiler: {
+ // For other options, see https://styled-components.com/docs/tooling#babel-plugin
+ styledComponents: true,
+ },
+ webpack(config) {
+ config.resolve.fallback = {
+ fs: false,
+ };
+ return config;
+ },
+ redirects: async () => [
+ {
+ source: '/',
+ destination: '/contracts',
+ permanent: false,
+ },
+ ],
+};
+
+const plugins = [
+ // Add more Next.js plugins to this list if needed.
+ withNx,
+];
+
+module.exports = composePlugins(...plugins)(nextConfig);
diff --git a/apps/operate/pages/_app.tsx b/apps/operate/pages/_app.tsx
new file mode 100644
index 00000000..5fd2d3d7
--- /dev/null
+++ b/apps/operate/pages/_app.tsx
@@ -0,0 +1,29 @@
+import type { AppProps } from 'next/app';
+import Head from 'next/head';
+
+import { AutonolasThemeProvider, GlobalStyles } from 'libs/ui-theme/src';
+
+import { Web3ModalProvider } from 'context/Web3ModalProvider';
+
+import { Layout } from '../components/Layout';
+
+const OperateApp = ({ Component, ...rest }: AppProps) => {
+ return (
+ <>
+
+
+ Operate
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default OperateApp;
diff --git a/apps/operate/pages/agents.tsx b/apps/operate/pages/agents.tsx
new file mode 100644
index 00000000..66271d07
--- /dev/null
+++ b/apps/operate/pages/agents.tsx
@@ -0,0 +1,3 @@
+import { AgentsPage } from 'components/Agents';
+
+export default AgentsPage;
diff --git a/apps/operate/pages/contracts.tsx b/apps/operate/pages/contracts.tsx
new file mode 100644
index 00000000..98389167
--- /dev/null
+++ b/apps/operate/pages/contracts.tsx
@@ -0,0 +1,3 @@
+import { ContractsPage } from 'components/Contracts';
+
+export default ContractsPage;
diff --git a/apps/operate/pages/disclaimer.tsx b/apps/operate/pages/disclaimer.tsx
new file mode 100644
index 00000000..9254b668
--- /dev/null
+++ b/apps/operate/pages/disclaimer.tsx
@@ -0,0 +1,3 @@
+import { DisclaimerPage } from 'components/Disclaimer';
+
+export default DisclaimerPage;
diff --git a/apps/operate/project.json b/apps/operate/project.json
new file mode 100644
index 00000000..a1fdcaca
--- /dev/null
+++ b/apps/operate/project.json
@@ -0,0 +1,58 @@
+{
+ "name": "operate",
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "apps/operate",
+ "projectType": "application",
+ "targets": {
+ "build": {
+ "executor": "@nx/next:build",
+ "outputs": ["{options.outputPath}"],
+ "defaultConfiguration": "production",
+ "options": {
+ "outputPath": "dist/apps/operate"
+ },
+ "configurations": {
+ "development": {
+ "outputPath": "apps/operate"
+ },
+ "production": {}
+ }
+ },
+ "serve": {
+ "executor": "@nx/next:server",
+ "defaultConfiguration": "development",
+ "options": {
+ "buildTarget": "operate:build",
+ "dev": true
+ },
+ "configurations": {
+ "development": {
+ "buildTarget": "operate:build:development",
+ "dev": true
+ },
+ "production": {
+ "buildTarget": "operate:build:production",
+ "dev": false
+ }
+ }
+ },
+ "export": {
+ "executor": "@nx/next:export",
+ "options": {
+ "buildTarget": "operate:build:production"
+ }
+ },
+ "lint": {
+ "executor": "@nx/eslint:lint",
+ "outputs": ["{options.outputFile}"]
+ },
+ "test": {
+ "executor": "@nx/jest:jest",
+ "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
+ "options": {
+ "jestConfig": "apps/operate/jest.config.js"
+ }
+ }
+ },
+ "tags": ["app"]
+}
diff --git a/apps/operate/public/favicon.ico b/apps/operate/public/favicon.ico
new file mode 100644
index 00000000..938fa3c8
Binary files /dev/null and b/apps/operate/public/favicon.ico differ
diff --git a/apps/operate/public/images/add-your-own.png b/apps/operate/public/images/add-your-own.png
new file mode 100644
index 00000000..2e170ae1
Binary files /dev/null and b/apps/operate/public/images/add-your-own.png differ
diff --git a/apps/operate/public/images/github.svg b/apps/operate/public/images/github.svg
new file mode 100644
index 00000000..df4d6e94
--- /dev/null
+++ b/apps/operate/public/images/github.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/operate/public/images/pearl.svg b/apps/operate/public/images/pearl.svg
new file mode 100644
index 00000000..81faa34d
--- /dev/null
+++ b/apps/operate/public/images/pearl.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/operate/public/images/prediction-agent.png b/apps/operate/public/images/prediction-agent.png
new file mode 100644
index 00000000..ef7f38bf
Binary files /dev/null and b/apps/operate/public/images/prediction-agent.png differ
diff --git a/apps/operate/public/robots.txt b/apps/operate/public/robots.txt
new file mode 100644
index 00000000..14267e90
--- /dev/null
+++ b/apps/operate/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Allow: /
\ No newline at end of file
diff --git a/apps/operate/tsconfig.json b/apps/operate/tsconfig.json
new file mode 100644
index 00000000..0d1e1250
--- /dev/null
+++ b/apps/operate/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "jsx": "preserve",
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "types": ["jest", "node", "react", "react-dom"],
+ "paths": {
+ "common-util/*": ["apps/operate/common-util/*"],
+ "components/*": ["apps/operate/components/*"],
+ "images/*": ["apps/operate/public/images/*"],
+ "hooks/*": ["apps/operate/hooks/*"],
+ "context/*": ["apps/operate/context/*"],
+ "types": ["apps/operate/types"]
+ }
+ },
+ "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"],
+ "exclude": ["node_modules", "jest.config.js", "src/**/*.spec.ts", "src/**/*.test.ts"]
+}
diff --git a/apps/operate/tsconfig.spec.json b/apps/operate/tsconfig.spec.json
new file mode 100644
index 00000000..1b3c2b61
--- /dev/null
+++ b/apps/operate/tsconfig.spec.json
@@ -0,0 +1,21 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"],
+ "jsx": "react-jsx"
+ },
+ "include": [
+ "jest.config.ts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.js",
+ "src/**/*.spec.js",
+ "src/**/*.test.jsx",
+ "src/**/*.spec.jsx",
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/apps/operate/types/index.ts b/apps/operate/types/index.ts
new file mode 100644
index 00000000..1a52a8cf
--- /dev/null
+++ b/apps/operate/types/index.ts
@@ -0,0 +1,18 @@
+import { Address } from 'viem';
+
+export type Metadata = {
+ name: string;
+ description: string;
+};
+
+export type StakingContract = {
+ key: Address;
+ address: Address;
+ chainId: number;
+ metadata: Metadata;
+ availableSlots: number;
+ maxSlots: number;
+ apy: number;
+ stakeRequired: string;
+ availableOn: 'pearl' | 'quickstart' | null;
+};
diff --git a/libs/common-contract-functions/src/index.ts b/libs/common-contract-functions/src/index.ts
index 3a96cc83..00f7f910 100644
--- a/libs/common-contract-functions/src/index.ts
+++ b/libs/common-contract-functions/src/index.ts
@@ -1,2 +1,4 @@
export * from './lib/useRewards';
export * from './lib/rewards';
+export * from './lib/useNominees';
+export * from './lib/useNomineesMetadata';
diff --git a/apps/govern/hooks/useNominees.ts b/libs/common-contract-functions/src/lib/useNominees.ts
similarity index 73%
rename from apps/govern/hooks/useNominees.ts
rename to libs/common-contract-functions/src/lib/useNominees.ts
index 4dc155ab..e902ef93 100644
--- a/apps/govern/hooks/useNominees.ts
+++ b/libs/common-contract-functions/src/lib/useNominees.ts
@@ -3,12 +3,11 @@ import { Address } from 'viem';
import { mainnet } from 'viem/chains';
import { useReadContract } from 'wagmi';
-import { VOTE_WEIGHTING } from 'libs/util-contracts/src/lib/abiAndAddresses';
-
-import { getBytes32FromAddress } from 'common-util/functions';
+import { VOTE_WEIGHTING } from 'libs/util-contracts/src';
+import { getBytes32FromAddress } from 'libs/util-functions/src';
export const useNominees = () => {
- const { data } = useReadContract({
+ const { data, isFetching } = useReadContract({
address: (VOTE_WEIGHTING.addresses as Record)[mainnet.id],
abi: VOTE_WEIGHTING.abi,
chainId: mainnet.id,
@@ -21,5 +20,5 @@ export const useNominees = () => {
},
});
- return { data };
+ return { data, isFetching };
};
diff --git a/apps/govern/hooks/useNomineesMetadata.ts b/libs/common-contract-functions/src/lib/useNomineesMetadata.ts
similarity index 93%
rename from apps/govern/hooks/useNomineesMetadata.ts
rename to libs/common-contract-functions/src/lib/useNomineesMetadata.ts
index ae4e5421..721eea62 100644
--- a/apps/govern/hooks/useNomineesMetadata.ts
+++ b/libs/common-contract-functions/src/lib/useNomineesMetadata.ts
@@ -4,9 +4,8 @@ import { Address } from 'viem';
import { useReadContracts } from 'wagmi';
import { GATEWAY_URL, HASH_PREFIX } from 'libs/util-constants/src';
-import { STAKING_TOKEN } from 'libs/util-contracts/src/lib/abiAndAddresses';
-
-import { getAddressFromBytes32, getBytes32FromAddress } from 'common-util/functions';
+import { STAKING_TOKEN } from 'libs/util-contracts/src';
+import { getAddressFromBytes32, getBytes32FromAddress } from 'libs/util-functions/src';
const BATCH_SIZE = 10;
const CONCURRENCY_LIMIT = 5;
@@ -93,5 +92,5 @@ export const useNomineesMetadata = (nominees: { account: Address; chainId: numbe
}
}, [contractsMetadata, data, getMetadata, isFetching, isLoading]);
- return { data: contractsMetadata };
+ return { data: contractsMetadata, isLoading };
};
diff --git a/libs/ui-components/src/index.ts b/libs/ui-components/src/index.ts
index 67047072..2af69469 100644
--- a/libs/ui-components/src/index.ts
+++ b/libs/ui-components/src/index.ts
@@ -1 +1,3 @@
export * from './lib/Footer';
+export * from './lib/Caption';
+export * from './lib/TextWithTooltip';
diff --git a/libs/ui-components/src/lib/Caption.tsx b/libs/ui-components/src/lib/Caption.tsx
new file mode 100644
index 00000000..164f2575
--- /dev/null
+++ b/libs/ui-components/src/lib/Caption.tsx
@@ -0,0 +1,15 @@
+import { Typography } from 'antd';
+import { ReactNode } from 'react';
+import styled from 'styled-components';
+
+const { Text } = Typography;
+
+const SmallText = styled(Text)`
+ font-size: 14px;
+`;
+
+export const Caption = ({ children, className }: { children: ReactNode; className?: string }) => (
+
+ {children}
+
+);
diff --git a/libs/ui-components/src/lib/TextWithTooltip.tsx b/libs/ui-components/src/lib/TextWithTooltip.tsx
new file mode 100644
index 00000000..23ef9b02
--- /dev/null
+++ b/libs/ui-components/src/lib/TextWithTooltip.tsx
@@ -0,0 +1,41 @@
+import { InfoCircleOutlined } from '@ant-design/icons';
+import { Tooltip, Typography } from 'antd';
+import { ReactNode } from 'react';
+
+import { COLOR } from 'libs/ui-theme/src';
+
+const { Paragraph, Text } = Typography;
+
+/**
+ * This component is used in forms to display a label with the "secondary" type
+ * and an info icon that has a tooltip when you hover over it.
+ */
+export const LabelWithTooltip = ({
+ text,
+ description,
+}: {
+ text: string;
+ description: string | ReactNode;
+}) => (
+ {description}}>
+
+ {text}
+
+
+);
+
+/**
+ * This component is used in tables or anywhere else to display text
+ * with an info icon and a tooltip on hover.
+ */
+export const TextWithTooltip = ({
+ text,
+ description,
+}: {
+ text: string;
+ description: string | ReactNode;
+}) => (
+ {description}}>
+ {text}
+
+);
diff --git a/libs/ui-components/tsconfig.json b/libs/ui-components/tsconfig.json
index f5b85657..a440e9d4 100644
--- a/libs/ui-components/tsconfig.json
+++ b/libs/ui-components/tsconfig.json
@@ -1,22 +1,26 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
- "module": "commonjs",
- "forceConsistentCasingInFileNames": true,
+ "jsx": "preserve",
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
"strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.lib.json"
- },
- {
- "path": "./tsconfig.spec.json"
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "types": ["jest", "node", "react", "react-dom"],
+ "paths": {
+ "libs/*": ["libs/*"]
}
- ]
+ },
+ "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"],
+ "exclude": ["node_modules", "jest.config.js", "src/**/*.spec.ts", "src/**/*.test.ts"]
}
diff --git a/libs/ui-theme/src/lib/GlobalStyles.tsx b/libs/ui-theme/src/lib/GlobalStyles.tsx
index ff03a500..b31a8aef 100644
--- a/libs/ui-theme/src/lib/GlobalStyles.tsx
+++ b/libs/ui-theme/src/lib/GlobalStyles.tsx
@@ -40,10 +40,10 @@ export const GlobalStyles = createGlobalStyle`
margin-bottom: 8px !important;
}
.mb-12 {
- margin-bottom: 12px;
+ margin-bottom: 12px !important;
}
.mb-16 {
- margin-bottom: 16px;
+ margin-bottom: 16px !important;
}
.mb-20 {
margin-bottom: 20px !important;
@@ -52,22 +52,22 @@ export const GlobalStyles = createGlobalStyle`
margin-bottom: 24px !important;
}
.mb-48 {
- margin-bottom: 48px;
+ margin-bottom: 48px !important;
}
.mt-0 {
- margin-top: 0px;
+ margin-top: 0px !important;
}
.mt-4 {
- margin-top: 4px;
+ margin-top: 4px !important;
}
.mt-8 {
- margin-top: 8px;
+ margin-top: 8px !important;
}
.mt-12 {
- margin-top: 12px;
+ margin-top: 12px !important;
}
.mt-16 {
- margin-top: 16px;
+ margin-top: 16px !important;
}
.mt-20 {
margin-top: 20px !important;
@@ -76,34 +76,37 @@ export const GlobalStyles = createGlobalStyle`
margin-top: 24px !important;
}
.mt-48 {
- margin-top: 48px;
+ margin-top: 48px !important;
}
.mr-8 {
- margin-right: 8px;
+ margin-right: 8px !important;
}
.mr-16 {
- margin-right: 16px;
+ margin-right: 16px !important;
}
.mr-24 {
- margin-right: 24px;
+ margin-right: 24px !important;
}
.ml-4 {
- margin-left: 4px;
+ margin-left: 4px !important;
}
.ml-8 {
- margin-left: 8px;
+ margin-left: 8px !important;
}
.p-0 {
padding: 0 !important;
}
+ .p-16 {
+ padding: 16px !important;
+ }
.pl-0 {
padding-left: 0px !important;
}
.pt-24 {
- padding-top: 24px;
+ padding-top: 24px !important;
}
.pt-48 {
- padding-top: 48px;
+ padding-top: 48px !important;
}
.block {
diff --git a/libs/ui-theme/src/lib/ThemeConfig.tsx b/libs/ui-theme/src/lib/ThemeConfig.tsx
index bd90a8a4..d9293147 100644
--- a/libs/ui-theme/src/lib/ThemeConfig.tsx
+++ b/libs/ui-theme/src/lib/ThemeConfig.tsx
@@ -60,6 +60,7 @@ export const THEME_CONFIG: ThemeConfig = {
headerSortHoverBg: '#c2cbd7',
headerSplitColor: COLOR.BORDER_GREY_2,
rowSelectedBg: COLOR.PRIMARY_BG,
+ rowExpandedBg: COLOR.WHITE,
rowSelectedHoverBg: '#efcfff',
},
Alert: {
diff --git a/libs/util-constants/src/index.ts b/libs/util-constants/src/index.ts
index 511ecbc7..dc1c7cc7 100644
--- a/libs/util-constants/src/index.ts
+++ b/libs/util-constants/src/index.ts
@@ -1,3 +1,4 @@
+export * from './lib/addresses';
export * from './lib/symbols';
export * from './lib/ipfs';
export * from './lib/exploreUrls';
diff --git a/libs/util-constants/src/lib/addresses.ts b/libs/util-constants/src/lib/addresses.ts
new file mode 100644
index 00000000..85e125d7
--- /dev/null
+++ b/libs/util-constants/src/lib/addresses.ts
@@ -0,0 +1,4 @@
+/**
+ * The address, to which non-allocated votes are sent
+ */
+export const RETAINER_ADDRESS = '0x000000000000000000000000000000000000dEaD';
diff --git a/libs/util-constants/src/lib/repoUrls.ts b/libs/util-constants/src/lib/repoUrls.ts
index 81c3a3fa..1c5973b3 100644
--- a/libs/util-constants/src/lib/repoUrls.ts
+++ b/libs/util-constants/src/lib/repoUrls.ts
@@ -2,3 +2,5 @@ export const GOVERN_REPO_URL =
'https://github.com/valory-xyz/autonolas-frontend-mono/tree/main/apps/govern';
export const LAUNCH_REPO_URL =
'https://github.com/valory-xyz/autonolas-frontend-mono/tree/main/apps/launch';
+export const OPERATE_REPO_URL =
+ 'https://github.com/valory-xyz/autonolas-frontend-mono/tree/main/apps/operate';
diff --git a/libs/util-constants/src/lib/rpcUrls.ts b/libs/util-constants/src/lib/rpcUrls.ts
index cebbe070..44882a46 100644
--- a/libs/util-constants/src/lib/rpcUrls.ts
+++ b/libs/util-constants/src/lib/rpcUrls.ts
@@ -1,12 +1,14 @@
-import { gnosis, mainnet, polygon } from 'wagmi/chains';
+import { arbitrum, base, celo, gnosis, mainnet, optimism, polygon } from 'wagmi/chains';
import { STAGING_CHAIN_ID } from '@autonolas/frontend-library';
-export const RPC_URLS = {
+export const RPC_URLS: Record = {
1:
(process.env.NEXT_PUBLIC_IS_CONNECTED_TO_TEST_NET === 'true'
? process.env.NEXT_PUBLIC_MAINNET_TEST_RPC
: process.env.NEXT_PUBLIC_MAINNET_URL) ?? mainnet.rpcUrls.default.http[0],
+
+ 10: process.env.NEXT_PUBLIC_OPTIMISM_URL ?? optimism.rpcUrls.default.http[0],
100:
(process.env.NEXT_PUBLIC_IS_CONNECTED_TO_TEST_NET === 'true'
? process.env.NEXT_PUBLIC_GNOSIS_TEST_RPC
@@ -15,6 +17,9 @@ export const RPC_URLS = {
(process.env.NEXT_PUBLIC_IS_CONNECTED_TO_TEST_NET === 'true'
? process.env.NEXT_PUBLIC_POLYGON_TEST_RPC
: process.env.NEXT_PUBLIC_POLYGON_URL) ?? polygon.rpcUrls.default.http[0],
+ 8453: process.env.NEXT_PUBLIC_BASE_URL ?? base.rpcUrls.default.http[0],
+ 42161: process.env.NEXT_PUBLIC_ARBITRUM_URL ?? arbitrum.rpcUrls.default.http[0],
+ 42220: process.env.NEXT_PUBLIC_CELO_URL ?? celo.rpcUrls.default.http[0],
[STAGING_CHAIN_ID]: 'http://127.0.0.1:8545',
};
diff --git a/libs/util-contracts/src/index.ts b/libs/util-contracts/src/index.ts
index 62ba4fbb..5f69dc87 100644
--- a/libs/util-contracts/src/index.ts
+++ b/libs/util-contracts/src/index.ts
@@ -1 +1,3 @@
export { TOKENOMICS } from './lib/abiAndAddresses/tokenomics';
+export { STAKING_TOKEN } from './lib/abiAndAddresses/stakingToken';
+export { VOTE_WEIGHTING } from './lib/abiAndAddresses/voteWeighting';
diff --git a/libs/util-contracts/src/lib/abiAndAddresses/stakingToken.js b/libs/util-contracts/src/lib/abiAndAddresses/stakingToken.ts
similarity index 100%
rename from libs/util-contracts/src/lib/abiAndAddresses/stakingToken.js
rename to libs/util-contracts/src/lib/abiAndAddresses/stakingToken.ts
diff --git a/libs/util-contracts/src/lib/abiAndAddresses/voteWeighting.js b/libs/util-contracts/src/lib/abiAndAddresses/voteWeighting.ts
similarity index 99%
rename from libs/util-contracts/src/lib/abiAndAddresses/voteWeighting.js
rename to libs/util-contracts/src/lib/abiAndAddresses/voteWeighting.ts
index 7e899ecc..5812a89a 100644
--- a/libs/util-contracts/src/lib/abiAndAddresses/voteWeighting.js
+++ b/libs/util-contracts/src/lib/abiAndAddresses/voteWeighting.ts
@@ -1,4 +1,6 @@
-export const VOTE_WEIGHTING = {
+import { Contract } from './types';
+
+export const VOTE_WEIGHTING: Contract = {
contractName: 'VoteWeighting',
addresses: {
1: '0x95418b46d5566D3d1ea62C12Aea91227E566c5c1',
diff --git a/libs/util-functions/src/lib/ethers.ts b/libs/util-functions/src/lib/ethers.ts
index 2869b63f..f8c97b8d 100644
--- a/libs/util-functions/src/lib/ethers.ts
+++ b/libs/util-functions/src/lib/ethers.ts
@@ -1,6 +1,10 @@
import { ethers } from 'ethers';
import { Address } from 'viem';
+export const getAddressFromBytes32 = (address: Address | string) => {
+ return ('0x' + address.slice(-40)) as Address;
+};
+
export const getBytes32FromAddress = (address: Address | string) => {
return ethers.zeroPadValue(address, 32) as Address;
};