diff --git a/docs/ENV_VERIFICATION_REPORT.md b/docs/ENV_VERIFICATION_REPORT.md
index 986ca7c..778f3dc 100644
--- a/docs/ENV_VERIFICATION_REPORT.md
+++ b/docs/ENV_VERIFICATION_REPORT.md
@@ -96,7 +96,7 @@
## ✅ Contract Address Verification
### Chain 138 Contracts (Verified Against Documentation)
-
+Have we
| Contract | Address | Status | Source |
|----------|---------|--------|--------|
| **CCIP Router** | `0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e` | ✅ Verified | CCIP_CONFIGURATION_STATUS.md |
diff --git a/frontend/public/explorer-spa.js b/frontend/public/explorer-spa.js
index 68e93a6..66f2f73 100644
--- a/frontend/public/explorer-spa.js
+++ b/frontend/public/explorer-spa.js
@@ -64,16 +64,68 @@
const EXPLORER_ORIGINS = ['https://explorer.d-bis.org', 'http://explorer.d-bis.org', 'http://192.168.11.140', 'https://192.168.11.140'];
const EXPLORER_ORIGIN = (typeof window !== 'undefined' && window.location && EXPLORER_ORIGINS.includes(window.location.origin)) ? window.location.origin : 'https://explorer.d-bis.org';
var I18N = {
- en: { home: 'Home', blocks: 'Blocks', transactions: 'Transactions', bridge: 'Bridge', weth: 'WETH', tokens: 'Tokens', analytics: 'Analytics', operator: 'Operator', watchlist: 'Watchlist', searchPlaceholder: 'Address, tx hash, block number, or token/contract name...', connectWallet: 'Connect Wallet', darkMode: 'Dark mode', lightMode: 'Light mode', back: 'Back', exportCsv: 'Export CSV', tokenBalances: 'Token Balances', internalTxns: 'Internal Txns', readContract: 'Read contract', writeContract: 'Write contract', addToWatchlist: 'Add to watchlist', removeFromWatchlist: 'Remove from watchlist', checkApprovals: 'Check token approvals', copied: 'Copied' },
- de: { home: 'Start', blocks: 'Blöcke', transactions: 'Transaktionen', bridge: 'Brücke', weth: 'WETH', tokens: 'Token', analytics: 'Analysen', operator: 'Operator', watchlist: 'Beobachtungsliste', searchPlaceholder: 'Adresse, Tx-Hash, Blocknummer oder Token/Vertrag…', connectWallet: 'Wallet verbinden', darkMode: 'Dunkelmodus', lightMode: 'Hellmodus', back: 'Zurück', exportCsv: 'CSV exportieren', tokenBalances: 'Token-Bestände', internalTxns: 'Interne Transaktionen', readContract: 'Vertrag lesen', writeContract: 'Vertrag schreiben', addToWatchlist: 'Zur Beobachtungsliste', removeFromWatchlist: 'Aus Beobachtungsliste entfernen', checkApprovals: 'Token-Freigaben prüfen', copied: 'Kopiert' },
- fr: { home: 'Accueil', blocks: 'Blocs', transactions: 'Transactions', bridge: 'Pont', weth: 'WETH', tokens: 'Jetons', analytics: 'Analyses', operator: 'Opérateur', watchlist: 'Liste de suivi', searchPlaceholder: 'Adresse, hash de tx, numéro de bloc ou nom de token/contrat…', connectWallet: 'Connecter le portefeuille', darkMode: 'Mode sombre', lightMode: 'Mode clair', back: 'Retour', exportCsv: 'Exporter CSV', tokenBalances: 'Soldes de jetons', internalTxns: 'Transactions internes', readContract: 'Lire le contrat', writeContract: 'Écrire le contrat', addToWatchlist: 'Ajouter à la liste', removeFromWatchlist: 'Retirer de la liste', checkApprovals: 'Vérifier les approbations', copied: 'Copié' }
+ en: { home: 'Home', blocks: 'Blocks', transactions: 'Transactions', bridge: 'Bridge', weth: 'WETH', tokens: 'Tokens', pools: 'Pools', more: 'More', analytics: 'Analytics', operator: 'Operator', watchlist: 'Watchlist', searchPlaceholder: 'Address, tx hash, block number, or token/contract name...', connectWallet: 'Connect Wallet', darkMode: 'Dark mode', lightMode: 'Light mode', back: 'Back', exportCsv: 'Export CSV', tokenBalances: 'Token Balances', internalTxns: 'Internal Txns', readContract: 'Read contract', writeContract: 'Write contract', addToWatchlist: 'Add to watchlist', removeFromWatchlist: 'Remove from watchlist', checkApprovals: 'Check token approvals', copied: 'Copied' },
+ de: { home: 'Start', blocks: 'Blöcke', transactions: 'Transaktionen', bridge: 'Brücke', weth: 'WETH', tokens: 'Token', pools: 'Pools', more: 'Mehr', analytics: 'Analysen', operator: 'Operator', watchlist: 'Beobachtungsliste', searchPlaceholder: 'Adresse, Tx-Hash, Blocknummer oder Token/Vertrag…', connectWallet: 'Wallet verbinden', darkMode: 'Dunkelmodus', lightMode: 'Hellmodus', back: 'Zurück', exportCsv: 'CSV exportieren', tokenBalances: 'Token-Bestände', internalTxns: 'Interne Transaktionen', readContract: 'Vertrag lesen', writeContract: 'Vertrag schreiben', addToWatchlist: 'Zur Beobachtungsliste', removeFromWatchlist: 'Aus Beobachtungsliste entfernen', checkApprovals: 'Token-Freigaben prüfen', copied: 'Kopiert' },
+ fr: { home: 'Accueil', blocks: 'Blocs', transactions: 'Transactions', bridge: 'Pont', weth: 'WETH', tokens: 'Jetons', pools: 'Pools', more: 'Plus', analytics: 'Analyses', operator: 'Opérateur', watchlist: 'Liste de suivi', searchPlaceholder: 'Adresse, hash de tx, numéro de bloc ou nom de token/contrat…', connectWallet: 'Connecter le portefeuille', darkMode: 'Mode sombre', lightMode: 'Mode clair', back: 'Retour', exportCsv: 'Exporter CSV', tokenBalances: 'Soldes de jetons', internalTxns: 'Transactions internes', readContract: 'Lire le contrat', writeContract: 'Écrire le contrat', addToWatchlist: 'Ajouter à la liste', removeFromWatchlist: 'Retirer de la liste', checkApprovals: 'Vérifier les approbations', copied: 'Copié' }
};
var currentLocale = (function(){ try { return localStorage.getItem('explorerLocale') || 'en'; } catch(e){ return 'en'; } })();
function t(key) { return (I18N[currentLocale] && I18N[currentLocale][key]) || I18N.en[key] || key; }
function setLocale(loc) { currentLocale = loc; try { localStorage.setItem('explorerLocale', loc); } catch(e){} if (typeof applyI18n === 'function') applyI18n(); }
function applyI18n() { document.querySelectorAll('[data-i18n]').forEach(function(el){ var k = el.getAttribute('data-i18n'); if (k) el.textContent = t(k); }); var searchIn = document.getElementById('searchInput'); if (searchIn) searchIn.placeholder = t('searchPlaceholder'); var localeSel = document.getElementById('localeSelect'); if (localeSel) localeSel.value = currentLocale; var wcBtn = document.getElementById('walletConnectBtn'); if (wcBtn) wcBtn.textContent = t('connectWallet'); }
- window._renderWatchlist = function() { var container = document.getElementById('watchlistContent'); if (!container) return; var list = getWatchlist(); if (list.length === 0) { container.innerHTML = '
No addresses in watchlist. Open an address and click "Add to watchlist".
'; return; } var html = 'Address Label '; list.forEach(function(addr){ var label = getAddressLabel(addr) || ''; html += '' + escapeHtml(shortenHash(addr)) + ' ' + escapeHtml(label) + ' Remove '; }); html += '
'; container.innerHTML = html; };
+ var _explorerPageFilters = {};
+ function normalizeExplorerFilter(value) { return String(value == null ? '' : value).trim().toLowerCase(); }
+ function getExplorerPageFilter(key) { return _explorerPageFilters[key] || ''; }
+ function setExplorerPageFilter(key, value) { _explorerPageFilters[key] = normalizeExplorerFilter(value); return _explorerPageFilters[key]; }
+ function clearExplorerPageFilter(key) { delete _explorerPageFilters[key]; return ''; }
+ function matchesExplorerFilter(haystack, filter) { if (!filter) return true; return String(haystack == null ? '' : haystack).toLowerCase().indexOf(filter) !== -1; }
+ function escapeAttr(value) { return escapeHtml(String(value == null ? '' : value)).replace(/"/g, '"'); }
+ function renderPageFilterBar(key, placeholder, helperText, reloadJs) {
+ var inputId = key + 'FilterInput';
+ var value = getExplorerPageFilter(key);
+ var safeReload = reloadJs ? String(reloadJs) : '';
+ var html = '';
+ html += ' ';
+ html += 'Apply ';
+ html += 'Clear ';
+ if (helperText) html += '' + escapeHtml(helperText) + ' ';
+ html += '
';
+ return html;
+ }
+ window.setExplorerPageFilter = setExplorerPageFilter;
+ window.clearExplorerPageFilter = clearExplorerPageFilter;
+ window._renderWatchlist = function() {
+ var container = document.getElementById('watchlistContent');
+ if (!container) return;
+ var list = getWatchlist();
+ var filter = getExplorerPageFilter('watchlist');
+ var filtered = filter ? list.filter(function(addr) {
+ return matchesExplorerFilter([addr, getAddressLabel(addr) || ''].join(' '), filter);
+ }) : list;
+ var filterBar = renderPageFilterBar('watchlist', 'Filter by address or label...', 'Filters your saved addresses.', 'window._renderWatchlist && window._renderWatchlist()');
+ if (list.length === 0) { container.innerHTML = filterBar + 'No addresses in watchlist. Open an address and click "Add to watchlist".
'; return; }
+ if (filtered.length === 0) { container.innerHTML = filterBar + 'No watchlist entries match the current filter.
'; return; }
+ var html = filterBar + 'Address Label ';
+ filtered.forEach(function(addr){ var label = getAddressLabel(addr) || ''; html += '' + escapeHtml(shortenHash(addr)) + ' ' + escapeHtml(label) + ' Remove '; });
+ html += '
';
+ container.innerHTML = html;
+ };
var KNOWN_ADDRESS_LABELS = { '0x89dd12025bfcd38a168455a44b400e913ed33be2': 'CCIP WETH9 Bridge', '0xe0e93247376aa097db308b92e6ba36ba015535d0': 'CCIP WETH10 Bridge', '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2': 'WETH9', '0xf4bb2e28688e89fcce3c0580d37d36a7672e8a9f': 'WETH10', '0x8078a09637e47fa5ed34f626046ea2094a5cde5e': 'CCIP Router', '0x105f8a15b819948a89153505762444ee9f324684': 'CCIP Sender' };
+ var LIQUIDITY_POOL_ROWS = [
+ { category: 'Public Liquidity Pools', poolPair: 'cUSDT / cUSDC', poolType: 'DODO PMM', address: '0x9fcB06Aa1FD5215DC0E91Fd098aeff4B62fEa5C8', status: 'Created', notes: 'Pool created via CreateCUSDTCUSDCPool.s.sol' },
+ { category: 'Public Liquidity Pools', poolPair: 'cUSDT / USDT (official)', poolType: 'DODO PMM', address: '0xa3Ee6091696B28e5497b6F491fA1e99047250c59', status: 'Created', notes: 'Pool created via CreateCUSDTUSDTPool.s.sol' },
+ { category: 'Public Liquidity Pools', poolPair: 'cUSDC / USDC (official)', poolType: 'DODO PMM', address: '0x90bd9Bf18Daa26Af3e814ea224032d015db58Ea5', status: 'Created', notes: 'Pool created via CreateCUSDCUSDCPool.s.sol' },
+ { category: 'Public Liquidity Pools', poolPair: 'cUSDT / XAU', poolType: 'DODO PMM', address: '', status: 'Not deployed', notes: 'Requires XAU token (not on chain)' },
+ { category: 'Public Liquidity Pools', poolPair: 'cUSDC / XAU', poolType: 'DODO PMM', address: '', status: 'Not deployed', notes: 'Requires XAU token' },
+ { category: 'Public Liquidity Pools', poolPair: 'cEURT / XAU', poolType: 'DODO PMM', address: '', status: 'Not deployed', notes: 'Requires XAU; cEURT is deployed' },
+ { category: 'Private Stabilization Pools', poolPair: 'cUSDT ↔ XAU', poolType: 'PrivatePoolRegistry', address: '', status: 'Not deployed', notes: 'Stabilizer-only swap path' },
+ { category: 'Private Stabilization Pools', poolPair: 'cUSDC ↔ XAU', poolType: 'PrivatePoolRegistry', address: '', status: 'Not deployed', notes: 'Stabilizer-only swap path' },
+ { category: 'Private Stabilization Pools', poolPair: 'cEURT ↔ XAU', poolType: 'PrivatePoolRegistry', address: '', status: 'Not deployed', notes: 'Requires cEURT + XAU' },
+ { category: 'Reserve Pools / Vault Backing', poolPair: 'ReserveSystem', poolType: 'Reserve', address: '0x607e97cD626f209facfE48c1464815DDE15B5093', status: 'Deployed', notes: 'Reserve core' },
+ { category: 'Reserve Pools / Vault Backing', poolPair: 'ReserveTokenIntegration', poolType: 'Reserve', address: '0x34B73e6EDFd9f85a7c25EeD31dcB13aB6E969b96', status: 'Deployed', notes: 'Reserve token integration' },
+ { category: 'Reserve Pools / Vault Backing', poolPair: 'StablecoinReserveVault', poolType: 'Reserve', address: '', status: 'Not on Chain 138', notes: 'Designed for Ethereum Mainnet' },
+ { category: 'Reserve Pools / Vault Backing', poolPair: 'Bridge_Vault', poolType: 'Vault', address: '0x31884f84555210FFB36a19D2471b8eBc7372d0A8', status: 'Deployed', notes: 'Bridge vault' },
+ { category: 'Bridge Liquidity Pool', poolPair: 'LiquidityPoolETH', poolType: 'Bridge LP', address: '', status: 'Placeholder', notes: 'ETH, WETH' }
+ ];
function getAddressLabel(addr) { if (!addr) return ''; var lower = addr.toLowerCase(); if (KNOWN_ADDRESS_LABELS[lower]) return KNOWN_ADDRESS_LABELS[lower]; try { var j = localStorage.getItem('explorerAddressLabels'); if (!j) return ''; var m = JSON.parse(j); return m[lower] || ''; } catch(e){ return ''; } }
function formatAddressWithLabel(addr) { if (!addr) return ''; var label = getAddressLabel(addr); return label ? escapeHtml(label) + ' (' + escapeHtml(shortenHash(addr)) + ')' : escapeHtml(shortenHash(addr)); }
function copyToClipboard(val, msg) { if (!val) return; try { navigator.clipboard.writeText(String(val)); showToast(msg || 'Copied', 'success'); } catch(e) { showToast('Copy failed', 'error'); } }
@@ -100,7 +152,7 @@
_blocksScrollAnimationId = null;
}
currentView = viewName;
- var detailViews = ['blockDetail','transactionDetail','addressDetail','tokenDetail','nftDetail','watchlist','searchResults','tokens'];
+ var detailViews = ['blockDetail','transactionDetail','addressDetail','tokenDetail','nftDetail','watchlist','searchResults','tokens','pools','more'];
if (detailViews.indexOf(viewName) === -1) currentDetailKey = '';
var homeEl = document.getElementById('homeView');
if (homeEl) homeEl.style.display = viewName === 'home' ? 'block' : 'none';
@@ -116,6 +168,8 @@
window.showWETHUtilities = function() { if (_inNavHandler) return; _inNavHandler = true; try { switchToView('weth'); if (window._showWETHUtilities) window._showWETHUtilities(); } finally { _inNavHandler = false; } };
window.showWETHTab = function() {};
window.showWatchlist = function() { if (_inNavHandler) return; _inNavHandler = true; try { switchToView('watchlist'); if (window._renderWatchlist) window._renderWatchlist(); } finally { _inNavHandler = false; } };
+ window.showPools = function() { if (_inNavHandler) return; _inNavHandler = true; try { switchToView('pools'); if (window._showPools) window._showPools(); } finally { _inNavHandler = false; } };
+ window.showMore = function() { if (_inNavHandler) return; _inNavHandler = true; try { switchToView('more'); if (window._showMore) window._showMore(); } finally { _inNavHandler = false; } };
window.showTokensList = function() { if (_inNavHandler) return; _inNavHandler = true; try { switchToView('tokens'); if (window._loadTokensList) window._loadTokensList(); } finally { _inNavHandler = false; } };
window.showAnalytics = function() { if (_inNavHandler) return; _inNavHandler = true; try { switchToView('analytics'); if (window._showAnalytics) window._showAnalytics(); } finally { _inNavHandler = false; } };
window.showOperator = function() { if (_inNavHandler) return; _inNavHandler = true; try { switchToView('operator'); if (window._showOperator) window._showOperator(); } finally { _inNavHandler = false; } };
@@ -168,10 +222,8 @@
// Show/hide navigation items based on track
const analyticsNav = document.getElementById('analyticsNav');
const operatorNav = document.getElementById('operatorNav');
- const moreWrap = document.getElementById('navDropdownMoreWrap');
if (analyticsNav) analyticsNav.style.display = hasAccess(3) ? 'block' : 'none';
if (operatorNav) operatorNav.style.display = hasAccess(4) ? 'block' : 'none';
- if (moreWrap) moreWrap.style.display = (hasAccess(3) || hasAccess(4)) ? '' : 'none';
}
// Wallet authentication
@@ -1265,7 +1317,7 @@
function showView(viewName) {
currentView = viewName;
- var detailViews = ['blockDetail','transactionDetail','addressDetail','tokenDetail','nftDetail','watchlist','searchResults','tokens'];
+ var detailViews = ['blockDetail','transactionDetail','addressDetail','tokenDetail','nftDetail','watchlist','searchResults','tokens','pools','more'];
if (detailViews.indexOf(viewName) === -1) currentDetailKey = '';
document.querySelectorAll('.detail-view').forEach(v => v.classList.remove('active'));
const homeView = document.getElementById('homeView');
@@ -1326,6 +1378,8 @@
if (parts[0] === 'bridge') { if (currentView !== 'bridge') showBridgeMonitoring(); return; }
if (parts[0] === 'weth') { if (currentView !== 'weth') showWETHUtilities(); return; }
if (parts[0] === 'watchlist') { if (currentView !== 'watchlist') showWatchlist(); return; }
+ if (parts[0] === 'pools') { if (currentView !== 'pools') showPools(); return; }
+ if (parts[0] === 'more') { if (currentView !== 'more') showMore(); return; }
if (parts[0] === 'tokens') { if (typeof showTokensList === 'function') showTokensList(); else focusSearchWithHint('token'); return; }
if (parts[0] === 'analytics') { if (currentView !== 'analytics') showAnalytics(); return; }
if (parts[0] === 'operator') { if (currentView !== 'operator') showOperator(); return; }
@@ -1425,6 +1479,16 @@
breadcrumbHTML += '/ ';
breadcrumbHTML += 'Token ' + escapeHtml(shortenHash(identifier)) + ' ';
break;
+ case 'pools':
+ breadcrumbContainer = document.getElementById('poolsBreadcrumb');
+ breadcrumbHTML += '/ ';
+ breadcrumbHTML += 'Pools ';
+ break;
+ case 'more':
+ breadcrumbContainer = document.getElementById('moreBreadcrumb');
+ breadcrumbHTML += '/ ';
+ breadcrumbHTML += 'More ';
+ break;
case 'nft':
breadcrumbContainer = document.getElementById('nftDetailBreadcrumb');
breadcrumbHTML += '/ ';
@@ -1799,22 +1863,30 @@
}
const limitedBlocks = blocks.slice(0, 10);
-
+ const blockFilter = getExplorerPageFilter('homeBlocks');
+ const filteredBlocks = blockFilter ? limitedBlocks.filter(function(block) {
+ var d = normalizeBlockDisplay(block);
+ return matchesExplorerFilter([d.blockNum, d.hash, d.txCount, d.timestampFormatted, d.timeAgo].join(' '), blockFilter);
+ }) : limitedBlocks;
+ const filterBar = renderPageFilterBar('homeBlocks', 'Filter blocks by number, hash, tx count, or age...', 'Filters the live block cards below.', 'loadLatestBlocks()');
+
if (limitedBlocks.length === 0) {
- if (container) container.innerHTML = 'No blocks found. Retry
';
+ if (container) container.innerHTML = filterBar + 'No blocks found. Retry
';
+ } else if (filteredBlocks.length === 0) {
+ if (container) container.innerHTML = filterBar + 'No blocks match the current filter.
';
} else {
// Create HTML with duplicated blocks for seamless infinite loop
- let html = '