Skip to main content

Architecture Overview

The AgrospAI Data Space Portal is a sophisticated decentralized application (dApp) that combines modern web technologies with blockchain infrastructure to create a secure, scalable data marketplace.
This document provides a technical overview of the portal’s architecture, covering the frontend framework, blockchain integration, data sources, and key components.

System Architecture

The portal follows a multi-layered architecture that separates concerns while maintaining seamless integration:

Technology Stack

The portal is built on a robust technology stack optimized for Web3 applications.

Frontend Framework

The portal uses Next.js 13 with the following configuration:
Next.js Configuration
module.exports = (phase, { defaultConfig }) => {
  const nextConfig = {
    output: 'standalone',
    webpack: (config, options) => {
      config.module.rules.push(
        {
          test: /\.svg$/,
          issuer: /\.(tsx|ts)$/,
          use: [{ loader: '@svgr/webpack', options: { icon: true } }]
        },
        {
          test: /\.gif$/,
          type: 'asset/resource'
        }
      )
      
      // Browser polyfills for crypto and networking
      const fallback = config.resolve.fallback || {}
      Object.assign(fallback, {
        http: require.resolve('stream-http'),
        https: require.resolve('https-browserify'),
        fs: false,
        crypto: false,
        os: false,
        stream: false
      })
      config.resolve.fallback = fallback
      
      return config
    },
    experimental: {
      instrumentationHook: true
    }
  }
  return nextConfig
}
Key Features:
  • Standalone output for containerization
  • Webpack customization for Web3 dependencies
  • SVG component support
  • Browser polyfills for crypto operations

Blockchain Integration

The portal integrates with blockchain through multiple layers:
1

Wagmi

Wagmi provides React Hooks for Ethereum:
Wagmi Client Setup
import { configureChains, createClient, erc20ABI } from 'wagmi'
import { ethers, Contract, Signer } from 'ethers'
import { getOceanConfig } from '../ocean'
import { getSupportedChains } from './chains'
import { MetaMaskConnector } from 'wagmi/connectors/metaMask'
import { jsonRpcProvider } from 'wagmi/providers/jsonRpc'

function getProvider() {
  return jsonRpcProvider({
    rpc: (chain) => {
      const config = getOceanConfig(chain.id)
      return { http: config.nodeUri }
    }
  })
}

const supportedChains = getSupportedChains(chainIdsSupported)
const { chains, provider, webSocketProvider } = configureChains(
  supportedChains,
  [getProvider()]
)

export const wagmiClient = createClient({
  autoConnect: true,
  connectors: [
    new MetaMaskConnector({ chains }),
    new EthersWalletConnector({ chains })
  ],
  provider,
  webSocketProvider
})
This configuration:
  • Supports multiple chains dynamically
  • Enables auto-connection on page load
  • Provides both HTTP and WebSocket providers
  • Integrates custom wallet connectors
2

Ethers.js

Ethers.js handles low-level blockchain operations:
Token Balance Check
export async function getTokenBalance(
  accountId: string,
  decimals: number,
  tokenAddress: string,
  web3Provider: ethers.providers.Provider
): Promise<string> {
  if (!web3Provider || !accountId || !tokenAddress) return

  try {
    const token = new Contract(tokenAddress, erc20ABI, web3Provider)
    const balance = await token.balanceOf(accountId)
    return balance ? getAdjustDecimalsValue(balance, decimals) : null
  } catch (e) {
    LoggerInstance.error(`ERROR: Failed to get the balance: ${e.message}`)
  }
}
3

ConnectKit

ConnectKit provides the wallet connection UI:
App Setup
import { connectKitTheme, wagmiClient } from '@utils/wallet'
import { ConnectKitProvider } from 'connectkit'
import { WagmiConfig } from 'wagmi'

function MyApp({ Component, pageProps }: AppProps): ReactElement {
  return (
    <WagmiConfig client={wagmiClient}>
      <ConnectKitProvider
        options={{ initialChainId: 0 }}
        customTheme={connectKitTheme}
      >
        <MarketMetadataProvider>
          {/* App components */}
        </MarketMetadataProvider>
      </ConnectKitProvider>
    </WagmiConfig>
  )
}
Custom theming ensures brand consistency:
ConnectKit Theme
export const connectKitTheme = {
  '--ck-font-family': 'var(--font-family-base)',
  '--ck-border-radius': 'var(--border-radius)',
  '--ck-overlay-background': 'var(--background-body-transparent)',
  '--ck-modal-box-shadow': '0 0 20px 20px var(--box-shadow-color)',
  '--ck-body-background': 'var(--background-body)',
  '--ck-body-color': 'var(--font-color-text)',
  '--ck-primary-button-border-radius': 'var(--border-radius)',
  '--ck-primary-button-color': 'var(--font-color-heading)',
  '--ck-primary-button-background': 'var(--background-content)',
  '--ck-secondary-button-border-radius': 'var(--border-radius)'
}

Ocean Protocol Integration

Ocean Protocol provides the core decentralized data exchange functionality.

Configuration

Ocean configuration is managed centrally:
Ocean Config
import { ConfigHelper, Config } from '@oceanprotocol/lib'
import { chains, getCustomChainIds } from '../../../chains.config'

export function getOceanConfig(network: string | number): Config {
  const filterBy = typeof network === 'string' ? 'network' : 'chainId'
  const customConfig = chains.find((c) => c[filterBy] === network)

  if (getCustomChainIds().includes(network as number))
    return customConfig as Config

  let config = new ConfigHelper().getConfig(
    network,
    process.env.NEXT_PUBLIC_INFURA_PROJECT_ID
  ) as Config
  
  return customConfig
    ? ({ ...config, ...customConfig } as Config)
    : (config as Config)
}
This allows seamless integration with both standard Ocean networks and custom Gaia-X networks like Pontus-X.

Smart Contracts

Key smart contracts include:
Creates ERC-721 NFTs representing asset ownership:
NFT Factory Address
nftFactoryAddress: process.env.NEXT_PUBLIC_NFT_FACTORY_ADDRESS
Mints ERC-20 datatokens for access control:
Datatoken Template
defaultDatatokenTemplateIndex: 2,
Enables fixed-price token swaps:
Fixed Rate Exchange
publisherMarketFixedSwapFee:
  process.env.NEXT_PUBLIC_PUBLISHER_MARKET_FIXED_SWAP_FEE || '0',
Distributes free datatokens:
Dispenser Config
allowFreePricing: process.env.NEXT_PUBLIC_ALLOW_FREE_PRICING || 'true',

Data Sources

The portal aggregates data from multiple sources:

Aquarius - Metadata Cache

Aquarius is Ocean Protocol’s metadata cache powered by Elasticsearch:
Aquarius Configuration
metadataCacheUri:
  process.env.NEXT_PUBLIC_METADATACACHE_URI || 'https://aquarius.pontus-x.eu',
Querying Aquarius:
Aquarius Query Example
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import { queryMetadata } from '@utils/aquarius'

const queryLatest = {
  query: {
    query_string: { query: `-isInPurgatory:true` }
  },
  sort: { created: 'desc' }
}

const result = await queryMetadata(query, source.token)
Aquarius supports full Elasticsearch query syntax, enabling complex searches, filters, and aggregations.

The Graph - Subgraph

The Graph indexes blockchain data and provides a GraphQL API:
GraphQL Query Example
import { gql, useQuery } from 'urql'

const query = gql`
  query TopSalesQuery {
    users(first: 20, orderBy: totalSales, orderDirection: desc) {
      id
      totalSales
    }
  }
`

function Component() {
  const { data } = useQuery(query, {}, { pollInterval: 5000 })
  return <div>{data}</div>
}
Subgraph Endpoint:
https://subgraph.test.pontus-x.eu/subgraphs/name/oceanprotocol/ocean-subgraph

Provider Service

The Provider service handles off-chain operations:
  • File encryption/decryption
  • Access validation
  • Compute job orchestration
  • Asset downloads
Provider Configuration
customProviderUrl: process.env.NEXT_PUBLIC_PROVIDER_URL,
Example Provider Usage:
DDO Encryption
import { ProviderInstance } from '@oceanprotocol/lib'

const ddoEncrypted = await ProviderInstance.encrypt(
  ddo,
  ddo.chainId,
  customProviderUrl || values.services[0].providerUrl.url,
  newAbortController()
)

State Management

The portal uses React Context API for global state:

MarketMetadataProvider

Manages market-wide configuration and metadata

UserPreferencesProvider

Handles user preferences like theme, language, and network selection

UrqlProvider

Provides GraphQL client for subgraph queries

AutomationProvider

Manages automation wallet features
Provider Hierarchy:
Provider Structure
<MarketMetadataProvider>
  <UrqlProvider>
    <UserPreferencesProvider>
      <AutomationProvider>
        <ConsentProvider>
          <SearchBarStatusProvider>
            <FilterProvider>
              <QueryClientProvider client={queryClient}>
                <App>
                  <Component {...pageProps} />
                </App>
              </QueryClientProvider>
            </FilterProvider>
          </SearchBarStatusProvider>
        </ConsentProvider>
      </AutomationProvider>
    </UserPreferencesProvider>
  </UrqlProvider>
</MarketMetadataProvider>

Publishing Workflow

The publishing process involves multiple steps with blockchain transactions:
1

Create Tokens

Create NFT and datatokens with pricing:
Token Creation
async function create(values: FormPublishData) {
  const config = getOceanConfig(chain?.id)
  LoggerInstance.log('[publish] using config: ', config)

  const { erc721Address, datatokenAddress, txHash } =
    await createTokensAndPricing(values, accountIdToUse, config, nftFactory)

  const isSuccess = Boolean(erc721Address && datatokenAddress && txHash)
  if (!isSuccess) throw new Error('No Token created. Please try again.')

  return { erc721Address, datatokenAddress }
}
2

Encrypt DDO

Build and encrypt the DDO (Decentralized Data Object):
DDO Encryption
async function encrypt(
  values: FormPublishData,
  erc721Address: string,
  datatokenAddress: string
) {
  const ddo = await transformPublishFormToDdo(
    values,
    datatokenAddress,
    erc721Address
  )

  const ddoEncrypted = await ProviderInstance.encrypt(
    ddo,
    ddo.chainId,
    customProviderUrl || values.services[0].providerUrl.url,
    newAbortController()
  )

  return { ddo, ddoEncrypted }
}
3

Publish Metadata

Write metadata to the NFT:
Metadata Publishing
async function publish(
  values: FormPublishData,
  ddo: DDO,
  ddoEncrypted: string
) {
  const res = await setNFTMetadataAndTokenURI(
    ddo,
    accountIdToUse,
    signerToUse,
    values.metadata.nft,
    newAbortController()
  )
  const tx = await res.wait()
  
  return { did: ddo.id }
}

Asset Access Flow

Accessing a dataset involves validation and payment:
Access Control
async function handleOrderOrDownload(dataParams?: UserCustomParameters) {
  setIsLoading(true)
  try {
    if (isOwned) {
      // User already owns access - download directly
      setIsDownloading(true)
      await Promise.all([
        downloadFile(signer, asset, accountId, validOrderTx, dataParams),
        new Promise((resolve) => setTimeout(resolve, 3000))
      ])
      setIsDownloading(false)
    } else {
      // User needs to purchase - create order
      const orderTx = await order(
        signer,
        asset,
        orderPriceAndFees,
        accountId,
        hasDatatoken
      )
      const tx = await orderTx.wait()
      if (!tx) throw new Error()
      
      setIsOwned(true)
      setValidOrderTx(tx.transactionHash)
    }
  } catch (error) {
    LoggerInstance.error(error)
    toast.error('An error occurred, please retry.')
  }
  setIsLoading(false)
}
All access operations require a valid signature from the connected wallet. Never share your private keys or seed phrase.

Compliance and Security

The portal implements several compliance and security features:

GDPR Compliance

Privacy Configuration
privacyPreferenceCenter:
  process.env.NEXT_PUBLIC_PRIVACY_PREFERENCE_CENTER || 'false',

defaultPrivacyPolicySlug: '/privacy/en',

Gaia-X Integration

Gaia-X Registries
allowedGaiaXRegistryDomains: [
  'https://registry.gaia-x.eu/v2206',
  'https://registry.lab.gaia-x.eu/v2206'
],

Compliance API

Compliance Endpoint
complianceUri:
  process.env.NEXT_PUBLIC_COMPLIANCE_URI ||
  'https://www.delta-dao.com/compliance',

complianceApiVersion:
  process.env.NEXT_PUBLIC_COMPLIANCE_API_VERSION || '2210',

Network Monitoring

The portal includes network health monitoring:
Network Alert Config
networkAlertConfig: {
  // Refresh interval - 30 seconds
  refreshInterval: 30000,
  // Block count error margin
  errorMargin: 10,
  // Status endpoints by chainId
  statusEndpoints: {
    32456: 'https://status.dev.pontus-x.eu/'
  }
}

Performance Optimizations

  • React Query for API response caching
  • Next.js page caching
  • Browser localStorage for preferences

Deployment Architecture

The portal supports multiple deployment strategies:
Build Configuration
{
  "scripts": {
    "build": "npm run postinstall && npm run pregenerate && next build",
    "build:static": "npm run build && next export",
    "serve": "serve -s public/"
  }
}

Standalone Mode

Docker-friendly standalone build for containerized deployments

Static Export

Static HTML export for CDN hosting (Netlify, Vercel, S3)

Development Tools

Testing

Test Configuration
{
  "scripts": {
    "test": "npm run pregenerate && npm run lint && npm run type-check && npm run jest",
    "jest": "jest -c .jest/jest.config.js",
    "jest:watch": "jest -c .jest/jest.config.js --watch"
  }
}
Testing Stack:
  • Jest for unit testing
  • Testing Library for component testing
  • Storybook for component development

Code Quality

Quality Scripts
{
  "scripts": {
    "lint": "eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx .",
    "lint:fix": "eslint --ignore-path .gitignore --ext .ts,.tsx . --fix",
    "format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json}' --write",
    "type-check": "tsc --noEmit"
  }
}

Extension Points

The architecture supports various extensions:
Add new blockchain networks via chains.config.js:
module.exports = {
  chains: [
    {
      chainId: 32456,
      network: 'pontusx',
      metadataCacheUri: 'https://aquarius.pontus-x.eu',
      nodeUri: 'https://rpc.pontus-x.eu'
    }
  ]
}
Extend UI with new components in src/components/@shared/
Add new data sources by creating providers in src/@context/
Integrate analytics via the analytics configuration:
Analytics Config
plausibleDataDomain: false,

Summary

The AgrospAI Data Space Portal architecture provides: Scalability - Modular design supports growth and new features
Security - Multiple layers of validation and encryption
Compliance - Built-in GDPR and Gaia-X support
Performance - Optimized with caching and code splitting
Extensibility - Clean interfaces for customization
Maintainability - TypeScript, testing, and documentation
For implementation details of specific features, refer to the source code in the repository. All major components include inline documentation.

Build docs developers (and LLMs) love