> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Read Data Channels

> LayerZero V2 Read Data Channels. Find contract addresses, endpoint IDs, and configuration for supported chains. LayerZero enables crosschain messaging.

export const ReadAddressesTable = ({stage}) => {
  const [isDark, setIsDark] = useState(true);
  const [readData, setReadData] = useState([]);
  const [isLoadingData, setIsLoadingData] = useState(true);
  const [loadError, setLoadError] = useState(null);
  const [selectedOriginChain, setSelectedOriginChain] = useState([]);
  const [selectedDVN, setSelectedDVN] = useState([]);
  const [selectedDataChain, setSelectedDataChain] = useState([]);
  const [originSearchTerm, setOriginSearchTerm] = useState('');
  const [dvnSearchTerm, setDvnSearchTerm] = useState('');
  const [dataChainSearchTerm, setDataChainSearchTerm] = useState('');
  const [isOriginSearchOpen, setIsOriginSearchOpen] = useState(false);
  const [isDvnSearchOpen, setIsDvnSearchOpen] = useState(false);
  const [isDataChainSearchOpen, setIsDataChainSearchOpen] = useState(false);
  const [copiedText, setCopiedText] = useState(null);
  const [failedIcons, setFailedIcons] = useState({});
  const [displayLimit, setDisplayLimit] = useState(30);
  const [isMobile, setIsMobile] = useState(false);
  const [expandedCards, setExpandedCards] = useState({});
  const originSearchRef = useRef(null);
  const dvnSearchRef = useRef(null);
  const dataChainSearchRef = useRef(null);
  useEffect(() => {
    if (typeof document === 'undefined') return;
    const checkTheme = () => {
      setIsDark(document.documentElement.classList.contains('dark'));
    };
    checkTheme();
    const observer = new MutationObserver(checkTheme);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const checkMobile = () => {
      setIsMobile(window.innerWidth < 768);
    };
    checkMobile();
    window.addEventListener('resize', checkMobile);
    return () => window.removeEventListener('resize', checkMobile);
  }, []);
  const tokens = {
    bgMain: isDark ? '#09090b' : '#ffffff',
    bgSecondary: isDark ? '#18181b' : '#f4f4f5',
    bgHover: isDark ? '#27272a' : '#e4e4e7',
    divider: isDark ? '#27272a' : '#e4e4e7',
    textPrimary: isDark ? '#fafafa' : '#09090b',
    textSecondary: isDark ? '#a1a1aa' : '#71717a',
    textTertiary: isDark ? '#71717a' : '#a1a1aa',
    accent: '#a77dff',
    accentHover: '#8b5cf6',
    space1: '4px',
    space2: '8px',
    space3: '12px',
    space4: '16px',
    space6: '24px',
    radiusSm: '4px',
    radiusMd: '8px',
    radiusLg: '12px',
    radiusFull: '9999px',
    shadowSm: isDark ? '0 1px 2px rgba(0,0,0,0.4)' : '0 1px 2px rgba(0,0,0,0.05)',
    shadowLg: isDark ? '0 10px 40px rgba(0,0,0,0.5)' : '0 10px 40px rgba(0,0,0,0.15)'
  };
  useEffect(() => {
    if (typeof window === 'undefined') return;
    setIsLoadingData(true);
    const fetchWithFallback = async (localPath, apiUrl) => {
      for (const prefix of ['/public', '']) {
        try {
          const response = await fetch(prefix + localPath);
          if (response.ok) {
            const text = await response.text();
            if (!text.trim().startsWith('<')) {
              return JSON.parse(text);
            }
          }
        } catch (e) {
          console.warn(`Fetch failed for ${prefix + localPath}:`, e.message);
        }
      }
      if (apiUrl) {
        try {
          const response = await fetch(apiUrl);
          if (response.ok) {
            return response.json();
          }
        } catch (e) {
          console.warn(`API fetch failed for ${apiUrl}:`, e.message);
        }
      }
      return null;
    };
    const loadData = async () => {
      try {
        const [deploymentsV2, dvnDeployments, dvnExclusions] = await Promise.all([fetchWithFallback('/data/deploymentsV2.json', 'https://metadata.layerzero-api.com/v1/metadata/deployments'), fetchWithFallback('/data/dvnDeployments.json', 'https://metadata.layerzero-api.com/v1/metadata/dvns'), fetchWithFallback('/data/dvnLzReadExclusion.json', null)]);
        if (!deploymentsV2 || !dvnDeployments) {
          throw new Error('Could not load required data files');
        }
        console.log('deploymentsV2 type:', Array.isArray(deploymentsV2) ? 'array' : 'object', Array.isArray(deploymentsV2) ? deploymentsV2.length : Object.keys(deploymentsV2).length);
        console.log('dvnDeployments type:', Array.isArray(dvnDeployments) ? 'array' : 'object', Array.isArray(dvnDeployments) ? dvnDeployments.length : Object.keys(dvnDeployments).length);
        let deployments = [];
        let chainDisplayNames = {};
        if (Array.isArray(deploymentsV2)) {
          deployments = deploymentsV2;
          deploymentsV2.forEach(d => {
            if (d.chainKey && d.chainDisplayName) {
              chainDisplayNames[d.chainKey] = d.chainDisplayName;
            }
          });
        } else {
          Object.entries(deploymentsV2).forEach(([key, chainData]) => {
            if (chainData.deployments && Array.isArray(chainData.deployments)) {
              const v2Dep = chainData.deployments.find(d => d.version === 2);
              if (v2Dep) {
                const chainKey = v2Dep.chainKey || chainData.chainKey || key.replace(/-mainnet$/, '').replace(/-testnet$/, '');
                const stage = v2Dep.stage || (key.includes('-testnet') ? 'testnet' : 'mainnet');
                const chainDetails = chainData.chainDetails || ({});
                const baseName = chainDetails.name || chainDetails.shortName || chainKey;
                const stageLabel = stage === 'mainnet' ? 'Mainnet' : 'Testnet';
                const displayName = chainData.chainDisplayName || v2Dep.chainDisplayName || `${baseName} ${stageLabel}`;
                chainDisplayNames[chainKey] = displayName;
                deployments.push({
                  ...v2Dep,
                  chainKey: chainKey,
                  stage: stage,
                  chainDisplayName: displayName
                });
              }
            }
          });
        }
        const chainsWithReadLib = deployments.filter(d => d.readLib1002?.address);
        console.log('Chains with ReadLib:', chainsWithReadLib.length);
        let dvnMap = {};
        if (Array.isArray(dvnDeployments)) {
          dvnDeployments.forEach(entry => {
            const dvnId = entry.id;
            if (!dvnId) return;
            if (!dvnMap[dvnId]) {
              dvnMap[dvnId] = {
                id: dvnId,
                name: entry.canonicalName || entry.dvnDisplayName?.replace(' (lzRead)', '') || dvnId,
                deployments: {}
              };
            }
            const chainKey = entry.chainKey;
            if (entry.lzReadCompatible === true) {
              dvnMap[dvnId].deployments[chainKey] = {
                address: entry.dvnAddress,
                lzReadCompatible: true
              };
            } else if (!dvnMap[dvnId].deployments[chainKey]) {
              dvnMap[dvnId].deployments[chainKey] = {
                address: entry.dvnAddress,
                lzReadCompatible: false
              };
            }
          });
        } else {
          Object.entries(dvnDeployments).forEach(([apiChainKey, chainData]) => {
            const dvns = chainData.dvns || ({});
            Object.entries(dvns).forEach(([address, dvnInfo]) => {
              const dvnId = dvnInfo.id;
              if (!dvnId) return;
              if (!dvnMap[dvnId]) {
                dvnMap[dvnId] = {
                  id: dvnId,
                  name: dvnInfo.canonicalName || dvnId,
                  deployments: {}
                };
              }
              const chainKey = chainData.chainName || apiChainKey;
              if (dvnInfo.lzReadCompatible === true) {
                dvnMap[dvnId].deployments[chainKey] = {
                  address: address,
                  lzReadCompatible: true
                };
              } else if (!dvnMap[dvnId].deployments[chainKey]) {
                dvnMap[dvnId].deployments[chainKey] = {
                  address: address,
                  lzReadCompatible: false
                };
              }
            });
          });
        }
        const lzReadDvns = Object.values(dvnMap).filter(dvn => Object.values(dvn.deployments).some(d => d.lzReadCompatible === true));
        console.log('Total DVNs:', Object.keys(dvnMap).length, 'lzRead DVNs:', lzReadDvns.length);
        if (lzReadDvns.length > 0) {
          console.log('Sample lzRead DVN:', lzReadDvns[0].id, Object.keys(lzReadDvns[0].deployments).slice(0, 5));
        }
        const exclusionMap = {};
        if (dvnExclusions?.dvns) {
          dvnExclusions.dvns.forEach(exclusion => {
            const dvnId = exclusion.dvnId || exclusion.dvn;
            const chains = exclusion.excludedChains || exclusion.chains || [];
            const chainKeys = chains.map(c => typeof c === 'string' ? c : c.chainKey);
            exclusionMap[dvnId] = new Set(chainKeys);
          });
        }
        const processedData = [];
        chainsWithReadLib.forEach(chain => {
          const chainKey = chain.chainKey;
          const chainStage = chain.stage || 'mainnet';
          const readLibAddress = chain.readLib1002?.address;
          const chainDisplayName = chain.chainDisplayName || chain.chainName || chainDisplayNames[chainKey] || chainKey;
          lzReadDvns.forEach(dvn => {
            const dvnId = dvn.id;
            const dvnName = dvn.name || dvnId;
            const dvnDeployment = dvn.deployments?.[chainKey];
            if (!dvnDeployment || !dvnDeployment.lzReadCompatible) return;
            const dvnAddressOnChain = dvnDeployment.address;
            const excludedChains = exclusionMap[dvnId] || new Set();
            const dataChains = chainsWithReadLib.filter(targetChain => {
              const targetKey = targetChain.chainKey;
              const dvnOnTarget = dvn.deployments?.[targetKey];
              if (!dvnOnTarget || !dvnOnTarget.lzReadCompatible) return false;
              if (excludedChains.has(targetKey)) return false;
              return true;
            }).map(targetChain => ({
              dataChainKey: targetChain.chainKey,
              dataChainDisplayName: targetChain.chainDisplayName || targetChain.chainName || chainDisplayNames[targetChain.chainKey] || targetChain.chainKey,
              dataChainEid: targetChain.eid || ''
            }));
            if (dataChains.length > 0) {
              processedData.push({
                dvnDisplayName: dvnName + ' (lzRead)',
                dvnId: dvnId,
                originChainKey: chainKey,
                originChainDisplayName: chainDisplayName,
                readLibAddressOnOriginChain: readLibAddress,
                dvnAddressOnOriginChain: dvnAddressOnChain,
                dataChains: dataChains
              });
            }
          });
        });
        console.log('Processed data entries:', processedData.length);
        if (processedData.length === 0) {
          console.log('No data found. chainsWithReadLib:', chainsWithReadLib.length, 'lzReadDvns:', lzReadDvns.length);
          throw new Error('No Read channels data found from processing');
        }
        setReadData(processedData);
        setIsLoadingData(false);
      } catch (err) {
        console.error('Error processing data, trying pre-processed file:', err);
        const preProcessedPaths = ['/data/read-addresses.json', './data/read-addresses.json', '../data/read-addresses.json'];
        for (const path of preProcessedPaths) {
          try {
            const response = await fetch(path);
            if (response.ok) {
              const text = await response.text();
              if (!text.trim().startsWith('<')) {
                const data = JSON.parse(text);
                if (Array.isArray(data) && data.length > 0) {
                  setReadData(data);
                  setIsLoadingData(false);
                  return;
                }
              }
            }
          } catch (e) {
            console.warn(`Pre-processed fallback failed for ${path}:`, e.message);
          }
        }
        setLoadError(err);
        setIsLoadingData(false);
      }
    };
    loadData();
  }, []);
  const handleCopy = useCallback((text, e) => {
    if (e) {
      e.preventDefault();
      e.stopPropagation();
    }
    if (typeof navigator !== 'undefined' && navigator.clipboard) {
      navigator.clipboard.writeText(text);
      setCopiedText(text);
      setTimeout(() => setCopiedText(null), 2000);
    }
  }, []);
  const CopyButton = ({text}) => {
    const isCopied = copiedText === text;
    const [isHovered, setIsHovered] = useState(false);
    return <button onClick={e => handleCopy(text, e)} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} style={{
      background: 'none',
      border: 'none',
      cursor: 'pointer',
      padding: '4px',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      opacity: isHovered ? 1 : 0.5,
      transition: 'opacity 0.2s',
      flexShrink: 0
    }}>
        {isCopied ? <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
            <path d="M2 7L5.5 10.5L12 4" stroke="#22c55e" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
          </svg> : <svg width="14" height="14" viewBox="0 0 20 20" fill="none">
            <path fillRule="evenodd" clipRule="evenodd" d="M4 3H14V5H15V3V2H14H4H3V3V15V16H4H6V15H4V3Z" fill={tokens.textSecondary} />
            <rect x="6.5" y="5.5" width="11" height="13" stroke={tokens.textSecondary} />
          </svg>}
      </button>;
  };
  const getInitials = name => {
    if (!name) return '?';
    const words = name.toString().split(' ');
    if (words.length === 1) {
      return name.slice(0, 2).toUpperCase();
    }
    return (words[0].charAt(0) + words[1].charAt(0)).toUpperCase();
  };
  const NetworkIcon = ({chainKey, displayName, size = 20}) => {
    const iconUrl = `https://icons-ckg.pages.dev/lz-dark/networks/${chainKey}.svg`;
    const hasFailed = failedIcons[`network-${chainKey}`];
    if (hasFailed) {
      return <div style={{
        width: size,
        height: size,
        minWidth: size,
        borderRadius: '50%',
        background: 'linear-gradient(135deg, #7c3aed 0%, #a855f7 100%)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: Math.max(8, size * 0.4),
        fontWeight: 600,
        color: '#fff',
        flexShrink: 0,
        margin: 0
      }}>
          {getInitials(displayName || chainKey)}
        </div>;
    }
    return <img src={iconUrl} alt="" style={{
      width: size,
      height: size,
      borderRadius: '50%',
      objectFit: 'contain',
      flexShrink: 0,
      margin: 0,
      display: 'block'
    }} onError={() => {
      setFailedIcons(prev => ({
        ...prev,
        [`network-${chainKey}`]: true
      }));
    }} />;
  };
  const DVNIcon = ({dvnKey, size = 20}) => {
    const iconUrl = `https://icons-ckg.pages.dev/lz-scan/dvns/${dvnKey}.svg`;
    const hasFailed = failedIcons[`dvn-${dvnKey}`];
    if (hasFailed) {
      return <div style={{
        width: size,
        height: size,
        minWidth: size,
        borderRadius: '50%',
        background: 'linear-gradient(135deg, #7c3aed 0%, #a855f7 100%)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: Math.max(8, size * 0.4),
        fontWeight: 600,
        color: '#fff',
        flexShrink: 0,
        margin: 0
      }}>
          {getInitials(dvnKey)}
        </div>;
    }
    return <img src={iconUrl} alt="" style={{
      width: size,
      height: size,
      borderRadius: '50%',
      objectFit: 'contain',
      flexShrink: 0,
      margin: 0,
      display: 'block'
    }} onError={() => {
      setFailedIcons(prev => ({
        ...prev,
        [`dvn-${dvnKey}`]: true
      }));
    }} />;
  };
  const originChainOptions = useMemo(() => {
    const chainMap = {};
    readData.forEach(item => {
      if (!chainMap[item.originChainKey]) {
        chainMap[item.originChainKey] = item.originChainDisplayName;
      }
    });
    return Object.keys(chainMap).map(chainKey => ({
      value: chainKey,
      label: chainMap[chainKey]
    })).sort((a, b) => a.label.localeCompare(b.label));
  }, [readData]);
  const dvnOptions = useMemo(() => {
    const dvnMap = new Map();
    readData.forEach(item => {
      dvnMap.set(item.dvnDisplayName, item.dvnId);
    });
    const options = [];
    dvnMap.forEach((dvnId, displayName) => {
      options.push({
        value: dvnId,
        label: displayName
      });
    });
    return options.sort((a, b) => a.label.localeCompare(b.label));
  }, [readData]);
  const dataChainOptions = useMemo(() => {
    const chainMap = {};
    readData.forEach(item => {
      if (item.dataChains) {
        item.dataChains.forEach(dataChain => {
          if (!chainMap[dataChain.dataChainKey]) {
            chainMap[dataChain.dataChainKey] = dataChain.dataChainDisplayName;
          }
        });
      }
    });
    return Object.keys(chainMap).map(chainKey => ({
      value: chainKey,
      label: chainMap[chainKey]
    })).sort((a, b) => a.label.localeCompare(b.label));
  }, [readData]);
  const filteredData = useMemo(() => {
    const selectedDvnIds = new Set(selectedDVN);
    return readData.filter(item => {
      if (selectedDVN.length > 0 && !selectedDvnIds.has(item.dvnId)) {
        return false;
      }
      if (selectedOriginChain.length > 0 && !selectedOriginChain.includes(item.originChainKey)) {
        return false;
      }
      if (selectedDataChain.length > 0) {
        const hasSelectedDataChain = item.dataChains && item.dataChains.some(dc => selectedDataChain.includes(dc.dataChainKey));
        if (!hasSelectedDataChain) return false;
      }
      return true;
    });
  }, [readData, selectedDVN, selectedOriginChain, selectedDataChain]);
  const limitedData = useMemo(() => {
    return filteredData.slice(0, displayLimit);
  }, [filteredData, displayLimit]);
  const hasMoreData = filteredData.length > displayLimit;
  useEffect(() => {
    const handleClickOutside = event => {
      if (originSearchRef.current && !originSearchRef.current.contains(event.target)) {
        setIsOriginSearchOpen(false);
      }
      if (dvnSearchRef.current && !dvnSearchRef.current.contains(event.target)) {
        setIsDvnSearchOpen(false);
      }
      if (dataChainSearchRef.current && !dataChainSearchRef.current.contains(event.target)) {
        setIsDataChainSearchOpen(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);
  const handleReset = useCallback(() => {
    setSelectedOriginChain([]);
    setSelectedDVN([]);
    setSelectedDataChain([]);
    setOriginSearchTerm('');
    setDvnSearchTerm('');
    setDataChainSearchTerm('');
    setDisplayLimit(30);
  }, []);
  const handleDownloadJson = useCallback(() => {
    const jsonString = JSON.stringify(filteredData, null, 2);
    const blob = new Blob([jsonString], {
      type: 'application/json'
    });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'read-addresses.json';
    a.click();
    URL.revokeObjectURL(url);
  }, [filteredData]);
  useEffect(() => {
    setDisplayLimit(30);
  }, [selectedOriginChain, selectedDVN, selectedDataChain]);
  const filteredOriginOptions = useMemo(() => {
    const lowerSearch = originSearchTerm.toLowerCase();
    return originChainOptions.filter(opt => opt.label.toLowerCase().includes(lowerSearch) || selectedOriginChain.includes(opt.value)).sort((a, b) => {
      const aSelected = selectedOriginChain.includes(a.value);
      const bSelected = selectedOriginChain.includes(b.value);
      if (aSelected && !bSelected) return -1;
      if (!aSelected && bSelected) return 1;
      return a.label.localeCompare(b.label);
    });
  }, [originChainOptions, originSearchTerm, selectedOriginChain]);
  const filteredDvnOptions = useMemo(() => {
    const lowerSearch = dvnSearchTerm.toLowerCase();
    return dvnOptions.filter(opt => opt.label.toLowerCase().includes(lowerSearch) || selectedDVN.includes(opt.value)).sort((a, b) => {
      const aSelected = selectedDVN.includes(a.value);
      const bSelected = selectedDVN.includes(b.value);
      if (aSelected && !bSelected) return -1;
      if (!aSelected && bSelected) return 1;
      return a.label.localeCompare(b.label);
    });
  }, [dvnOptions, dvnSearchTerm, selectedDVN]);
  const filteredDataChainOptions = useMemo(() => {
    const lowerSearch = dataChainSearchTerm.toLowerCase();
    return dataChainOptions.filter(opt => opt.label.toLowerCase().includes(lowerSearch) || selectedDataChain.includes(opt.value)).sort((a, b) => {
      const aSelected = selectedDataChain.includes(a.value);
      const bSelected = selectedDataChain.includes(b.value);
      if (aSelected && !bSelected) return -1;
      if (!aSelected && bSelected) return 1;
      return a.label.localeCompare(b.label);
    });
  }, [dataChainOptions, dataChainSearchTerm, selectedDataChain]);
  const getExplorerUrl = chainKey => `https://layerzeroscan.com/api/explorer/${chainKey}/address`;
  if (isLoadingData) {
    return <div style={{
      padding: tokens.space6,
      textAlign: 'center',
      color: tokens.textSecondary,
      background: tokens.bgMain,
      borderRadius: tokens.radiusLg,
      border: `1px solid ${tokens.divider}`
    }}>
        Loading Read Channels data...
      </div>;
  }
  if (loadError) {
    return <div style={{
      padding: tokens.space6,
      textAlign: 'center',
      color: '#ef4444',
      background: tokens.bgMain,
      borderRadius: tokens.radiusLg,
      border: `1px solid ${tokens.divider}`
    }}>
        <div>Error loading Read Channels data.</div>
        <div style={{
      fontSize: '12px',
      marginTop: '8px',
      color: tokens.textSecondary
    }}>
          {loadError.message || 'Please try refreshing the page.'}
        </div>
      </div>;
  }
  return <div className="read-root" style={{
    fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',
    margin: 0,
    padding: 0
  }}>
      <style>{`
        .read-root {
          width: 100% !important;
        }
        .read-table-container {
        }
        .read-table-container table {
          margin: 0 !important;
          table-layout: fixed !important;
          width: 100% !important;
        }
        .read-table-container a,
        .read-mobile-card a {
          text-decoration: none !important;
          border-bottom: none !important;
          box-shadow: none !important;
          background-image: none !important;
        }
        .read-table-container img,
        .read-mobile-card img {
          margin: 0 !important;
          margin-top: 0 !important;
          margin-bottom: 0 !important;
        }
        .read-table-container td {
          line-height: 1.4 !important;
        }
        .read-table-container td,
        .read-table-container th {
          overflow: hidden !important;
          text-overflow: ellipsis !important;
        }
        .read-root [class*="my-[1em]"] {
          margin-top: 0 !important;
        }
        .read-root [class*="py-[1em]"] {
          padding-top: 0 !important;
        }
        .read-root [class*="overflow-x-auto"] {
          overflow-x: visible !important;
        }
        .read-table-wrapper {
          margin-top: 16px !important;
        }
        .read-download-btn,
        .read-reset-btn {
          text-decoration: none !important;
          border-bottom: none !important;
          box-shadow: none !important;
          background-image: none !important;
        }
        @media (max-width: 767px) {
          .read-root {
            max-width: 100% !important;
          }
          .read-filter-inputs {
            flex-direction: column !important;
          }
          .read-filter-inputs > div {
            width: 100% !important;
            min-width: unset !important;
          }
          .read-header-bar {
            flex-direction: column !important;
            align-items: stretch !important;
            gap: 12px !important;
          }
          .read-header-buttons {
            display: flex !important;
            gap: 8px !important;
            width: 100% !important;
          }
          .read-header-buttons button {
            flex: 1 !important;
          }
        }
      `}</style>

      {}
      <div className="read-header-bar" style={{
    display: 'flex',
    alignItems: 'center',
    padding: tokens.space4,
    gap: tokens.space4,
    fontSize: '14px',
    background: tokens.bgMain,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    marginBottom: tokens.space4,
    flexWrap: 'wrap'
  }}>
        {}
        <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: tokens.space2,
    padding: `${tokens.space2} ${tokens.space3}`,
    background: tokens.bgSecondary,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusMd,
    height: '40px',
    boxSizing: 'border-box',
    flexShrink: 0
  }}>
          <span style={{
    fontWeight: 500,
    color: tokens.textPrimary,
    whiteSpace: 'nowrap'
  }}>Read Channels</span>
          <span style={{
    background: tokens.accent,
    color: 'white',
    padding: `2px ${tokens.space2}`,
    borderRadius: tokens.radiusFull,
    fontSize: '12px',
    fontWeight: 600,
    minWidth: '24px',
    textAlign: 'center'
  }}>
            {filteredData.length}
          </span>
        </div>

        {}
        {!isMobile && <div style={{
    flex: 1
  }} />}

        {}
        <div className="read-header-buttons" style={{
    display: 'flex',
    gap: tokens.space2
  }}>
          <button onClick={handleDownloadJson} className="read-download-btn" style={{
    padding: `${tokens.space2} ${tokens.space4}`,
    background: tokens.accent,
    color: 'white',
    border: 'none',
    borderRadius: tokens.radiusMd,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '13px',
    transition: 'background 0.15s'
  }} onMouseEnter={e => e.currentTarget.style.background = tokens.accentHover} onMouseLeave={e => e.currentTarget.style.background = tokens.accent}>
            Download JSON
          </button>
          <button onClick={handleReset} className="read-reset-btn" style={{
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    padding: `${tokens.space2} ${tokens.space4}`,
    color: tokens.textPrimary,
    background: tokens.bgMain,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '14px',
    transition: 'all 0.15s'
  }} onMouseEnter={e => {
    e.currentTarget.style.borderColor = tokens.accent;
    e.currentTarget.style.color = tokens.accent;
  }} onMouseLeave={e => {
    e.currentTarget.style.borderColor = tokens.divider;
    e.currentTarget.style.color = tokens.textPrimary;
  }}>
            Reset
          </button>
        </div>
      </div>

      {}
      <div style={{
    display: 'flex',
    alignItems: 'flex-start',
    padding: tokens.space4,
    gap: tokens.space4,
    background: tokens.bgMain,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    marginBottom: tokens.space4,
    flexWrap: 'wrap'
  }}>
        {}
        {(selectedOriginChain.length > 0 || selectedDVN.length > 0 || selectedDataChain.length > 0) && <div style={{
    width: '100%',
    display: 'flex',
    flexWrap: 'wrap',
    gap: '6px',
    marginBottom: tokens.space2
  }}>
            {selectedOriginChain.map(chainKey => {
    const opt = originChainOptions.find(o => o.value === chainKey);
    return <div key={`origin-${chainKey}`} style={{
      display: 'flex',
      alignItems: 'center',
      gap: '6px',
      background: isDark ? 'rgba(167, 125, 255, 0.15)' : 'rgba(167, 125, 255, 0.1)',
      padding: '4px 10px',
      borderRadius: tokens.radiusFull,
      border: `1px solid ${isDark ? 'rgba(167, 125, 255, 0.3)' : 'rgba(167, 125, 255, 0.2)'}`
    }}>
                  <NetworkIcon chainKey={chainKey} size={16} />
                  <span style={{
      fontSize: '12px',
      color: tokens.textPrimary,
      fontWeight: 500
    }}>{opt?.label || chainKey}</span>
                  <button onClick={() => setSelectedOriginChain(selectedOriginChain.filter(c => c !== chainKey))} style={{
      background: 'none',
      border: 'none',
      cursor: 'pointer',
      padding: 0,
      color: tokens.textSecondary,
      fontSize: '16px',
      lineHeight: 1
    }}>
                    ×
                  </button>
                </div>;
  })}
            {selectedDVN.map(dvnId => {
    const opt = dvnOptions.find(o => o.value === dvnId);
    return <div key={`dvn-${dvnId}`} style={{
      display: 'flex',
      alignItems: 'center',
      gap: '6px',
      background: isDark ? 'rgba(167, 125, 255, 0.15)' : 'rgba(167, 125, 255, 0.1)',
      padding: '4px 10px',
      borderRadius: tokens.radiusFull,
      border: `1px solid ${isDark ? 'rgba(167, 125, 255, 0.3)' : 'rgba(167, 125, 255, 0.2)'}`
    }}>
                  <DVNIcon dvnKey={dvnId} size={16} />
                  <span style={{
      fontSize: '12px',
      color: tokens.textPrimary,
      fontWeight: 500
    }}>{opt?.label || dvnId}</span>
                  <button onClick={() => setSelectedDVN(selectedDVN.filter(d => d !== dvnId))} style={{
      background: 'none',
      border: 'none',
      cursor: 'pointer',
      padding: 0,
      color: tokens.textSecondary,
      fontSize: '16px',
      lineHeight: 1
    }}>
                    ×
                  </button>
                </div>;
  })}
            {selectedDataChain.map(chainKey => {
    const opt = dataChainOptions.find(o => o.value === chainKey);
    return <div key={`data-${chainKey}`} style={{
      display: 'flex',
      alignItems: 'center',
      gap: '6px',
      background: isDark ? 'rgba(59, 130, 246, 0.15)' : 'rgba(59, 130, 246, 0.1)',
      padding: '4px 10px',
      borderRadius: tokens.radiusFull,
      border: `1px solid ${isDark ? 'rgba(59, 130, 246, 0.3)' : 'rgba(59, 130, 246, 0.2)'}`
    }}>
                  <NetworkIcon chainKey={chainKey} size={16} />
                  <span style={{
      fontSize: '12px',
      color: tokens.textPrimary,
      fontWeight: 500
    }}>{opt?.label || chainKey}</span>
                  <button onClick={() => setSelectedDataChain(selectedDataChain.filter(c => c !== chainKey))} style={{
      background: 'none',
      border: 'none',
      cursor: 'pointer',
      padding: 0,
      color: tokens.textSecondary,
      fontSize: '16px',
      lineHeight: 1
    }}>
                    ×
                  </button>
                </div>;
  })}
          </div>}

        {}
        <div className="read-filter-inputs" style={{
    display: 'flex',
    gap: tokens.space3,
    flexWrap: 'wrap',
    flex: 1
  }}>
          {}
          <div style={{
    position: 'relative',
    minWidth: '200px',
    flex: 1
  }} ref={originSearchRef}>
            <div style={{
    fontSize: '12px',
    fontWeight: 500,
    color: tokens.textSecondary,
    marginBottom: '4px'
  }}>Origin Chain</div>
            <input type="text" placeholder="Search origin chains..." value={originSearchTerm} onChange={e => {
    setOriginSearchTerm(e.target.value);
    setIsOriginSearchOpen(true);
  }} onFocus={() => setIsOriginSearchOpen(true)} style={{
    width: '100%',
    padding: `${tokens.space2} ${tokens.space3}`,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    background: tokens.bgSecondary,
    color: tokens.textPrimary,
    fontSize: '14px',
    outline: 'none',
    boxSizing: 'border-box'
  }} />
            {isOriginSearchOpen && filteredOriginOptions.length > 0 && <div style={{
    position: 'absolute',
    top: '100%',
    left: 0,
    right: 0,
    marginTop: tokens.space1,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    background: tokens.bgMain,
    zIndex: 1000,
    boxShadow: tokens.shadowLg,
    maxHeight: '250px',
    overflowY: 'auto',
    padding: `${tokens.space1} 0`
  }}>
                {filteredOriginOptions.slice(0, 30).map(opt => {
    const isSelected = selectedOriginChain.includes(opt.value);
    return <div key={opt.value} onClick={() => {
      const newSelected = isSelected ? selectedOriginChain.filter(c => c !== opt.value) : [...selectedOriginChain, opt.value];
      setSelectedOriginChain(newSelected);
      setOriginSearchTerm('');
    }} style={{
      display: 'flex',
      alignItems: 'center',
      gap: '8px',
      padding: '8px 12px',
      cursor: 'pointer',
      background: isSelected ? tokens.bgSecondary : tokens.bgMain,
      fontWeight: isSelected ? 500 : 400,
      transition: 'background 0.15s'
    }} onMouseEnter={e => {
      if (!isSelected) e.currentTarget.style.background = tokens.bgSecondary;
    }} onMouseLeave={e => {
      if (!isSelected) e.currentTarget.style.background = tokens.bgMain;
    }}>
                      <NetworkIcon chainKey={opt.value} size={18} />
                      <span style={{
      fontSize: '14px',
      color: tokens.textPrimary
    }}>{opt.label}</span>
                    </div>;
  })}
              </div>}
          </div>

          {}
          <div style={{
    position: 'relative',
    minWidth: '200px',
    flex: 1
  }} ref={dvnSearchRef}>
            <div style={{
    fontSize: '12px',
    fontWeight: 500,
    color: tokens.textSecondary,
    marginBottom: '4px'
  }}>DVN Provider</div>
            <input type="text" placeholder="Search DVNs..." value={dvnSearchTerm} onChange={e => {
    setDvnSearchTerm(e.target.value);
    setIsDvnSearchOpen(true);
  }} onFocus={() => setIsDvnSearchOpen(true)} style={{
    width: '100%',
    padding: `${tokens.space2} ${tokens.space3}`,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    background: tokens.bgSecondary,
    color: tokens.textPrimary,
    fontSize: '14px',
    outline: 'none',
    boxSizing: 'border-box'
  }} />
            {isDvnSearchOpen && filteredDvnOptions.length > 0 && <div style={{
    position: 'absolute',
    top: '100%',
    left: 0,
    right: 0,
    marginTop: tokens.space1,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    background: tokens.bgMain,
    zIndex: 1000,
    boxShadow: tokens.shadowLg,
    maxHeight: '250px',
    overflowY: 'auto',
    padding: `${tokens.space1} 0`
  }}>
                {filteredDvnOptions.slice(0, 30).map(opt => {
    const isSelected = selectedDVN.includes(opt.value);
    return <div key={opt.value} onClick={() => {
      const newSelected = isSelected ? selectedDVN.filter(d => d !== opt.value) : [...selectedDVN, opt.value];
      setSelectedDVN(newSelected);
      setDvnSearchTerm('');
    }} style={{
      display: 'flex',
      alignItems: 'center',
      gap: '8px',
      padding: '8px 12px',
      cursor: 'pointer',
      background: isSelected ? tokens.bgSecondary : tokens.bgMain,
      fontWeight: isSelected ? 500 : 400,
      transition: 'background 0.15s'
    }} onMouseEnter={e => {
      if (!isSelected) e.currentTarget.style.background = tokens.bgSecondary;
    }} onMouseLeave={e => {
      if (!isSelected) e.currentTarget.style.background = tokens.bgMain;
    }}>
                      <DVNIcon dvnKey={opt.value} size={18} />
                      <span style={{
      fontSize: '14px',
      color: tokens.textPrimary
    }}>{opt.label}</span>
                    </div>;
  })}
              </div>}
          </div>

          {}
          <div style={{
    position: 'relative',
    minWidth: '200px',
    flex: 1
  }} ref={dataChainSearchRef}>
            <div style={{
    fontSize: '12px',
    fontWeight: 500,
    color: tokens.textSecondary,
    marginBottom: '4px'
  }}>Target Data Chain</div>
            <input type="text" placeholder="Search data chains..." value={dataChainSearchTerm} onChange={e => {
    setDataChainSearchTerm(e.target.value);
    setIsDataChainSearchOpen(true);
  }} onFocus={() => setIsDataChainSearchOpen(true)} style={{
    width: '100%',
    padding: `${tokens.space2} ${tokens.space3}`,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    background: tokens.bgSecondary,
    color: tokens.textPrimary,
    fontSize: '14px',
    outline: 'none',
    boxSizing: 'border-box'
  }} />
            {isDataChainSearchOpen && filteredDataChainOptions.length > 0 && <div style={{
    position: 'absolute',
    top: '100%',
    left: 0,
    right: 0,
    marginTop: tokens.space1,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    background: tokens.bgMain,
    zIndex: 1000,
    boxShadow: tokens.shadowLg,
    maxHeight: '250px',
    overflowY: 'auto',
    padding: `${tokens.space1} 0`
  }}>
                {filteredDataChainOptions.slice(0, 30).map(opt => {
    const isSelected = selectedDataChain.includes(opt.value);
    return <div key={opt.value} onClick={() => {
      const newSelected = isSelected ? selectedDataChain.filter(c => c !== opt.value) : [...selectedDataChain, opt.value];
      setSelectedDataChain(newSelected);
      setDataChainSearchTerm('');
    }} style={{
      display: 'flex',
      alignItems: 'center',
      gap: '8px',
      padding: '8px 12px',
      cursor: 'pointer',
      background: isSelected ? tokens.bgSecondary : tokens.bgMain,
      fontWeight: isSelected ? 500 : 400,
      transition: 'background 0.15s'
    }} onMouseEnter={e => {
      if (!isSelected) e.currentTarget.style.background = tokens.bgSecondary;
    }} onMouseLeave={e => {
      if (!isSelected) e.currentTarget.style.background = tokens.bgMain;
    }}>
                      <NetworkIcon chainKey={opt.value} size={18} />
                      <span style={{
      fontSize: '14px',
      color: tokens.textPrimary
    }}>{opt.label}</span>
                    </div>;
  })}
              </div>}
          </div>
        </div>
      </div>

      {}
      {isMobile ? <div className="read-mobile-cards" style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '12px'
  }}>
          {limitedData.length === 0 ? <div style={{
    textAlign: 'center',
    padding: tokens.space6,
    color: tokens.textTertiary,
    background: tokens.bgMain,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg
  }}>
              No Read Channels found.
            </div> : limitedData.map((item, index) => <div key={`mobile-${item.dvnId}-${item.originChainKey}-${index}`} className="read-mobile-card" style={{
    background: tokens.bgMain,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    overflow: 'hidden'
  }}>
                {}
                <div style={{
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: '12px',
    background: tokens.bgSecondary,
    borderBottom: `1px solid ${tokens.divider}`,
    gap: '8px',
    flexWrap: 'wrap'
  }}>
                  <a href={`/v2/deployments/chains/${item.originChainKey}`} style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    color: tokens.textPrimary,
    textDecoration: 'none',
    flex: 1,
    minWidth: '120px'
  }}>
                    <NetworkIcon chainKey={item.originChainKey} size={24} />
                    <span style={{
    fontWeight: 600,
    fontSize: '14px'
  }}>{item.originChainDisplayName}</span>
                  </a>
                  <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '6px',
    background: tokens.bgMain,
    padding: '4px 10px',
    borderRadius: tokens.radiusFull,
    border: `1px solid ${tokens.divider}`
  }}>
                    <DVNIcon dvnKey={item.dvnId} size={18} />
                    <span style={{
    fontSize: '12px',
    color: tokens.textSecondary,
    fontWeight: 500
  }}>
                      {item.dvnDisplayName}
                    </span>
                  </div>
                </div>

                {}
                <div style={{
    padding: '12px',
    display: 'flex',
    flexDirection: 'column',
    gap: '12px'
  }}>
                  {}
                  <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center'
  }}>
                    <span style={{
    fontSize: '12px',
    color: tokens.textSecondary,
    fontWeight: 500
  }}>Channel ID</span>
                    <span style={{
    fontFamily: 'ui-monospace, monospace',
    fontSize: '12px',
    color: tokens.textPrimary
  }}>
                      4294967295
                    </span>
                  </div>

                  {}
                  {item.readLibAddressOnOriginChain && <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'flex-start',
    gap: '8px'
  }}>
                      <span style={{
    fontSize: '12px',
    color: tokens.textSecondary,
    fontWeight: 500
  }}>Read Library</span>
                      <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '6px'
  }}>
                        <a href={`${getExplorerUrl(item.originChainKey)}/${item.readLibAddressOnOriginChain}`} target="_blank" rel="noopener noreferrer" style={{
    fontFamily: 'ui-monospace, monospace',
    fontSize: '11px',
    color: tokens.accent,
    textDecoration: 'none'
  }}>
                          {item.readLibAddressOnOriginChain.slice(0, 6)}...{item.readLibAddressOnOriginChain.slice(-4)}
                        </a>
                        <CopyButton text={item.readLibAddressOnOriginChain} />
                      </div>
                    </div>}

                  {}
                  {item.dvnAddressOnOriginChain && <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'flex-start',
    gap: '8px'
  }}>
                      <span style={{
    fontSize: '12px',
    color: tokens.textSecondary,
    fontWeight: 500
  }}>DVN Address</span>
                      <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '6px'
  }}>
                        <a href={`${getExplorerUrl(item.originChainKey)}/${item.dvnAddressOnOriginChain}`} target="_blank" rel="noopener noreferrer" style={{
    fontFamily: 'ui-monospace, monospace',
    fontSize: '11px',
    color: tokens.accent,
    textDecoration: 'none'
  }}>
                          {item.dvnAddressOnOriginChain.slice(0, 6)}...{item.dvnAddressOnOriginChain.slice(-4)}
                        </a>
                        <CopyButton text={item.dvnAddressOnOriginChain} />
                      </div>
                    </div>}

                  {}
                  {item.dataChains && item.dataChains.length > 0 && (() => {
    const cardKey = `${item.dvnId}-${item.originChainKey}-${index}`;
    const isExpanded = expandedCards[cardKey];
    const sortedChains = [...item.dataChains].sort((a, b) => (a.dataChainDisplayName || '').localeCompare(b.dataChainDisplayName || ''));
    const visibleChains = isExpanded ? sortedChains : sortedChains.slice(0, 5);
    const hasMore = item.dataChains.length > 5;
    return <div style={{
      borderTop: `1px solid ${tokens.divider}`,
      paddingTop: '12px',
      marginTop: '4px'
    }}>
                        <div style={{
      fontSize: '12px',
      color: tokens.textSecondary,
      fontWeight: 500,
      marginBottom: '8px'
    }}>
                          Target Data Chains ({item.dataChains.length})
                        </div>
                        <div style={{
      display: 'flex',
      flexWrap: 'wrap',
      gap: '6px'
    }}>
                          {visibleChains.map((dataChain, idx) => <a key={idx} href={`/v2/deployments/chains/${dataChain.dataChainKey}`} style={{
      display: 'flex',
      alignItems: 'center',
      gap: '4px',
      background: tokens.bgSecondary,
      padding: '4px 8px',
      borderRadius: tokens.radiusFull,
      border: `1px solid ${tokens.divider}`,
      color: tokens.textPrimary,
      textDecoration: 'none',
      fontSize: '11px'
    }}>
                              <NetworkIcon chainKey={dataChain.dataChainKey} size={14} />
                              <span style={{
      fontWeight: 500
    }}>{dataChain.dataChainDisplayName}</span>
                            </a>)}
                          {hasMore && <button onClick={() => setExpandedCards(prev => ({
      ...prev,
      [cardKey]: !prev[cardKey]
    }))} style={{
      display: 'flex',
      alignItems: 'center',
      background: isExpanded ? tokens.accent : tokens.bgSecondary,
      padding: '4px 10px',
      borderRadius: tokens.radiusFull,
      border: `1px solid ${isExpanded ? tokens.accent : tokens.divider}`,
      fontSize: '11px',
      color: isExpanded ? 'white' : tokens.accent,
      fontWeight: 500,
      cursor: 'pointer',
      transition: 'all 0.15s'
    }}>
                              {isExpanded ? 'Show less' : `+${item.dataChains.length - 5} more`}
                            </button>}
                        </div>
                      </div>;
  })()}
                </div>
              </div>)}

          {}
          {hasMoreData && <div style={{
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    padding: tokens.space4,
    gap: tokens.space3
  }}>
              <span style={{
    color: tokens.textSecondary,
    fontSize: '13px'
  }}>
                Showing {limitedData.length} of {filteredData.length} channels
              </span>
              <div style={{
    display: 'flex',
    gap: tokens.space2,
    width: '100%'
  }}>
                <button onClick={() => setDisplayLimit(prev => prev + 30)} style={{
    flex: 1,
    padding: `${tokens.space3} ${tokens.space4}`,
    background: tokens.accent,
    color: 'white',
    border: 'none',
    borderRadius: tokens.radiusMd,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '14px'
  }}>
                  Load more
                </button>
                <button onClick={() => setDisplayLimit(filteredData.length)} style={{
    flex: 1,
    padding: `${tokens.space3} ${tokens.space4}`,
    background: 'transparent',
    color: tokens.textSecondary,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusMd,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '14px'
  }}>
                  Show all
                </button>
              </div>
            </div>}
        </div> : <div className="read-table-container read-table-wrapper" style={{
    background: tokens.bgMain,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusLg,
    overflow: 'hidden',
    boxShadow: tokens.shadowSm
  }}>
        <div style={{
    overflowX: 'auto'
  }}>
          <table style={{
    width: '100%',
    borderCollapse: 'collapse',
    fontSize: '13px'
  }}>
            <thead>
              <tr>
                <th style={{
    background: tokens.bgSecondary,
    padding: '8px 10px',
    textAlign: 'center',
    fontWeight: 600,
    color: tokens.textPrimary,
    borderBottom: `1px solid ${tokens.divider}`,
    whiteSpace: 'nowrap',
    width: '17%',
    fontSize: '12px'
  }}>
                  Origin Chain
                </th>
                <th style={{
    background: tokens.bgSecondary,
    padding: '8px 10px',
    textAlign: 'center',
    fontWeight: 600,
    color: tokens.textPrimary,
    borderBottom: `1px solid ${tokens.divider}`,
    whiteSpace: 'nowrap',
    width: '14%',
    fontSize: '12px'
  }}>
                  DVN
                </th>
                <th style={{
    background: tokens.bgSecondary,
    padding: '8px 10px',
    textAlign: 'center',
    fontWeight: 600,
    color: tokens.textPrimary,
    borderBottom: `1px solid ${tokens.divider}`,
    whiteSpace: 'nowrap',
    width: '12%',
    fontSize: '12px'
  }}>
                  Channel ID
                </th>
                <th style={{
    background: tokens.bgSecondary,
    padding: '8px 10px',
    textAlign: 'center',
    fontWeight: 600,
    color: tokens.textPrimary,
    borderBottom: `1px solid ${tokens.divider}`,
    whiteSpace: 'nowrap',
    width: '18%',
    fontSize: '12px'
  }}>
                  Read Library
                </th>
                <th style={{
    background: tokens.bgSecondary,
    padding: '8px 10px',
    textAlign: 'center',
    fontWeight: 600,
    color: tokens.textPrimary,
    borderBottom: `1px solid ${tokens.divider}`,
    whiteSpace: 'nowrap',
    width: '18%',
    fontSize: '12px'
  }}>
                  DVN Address
                </th>
                <th style={{
    background: tokens.bgSecondary,
    padding: '8px 10px',
    textAlign: 'center',
    fontWeight: 600,
    color: tokens.textPrimary,
    borderBottom: `1px solid ${tokens.divider}`,
    whiteSpace: 'nowrap',
    width: '21%',
    fontSize: '12px'
  }}>
                  Data Chains
                </th>
              </tr>
            </thead>
            <tbody>
              {limitedData.length === 0 ? <tr>
                  <td colSpan={6} style={{
    textAlign: 'center',
    padding: tokens.space6,
    color: tokens.textTertiary
  }}>
                    No Read Channels found.
                  </td>
                </tr> : limitedData.map((item, index) => <tr key={`${item.dvnId}-${item.originChainKey}-${index}`} style={{
    background: tokens.bgMain,
    transition: 'background 0.15s'
  }} onMouseEnter={e => e.currentTarget.style.background = tokens.bgSecondary} onMouseLeave={e => e.currentTarget.style.background = tokens.bgMain}>
                    {}
                    <td style={{
    padding: '10px',
    borderBottom: `1px solid ${tokens.divider}`,
    verticalAlign: 'middle'
  }}>
                      <a href={`/v2/deployments/chains/${item.originChainKey}`} style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    color: tokens.textPrimary,
    textDecoration: 'none'
  }} onMouseEnter={e => e.currentTarget.querySelector('span').style.color = tokens.accent} onMouseLeave={e => e.currentTarget.querySelector('span').style.color = tokens.textPrimary}>
                        <NetworkIcon chainKey={item.originChainKey} size={20} />
                        <span style={{
    fontWeight: 500,
    transition: 'color 0.15s',
    fontSize: '13px'
  }}>{item.originChainDisplayName}</span>
                      </a>
                    </td>

                    {}
                    <td style={{
    padding: '10px',
    borderBottom: `1px solid ${tokens.divider}`,
    verticalAlign: 'middle'
  }}>
                      <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px'
  }}>
                        <DVNIcon dvnKey={item.dvnId} size={20} />
                        <span style={{
    fontWeight: 500,
    color: tokens.textPrimary,
    fontSize: '13px'
  }}>
                          {item.dvnDisplayName}
                        </span>
                      </div>
                    </td>

                    {}
                    <td style={{
    padding: '10px',
    borderBottom: `1px solid ${tokens.divider}`,
    verticalAlign: 'middle',
    textAlign: 'center'
  }}>
                      <span style={{
    fontFamily: 'ui-monospace, monospace',
    fontSize: '12px',
    color: tokens.textSecondary
  }}>
                        4294967295
                      </span>
                    </td>

                    {}
                    <td style={{
    padding: '10px',
    borderBottom: `1px solid ${tokens.divider}`,
    verticalAlign: 'middle'
  }}>
                      {item.readLibAddressOnOriginChain && <div style={{
    display: 'flex',
    alignItems: 'flex-start',
    gap: '8px'
  }}>
                          <a href={`${getExplorerUrl(item.originChainKey)}/${item.readLibAddressOnOriginChain}`} target="_blank" rel="noopener noreferrer" style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '2px',
    color: tokens.textPrimary,
    textDecoration: 'none'
  }} onMouseEnter={e => e.currentTarget.style.color = tokens.accent} onMouseLeave={e => e.currentTarget.style.color = tokens.textPrimary}>
                            <span style={{
    fontWeight: 500,
    fontSize: '13px'
  }}>ReadLib1002</span>
                            <span style={{
    fontFamily: 'ui-monospace, monospace',
    fontSize: '11px',
    color: tokens.textTertiary
  }}>
                              {item.readLibAddressOnOriginChain.slice(0, 6)}...{item.readLibAddressOnOriginChain.slice(-4)}
                            </span>
                          </a>
                          <CopyButton text={item.readLibAddressOnOriginChain} />
                        </div>}
                    </td>

                    {}
                    <td style={{
    padding: '10px',
    borderBottom: `1px solid ${tokens.divider}`,
    verticalAlign: 'middle'
  }}>
                      {item.dvnAddressOnOriginChain && <div style={{
    display: 'flex',
    alignItems: 'flex-start',
    gap: '8px'
  }}>
                          <a href={`${getExplorerUrl(item.originChainKey)}/${item.dvnAddressOnOriginChain}`} target="_blank" rel="noopener noreferrer" style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '2px',
    color: tokens.textPrimary,
    textDecoration: 'none'
  }} onMouseEnter={e => e.currentTarget.style.color = tokens.accent} onMouseLeave={e => e.currentTarget.style.color = tokens.textPrimary}>
                            <span style={{
    fontWeight: 500,
    fontSize: '13px'
  }}>DVN</span>
                            <span style={{
    fontFamily: 'ui-monospace, monospace',
    fontSize: '11px',
    color: tokens.textTertiary
  }}>
                              {item.dvnAddressOnOriginChain.slice(0, 6)}...{item.dvnAddressOnOriginChain.slice(-4)}
                            </span>
                          </a>
                          <CopyButton text={item.dvnAddressOnOriginChain} />
                        </div>}
                    </td>

                    {}
                    <td style={{
    padding: '10px',
    borderBottom: `1px solid ${tokens.divider}`,
    verticalAlign: 'top'
  }}>
                      {item.dataChains && item.dataChains.length > 0 ? <div style={{
    display: 'flex',
    flexDirection: 'column',
    gap: '6px'
  }}>
                          {[...item.dataChains].sort((a, b) => (a.dataChainDisplayName || '').localeCompare(b.dataChainDisplayName || '')).map((dataChain, idx) => <a key={idx} href={`/v2/deployments/chains/${dataChain.dataChainKey}`} style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    color: tokens.textPrimary,
    textDecoration: 'none'
  }} onMouseEnter={e => e.currentTarget.querySelector('span').style.color = tokens.accent} onMouseLeave={e => e.currentTarget.querySelector('span').style.color = tokens.textPrimary}>
                              <NetworkIcon chainKey={dataChain.dataChainKey} size={16} />
                              <span style={{
    fontWeight: 500,
    transition: 'color 0.15s',
    fontSize: '12px'
  }}>
                                {dataChain.dataChainDisplayName}
                                {dataChain.dataChainEid && <span style={{
    color: tokens.textSecondary,
    fontWeight: 400,
    marginLeft: '4px'
  }}>
                                    ({dataChain.dataChainEid})
                                  </span>}
                              </span>
                            </a>)}
                        </div> : <span style={{
    color: tokens.textTertiary,
    fontSize: '13px'
  }}>No supported data chains.</span>}
                    </td>
                  </tr>)}
            </tbody>
          </table>
        </div>

        {}
        {hasMoreData && <div style={{
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    padding: tokens.space4,
    borderTop: `1px solid ${tokens.divider}`,
    gap: tokens.space3
  }}>
            <span style={{
    color: tokens.textSecondary,
    fontSize: '13px'
  }}>
              Showing {limitedData.length} of {filteredData.length} channels
            </span>
            <button onClick={() => setDisplayLimit(prev => prev + 30)} style={{
    padding: `${tokens.space2} ${tokens.space4}`,
    background: tokens.accent,
    color: 'white',
    border: 'none',
    borderRadius: tokens.radiusMd,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '13px',
    transition: 'background 0.15s'
  }} onMouseEnter={e => e.currentTarget.style.background = tokens.accentHover} onMouseLeave={e => e.currentTarget.style.background = tokens.accent}>
              Load more
            </button>
            <button onClick={() => setDisplayLimit(filteredData.length)} style={{
    padding: `${tokens.space2} ${tokens.space4}`,
    background: 'transparent',
    color: tokens.textSecondary,
    border: `1px solid ${tokens.divider}`,
    borderRadius: tokens.radiusMd,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '13px',
    transition: 'all 0.15s'
  }} onMouseEnter={e => {
    e.currentTarget.style.borderColor = tokens.accent;
    e.currentTarget.style.color = tokens.accent;
  }} onMouseLeave={e => {
    e.currentTarget.style.borderColor = tokens.divider;
    e.currentTarget.style.color = tokens.textSecondary;
  }}>
              Show all
            </button>
          </div>}
      </div>}
    </div>;
};

All of the **LayerZero Read** specific contract addresses and supported chains.

<Tip>
  Select either an origin chain to request and receive data to, or a data chain to specify where to read data from. The table will update dynamically.
</Tip>

<ReadAddressesTable />

### Next Steps

Add these DVNs to your OApp configuration by following the CLI Guide or an appropriate quickstart:

* [CLI Guide](../get-started/create-lz-oapp/start)
* [OApp Quickstart](../developers/evm/oapp/overview)
* [OFT Quickstart](../developers/evm/oft/quickstart)
* [lzRead Quickstart](../developers/evm/lzread/overview)
