> ## 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.

# OFT Ecosystem & Stargate Assets

> LayerZero V2 OFT Ecosystem & Stargate Assets. Find contract addresses, endpoint IDs, and configuration for supported chains. LayerZero enables crosschain...

export const StargateEcosystemAssetsTable = () => {
  const {useState, useEffect, useMemo, useCallback, useRef} = React;
  const [isDark, setIsDark] = useState(true);
  useEffect(() => {
    if (typeof window === '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();
  }, []);
  const colors = useMemo(() => ({
    bg: isDark ? '#18181b' : '#ffffff',
    bgSecondary: isDark ? '#27272a' : '#f4f4f5',
    bgTertiary: isDark ? '#3f3f46' : '#e4e4e7',
    border: isDark ? '#3f3f46' : '#e4e4e7',
    borderHover: '#a855f7',
    text: isDark ? '#fafafa' : '#18181b',
    textSecondary: isDark ? '#a1a1aa' : '#52525b',
    textMuted: '#71717a',
    accent: '#a855f7',
    accentHover: '#9333ea',
    hoverBg: isDark ? '#3f3f46' : '#e4e4e7',
    rowHover: isDark ? '#27272a' : '#f4f4f5'
  }), [isDark]);
  const [hovered, setHovered] = useState(null);
  const getInitialParams = () => {
    if (typeof window === 'undefined') return {
      stages: ['mainnet'],
      chains: [],
      assets: [],
      issuers: []
    };
    const params = new URLSearchParams(window.location.search);
    return {
      stages: params.get('stages') ? params.get('stages').split(',') : ['mainnet'],
      chains: params.get('chains') ? params.get('chains').split(',').map(c => c.toLowerCase()) : [],
      assets: params.get('assets') ? params.get('assets').split(',').map(a => a.toUpperCase()) : [],
      issuers: params.get('issuers') ? params.get('issuers').split(',') : []
    };
  };
  const initialParams = getInitialParams();
  const [selectedStage, setSelectedStage] = useState(initialParams.stages);
  const [selectedChains, setSelectedChains] = useState(initialParams.chains);
  const [selectedAssets, setSelectedAssets] = useState(initialParams.assets);
  const [selectedIssuers, setSelectedIssuers] = useState(initialParams.issuers);
  const [assetSearchTerm, setAssetSearchTerm] = useState('');
  const [issuerSearchTerm, setIssuerSearchTerm] = useState('');
  const [chainSearchTerm, setChainSearchTerm] = useState('');
  const [debouncedAssetSearch, setDebouncedAssetSearch] = useState('');
  const [debouncedIssuerSearch, setDebouncedIssuerSearch] = useState('');
  const [debouncedChainSearch, setDebouncedChainSearch] = useState('');
  const [isAssetSearchOpen, setIsAssetSearchOpen] = useState(false);
  const [isIssuerSearchOpen, setIsIssuerSearchOpen] = useState(false);
  const [isChainSearchOpen, setIsChainSearchOpen] = useState(false);
  const assetSearchRef = useRef(null);
  const issuerSearchRef = useRef(null);
  const chainSearchRef = useRef(null);
  const [assets, setAssets] = useState([]);
  const [isLoadingAssets, setIsLoadingAssets] = useState(true);
  const [deploymentsData, setDeploymentsData] = useState([]);
  const [isLoadingDeployments, setIsLoadingDeployments] = useState(true);
  const [copied, setCopied] = useState(null);
  const [isMobile, setIsMobile] = useState(false);
  const [iconErrors, setIconErrors] = useState({});
  const [hoveredRow, setHoveredRow] = useState(null);
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedAssetSearch(assetSearchTerm), 150);
    return () => clearTimeout(timer);
  }, [assetSearchTerm]);
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedIssuerSearch(issuerSearchTerm), 150);
    return () => clearTimeout(timer);
  }, [issuerSearchTerm]);
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedChainSearch(chainSearchTerm), 150);
    return () => clearTimeout(timer);
  }, [chainSearchTerm]);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const checkMobile = () => setIsMobile(window.innerWidth <= 768);
    checkMobile();
    window.addEventListener('resize', checkMobile);
    return () => window.removeEventListener('resize', checkMobile);
  }, []);
  const getExplorerUrl = useCallback(chainKey => 'https://layerzeroscan.com/api/explorer/' + chainKey + '/address', []);
  const getAssetIcon = useCallback(assetSymbol => {
    if (!assetSymbol) return null;
    const normalized = assetSymbol.replace(/\.e$/i, '').toUpperCase();
    return 'https://icons-ckg.pages.dev/lz-scan/tokens/' + normalized.toLowerCase() + '.svg';
  }, []);
  const getNetworkIcon = useCallback(chainKey => {
    if (iconErrors['network-' + chainKey]) {
      return 'https://icons-ckg.pages.dev/lz-scan/networks/default.svg';
    }
    return 'https://icons-ckg.pages.dev/lz-scan/networks/' + chainKey + '.svg';
  }, [iconErrors]);
  const normalizeAssetSymbol = useCallback(symbol => {
    if (!symbol) return '';
    let normalized = symbol.replace(/\.e$/i, '');
    if (normalized === 'WETH' || normalized === 'SETH') normalized = 'ETH';
    return normalized.toUpperCase();
  }, []);
  const standardizeAssetType = useCallback(asset => {
    if (asset.source === 'stargate') {
      if (asset.stargateType === 'OFT') return 'StargateOFT';
      if (asset.stargateType === 'POOL' || asset.stargateType === 'NATIVE') return 'StargatePool';
    }
    const type = asset.assetType || asset.type || 'OFT';
    if (type === 'OFT_ADAPTER') return 'OFTAdapter';
    if (type === 'NATIVE_OFT_ADAPTER') return 'NativeOFTAdapter';
    if (type === 'OFT') return 'OFT';
    return type;
  }, []);
  const handleCopy = useCallback((text, id) => {
    if (typeof window !== 'undefined' && window.navigator && window.navigator.clipboard) {
      window.navigator.clipboard.writeText(text);
      setCopied(id);
      setTimeout(() => setCopied(null), 1000);
    }
  }, []);
  const handleIconError = useCallback(id => {
    setIconErrors(prev => ({
      ...prev,
      [id]: true
    }));
  }, []);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    setIsLoadingDeployments(true);
    const fetchWithFallback = async () => {
      for (const prefix of ['/public/data/', '/data/']) {
        try {
          const response = await fetch(prefix + 'deploymentsV2.json');
          if (response.ok) {
            const text = await response.text();
            if (!text.trim().startsWith('<')) {
              return JSON.parse(text);
            }
          }
        } catch (e) {
          console.warn('Fetch from ' + prefix + ' failed:', e.message);
        }
      }
      try {
        const response = await fetch('https://metadata.layerzero-api.com/v1/metadata/deployments');
        if (response.ok) {
          const data = await response.json();
          if (data && !Array.isArray(data)) {
            const result = [];
            Object.entries(data).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;
                  result.push({
                    ...v2Dep,
                    chainKey: chainKey,
                    stage: stage,
                    chainDisplayName: displayName,
                    eid: v2Dep.eid?.toString(),
                    nativeChainId: v2Dep.nativeChainId || chainDetails.chainId || chainDetails.nativeChainId || chainData.nativeChainId
                  });
                }
              }
            });
            return result;
          }
          return data;
        }
      } catch (e) {
        console.error('API fetch failed:', e.message);
      }
      return [];
    };
    fetchWithFallback().then(data => {
      setDeploymentsData(data || []);
    }).catch(err => console.error('Error loading deployments:', err)).finally(() => setIsLoadingDeployments(false));
  }, []);
  const normalizeChainKey = useCallback(key => {
    if (!key) return '';
    let normalized = key.toLowerCase().replace(/-/g, '');
    const aliases = {
      'zkpolygon': 'zkevm',
      'zkconsensys': 'zkevm',
      'polygonzkevm': 'zkevm'
    };
    return aliases[normalized] || normalized;
  }, []);
  const chainToEidMap = useMemo(() => {
    const map = {};
    deploymentsData.forEach(d => {
      const key = normalizeChainKey(d.chainKey);
      if (!map[key]) {
        map[key] = {
          eid: d.eid,
          nativeChainId: d.nativeChainId,
          chainDisplayName: d.chainDisplayName,
          stage: d.stage,
          originalKey: d.chainKey
        };
      }
    });
    return map;
  }, [deploymentsData, normalizeChainKey]);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const fetchAssets = async () => {
      setIsLoadingAssets(true);
      try {
        const stagesToFetch = selectedStage.length > 0 ? selectedStage : ['mainnet', 'testnet'];
        const uniqueStages = [...new Set(stagesToFetch)];
        const stargatePromises = uniqueStages.map(async stg => {
          const apiUrl = stg === 'mainnet' ? 'https://mainnet.stargate-api.com/v1/metadata?version=v2' : 'https://testnet.stargate-api.com/v1/metadata?version=v2';
          try {
            const response = await fetch(apiUrl);
            if (!response.ok) return [];
            const data = await response.json();
            return (data?.data?.v2 || []).map(asset => ({
              ...asset,
              stage: stg,
              source: 'stargate',
              issuer: 'Stargate'
            }));
          } catch {
            return [];
          }
        });
        let ecosystemOFTs = [];
        try {
          const oftResponse = await fetch('https://metadata.layerzero-api.com/v1/metadata/experiment/ofts/list');
          if (oftResponse.ok) {
            const oftData = await oftResponse.json();
            ecosystemOFTs = Object.entries(oftData).flatMap(([symbol, deploymentGroups]) => {
              return deploymentGroups.flatMap(group => {
                return Object.entries(group.deployments || ({})).map(([chainKey, deployment]) => {
                  const isTestnet = chainKey.toLowerCase().includes('testnet') || chainKey.toLowerCase().includes('sepolia') || chainKey.toLowerCase().includes('goerli');
                  return {
                    chainKey: chainKey.toLowerCase(),
                    address: deployment.address,
                    token: {
                      symbol,
                      name: group.name || symbol
                    },
                    assetType: deployment.type || 'OFT',
                    localDecimals: deployment.localDecimals,
                    sharedDecimals: group.sharedDecimals,
                    stage: isTestnet ? 'testnet' : 'mainnet',
                    source: 'ecosystem',
                    issuer: group.issuer || deployment.issuer || group.name || symbol,
                    innerToken: deployment.innerToken
                  };
                });
              });
            });
          }
        } catch (err) {
          console.warn('Failed to fetch ecosystem OFTs:', err);
        }
        const stargateResults = await Promise.all(stargatePromises);
        const allAssets = [...stargateResults.flat(), ...ecosystemOFTs];
        const uniqueAssets = allAssets.reduce((acc, asset) => {
          const key = (asset.chainKey + '-' + asset.address + '-' + asset.stage).toLowerCase();
          if (!acc[key]) acc[key] = asset;
          return acc;
        }, {});
        setAssets(Object.values(uniqueAssets));
      } catch (err) {
        console.error('Error fetching assets:', err);
        setAssets([]);
      } finally {
        setIsLoadingAssets(false);
      }
    };
    fetchAssets();
  }, [selectedStage]);
  const assetsWithChainInfo = useMemo(() => {
    return assets.map(asset => {
      const normalizedKey = normalizeChainKey(asset.chainKey);
      const chainInfo = chainToEidMap[normalizedKey] || ({});
      let tokenAddress = asset.address;
      if (asset.source === 'stargate') tokenAddress = asset.token?.address || asset.address; else if (asset.source === 'ecosystem') tokenAddress = asset.innerToken || asset.address;
      return {
        ...asset,
        eid: chainInfo.eid || 'N/A',
        nativeChainId: chainInfo.nativeChainId || 'N/A',
        chainDisplayName: chainInfo.chainDisplayName || asset.chainKey,
        assetName: asset.token?.name || asset.token?.symbol || 'Unknown',
        assetSymbol: asset.token?.symbol || 'Unknown',
        assetType: standardizeAssetType(asset),
        issuer: asset.issuer || 'Unknown',
        tokenAddress
      };
    });
  }, [assets, chainToEidMap, standardizeAssetType, normalizeChainKey]);
  const chains = useMemo(() => {
    const uniqueChains = new Map();
    assetsWithChainInfo.forEach(asset => {
      const chainKey = asset.chainKey.toLowerCase();
      if (!uniqueChains.has(chainKey)) {
        uniqueChains.set(chainKey, {
          value: chainKey,
          label: asset.chainDisplayName,
          type: 'chain'
        });
      }
    });
    return Array.from(uniqueChains.values()).sort((a, b) => a.label.localeCompare(b.label));
  }, [assetsWithChainInfo]);
  const chainInfoMap = useMemo(() => {
    const map = new Map();
    chains.forEach(chain => map.set(chain.value, chain));
    return map;
  }, [chains]);
  const uniqueAssetsList = useMemo(() => {
    const assetMap = new Map();
    assetsWithChainInfo.forEach(asset => {
      const symbol = asset.assetSymbol;
      if (!assetMap.has(symbol)) {
        assetMap.set(symbol, {
          value: symbol,
          label: symbol,
          normalizedLabel: normalizeAssetSymbol(symbol),
          type: 'asset'
        });
      }
    });
    return Array.from(assetMap.values()).sort((a, b) => a.label.localeCompare(b.label));
  }, [assetsWithChainInfo, normalizeAssetSymbol]);
  const uniqueIssuers = useMemo(() => {
    const issuerMap = new Map();
    assetsWithChainInfo.forEach(asset => {
      if (!issuerMap.has(asset.issuer)) {
        issuerMap.set(asset.issuer, {
          value: asset.issuer,
          label: asset.issuer,
          type: 'issuer'
        });
      }
    });
    return Array.from(issuerMap.values()).sort((a, b) => a.label.localeCompare(b.label));
  }, [assetsWithChainInfo]);
  const selectedStageSet = useMemo(() => new Set(selectedStage), [selectedStage]);
  const selectedChainsSet = useMemo(() => new Set(selectedChains), [selectedChains]);
  const selectedIssuersSet = useMemo(() => new Set(selectedIssuers), [selectedIssuers]);
  const selectedAssetsNormalized = useMemo(() => {
    const normalSet = new Set();
    const exactSet = new Set();
    selectedAssets.forEach(asset => {
      exactSet.add(asset.toLowerCase());
      normalSet.add(normalizeAssetSymbol(asset).toLowerCase());
    });
    return {
      exact: exactSet,
      normalized: normalSet
    };
  }, [selectedAssets, normalizeAssetSymbol]);
  const filteredData = useMemo(() => {
    let data = assetsWithChainInfo.filter(asset => {
      const assetStage = asset.stage || 'mainnet';
      const chainKey = asset.chainKey.toLowerCase();
      const matchesStage = selectedStage.length === 0 || selectedStageSet.has(assetStage);
      const matchesChain = selectedChains.length === 0 || selectedChainsSet.has(chainKey);
      const matchesAsset = selectedAssets.length === 0 || selectedAssetsNormalized.exact.has(asset.assetSymbol.toLowerCase()) || selectedAssetsNormalized.normalized.has(normalizeAssetSymbol(asset.assetSymbol).toLowerCase());
      const matchesIssuer = selectedIssuers.length === 0 || selectedIssuersSet.has(asset.issuer);
      return matchesStage && matchesChain && matchesAsset && matchesIssuer;
    });
    data.sort((a, b) => {
      const c = a.chainDisplayName.localeCompare(b.chainDisplayName);
      return c !== 0 ? c : a.assetSymbol.localeCompare(b.assetSymbol);
    });
    return data;
  }, [assetsWithChainInfo, selectedStage, selectedStageSet, selectedChains, selectedChainsSet, selectedAssets, selectedAssetsNormalized, selectedIssuers, selectedIssuersSet, normalizeAssetSymbol]);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const handleClickOutside = e => {
      if (assetSearchRef.current && !assetSearchRef.current.contains(e.target)) setIsAssetSearchOpen(false);
      if (issuerSearchRef.current && !issuerSearchRef.current.contains(e.target)) setIsIssuerSearchOpen(false);
      if (chainSearchRef.current && !chainSearchRef.current.contains(e.target)) setIsChainSearchOpen(false);
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, []);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const timer = setTimeout(() => {
      const searchParams = new URLSearchParams();
      if (selectedStage.length > 0) searchParams.set('stages', selectedStage.join(','));
      if (selectedChains.length > 0) searchParams.set('chains', selectedChains.join(','));
      if (selectedAssets.length > 0) searchParams.set('assets', selectedAssets.join(','));
      if (selectedIssuers.length > 0) searchParams.set('issuers', selectedIssuers.join(','));
      window.history.replaceState({}, '', window.location.pathname + (searchParams.toString() ? '?' + searchParams.toString() : ''));
    }, 300);
    return () => clearTimeout(timer);
  }, [selectedStage, selectedChains, selectedAssets, selectedIssuers]);
  const handleReset = useCallback(() => {
    setSelectedChains([]);
    setSelectedAssets([]);
    setSelectedIssuers([]);
    setAssetSearchTerm('');
    setIssuerSearchTerm('');
    setChainSearchTerm('');
    setSelectedStage(['mainnet']);
    setIsAssetSearchOpen(false);
    setIsIssuerSearchOpen(false);
    setIsChainSearchOpen(false);
  }, []);
  const filteredAssetResults = useMemo(() => {
    if (!debouncedAssetSearch) return uniqueAssetsList.slice(0, 50);
    const s = debouncedAssetSearch.toLowerCase();
    return uniqueAssetsList.filter(a => a.label.toLowerCase().includes(s) || a.normalizedLabel.toLowerCase().includes(s)).slice(0, 50);
  }, [uniqueAssetsList, debouncedAssetSearch]);
  const filteredIssuerResults = useMemo(() => {
    if (!debouncedIssuerSearch) return uniqueIssuers.slice(0, 50);
    const s = debouncedIssuerSearch.toLowerCase();
    return uniqueIssuers.filter(i => i.label.toLowerCase().includes(s)).slice(0, 50);
  }, [uniqueIssuers, debouncedIssuerSearch]);
  const filteredChainResults = useMemo(() => {
    if (!debouncedChainSearch) return chains.slice(0, 50);
    const s = debouncedChainSearch.toLowerCase();
    return chains.filter(c => c.label.toLowerCase().includes(s)).slice(0, 50);
  }, [chains, debouncedChainSearch]);
  const handleExportCSV = useCallback(() => {
    const headers = ['Chain', 'Chain ID', 'Asset Name', 'Asset Symbol', 'Issuer', 'Asset Type', 'OFT Address', 'Token Address', 'Endpoint ID', 'Stage'];
    const rows = filteredData.map(a => [a.chainDisplayName, a.nativeChainId, a.assetName, a.assetSymbol, a.issuer, a.assetType, a.address, a.tokenAddress, a.eid, a.stage]);
    const csv = [headers.join(','), ...rows.map(r => r.map(c => '"' + c + '"').join(','))].join('\n');
    const blob = new Blob([csv], {
      type: 'text/csv;charset=utf-8;'
    });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = 'stargate-ecosystem-assets-' + new Date().toISOString().split('T')[0] + '.csv';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }, [filteredData]);
  const handleAssetSelect = useCallback(v => {
    setSelectedAssets(p => p.includes(v) ? p.filter(a => a !== v) : [...p, v]);
    setAssetSearchTerm('');
    setIsAssetSearchOpen(false);
  }, []);
  const handleIssuerSelect = useCallback(v => {
    setSelectedIssuers(p => p.includes(v) ? p.filter(i => i !== v) : [...p, v]);
    setIssuerSearchTerm('');
    setIsIssuerSearchOpen(false);
  }, []);
  const handleChainSelect = useCallback(v => {
    setSelectedChains(p => p.includes(v) ? p.filter(c => c !== v) : [...p, v]);
    setChainSearchTerm('');
    setIsChainSearchOpen(false);
  }, []);
  const handleStageToggle = useCallback(v => {
    setSelectedStage(p => p.includes(v) ? p.filter(s => s !== v) : [...p, v]);
  }, []);
  const removeAsset = useCallback(a => setSelectedAssets(p => p.filter(x => x !== a)), []);
  const removeIssuer = useCallback(i => setSelectedIssuers(p => p.filter(x => x !== i)), []);
  const removeChain = useCallback(c => setSelectedChains(p => p.filter(x => x !== c)), []);
  const CopyIcon = () => <svg width="14" height="14" viewBox="0 0 20 20" fill="none"><path fillRule="evenodd" clipRule="evenodd" d="M4 3H14V5H15V3V2H14H4H3V3V15V16H4H6V15H4V3Z" fill="currentColor" /><rect x="6.5" y="5.5" width="11" height="13" stroke="currentColor" /></svg>;
  const CheckIcon = () => <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M1.5 6L4.5 9L10.5 3" stroke="#9EFC7E" strokeWidth="1.5" /></svg>;
  if (isLoadingDeployments || isLoadingAssets) {
    return <div style={{
      width: '100%',
      background: colors.bg,
      border: '1px solid ' + colors.border,
      borderRadius: '8px',
      overflow: 'hidden',
      marginTop: '16px'
    }}>
        <div style={{
      height: '60px',
      background: colors.bgSecondary
    }} />
        {[...Array(10)].map((_, i) => <div key={i} style={{
      height: '40px',
      borderBottom: '1px solid ' + colors.border,
      background: colors.bgSecondary
    }} />)}
      </div>;
  }
  return <div className="sg-table-root" style={{
    width: '100%',
    margin: '0 auto'
  }}>
      <style>{`
        .sg-table-root [data-table-wrapper] {
          width: 100% !important;
          max-width: 100% !important;
          margin: 0 !important;
          padding: 0 !important;
          contain: none !important;
          overflow: visible !important;
        }
        .sg-table-root [data-table-wrapper] > div {
          padding: 0 !important;
          max-width: 100% !important;
        }
        .sg-table-root [class*="overflow-x-auto"] {
          overflow-x: visible !important;
        }
        .sg-table-root table {
          margin: 0 !important;
          width: 100% !important;
          min-width: 100% !important;
        }
        .sg-table-root th:first-child,
        .sg-table-root td:first-child {
          padding-left: 3rem !important;
        }
        .sg-table-root th:last-child,
        .sg-table-root td:last-child {
          padding-right: 3rem !important;
        }
        .sg-table-root img {
          margin: 0 !important;
        }
      `}</style>
      {}
      <div style={{
    display: 'flex',
    alignItems: 'center',
    padding: '12px 16px',
    gap: '12px',
    fontSize: '14px',
    width: '100%',
    background: colors.bg,
    border: '1px solid ' + colors.border,
    borderRadius: '8px',
    marginBottom: '12px',
    flexWrap: 'wrap',
    boxSizing: 'border-box'
  }}>
        <div style={{
    flexShrink: 0,
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    padding: '4px 12px',
    background: colors.bgSecondary,
    border: '1px solid ' + colors.border,
    borderRadius: '6px'
  }}>
          <span style={{
    fontWeight: 500,
    fontSize: '14px',
    color: colors.text
  }}>Assets</span>
          <span style={{
    background: colors.accent,
    color: 'white',
    padding: '2px 8px',
    borderRadius: '9999px',
    fontSize: '12px',
    fontWeight: 600,
    minWidth: '24px',
    textAlign: 'center'
  }}>{filteredData.length}</span>
        </div>
        <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '4px',
    padding: '4px',
    background: colors.bgSecondary,
    border: '1px solid ' + colors.border,
    borderRadius: '6px',
    flexShrink: 0
  }}>
          {[{
    value: 'mainnet',
    label: 'Mainnet'
  }, {
    value: 'testnet',
    label: 'Testnet'
  }].map(stage => <button key={stage.value} onClick={() => handleStageToggle(stage.value)} onMouseEnter={() => setHovered('stage-' + stage.value)} onMouseLeave={() => setHovered(null)} style={{
    padding: '6px 12px',
    border: 'none',
    background: selectedStage.includes(stage.value) ? colors.accent : hovered === 'stage-' + stage.value ? colors.hoverBg : 'transparent',
    color: selectedStage.includes(stage.value) ? 'white' : colors.textSecondary,
    cursor: 'pointer',
    borderRadius: '4px',
    fontSize: '13px',
    fontWeight: 500,
    transition: 'all 0.15s'
  }}>{stage.label}</button>)}
        </div>
      </div>

      {}
      <div style={{
    display: 'flex',
    alignItems: 'flex-start',
    padding: '12px 16px',
    gap: '12px',
    fontSize: '14px',
    width: '100%',
    background: colors.bg,
    border: '1px solid ' + colors.border,
    borderRadius: '8px',
    marginBottom: '12px',
    flexWrap: 'wrap',
    boxSizing: 'border-box'
  }}>
        <div style={{
    position: 'relative',
    width: '100%',
    flex: 1,
    display: 'flex',
    flexDirection: 'column',
    gap: '10px'
  }}>
          <div style={{
    display: 'flex',
    gap: '8px',
    width: '100%',
    flexWrap: 'wrap',
    alignItems: 'center'
  }}>
            {}
            {selectedAssets.map(asset => <div key={asset} style={{
    display: 'inline-flex',
    alignItems: 'center',
    gap: '5px',
    padding: '6px 10px',
    background: colors.bgSecondary,
    border: '1px solid ' + colors.border,
    borderRadius: '6px',
    fontSize: '12px',
    height: '34px',
    boxSizing: 'border-box'
  }}>
                <img src={getAssetIcon(asset)} alt="" style={{
    width: 14,
    height: 14,
    borderRadius: '50%'
  }} onError={e => {
    e.target.style.display = 'none';
  }} />
                <span style={{
    fontWeight: 500,
    color: colors.text
  }}>{asset}</span>
                <button onClick={() => removeAsset(asset)} onMouseEnter={() => setHovered('rm-asset-' + asset)} onMouseLeave={() => setHovered(null)} style={{
    background: 'none',
    border: 'none',
    color: hovered === 'rm-asset-' + asset ? colors.accent : colors.textMuted,
    cursor: 'pointer',
    padding: '0 2px',
    fontSize: '14px',
    lineHeight: 1,
    marginLeft: '2px'
  }}>×</button>
              </div>)}
            {selectedIssuers.map(issuer => <div key={issuer} style={{
    display: 'inline-flex',
    alignItems: 'center',
    gap: '5px',
    padding: '6px 10px',
    background: colors.bgSecondary,
    border: '1px solid ' + colors.border,
    borderRadius: '6px',
    fontSize: '12px',
    height: '34px',
    boxSizing: 'border-box'
  }}>
                <span style={{
    fontWeight: 500,
    color: colors.text
  }}>{issuer}</span>
                <button onClick={() => removeIssuer(issuer)} onMouseEnter={() => setHovered('rm-issuer-' + issuer)} onMouseLeave={() => setHovered(null)} style={{
    background: 'none',
    border: 'none',
    color: hovered === 'rm-issuer-' + issuer ? colors.accent : colors.textMuted,
    cursor: 'pointer',
    padding: '0 2px',
    fontSize: '14px',
    lineHeight: 1,
    marginLeft: '2px'
  }}>×</button>
              </div>)}
            {selectedChains.map(chain => {
    const info = chainInfoMap.get(chain);
    return <div key={chain} style={{
      display: 'inline-flex',
      alignItems: 'center',
      gap: '5px',
      padding: '6px 10px',
      background: colors.bgSecondary,
      border: '1px solid ' + colors.border,
      borderRadius: '6px',
      fontSize: '12px',
      height: '34px',
      boxSizing: 'border-box'
    }}>
                  <img src={getNetworkIcon(chain)} alt="" style={{
      width: 14,
      height: 14
    }} onError={e => {
      handleIconError('network-' + chain);
      e.target.src = 'https://icons-ckg.pages.dev/lz-scan/networks/default.svg';
    }} />
                  <span style={{
      fontWeight: 500,
      color: colors.text
    }}>{info?.label || chain}</span>
                  <button onClick={() => removeChain(chain)} onMouseEnter={() => setHovered('rm-chain-' + chain)} onMouseLeave={() => setHovered(null)} style={{
      background: 'none',
      border: 'none',
      color: hovered === 'rm-chain-' + chain ? colors.accent : colors.textMuted,
      cursor: 'pointer',
      padding: '0 2px',
      fontSize: '14px',
      lineHeight: 1,
      marginLeft: '2px'
    }}>×</button>
                </div>;
  })}
            {}
            <div style={{
    position: 'relative',
    flex: 1,
    minWidth: '150px'
  }} ref={assetSearchRef}>
              <input type="text" placeholder="Search assets..." value={assetSearchTerm} onChange={e => setAssetSearchTerm(e.target.value)} onFocus={() => setIsAssetSearchOpen(true)} style={{
    width: '100%',
    height: '34px',
    padding: '0 12px',
    borderRadius: '6px',
    border: '1px solid ' + (isAssetSearchOpen ? colors.accent : colors.border),
    background: colors.bgSecondary,
    fontSize: '13px',
    color: colors.text,
    boxSizing: 'border-box',
    outline: 'none',
    transition: 'border-color 0.15s'
  }} />
              {isAssetSearchOpen && filteredAssetResults.length > 0 && <div style={{
    position: 'absolute',
    top: '100%',
    left: 0,
    right: 0,
    marginTop: '4px',
    border: '1px solid ' + colors.border,
    borderRadius: '6px',
    background: colors.bg,
    zIndex: 1000,
    boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
    maxHeight: '280px',
    overflowY: 'auto'
  }}>
                  <div style={{
    padding: '6px 10px',
    fontSize: '10px',
    fontWeight: 600,
    color: colors.textMuted,
    textTransform: 'uppercase',
    letterSpacing: '0.5px',
    background: colors.bgSecondary,
    borderBottom: '1px solid ' + colors.border
  }}>Assets</div>
                  {filteredAssetResults.map(asset => <div key={asset.value} onClick={() => handleAssetSelect(asset.value)} onMouseEnter={() => setHovered('dd-asset-' + asset.value)} onMouseLeave={() => setHovered(null)} style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    padding: '8px 10px',
    cursor: 'pointer',
    color: colors.text,
    background: hovered === 'dd-asset-' + asset.value || selectedAssets.includes(asset.value) ? colors.bgSecondary : colors.bg,
    fontWeight: selectedAssets.includes(asset.value) ? 500 : 400,
    transition: 'background 0.1s'
  }}>
                      <img src={getAssetIcon(asset.label)} alt="" style={{
    width: 16,
    height: 16,
    borderRadius: '50%'
  }} onError={e => {
    e.target.style.display = 'none';
  }} />
                      <span style={{
    fontSize: '13px'
  }}>{asset.label}</span>
                    </div>)}
                </div>}
            </div>

            {}
            <div style={{
    position: 'relative',
    flex: 1,
    minWidth: '150px'
  }} ref={issuerSearchRef}>
              <input type="text" placeholder="Search issuers..." value={issuerSearchTerm} onChange={e => setIssuerSearchTerm(e.target.value)} onFocus={() => setIsIssuerSearchOpen(true)} style={{
    width: '100%',
    height: '34px',
    padding: '0 12px',
    borderRadius: '6px',
    border: '1px solid ' + (isIssuerSearchOpen ? colors.accent : colors.border),
    background: colors.bgSecondary,
    fontSize: '13px',
    color: colors.text,
    boxSizing: 'border-box',
    outline: 'none',
    transition: 'border-color 0.15s'
  }} />
              {isIssuerSearchOpen && filteredIssuerResults.length > 0 && <div style={{
    position: 'absolute',
    top: '100%',
    left: 0,
    right: 0,
    marginTop: '4px',
    border: '1px solid ' + colors.border,
    borderRadius: '6px',
    background: colors.bg,
    zIndex: 1000,
    boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
    maxHeight: '280px',
    overflowY: 'auto'
  }}>
                  <div style={{
    padding: '6px 10px',
    fontSize: '10px',
    fontWeight: 600,
    color: colors.textMuted,
    textTransform: 'uppercase',
    letterSpacing: '0.5px',
    background: colors.bgSecondary,
    borderBottom: '1px solid ' + colors.border
  }}>Issuers</div>
                  {filteredIssuerResults.map(issuer => <div key={issuer.value} onClick={() => handleIssuerSelect(issuer.value)} onMouseEnter={() => setHovered('dd-issuer-' + issuer.value)} onMouseLeave={() => setHovered(null)} style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    padding: '8px 10px',
    cursor: 'pointer',
    color: colors.text,
    background: hovered === 'dd-issuer-' + issuer.value || selectedIssuers.includes(issuer.value) ? colors.bgSecondary : colors.bg,
    fontWeight: selectedIssuers.includes(issuer.value) ? 500 : 400,
    transition: 'background 0.1s'
  }}>
                      <span style={{
    fontSize: '13px'
  }}>{issuer.label}</span>
                    </div>)}
                </div>}
            </div>

            {}
            <div style={{
    position: 'relative',
    flex: 1,
    minWidth: '150px'
  }} ref={chainSearchRef}>
              <input type="text" placeholder="Search chains..." value={chainSearchTerm} onChange={e => setChainSearchTerm(e.target.value)} onFocus={() => setIsChainSearchOpen(true)} style={{
    width: '100%',
    height: '34px',
    padding: '0 12px',
    borderRadius: '6px',
    border: '1px solid ' + (isChainSearchOpen ? colors.accent : colors.border),
    background: colors.bgSecondary,
    fontSize: '13px',
    color: colors.text,
    boxSizing: 'border-box',
    outline: 'none',
    transition: 'border-color 0.15s'
  }} />
              {isChainSearchOpen && filteredChainResults.length > 0 && <div style={{
    position: 'absolute',
    top: '100%',
    left: 0,
    right: 0,
    marginTop: '4px',
    border: '1px solid ' + colors.border,
    borderRadius: '6px',
    background: colors.bg,
    zIndex: 1000,
    boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
    maxHeight: '280px',
    overflowY: 'auto'
  }}>
                  <div style={{
    padding: '6px 10px',
    fontSize: '10px',
    fontWeight: 600,
    color: colors.textMuted,
    textTransform: 'uppercase',
    letterSpacing: '0.5px',
    background: colors.bgSecondary,
    borderBottom: '1px solid ' + colors.border
  }}>Chains</div>
                  {filteredChainResults.map(chain => <div key={chain.value} onClick={() => handleChainSelect(chain.value)} onMouseEnter={() => setHovered('dd-chain-' + chain.value)} onMouseLeave={() => setHovered(null)} style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    padding: '8px 10px',
    cursor: 'pointer',
    color: colors.text,
    background: hovered === 'dd-chain-' + chain.value || selectedChains.includes(chain.value) ? colors.bgSecondary : colors.bg,
    fontWeight: selectedChains.includes(chain.value) ? 500 : 400,
    transition: 'background 0.1s'
  }}>
                      <img src={getNetworkIcon(chain.value)} alt="" style={{
    width: 16,
    height: 16
  }} onError={e => {
    handleIconError('network-' + chain.value);
    e.target.src = 'https://icons-ckg.pages.dev/lz-scan/networks/default.svg';
  }} />
                      <span style={{
    fontSize: '13px'
  }}>{chain.label}</span>
                    </div>)}
                </div>}
            </div>
          </div>
        </div>
        <div style={{
    display: 'flex',
    gap: '8px',
    flexShrink: 0,
    alignSelf: 'flex-start'
  }}>
          <button onClick={handleExportCSV} onMouseEnter={() => setHovered('btn-export')} onMouseLeave={() => setHovered(null)} style={{
    height: '34px',
    border: '1px solid ' + (hovered === 'btn-export' ? colors.accent : colors.border),
    borderRadius: '6px',
    padding: '0 14px',
    color: hovered === 'btn-export' ? colors.accent : colors.text,
    background: hovered === 'btn-export' ? colors.hoverBg : colors.bgSecondary,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '13px',
    transition: 'all 0.15s'
  }}>Export CSV</button>
          <button onClick={handleReset} onMouseEnter={() => setHovered('btn-reset')} onMouseLeave={() => setHovered(null)} style={{
    height: '34px',
    border: '1px solid ' + (hovered === 'btn-reset' ? colors.accent : colors.border),
    borderRadius: '6px',
    padding: '0 14px',
    color: hovered === 'btn-reset' ? colors.accent : colors.text,
    background: hovered === 'btn-reset' ? colors.hoverBg : colors.bgSecondary,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '13px',
    transition: 'all 0.15s'
  }}>Reset</button>
        </div>
      </div>

      {}
      {isMobile ? <div>
          {filteredData.length > 0 ? filteredData.slice(0, 50).map((asset, index) => {
    const assetIconUrl = getAssetIcon(asset.assetSymbol);
    const explorerUrl = getExplorerUrl(asset.chainKey);
    const cardId = 'card-' + index;
    return <div key={asset.chainKey + '-' + asset.address + '-' + index} onMouseEnter={() => setHovered(cardId)} onMouseLeave={() => setHovered(null)} style={{
      background: colors.bg,
      border: '1px solid ' + (hovered === cardId ? colors.accent : colors.border),
      borderRadius: '8px',
      padding: '12px',
      display: 'flex',
      flexDirection: 'column',
      gap: '8px',
      marginBottom: '10px',
      transition: 'border-color 0.15s'
    }}>
                  <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '8px',
      paddingBottom: '8px',
      borderBottom: '1px solid ' + colors.border
    }}>
                    <img src={getNetworkIcon(asset.chainKey)} alt="" style={{
      width: 22,
      height: 22
    }} onError={e => {
      handleIconError('network-' + asset.chainKey);
      e.target.src = 'https://icons-ckg.pages.dev/lz-scan/networks/default.svg';
    }} />
                    <div>
                      <div style={{
      fontSize: '14px',
      fontWeight: 500,
      color: colors.text
    }}>{asset.chainDisplayName}</div>
                      <div style={{
      fontSize: '11px',
      color: colors.textMuted
    }}>Chain ID: {asset.nativeChainId}</div>
                    </div>
                  </div>
                  <div style={{
      display: 'flex',
      justifyContent: 'space-between',
      fontSize: '12px'
    }}>
                    <span style={{
      color: colors.textMuted
    }}>Asset</span>
                    <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '4px'
    }}>
                      {assetIconUrl && <img src={assetIconUrl} alt="" style={{
      width: 14,
      height: 14,
      borderRadius: '50%'
    }} onError={e => {
      e.target.style.display = 'none';
    }} />}
                      <span style={{
      fontWeight: 500,
      color: colors.text
    }}>{asset.assetSymbol}</span>
                    </div>
                  </div>
                  <div style={{
      display: 'flex',
      justifyContent: 'space-between',
      fontSize: '12px'
    }}>
                    <span style={{
      color: colors.textMuted
    }}>Issuer</span>
                    <span style={{
      color: colors.text
    }}>{asset.issuer}</span>
                  </div>
                  <div style={{
      display: 'flex',
      justifyContent: 'space-between',
      fontSize: '12px'
    }}>
                    <span style={{
      color: colors.textMuted
    }}>Type</span>
                    <span style={{
      color: colors.textSecondary
    }}>{asset.assetType}</span>
                  </div>
                  <div style={{
      display: 'flex',
      flexDirection: 'column',
      gap: '4px',
      padding: '8px',
      background: colors.bgSecondary,
      borderRadius: '6px'
    }}>
                    <div style={{
      display: 'flex',
      justifyContent: 'space-between',
      fontSize: '12px'
    }}>
                      <span style={{
      color: colors.textMuted
    }}>OFT</span>
                      <a href={explorerUrl + '/' + asset.address} target="_blank" rel="noreferrer" style={{
      fontSize: '11px',
      fontFamily: 'monospace',
      color: colors.accent,
      textDecoration: 'none'
    }}>{asset.address.slice(0, 6)}...{asset.address.slice(-4)}</a>
                    </div>
                    <div style={{
      display: 'flex',
      justifyContent: 'space-between',
      fontSize: '12px'
    }}>
                      <span style={{
      color: colors.textMuted
    }}>Token</span>
                      <a href={explorerUrl + '/' + asset.tokenAddress} target="_blank" rel="noreferrer" style={{
      fontSize: '11px',
      fontFamily: 'monospace',
      color: colors.accent,
      textDecoration: 'none'
    }}>{asset.tokenAddress.slice(0, 6)}...{asset.tokenAddress.slice(-4)}</a>
                    </div>
                  </div>
                  <div style={{
      display: 'flex',
      justifyContent: 'space-between',
      fontSize: '12px'
    }}>
                    <span style={{
      color: colors.textMuted
    }}>Endpoint ID</span>
                    <span style={{
      color: colors.text
    }}>{asset.eid}</span>
                  </div>
                </div>;
  }) : <div style={{
    textAlign: 'center',
    padding: '24px',
    color: colors.textMuted
  }}>
              <p>No assets found matching your criteria.</p>
              <button onClick={handleReset} style={{
    border: '1px solid ' + colors.border,
    borderRadius: '6px',
    padding: '8px 14px',
    color: colors.text,
    background: colors.bgSecondary,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '13px',
    marginTop: '8px'
  }}>Reset Filters</button>
            </div>}
          {filteredData.length > 50 && <div style={{
    textAlign: 'center',
    padding: '12px',
    color: colors.textMuted,
    fontSize: '12px'
  }}>Showing 50 of {filteredData.length} assets. Use filters to narrow results.</div>}
        </div> : <div style={{
    width: '100%',
    background: colors.bg,
    border: '1px solid ' + colors.border,
    borderRadius: '8px',
    boxSizing: 'border-box'
  }}>
          <div style={{
    overflowX: 'auto',
    overflowY: 'auto',
    maxHeight: 'calc(100vh - 320px)',
    minHeight: '400px'
  }}>
            <table style={{
    minWidth: '900px',
    borderCollapse: 'collapse',
    fontSize: '13px',
    background: colors.bg
  }}>
              <thead>
                <tr>
                  {['Chain', 'Asset', 'Issuer', 'Type', 'Contract Address'].map(h => <th key={h} style={{
    background: 'transparent',
    padding: '10px 1.5rem',
    textAlign: 'left',
    fontWeight: 400,
    color: colors.textMuted,
    borderBottom: '1px solid ' + colors.border,
    whiteSpace: 'nowrap',
    position: 'sticky',
    top: 0,
    zIndex: 10,
    fontSize: '12px',
    textTransform: 'none',
    letterSpacing: 'normal'
  }}>{h}</th>)}
                </tr>
              </thead>
              <tbody>
                {filteredData.length > 0 ? filteredData.slice(0, 100).map((asset, index) => {
    const assetIconUrl = getAssetIcon(asset.assetSymbol);
    const explorerUrl = getExplorerUrl(asset.chainKey);
    const rowId = 'row-' + index;
    return <tr key={asset.chainKey + '-' + asset.address + '-' + index} onMouseEnter={() => setHoveredRow(rowId)} onMouseLeave={() => setHoveredRow(null)} style={{
      background: hoveredRow === rowId ? colors.rowHover : colors.bg,
      transition: 'background 0.1s'
    }}>
                        <td style={{
      padding: '10px 1.5rem',
      borderBottom: '1px solid ' + colors.border,
      verticalAlign: 'middle'
    }}>
                          <div style={{
      display: 'flex',
      alignItems: 'flex-start',
      gap: '8px',
      color: colors.text
    }}>
                            <img src={getNetworkIcon(asset.chainKey)} alt="" style={{
      width: 20,
      height: 20,
      flexShrink: 0,
      marginTop: '2px'
    }} onError={e => {
      handleIconError('network-' + asset.chainKey);
      e.target.src = 'https://icons-ckg.pages.dev/lz-scan/networks/default.svg';
    }} />
                            <div style={{
      flex: 1,
      minWidth: 0
    }}>
                              <div style={{
      fontSize: '13px',
      fontWeight: 500,
      color: colors.text,
      whiteSpace: 'nowrap'
    }}>{asset.chainDisplayName}</div>
                              <div style={{
      fontSize: '11px',
      color: colors.textMuted,
      marginTop: '1px'
    }}>
                                <div>Chain ID: {asset.nativeChainId}</div>
                                <div>Endpoint ID: {asset.eid}</div>
                              </div>
                            </div>
                          </div>
                        </td>
                        <td style={{
      padding: '10px 1.5rem',
      borderBottom: '1px solid ' + colors.border,
      verticalAlign: 'middle'
    }}>
                          <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '6px'
    }}>
                            {assetIconUrl && <img src={assetIconUrl} alt="" style={{
      width: 18,
      height: 18,
      borderRadius: '50%'
    }} onError={e => {
      e.target.style.display = 'none';
    }} />}
                            <span style={{
      fontSize: '13px',
      fontWeight: 500,
      color: colors.text
    }}>{asset.assetSymbol}</span>
                          </div>
                        </td>
                        <td style={{
      padding: '10px 1.5rem',
      borderBottom: '1px solid ' + colors.border,
      verticalAlign: 'middle'
    }}>
                          <span style={{
      fontSize: '12px',
      color: colors.text
    }}>{asset.issuer}</span>
                        </td>
                        <td style={{
      padding: '10px 1.5rem',
      borderBottom: '1px solid ' + colors.border,
      verticalAlign: 'middle'
    }}>
                          <span style={{
      fontSize: '11px',
      color: colors.textSecondary
    }}>{asset.assetType}</span>
                        </td>
                        <td style={{
      padding: '10px 1.5rem',
      borderBottom: '1px solid ' + colors.border,
      verticalAlign: 'middle'
    }}>
                          <div style={{
      display: 'flex',
      flexDirection: 'column',
      gap: '4px'
    }}>
                            <div onMouseEnter={() => setHovered('contract-oft-' + index)} onMouseLeave={() => setHovered(null)} style={{
      display: 'flex',
      alignItems: 'center',
      padding: '5px 8px',
      fontSize: '11px',
      color: colors.text,
      background: colors.bgSecondary,
      border: '1px solid ' + (hovered === 'contract-oft-' + index ? colors.accent : colors.border),
      borderRadius: '5px',
      transition: 'border-color 0.15s'
    }}>
                              <a href={explorerUrl + '/' + asset.address} target="_blank" rel="noreferrer" style={{
      display: 'flex',
      alignItems: 'center',
      textDecoration: 'none',
      color: 'inherit',
      flex: 1,
      minWidth: 0
    }}>
                                <span style={{
      fontWeight: 500,
      color: colors.text,
      marginRight: '6px',
      fontSize: '11px'
    }}>OFT</span>
                                <span style={{
      fontFamily: 'monospace',
      fontSize: '10px',
      color: colors.textSecondary,
      overflow: 'hidden',
      textOverflow: 'ellipsis',
      whiteSpace: 'nowrap'
    }} title={asset.address}>{asset.address.slice(0, 16)}...</span>
                              </a>
                              <button onClick={() => handleCopy(asset.address, 'oft-' + index)} style={{
      padding: '2px',
      background: 'transparent',
      border: 'none',
      color: colors.textMuted,
      cursor: 'pointer',
      marginLeft: '4px'
    }} title="Copy">{copied === 'oft-' + index ? <CheckIcon /> : <CopyIcon />}</button>
                            </div>
                            <div onMouseEnter={() => setHovered('contract-token-' + index)} onMouseLeave={() => setHovered(null)} style={{
      display: 'flex',
      alignItems: 'center',
      padding: '5px 8px',
      fontSize: '11px',
      color: colors.text,
      background: colors.bgSecondary,
      border: '1px solid ' + (hovered === 'contract-token-' + index ? colors.accent : colors.border),
      borderRadius: '5px',
      transition: 'border-color 0.15s'
    }}>
                              <a href={explorerUrl + '/' + asset.tokenAddress} target="_blank" rel="noreferrer" style={{
      display: 'flex',
      alignItems: 'center',
      textDecoration: 'none',
      color: 'inherit',
      flex: 1,
      minWidth: 0
    }}>
                                <span style={{
      fontWeight: 500,
      color: colors.text,
      marginRight: '6px',
      fontSize: '11px'
    }}>Token</span>
                                <span style={{
      fontFamily: 'monospace',
      fontSize: '10px',
      color: colors.textSecondary,
      overflow: 'hidden',
      textOverflow: 'ellipsis',
      whiteSpace: 'nowrap'
    }} title={asset.tokenAddress}>{asset.tokenAddress.slice(0, 16)}...</span>
                              </a>
                              <button onClick={() => handleCopy(asset.tokenAddress, 'token-' + index)} style={{
      padding: '2px',
      background: 'transparent',
      border: 'none',
      color: colors.textMuted,
      cursor: 'pointer',
      marginLeft: '4px'
    }} title="Copy">{copied === 'token-' + index ? <CheckIcon /> : <CopyIcon />}</button>
                            </div>
                          </div>
                        </td>
                      </tr>;
  }) : <tr>
                    <td colSpan={5} style={{
    padding: '12px 1.5rem',
    borderBottom: '1px solid ' + colors.border
  }}>
                      <div style={{
    textAlign: 'center',
    padding: '24px',
    color: colors.textMuted
  }}>
                        <p>No assets found matching your criteria.</p>
                        <button onClick={handleReset} style={{
    border: '1px solid ' + colors.border,
    borderRadius: '6px',
    padding: '8px 14px',
    color: colors.text,
    background: colors.bgSecondary,
    cursor: 'pointer',
    fontWeight: 500,
    fontSize: '13px',
    marginTop: '8px'
  }}>Reset Filters</button>
                      </div>
                    </td>
                  </tr>}
              </tbody>
            </table>
          </div>
          {filteredData.length > 100 && <div style={{
    textAlign: 'center',
    padding: '12px',
    color: colors.textMuted,
    fontSize: '12px',
    borderTop: '1px solid ' + colors.border
  }}>Showing 100 of {filteredData.length} assets. Use filters to narrow results.</div>}
        </div>}
    </div>;
};

Browse [Omnichain Fungible Token (OFT)](/v2/concepts/applications/oft-standard) deployments from various asset issuers across LayerZero-supported chains. OFTs enable seamless crosschain value transfer and can be implemented either as part of the token contract itself (OFT) or as a separate adapter contract wrapping an existing token (OFT Adapter).

<Tip>
  ### Programmatic Access

  All assets displayed on this page are available programmatically via LayerZero metadata endpoints:

  * **Stargate Assets:** [mainnet](https://mainnet.stargate-api.com/v1/metadata?version=v2) and [testnet](https://testnet.stargate-api.com/v1/metadata?version=v2)
  * **Ecosystem OFTs:** [both mainnet and testnet](https://metadata.layerzero-api.com/v1/metadata/experiment/ofts/list)

  Learn more about [LayerZero Metadata APIs](/v2/tools/endpoint-metadata).
</Tip>

<Info>
  ### Asset Types

  **Stargate-Managed Assets:**

  * **StargatePool** - Native asset liquidity pools on Native chains managed by Stargate Finance, providing deep liquidity for crosschain transfers
  * **StargateOFT** - Minted token representations on Hydra chains backed by StargatePool liquidity, managed by Stargate Finance

  Stargate assets use an **opinionated security stack configured by LayerZero Labs**, ensuring consistent security standards across all Stargate deployments. Learn more about [Stargate's architecture and security model](/v2/concepts/applications/stargate-finance).

  **Asset Issuer-Owned Deployments:**

  * **OFT** - Omnichain Fungible Token standard implementation deployed and fully owned by asset issuers
  * **OFTAdapter** - Adapter contract wrapping existing tokens to enable omnichain functionality, deployed and owned by asset issuers
  * **NativeOFTAdapter** - Specialized adapter for native gas tokens (e.g., ETH), deployed and owned by asset issuers

  These deployments use **security configurations determined by each asset issuer**, allowing projects to customize their crosschain security parameters according to their specific requirements. For details on available security configuration options and DVN (Decentralized Verifier Network) setup, see [Security Stack (DVNs)](/v2/concepts/modular-security/security-stack-dvns) and [DVN & Executor Configuration](/v2/developers/evm/configuration/dvn-executor-config).

  **Standard Interface:** All assets implement LayerZero's `IOFT` interface, ensuring consistent crosschain functionality and interoperability across the ecosystem.
</Info>

<StargateEcosystemAssetsTable />

## About Crosschain Assets

All assets listed implement LayerZero's `IOFT` (Omnichain Fungible Token Interface), providing a standardized interface for crosschain token transfers. This includes both Stargate-managed assets (StargatePool and StargateOFT) and ecosystem OFT deployments. The `IOFT` interface ensures consistent functionality across all implementations, enabling seamless integration with LayerZero OApps and composability between different asset types.

### Stargate V2

Stargate V2 provides unified liquidity pools for major stablecoins and native assets across multiple chains. StargatePool contracts hold native assets on core chains with deep liquidity, while StargateOFT contracts provide minted representations backed by pool liquidity on Hydra chains.

**Security Configuration:** Stargate uses an opinionated security stack configured and maintained by LayerZero Labs, ensuring consistent security standards across all Stargate assets. This includes carefully selected DVNs (Decentralized Verifier Networks) and security parameters optimized for high-value asset transfers.

Learn more at [Stargate Concepts](/v2/concepts/applications/stargate-finance) and [Stargate Finance Documentation](https://docs.stargate.finance).

### Ecosystem OFTs

Asset issuers can deploy their own OFT implementations to enable native omnichain functionality. These deployments are fully controlled by the asset issuers and can be configured according to their specific requirements.

**Security Configuration:** Each asset issuer determines their own security parameters, including DVN selection, executor configuration, and pathway-specific settings. This flexibility allows projects to tailor their security model to their specific use cases and risk tolerance.
