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} + +
+ {description} +
+ {comingSoon ? ( + + ) : ( + <> + {run && ( + + )} +
+ {learnMore && ( + + )} + {gpt && ( + + )} + + )} +
+
+ + ); +}; + +export const AgentsPage = () => { + return ( + + + {agents.map((agent) => ( + + ))} + + + + + + + + + Want people to run <b>your</b> agent? + + + Build an autonomous service using Open Autonomy. Then, simply submit a pull + request including the quickstart. + + + + + + + + + ); +}; 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 = Pearl app; + text = 'Pearl'; + href = 'https://olas.network/operate#download'; + break; + case 'quickstart': + icon = Github; + 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 ( + + ); + } + + return ( + + ); + }, + }, +]; + +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) => ( + <> + + + {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 + + +
    +
  1. + 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. +
  2. + +
  3. + 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. +
  4. + +
  5. + By accessing this app, you represent and warrant +
      +
    1. + that you are of legal age and that you will comply with any laws applicable to you and + not engage in any illegal activities; +
    2. +
    3. + 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; +
    4. +
    5. + 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; +
    6. +
    7. + that you are responsible for any tax obligations arising out of the interaction with + this app. +
    8. +
    +
  6. + +
  7. + 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. +
  8. +
+
+ +
+); 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; };
Contract description