From b6c6680dec5c6836e49434f3af1763baadc97602 Mon Sep 17 00:00:00 2001 From: Sebastian Abromeit <38963270+Abrom8@users.noreply.github.com> Date: Wed, 20 Mar 2024 16:48:55 +0100 Subject: [PATCH] Merge pull request #86 from deltaDAO/feat/alignment Feat/alignment --- app.config.js | 13 ++- content/site.json | 66 +++++++++++---- .../Automation/AutomationProvider.tsx | 34 ++++---- src/@context/MarketMetadata/_types.ts | 12 +++ src/@hooks/useBalance.tsx | 21 +++-- src/@types/AssetExtended.d.ts | 4 +- src/@types/TokenBalance.d.ts | 8 ++ src/@utils/subgraph.ts | 4 +- src/@utils/wallet/index.ts | 6 +- src/components/@shared/AssetList/index.tsx | 27 +++++- src/components/@shared/MenuDropdown/index.tsx | 8 +- .../@shared/NetworkStatus/index.tsx | 84 +++++++++++++++++++ .../@shared/Page/PageHeader.module.css | 5 +- src/components/@shared/Page/PageHeader.tsx | 12 ++- .../Compute/FormComputeDataset.tsx | 4 +- src/components/Asset/AssetActions/index.tsx | 4 +- src/components/Header/Menu.tsx | 10 +-- .../UserPreferences/Automation/Balance.tsx | 16 ++-- .../UserPreferences/Automation/Details.tsx | 8 +- .../UserPreferences/Automation/index.tsx | 20 ++--- src/components/Home/Ecosystem/Card.tsx | 28 +++++-- src/components/Home/Ecosystem/index.tsx | 1 + 22 files changed, 294 insertions(+), 101 deletions(-) create mode 100644 src/components/@shared/NetworkStatus/index.tsx diff --git a/app.config.js b/app.config.js index 7eb5da50d..4ac3cca72 100644 --- a/app.config.js +++ b/app.config.js @@ -95,5 +95,16 @@ module.exports = { }, // Display alert banner for the developer preview deployment - showPreviewAlert: process.env.NEXT_PUBLIC_SHOW_PREVIEW_ALERT || 'false' + showPreviewAlert: process.env.NEXT_PUBLIC_SHOW_PREVIEW_ALERT || 'false', + + networkAlertConfig: { + // Refresh interval for network status - 30 sec + refreshInterval: 30000, + // Margin of error for block count (how much difference between min / max block numbers before showing an alert) + errorMargin: 10, + // Map chainIds to their respective status endpoints + statusEndpoints: { + 100: 'https://status.genx.delta-dao.com/api/check-blocks' + } + } } diff --git a/content/site.json b/content/site.json index d65dac7e2..614584200 100644 --- a/content/site.json +++ b/content/site.json @@ -26,70 +26,104 @@ "subItems": [ { "name": "Pontus-X", - "link": "https://pontus-x.eu/" + "link": "https://pontus-x.eu/", + "isLive": true }, { "name": "GX4M moveID", "link": "https://portal.moveid.eu/", "description": "moveID aims to develop a decentralized digital mobility identity infrastructure for Europe.", "image": "moveid-logo.webp", - "category": "mobility" + "category": "mobility", + "isLive": true }, { "name": "Cooperants", "link": "https://cooperants.pontus-x.eu/", "description": "The world's only collaborative alliance in the Aeronautics and space sector that solves pressing digital collaboration problems by creating a common data space.", "image": "cooperants-logo.webp", - "category": "space" + "category": "manufacturing", + "isLive": true }, { "name": "Airbus", "link": "https://airbus.pontus-x.eu/", "description": "For those who make this world a safer place. We pioneer defense and space for their mission success.", "image": "airbus-logo.webp", - "category": "space" + "category": "manufacturing", + "isLive": true }, { "name": "Service-Meister", "link": "https://servicemeister.pontus-x.eu/", "description": "An AI-based Service Ecosystem for Technical Service in the Age of Industry 4.0.", "image": "service-meister-logo.webp", - "category": "manufacturing" - }, - { - "name": "Future Mobility Marketplace", - "link": "https://marketplace.future-mobility-alliance.org/", - "description": "Find or publish datasets and AI algorithms for solving challenges in mobility.", - "image": "fmdm-logo.webp", - "category": "mobility" + "category": "AI", + "isLive": true }, { "name": "Berlin State Library", "link": "https://sbb.pontus-x.eu/", "description": "Explore the Open Science Ecosystem, a collaboration of libraries, universities, and science pioneers under Gaia-X principles for limitless shared knowledge.", "image": "sbb-logo.webp", - "category": "science" + "category": "language models", + "isLive": true + }, + { + "name": "Flex4Res", + "link": "https://flex4res.pontus-x.eu/", + "description": "Flex4Res stands for Data spaces for FLEXible production lines and supply chains FOR RESilient manufacturing. Flex4Res develops an open platform for secure and sovereign data exchange", + "image": "flex4res-logo.webp", + "category": "manufacturing", + "isLive": true + }, + { + "name": "Future Mobility Marketplace", + "link": "https://marketplace.future-mobility-alliance.org/", + "description": "Find or publish datasets and AI algorithms for solving challenges in mobility.", + "image": "fmdm-logo.webp", + "category": "mobility", + "isLive": true }, { "name": "EnergySHR", "link": "https://www.energyshr.nl/", "description": "A platform for sharing (SHR) datasets and AI algorithms that contribute to solving challenges of the energy transition.", "image": "energy-shr-logo.webp", - "category": "science" + "category": "energy", + "isLive": true }, { "name": "Clinical Insights Exchange", "link": "https://cix.triall.io/", "description": "A federated data platform to change clinical data assets and analytics services in a trusted, privacy-friendly environment.", "image": "clinical-insights-exchange-logo.webp", - "category": "health" + "category": "health", + "isLive": true }, { "name": "UdL Research Data Space", "link": "https://dataspace.angliru.udl.cat/", "description": "An open research platform following the FAIR guiding principles of findability, accessibility, interoperability and reusability, while guaranteeing data sovereignty.", "image": "udl-logo.webp", - "category": "science" + "category": "agriculture", + "isLive": true + }, + { + "name": "Under Construction: ACCURATE", + "link": "https://ec.europa.eu/info/funding-tenders/opportunities/portal/screen/how-to-participate/org-details/999999999/project/101138269/program/43108390/details", + "description": "Gaia-X Lighthouse ACCURATE aims to boost the competitiveness of European manufacturing companies with DSS and Manufacturing as a Service frameworks. ", + "image": "accurate-logo.webp", + "category": "manufacturing", + "isLive": false + }, + { + "name": "deltaDAO AI marketplace", + "link": "https://market.delta-dao.com/", + "description": "With deltaDAO's AI Marketplace, you have the freedom to decide how your AI service is offered. By keeping your AI private, you retain full control and do not disclose your valuable IP.", + "image": "deltadao-logo.webp", + "category": "AI", + "isLive": false } ] } diff --git a/src/@context/Automation/AutomationProvider.tsx b/src/@context/Automation/AutomationProvider.tsx index f75f67026..db4fb3c3b 100644 --- a/src/@context/Automation/AutomationProvider.tsx +++ b/src/@context/Automation/AutomationProvider.tsx @@ -21,16 +21,11 @@ export enum AUTOMATION_MODES { ADVANCED = 'advanced' } -export interface NativeTokenBalance { - symbol: string - balance: string -} export interface AutomationProviderValue { autoWallet: Wallet autoWalletAddress: string isAutomationEnabled: boolean balance: UserBalance - nativeBalance: NativeTokenBalance isLoading: boolean decryptPercentage: number hasValidEncryptedWallet: boolean @@ -65,8 +60,12 @@ function AutomationProvider({ children }) { address: autoWallet?.address as `0x${string}` }) - const [nativeBalance, setNativeBalance] = useState() - const [balance, setBalance] = useState({}) + const [balance, setBalance] = useState({ + native: { + symbol: 'eth', + balance: '0' + } + }) const [hasDeleteRequest, setHasDeleteRequest] = useState(false) @@ -98,16 +97,22 @@ function AutomationProvider({ children }) { if (!autoWallet) return try { + const newBalance: UserBalance = { + native: { + symbol: 'eth', + balance: '0' + } + } if (balanceNativeToken) - setNativeBalance({ - symbol: balanceNativeToken?.symbol.toLowerCase() || 'ETH', - balance: balanceNativeToken?.formatted - }) + newBalance.native.symbol = + balanceNativeToken?.symbol.toLowerCase() || 'eth' + newBalance.native.balance = balanceNativeToken?.formatted if (approvedBaseTokens?.length > 0) { - const newBalance = await getApprovedTokenBalances(autoWallet?.address) - setBalance(newBalance) - } else setBalance(undefined) + const approved = await getApprovedTokenBalances(autoWallet?.address) + newBalance.approved = approved + } + setBalance(newBalance) } catch (error) { LoggerInstance.error('[AutomationProvider] Error: ', error.message) } @@ -207,7 +212,6 @@ function AutomationProvider({ children }) { autoWallet, autoWalletAddress, balance, - nativeBalance, isAutomationEnabled, isLoading, decryptPercentage, diff --git a/src/@context/MarketMetadata/_types.ts b/src/@context/MarketMetadata/_types.ts index 3151179f8..899987003 100644 --- a/src/@context/MarketMetadata/_types.ts +++ b/src/@context/MarketMetadata/_types.ts @@ -36,6 +36,16 @@ export interface AppConfig { roughTxGasEstimate: number } showPreviewAlert: string + networkAlertConfig: { + // Refresh interval for network status - 30 sec + refreshInterval: number + // Margin of error for block count (how much difference between min / max block numbers before showing an alert) + errorMargin: number + // Map chainIds to their respective status endpoints + statusEndpoints: { + [chainId: number]: string + } + } } export interface SiteContent { siteTitle: string @@ -52,12 +62,14 @@ export interface SiteContent { description?: string image?: string category?: string + isLive?: boolean subItems?: { name: string link: string description?: string image?: string category?: string + isLive?: boolean }[] }[] }[] diff --git a/src/@hooks/useBalance.tsx b/src/@hooks/useBalance.tsx index 2eacb2f38..9462ae76b 100644 --- a/src/@hooks/useBalance.tsx +++ b/src/@hooks/useBalance.tsx @@ -11,7 +11,7 @@ import { getTokenBalance } from '@utils/wallet' interface BalanceProviderValue { balance: UserBalance - getApprovedTokenBalances: (address: string) => Promise + getApprovedTokenBalances: (address: string) => Promise } function useBalance(): BalanceProviderValue { @@ -22,12 +22,15 @@ function useBalance(): BalanceProviderValue { const { chain } = useNetwork() const [balance, setBalance] = useState({ - eth: '0' + native: { + symbol: 'eth', + balance: '0' + } }) const getApprovedTokenBalances = useCallback( - async (address: string): Promise => { - const newBalance: UserBalance = {} + async (address: string): Promise => { + const newBalance: TokenBalances = {} if (approvedBaseTokens?.length > 0) { await Promise.allSettled( @@ -64,11 +67,13 @@ function useBalance(): BalanceProviderValue { try { const userBalance = balanceNativeToken?.formatted const key = balanceNativeToken?.symbol.toLowerCase() - const newNativeBalance: UserBalance = { [key]: userBalance } - const newBalance = { - ...newNativeBalance, - ...(await getApprovedTokenBalances(address)) + const newBalance: UserBalance = { + native: { + symbol: key, + balance: userBalance + }, + approved: await getApprovedTokenBalances(address) } setBalance(newBalance) diff --git a/src/@types/AssetExtended.d.ts b/src/@types/AssetExtended.d.ts index 71d63e265..abb730c45 100644 --- a/src/@types/AssetExtended.d.ts +++ b/src/@types/AssetExtended.d.ts @@ -1,4 +1,4 @@ -import { Asset } from '@oceanprotocol/lib' +import { Asset, Metadata } from '@oceanprotocol/lib' // declaring into global scope to be able to use this as // ambiant types despite the above imports @@ -6,7 +6,7 @@ declare global { interface AssetExtended extends Asset { accessDetails?: AccessDetails views?: number - metadata: MetadataExtended + metadata: Metadata services: ServiceExtended[] } } diff --git a/src/@types/TokenBalance.d.ts b/src/@types/TokenBalance.d.ts index d51e735d1..bd117f8c5 100644 --- a/src/@types/TokenBalance.d.ts +++ b/src/@types/TokenBalance.d.ts @@ -1,3 +1,11 @@ interface UserBalance { + native: { + symbol: string + balance: string + } + approved?: TokenBalances +} + +interface TokenBalances { [key: string]: string } diff --git a/src/@utils/subgraph.ts b/src/@utils/subgraph.ts index 3cc588751..613e31e9e 100644 --- a/src/@utils/subgraph.ts +++ b/src/@utils/subgraph.ts @@ -58,6 +58,7 @@ const OpcsApprovedTokensQuery = gql` export const tokenAddressesEUROe = { 100: '0xe974c4894996e012399dedbda0be7314a73bbff1', 137: '0x820802Fa8a99901F52e39acD21177b0BE6EE2974', + 32456: '0x8A4826071983655805bF4f29828577Cd6b1aC0cB', 80001: '0xA089a21902914C3f3325dBE2334E9B466071E5f1' } @@ -187,7 +188,8 @@ export async function getOpcsApprovedTokens( ...approvedTokens, { address: tokenAddressesEUROe[chainId], - decimals: 6, + // TODO: revert once decimals changed to 6 on pontus-x + decimals: chainId === 32456 ? 18 : 6, name: 'EUROe', symbol: 'EUROe' } diff --git a/src/@utils/wallet/index.ts b/src/@utils/wallet/index.ts index b37f41878..9121e1af8 100644 --- a/src/@utils/wallet/index.ts +++ b/src/@utils/wallet/index.ts @@ -176,12 +176,12 @@ export async function getTokenBalance( } } -export function getTokenBalanceFromSymbol( +export function getApprovedTokenBalanceFromSymbol( balance: UserBalance, symbol: string ): string { if (!symbol) return - - const baseTokenBalance = balance?.[symbol.toLocaleLowerCase()] + const { approved } = balance + const baseTokenBalance = approved?.[symbol.toLocaleLowerCase()] return baseTokenBalance || '0' } diff --git a/src/components/@shared/AssetList/index.tsx b/src/components/@shared/AssetList/index.tsx index e02f1bf24..83f1a5f14 100644 --- a/src/components/@shared/AssetList/index.tsx +++ b/src/components/@shared/AssetList/index.tsx @@ -1,5 +1,5 @@ import AssetTeaser from '@shared/AssetTeaser' -import { ReactElement, useState } from 'react' +import { ReactElement, useEffect, useState } from 'react' import Pagination from '@shared/Pagination' import styles from './index.module.css' import AssetTitle from '@shared/AssetListTitle' @@ -10,8 +10,20 @@ import { getServiceByName } from '@utils/ddo' import AssetViewSelector, { AssetViewOptions } from './AssetViewSelector' import Time from '../atoms/Time' import Loader from '../atoms/Loader' +import NetworkName from '../NetworkName' +import { useUserPreferences } from '../../../@context/UserPreferences' +import { ChainDoesNotSupportMulticallError } from 'wagmi' -const columns: TableOceanColumn[] = [ +const networkColumn: TableOceanColumn = { + name: 'Network', + selector: (row) => { + const { chainId } = row + return + }, + maxWidth: '10rem' +} + +const tableColumns: TableOceanColumn[] = [ { name: 'Dataset', selector: (row) => { @@ -94,6 +106,17 @@ export default function AssetList({ showAssetViewSelector, defaultAssetView }: AssetListProps): ReactElement { + const { chainIds } = useUserPreferences() + + const [columns, setColumns] = useState(tableColumns) + + useEffect(() => { + if (chainIds.length > 1) { + const [datasetColumn, ...otherColumns] = tableColumns + setColumns([datasetColumn, networkColumn, ...otherColumns]) + } else setColumns(tableColumns) + }, [chainIds]) + const [activeAssetView, setActiveAssetView] = useState( defaultAssetView || AssetViewOptions.Grid ) diff --git a/src/components/@shared/MenuDropdown/index.tsx b/src/components/@shared/MenuDropdown/index.tsx index 55022108f..0c68695d3 100644 --- a/src/components/@shared/MenuDropdown/index.tsx +++ b/src/components/@shared/MenuDropdown/index.tsx @@ -40,17 +40,13 @@ export default function MenuDropdown({ content={
    {items.map((item, i) => { - const { name, link, subItems } = item + const { name, subItems } = item return (
  • {subItems && subItems.length > 0 ? ( ) : ( - + )}
  • ) diff --git a/src/components/@shared/NetworkStatus/index.tsx b/src/components/@shared/NetworkStatus/index.tsx new file mode 100644 index 000000000..73ad4f8eb --- /dev/null +++ b/src/components/@shared/NetworkStatus/index.tsx @@ -0,0 +1,84 @@ +import { ReactElement, useCallback, useEffect, useState } from 'react' +import { useNetwork } from 'wagmi' +import { useMarketMetadata } from '../../../@context/MarketMetadata' +import Alert from '../atoms/Alert' +import axios from 'axios' +import { LoggerInstance } from '@oceanprotocol/lib' + +export default function NetworkStatus({ + className +}: { + className?: string +}): ReactElement { + const [showNetworkAlert, setShowNetworkAlert] = useState(false) + const [network, setNetwork] = useState() + const { appConfig } = useMarketMetadata() + const { chain } = useNetwork() + + const { networkAlertConfig } = appConfig + + const fetchNetworkStatus = useCallback( + async (chainId: number) => { + if (!chainId) return + setNetwork(chain?.name) + const apiEndpoint = networkAlertConfig.statusEndpoints[chainId] + if (!apiEndpoint) return + LoggerInstance.log(`[NetworkStatus] retrieving network status`, { + apiEndpoint + }) + try { + const result = await axios.get(apiEndpoint) + const { Nodes } = result.data + const { nodes }: { nodes: { [node: string]: number } } = Nodes + let minBlock: number + let maxBlock: number + Object.values(nodes).forEach((block) => { + if (!minBlock || block < minBlock) minBlock = block + if (!maxBlock || block > maxBlock) maxBlock = block + }) + const hasError = maxBlock - minBlock > networkAlertConfig.errorMargin + setShowNetworkAlert(hasError) + LoggerInstance.log(`[NetworkStatus] network status updated:`, { + minBlock, + maxBlock, + hasError + }) + } catch (error) { + LoggerInstance.error( + `[NetworkStatus] could not retrieve network status:`, + error.message + ) + } + }, + [networkAlertConfig, chain] + ) + + useEffect(() => { + if (!chain?.id) return + + fetchNetworkStatus(chain?.id) + + // init periodic refresh for network status + const networkStatusInterval = setInterval( + () => fetchNetworkStatus(chain?.id), + networkAlertConfig.refreshInterval + ) + + return () => { + clearInterval(networkStatusInterval) + } + }, [chain, fetchNetworkStatus]) + + return ( + showNetworkAlert && ( + setShowNetworkAlert(false)} + className={className} + /> + ) + ) +} diff --git a/src/components/@shared/Page/PageHeader.module.css b/src/components/@shared/Page/PageHeader.module.css index ec95acd3a..fa68c0290 100644 --- a/src/components/@shared/Page/PageHeader.module.css +++ b/src/components/@shared/Page/PageHeader.module.css @@ -11,7 +11,6 @@ .header { margin-bottom: var(--spacer); - max-width: 50rem; } .homeWrapper .header { @@ -75,3 +74,7 @@ margin-top: calc(var(--spacer) / 2); max-width: 30rem; } + +.networkAlert { + margin: var(--spacer) auto; +} diff --git a/src/components/@shared/Page/PageHeader.tsx b/src/components/@shared/Page/PageHeader.tsx index a364e131b..89c9eab98 100644 --- a/src/components/@shared/Page/PageHeader.tsx +++ b/src/components/@shared/Page/PageHeader.tsx @@ -1,8 +1,9 @@ -import { ReactElement } from 'react' +import SearchBar from '@components/Header/SearchBar' +import Markdown from '@shared/Markdown' import classNames from 'classnames/bind' +import { ReactElement } from 'react' +import NetworkStatus from '../NetworkStatus' import styles from './PageHeader.module.css' -import Markdown from '@shared/Markdown' -import SearchBar from '@components/Header/SearchBar' const cx = classNames.bind(styles) @@ -36,7 +37,10 @@ export default function PageHeader({ ) : ( -

    {title}

    +
    +

    {title}

    + +
    )} {description && ( diff --git a/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx b/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx index 49aae0c78..2b79c5431 100644 --- a/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx +++ b/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx @@ -10,7 +10,7 @@ import { useAsset } from '@context/Asset' import content from '../../../../../content/pages/startComputeDataset.json' import { Asset, ComputeEnvironment, ZERO_ADDRESS } from '@oceanprotocol/lib' import { getAccessDetails } from '@utils/accessDetailsAndPricing' -import { getTokenBalanceFromSymbol } from '@utils/wallet' +import { getApprovedTokenBalanceFromSymbol } from '@utils/wallet' import { MAX_DECIMALS } from '@utils/constants' import Decimal from 'decimal.js' import { useAccount } from 'wagmi' @@ -273,7 +273,7 @@ export default function FormStartCompute({ totalPrices.forEach((price) => { const balanceToUse = isAutomationEnabled ? automationBalance : balance - const baseTokenBalance = getTokenBalanceFromSymbol( + const baseTokenBalance = getApprovedTokenBalanceFromSymbol( balanceToUse, price.symbol ) diff --git a/src/components/Asset/AssetActions/index.tsx b/src/components/Asset/AssetActions/index.tsx index f05cc638b..e62e3f82d 100644 --- a/src/components/Asset/AssetActions/index.tsx +++ b/src/components/Asset/AssetActions/index.tsx @@ -11,7 +11,7 @@ import { useIsMounted } from '@hooks/useIsMounted' import styles from './index.module.css' import { useFormikContext } from 'formik' import { FormPublishData } from '@components/Publish/_types' -import { getTokenBalanceFromSymbol } from '@utils/wallet' +import { getApprovedTokenBalanceFromSymbol } from '@utils/wallet' import AssetStats from './AssetStats' import { isAddressWhitelisted } from '@utils/ddo' import { useAccount, useProvider, useNetwork, useSigner } from 'wagmi' @@ -154,7 +154,7 @@ export default function AssetActions({ const balanceToUse = isAutomationEnabled ? automationBalance : balance - const baseTokenBalance = getTokenBalanceFromSymbol( + const baseTokenBalance = getApprovedTokenBalanceFromSymbol( balanceToUse, asset?.accessDetails?.baseToken?.symbol ) diff --git a/src/components/Header/Menu.tsx b/src/components/Header/Menu.tsx index 929c55638..394154744 100644 --- a/src/components/Header/Menu.tsx +++ b/src/components/Header/Menu.tsx @@ -11,7 +11,6 @@ import MenuDropdown from '@components/@shared/MenuDropdown' import SearchButton from './SearchButton' import Button from '@components/@shared/atoms/Button' import UserPreferences from './UserPreferences' -import { useAutomation } from '../../@context/Automation/AutomationProvider' import Automation from './UserPreferences/Automation' const Wallet = loadable(() => import('./Wallet')) @@ -25,9 +24,10 @@ declare type MenuItem = { image?: string category?: string className?: string + isLive?: boolean } -export function MenuLink({ name, link, className }: MenuItem) { +export function MenuLink({ name, link, className, isLive }: MenuItem) { const router = useRouter() const basePath = router?.pathname.split(/[/?]/)[1] @@ -39,7 +39,9 @@ export function MenuLink({ name, link, className }: MenuItem) { [className]: className }) - return ( + return isLive === false ? ( + <> + ) : ( + {live ? ( + + ) : ( + + )} diff --git a/src/components/Home/Ecosystem/index.tsx b/src/components/Home/Ecosystem/index.tsx index 664cf3e4d..de3ca4960 100644 --- a/src/components/Home/Ecosystem/index.tsx +++ b/src/components/Home/Ecosystem/index.tsx @@ -18,6 +18,7 @@ export default function Ecosystem() { image={portal.image} link={portal.link} title={portal.name} + live={portal.isLive} /> ))}