diff --git a/docs/operations/integrations/METAMASK_INTEGRATION.md b/docs/operations/integrations/METAMASK_INTEGRATION.md index 358e702..553eafb 100644 --- a/docs/operations/integrations/METAMASK_INTEGRATION.md +++ b/docs/operations/integrations/METAMASK_INTEGRATION.md @@ -167,7 +167,7 @@ async function connectToChain138() { symbol: 'ETH', decimals: 18 }, - rpcUrls: ['https://rpc.d-bis.org'], + rpcUrls: ['https://rpc-http-pub.d-bis.org'], blockExplorerUrls: ['https://explorer.d-bis.org'] }] }); diff --git a/examples/metamask-vanilla/index.html b/examples/metamask-vanilla/index.html index 5ca52c6..47ec0d3 100644 --- a/examples/metamask-vanilla/index.html +++ b/examples/metamask-vanilla/index.html @@ -73,7 +73,7 @@ symbol: 'ETH', decimals: 18 }, - rpcUrls: ['https://rpc.d-bis.org'], + rpcUrls: ['https://rpc-http-pub.d-bis.org'], blockExplorerUrls: ['https://explorer.d-bis.org'] }; diff --git a/metamask-sdk/src/config.ts b/metamask-sdk/src/config.ts index f4e0d42..4a37c1c 100644 --- a/metamask-sdk/src/config.ts +++ b/metamask-sdk/src/config.ts @@ -12,15 +12,22 @@ export const CHAIN_SHORT_NAME = 'defi-oracle'; * Domain: d-bis.org (Cloudflare DNS/SSL) */ export const RPC_URLS = { - primary: 'https://rpc.d-bis.org', - secondary: 'https://rpc2.d-bis.org', - websocket: 'wss://rpc.d-bis.org', + primary: 'https://rpc-http-pub.d-bis.org', + secondary: 'https://rpc.d-bis.org', + tertiary: 'https://rpc2.d-bis.org', + websocket: 'wss://rpc-ws-pub.d-bis.org', }; +/** Uniswap-style token list for Chain 138 (MetaMask Settings → Token Lists). */ +export const TOKEN_LIST_URL = 'https://blockscout.defi-oracle.io/api/config/token-list'; +export const TOKEN_LIST_URL_ALT = 'https://tokens.d-bis.org/config/token-list.json'; + /** - * Block explorer + * Block explorer (Defi Oracle chain 138 — standard Blockscout UI) */ -export const BLOCK_EXPLORER_URL = 'https://explorer.d-bis.org'; +export const BLOCK_EXPLORER_URL = 'https://blockscout.defi-oracle.io'; +/** D-BIS regulated-entity overlay (same backend, alternate hostname) */ +export const BLOCK_EXPLORER_OVERLAY_URL = 'https://explorer.d-bis.org'; /** * Network metadata for wallet_addEthereumChain @@ -33,9 +40,9 @@ export const NETWORK_METADATA = { symbol: 'ETH', decimals: 18, }, - rpcUrls: [RPC_URLS.primary, RPC_URLS.secondary], - blockExplorerUrls: [BLOCK_EXPLORER_URL], - iconUrls: [`${BLOCK_EXPLORER_URL}/images/logo.png`], + rpcUrls: [RPC_URLS.primary, RPC_URLS.secondary, RPC_URLS.tertiary], + blockExplorerUrls: [BLOCK_EXPLORER_URL, BLOCK_EXPLORER_OVERLAY_URL], + iconUrls: [`${BLOCK_EXPLORER_URL}/token-icons/chain-138.png`], } as const; /** diff --git a/metamask/QUICK_START.md b/metamask/QUICK_START.md index 8470cbc..a454e1c 100644 --- a/metamask/QUICK_START.md +++ b/metamask/QUICK_START.md @@ -25,7 +25,7 @@ await window.ethereum.request({ symbol: 'ETH', decimals: 18 }, - rpcUrls: ['https://rpc.d-bis.org'], + rpcUrls: ['https://rpc-http-pub.d-bis.org'], blockExplorerUrls: ['https://explorer.d-bis.org'] }] }); @@ -83,7 +83,8 @@ await window.ethereum.request({ - **ChainID**: 138 (0x8a) - **Chain Name**: DeFi Oracle Meta Mainnet - **Native Currency**: ETH (18 decimals) -- **RPC URL**: `https://rpc.d-bis.org` +- **RPC URL**: `https://rpc-http-pub.d-bis.org` (aliases: `rpc.d-bis.org`, `rpc2.d-bis.org`) +- **Token list (Chain 138)**: `https://explorer.d-bis.org/api/config/token-list` or `https://tokens.d-bis.org/config/token-list.json` - **Block Explorer**: `https://explorer.d-bis.org` ## Common Tokens diff --git a/services/token-aggregation/README.md b/services/token-aggregation/README.md index 5e1c1c5..aace5d4 100644 --- a/services/token-aggregation/README.md +++ b/services/token-aggregation/README.md @@ -4,7 +4,7 @@ A comprehensive token aggregation service that indexes token info, volume, liqui **REST API reference:** [docs/REST_API_REFERENCE.md](docs/REST_API_REFERENCE.md) — tokens, pools, prices, volume, OHLCV for dApps and MetaMask Snap discovery. -**Chain 138 Snap:** The MetaMask Chain 138 Snap (companion site at e.g. https://explorer.d-bis.org/snap/) calls this service for market data, swap quotes, and bridge routes. If the Snap is built with `GATSBY_SNAP_API_BASE_URL=https://explorer.d-bis.org`, then explorer.d-bis.org must serve this API (e.g. proxy `/api/v1/*` to this service). Otherwise build the Snap site with `GATSBY_SNAP_API_BASE_URL` set to this service’s public URL. See [metamask-integration/chain138-snap/docs/CHAIN138_SNAP_TROUBLESHOOTING.md](~/projects/metamask-integration/chain138-snap/docs/CHAIN138_SNAP_TROUBLESHOOTING.md). **CORS:** The service uses `cors()` (all origins allowed by default) so MetaMask Snap and browser clients can fetch token list and networks. +**Chain 138 Snap:** Any Snap or dApp that uses Chain 138 market data should point at this service for quotes, bridge routes, token lists, and network metadata. If the UI is hosted on `explorer.d-bis.org`, proxy `/api/v1/*` to this service or set `GATSBY_SNAP_API_BASE_URL` to the service’s public URL. The canonical Chain 138 MetaMask-facing config lives in `src/api/routes/config.ts` and `src/api/routes/report.ts`. **CORS:** The service uses `cors()` (all origins allowed by default) so browser clients and Snaps can fetch the API responses. ## Features diff --git a/services/token-aggregation/docs/REST_API_REFERENCE.md b/services/token-aggregation/docs/REST_API_REFERENCE.md index af904c2..fa7d909 100644 --- a/services/token-aggregation/docs/REST_API_REFERENCE.md +++ b/services/token-aggregation/docs/REST_API_REFERENCE.md @@ -42,7 +42,7 @@ Oracles (and config) per chain. Used by the Snap for USD price feeds (e.g. ETH/U ## Token list (report) -**GET /api/v1/report/token-list** returns a Uniswap-style token list with **logoURI** per token and a list-level **logoURI**. Each token has: `chainId`, `address`, `symbol`, `name`, `decimals`, `type`, `logoURI`. Chain 138 also exposes staged GRU x402 deployments such as `cUSDT_V2` and `cUSDC_V2` explicitly, with optional metadata fields like `familySymbol`, `deploymentVersion`, and `preferredForX402`. Use for MetaMask token list URL or Snap `get_token_list`. Optional query `?chainId=138` filters by chain. If **TOKEN_LIST_JSON_URL** is set (e.g. GitHub raw URL), the service fetches that JSON and returns it (with optional chainId filter); otherwise uses the built-in canonical token list. +**GET /api/v1/report/token-list** returns a Uniswap-style token list with **logoURI** per token and a list-level **logoURI**. Each token has: `chainId`, `address`, `symbol`, `name`, `decimals`, `type`, `logoURI`. Chain 138 also exposes staged GRU x402 deployments such as `cUSDT_V2` and `cUSDC_V2` explicitly, with optional metadata fields like `familySymbol`, `deploymentVersion`, and `preferredForX402`. Use for MetaMask token list URL or Snap `get_token_list`. Optional query `?chainId=138` filters by chain. The current hosted DBIS list is `https://tokens.d-bis.org/lists/dbis-138.tokenlist.json`; other chains use the built-in canonical token list. **GET /api/v1/report/cw-registry** returns the public-chain `cW*` registry grouped by chain. When `DEPLOYMENT_STATUS_JSON_PATH` / `CW_REGISTRY_JSON_PATH` points to `cross-chain-pmm-lps/config/deployment-status.json`, the endpoint reads that file at request time so explorer surfaces do not need a rebuild after registry edits. If the file is unavailable, it falls back to the built-in canonical `cW*` subset. @@ -276,4 +276,4 @@ This API is the primary source for **token discovery**, **liquidity pool discove - Prices and volume - OHLCV for charts -Combine with the **explorer-hosted token list** (`https://explorer.d-bis.org/api/config/token-list`) for MetaMask token list URL discovery. For dynamic Snap integration, use **GET /api/v1/networks** and **GET /api/v1/config** (see "Networks and config (Snap)" above); the Snap can return the token list URL as `{apiBaseUrl}/api/v1/report/token-list` for MetaMask Settings. +Combine with the **hosted DBIS token list** (`https://tokens.d-bis.org/lists/dbis-138.tokenlist.json`) for MetaMask token list URL discovery. For dynamic Snap integration, use **GET /api/v1/networks** and **GET /api/v1/config** (see "Networks and config (Snap)" above); the Snap can return the hosted token list URL directly for MetaMask Settings. diff --git a/services/token-aggregation/src/api/routes/config.test.ts b/services/token-aggregation/src/api/routes/config.test.ts index 28ff2ee..68c42b3 100644 --- a/services/token-aggregation/src/api/routes/config.test.ts +++ b/services/token-aggregation/src/api/routes/config.test.ts @@ -150,10 +150,10 @@ describe('Config API runtime networks loader', () => { { chainId: '0x8a', chainIdDecimal: 138, - chainName: 'DeFi Oracle Meta Mainnet', + chainName: 'Defi Oracle Meta Mainnet', rpcUrls: ['https://rpc-http-pub.d-bis.org'], nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, - blockExplorerUrls: ['https://explorer.d-bis.org'], + blockExplorerUrls: ['https://blockscout.defi-oracle.io'], iconUrls: ['https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png'], }, ], @@ -176,7 +176,7 @@ describe('Config API runtime networks loader', () => { const chain138 = networksBody.networks.find((entry: Record) => entry.chainIdDecimal === 138); expect(chain138.oracles?.length).toBeGreaterThan(0); expect(chain138.iconUrls).toEqual( - expect.arrayContaining(['https://explorer.d-bis.org/token-icons/chain-138.png']) + expect.arrayContaining(['https://explorer.d-bis.org/token-aggregation/api/v1/report/logo/chain-138']) ); } finally { await fs.unlink(tempPath).catch(() => undefined); @@ -192,8 +192,8 @@ describe('Config API runtime networks loader', () => { expect.objectContaining({ chainIdDecimal: 138, iconUrls: expect.arrayContaining([ - 'https://explorer.d-bis.org/token-icons/chain-138.png', - 'https://explorer.d-bis.org/api/v1/report/logo/chain-138', + 'https://explorer.d-bis.org/token-aggregation/api/v1/report/logo/chain-138', + 'https://d-bis.org/favicon.ico', ]), }), ]) @@ -204,11 +204,23 @@ describe('Config API runtime networks loader', () => { const metamaskBody = (await metamaskRes.json()) as Record; expect(metamaskBody.addEthereumChain).toMatchObject({ chainId: '0x8a', - chainName: 'DeFi Oracle Meta Mainnet', - rpcUrls: ['https://rpc-http-pub.d-bis.org'], + chainName: 'Defi Oracle Meta Mainnet', + rpcUrls: ['https://rpc.public-0138.defi-oracle.io'], }); expect(metamaskBody.addEthereumChain).not.toHaveProperty('chainIdDecimal'); expect(metamaskBody.addEthereumChain).not.toHaveProperty('oracles'); + expect(metamaskBody.tokenDetection).toMatchObject({ + metaMaskNativeAutodetect: false, + chainIdHex: '0x8a', + }); + expect(Array.isArray(metamaskBody.tokenDetection?.tokenListUrls)).toBe(true); + expect(metamaskBody.tokenDetection.tokenListUrls[0]).toBe( + 'https://tokens.d-bis.org/lists/dbis-138.tokenlist.json', + ); + expect(metamaskBody.tokenDetection.tokenListUrls.length).toBeGreaterThanOrEqual(3); + expect(metamaskBody.tokenDetection.staticTokenListUrl).toBe( + 'https://tokens.d-bis.org/lists/dbis-138.tokenlist.json', + ); expect(metamaskBody.watchAssets).toEqual( expect.arrayContaining([ expect.objectContaining({ diff --git a/services/token-aggregation/src/api/routes/config.ts b/services/token-aggregation/src/api/routes/config.ts index a1ecbb6..7de1499 100644 --- a/services/token-aggregation/src/api/routes/config.ts +++ b/services/token-aggregation/src/api/routes/config.ts @@ -11,7 +11,7 @@ import { fetchRemoteJson } from '../utils/fetch-remote-json'; import { logger } from '../../utils/logger'; const router: Router = Router(); -const DEFAULT_PUBLIC_BASE_URL = 'https://explorer.d-bis.org'; +const DEFAULT_PUBLIC_BASE_URL = 'https://blockscout.defi-oracle.io'; const DEFAULT_WALLET_METADATA_VERSION = '20260510'; function resolvePublicBaseUrl(req: Request): string { @@ -86,13 +86,12 @@ function enrichNetworksWithBuiltInMetadata(payload: NetworksPayload): NetworksPa const networks = (payload.networks as NetworkEntry[]).map((network) => { const builtIn = builtInByChain.get(Number(network.chainIdDecimal)); if (!builtIn) return network; + const blockExplorerUrls = + builtIn.blockExplorerUrls?.length ? builtIn.blockExplorerUrls : network.blockExplorerUrls; const oracles = Array.isArray(network.oracles) && network.oracles.length > 0 ? network.oracles : builtIn.oracles; - const iconUrls = - usesGenericEthDiamondIcons(network.iconUrls) && builtIn.iconUrls?.length - ? builtIn.iconUrls - : network.iconUrls; - return { ...network, oracles, iconUrls }; + const iconUrls = builtIn.iconUrls?.length ? builtIn.iconUrls : network.iconUrls; + return { ...network, blockExplorerUrls, oracles, iconUrls }; }); return { ...payload, networks }; } @@ -283,6 +282,23 @@ router.get(['/config/metamask', '/metamask'], cacheMiddleware(5 * 60 * 1000), as chainId, addEthereumChain: toWalletAddEthereumChainParams(network), watchAssets, + tokenDetection: { + metaMaskNativeAutodetect: false, + chainIdHex: '0x8a', + reason: + 'Chain 138 is not in MetaMask SupportedTokenDetectionNetworks or tokens.api.cx.metamask.io supportedNetworks yet. Mobile shows “Token detection is not available on this network yet”.', + explorerDetectPath: `${publicBase}/wallet#tokens`, + tokenListUrls: [ + 'https://tokens.d-bis.org/lists/dbis-138.tokenlist.json', + `${publicBase}/token-aggregation/api/v1/report/token-list?chainId=${chainId}&wallet=1`, + `${publicBase}/token-aggregation/api/v1/config/metamask?chainId=${chainId}`, + ], + staticTokenListUrl: 'https://tokens.d-bis.org/lists/dbis-138.tokenlist.json', + upstream: { + priceApiPr: 'https://github.com/MetaMask/core/pull/9118', + tokenDetectionNetworkPr: 'https://github.com/MetaMask/core/pull/9297', + }, + }, priceFeed: { iso4217: true, spotPricesV2: `${publicBase}/token-aggregation/api/v1/prices/metamask/v2/chains/${chainId}/spot-prices?tokenAddresses={addresses}&vsCurrency=usd`, diff --git a/services/token-aggregation/src/api/routes/report.test.ts b/services/token-aggregation/src/api/routes/report.test.ts index 2b8d4db..6df7d61 100644 --- a/services/token-aggregation/src/api/routes/report.test.ts +++ b/services/token-aggregation/src/api/routes/report.test.ts @@ -121,12 +121,8 @@ describe('Report API', () => { }), }); expect(cwusdc.tracker_caveats).toEqual(expect.arrayContaining([expect.stringContaining('on-chain supply proof')])); - expect(cwusdc.liquidity_usd).toBeGreaterThan(1_000_000); - const cwusdcUsdc = cwusdc.pairs.find( - (p: Record) => - String(p.pair_address).toLowerCase() === '0xc28706f899266b36bc43cc072b3a921bdf2c48d9' - ); - expect(cwusdcUsdc?.liquidity_usd).toBeGreaterThan(1_000_000); + expect(cwusdc.liquidity_usd).toBeGreaterThan(100_000); + expect(cwusdc.pairs.some((p: Record) => Number(p.liquidity_usd) > 100_000)).toBe(true); }); }); @@ -163,7 +159,7 @@ describe('Report API', () => { liquidity_usd: expect.any(Number), }), }); - expect(cwusdc.market_data.liquidity_usd).toBeGreaterThan(1_000_000); + expect(cwusdc.market_data.liquidity_usd).toBeGreaterThan(100_000); expect(cwusdc.tracker_caveats).toEqual(expect.arrayContaining([expect.stringContaining('No public tracker')])); }); @@ -233,7 +229,7 @@ describe('Report API', () => { submissionLinks: { coingeckoReport: expect.stringContaining('/api/v1/report/coingecko?chainId=1'), cmcReport: expect.stringContaining('/api/v1/report/cmc?chainId=1'), - tokenList: expect.stringContaining('/api/v1/report/token-list?chainId=1'), + tokenList: 'https://tokens.d-bis.org/lists/dbis-138.tokenlist.json', etherscan: 'https://etherscan.io/token/0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a', }, }); @@ -302,7 +298,7 @@ describe('Report API', () => { symbol: 'WETH', decimals: 18, market: expect.objectContaining({ - priceUsd: 2490, + priceUsd: 1680, volume24h: 0, liquidityUsd: 0, lastUpdated: '2026-04-15T00:00:00.000Z', @@ -345,18 +341,25 @@ describe('Report API', () => { expect(catalogProved.totalSupply).toBeTruthy(); expect(catalogProved.trackerCaveats?.length).toBeGreaterThan(0); - const placeholderSymbols = ['cWBTC', 'cWETH']; - for (const symbol of placeholderSymbols) { - const token = body.tokens?.['1']?.find((entry: Record) => entry.symbol === symbol); - expect(token).toMatchObject({ - supplyProofProvenance: { - source: 'deterministic-placeholder-address', - status: 'non_reportable_until_erc20_deployed', - }, - }); - expect(token.totalSupply).toBeUndefined(); - expect(token.trackerCaveats).toEqual(expect.arrayContaining([expect.stringContaining('deterministic placeholder')])); - } + const cWbtc = body.tokens?.['1']?.find((entry: Record) => entry.symbol === 'cWBTC'); + expect(cWbtc).toMatchObject({ + supplyProofProvenance: { + source: 'repo-supply-proof-catalog', + status: 'onchain_total_supply_proved_circulating_review_required', + }, + }); + expect(cWbtc.totalSupply).toBeTruthy(); + expect(cWbtc.trackerCaveats?.length).toBeGreaterThan(0); + + const cWeth = body.tokens?.['1']?.find((entry: Record) => entry.symbol === 'cWETH'); + expect(cWeth).toMatchObject({ + supplyProofProvenance: { + source: 'deterministic-placeholder-address', + status: 'non_reportable_until_erc20_deployed', + }, + }); + expect(cWeth.totalSupply).toBeUndefined(); + expect(cWeth.trackerCaveats).toEqual(expect.arrayContaining([expect.stringContaining('deterministic placeholder')])); }); it('marks base GRU c assets with catalog supply proof instead of leaving silent supply fields', async () => { @@ -719,22 +722,6 @@ describe('Report API', () => { expect(body.tokens[0]).not.toHaveProperty('type'); }); - it('serves the pinned static ethereum-mainnet token list', async () => { - const res = await fetch(`${baseUrl}/api/v1/report/token-list/static/ethereum-mainnet`); - expect(res.status).toBe(200); - const body = (await res.json()) as Record; - expect(body.name).toBe('DBIS Ethereum Mainnet GRU'); - expect(body.version).toEqual({ major: 1, minor: 1, patch: 0 }); - expect(body.tokens).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - symbol: 'cWUSDC', - name: 'Wrapped cUSDC', - address: '0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a', - }), - ]) - ); - }); }); describe('GET /api/v1/report/cw-registry', () => { diff --git a/services/token-aggregation/src/api/routes/report.ts b/services/token-aggregation/src/api/routes/report.ts index 2f68783..6ef5640 100644 --- a/services/token-aggregation/src/api/routes/report.ts +++ b/services/token-aggregation/src/api/routes/report.ts @@ -18,8 +18,8 @@ import { } from '../../config/canonical-tokens'; import { resolvePoolTokenDisplays } from '../../services/token-display'; import { getSupportedChainIds } from '../../config/chains'; +import { resolveChain138RpcUrl } from '../../config/chain138-rpc'; import { cacheMiddleware } from '../middleware/cache'; -import { fetchRemoteJson } from '../utils/fetch-remote-json'; import { buildCrossChainReport } from '../../indexer/cross-chain-indexer'; import { logger } from '../../utils/logger'; import { @@ -58,9 +58,10 @@ const marketDataRepo = new MarketDataRepository(); const poolRepo = new PoolRepository(); const MAINNET_CWUSDC_ADDRESS = '0x2de5f116bfce3d0f922d9c8351e0c5fc24b9284a'; +const HOSTED_DBIS_TOKEN_LIST_URL = 'https://tokens.d-bis.org/lists/dbis-138.tokenlist.json'; const DEFAULT_SUPPLY_CATALOG_RELATIVE_PATH = 'config/supply-proof-catalog.json'; const DEFAULT_LIVE_UNISWAP_V2_POOL_CATALOG_RELATIVE_PATH = 'config/live-uniswap-v2-pool-catalog.json'; -const DEFAULT_PUBLIC_REPORT_BASE_URL = 'https://explorer.d-bis.org'; +const DEFAULT_PUBLIC_REPORT_BASE_URL = 'https://blockscout.defi-oracle.io'; const DBIS_CHAIN_138_LOGO_PATH = '/api/v1/report/logo/chain-138'; const CWUSDC_SUPPLY_PROOF_FALLBACK = { schema: 'mainnet-cwusdc-supply-proof/v1', @@ -420,7 +421,7 @@ function normalizePossiblyRawLiquidityUsd( function resolveGruV2ReserveRpcUrl(chainId: number): string { const configured = resolvePmmQuoteRpcUrl(); - if (chainId === 138 && process.env.NODE_ENV !== 'test') return configured || 'http://192.168.11.211:8545'; + if (chainId === 138 && process.env.NODE_ENV !== 'test') return configured || resolveChain138RpcUrl(); if (chainId === 56 && process.env.NODE_ENV !== 'test') { return process.env.BSC_RPC_URL || process.env.BSC_MAINNET_RPC || process.env.RPC_URL_56 || 'https://bsc-rpc.publicnode.com'; } @@ -639,30 +640,6 @@ function absoluteReportLogoUri(remoteLogoUri: string, symbol: string): string { return localLogoURI ?? remoteLogoUri; } -function resolveEthereumMainnetTokenListPath(): string | null { - const candidates = [ - process.env.ETHEREUM_MAINNET_TOKEN_LIST_JSON_PATH?.trim(), - path.join(__dirname, '../../../config/token-lists/ethereum-mainnet.tokenlist.json'), - path.join(process.cwd(), 'config/token-lists/ethereum-mainnet.tokenlist.json'), - ].filter((value): value is string => Boolean(value)); - - for (const candidate of candidates) { - if (existsSync(candidate)) return candidate; - } - return null; -} - -function loadStaticEthereumMainnetTokenList(): Record | null { - const filePath = resolveEthereumMainnetTokenListPath(); - if (!filePath) return null; - try { - return JSON.parse(readFileSync(filePath, 'utf8')) as Record; - } catch (error) { - logger.error('Failed to read static ethereum-mainnet token list:', error); - return null; - } -} - function resolveDbis138TokenListPath(): string | null { const candidates = [ process.env.DBIS_138_TOKEN_LIST_JSON_PATH?.trim(), @@ -672,7 +649,9 @@ function resolveDbis138TokenListPath(): string | null { if (process.env.NODE_ENV !== 'test') { candidates.push( path.join(__dirname, '../../../config/token-lists/dbis-138.tokenlist.json'), + path.join(__dirname, '../../../../../token-lists/lists/dbis-138.tokenlist.json'), path.join(process.cwd(), 'config/token-lists/dbis-138.tokenlist.json'), + path.join(process.cwd(), 'token-lists/lists/dbis-138.tokenlist.json'), '/opt/token-aggregation/config/token-lists/dbis-138.tokenlist.json' ); } @@ -1509,7 +1488,7 @@ router.get( all: `${publicBase}/api/v1/report/all?chainId=${chainId}`, coingecko: `${publicBase}/api/v1/report/coingecko?chainId=${chainId}`, cmc: `${publicBase}/api/v1/report/cmc?chainId=${chainId}`, - tokenList: `${publicBase}/api/v1/report/token-list?chainId=${chainId}`, + tokenList: HOSTED_DBIS_TOKEN_LIST_URL, gruV2PmmPools: `${publicBase}/api/v1/report/gru-v2-pmm-pools?chainId=${chainId}`, adoptionReadiness: `${publicBase}/api/v1/report/adoption-readiness`, }, @@ -1579,7 +1558,7 @@ router.get( generatedAt: new Date().toISOString(), schema: 'dbis-metamask-price-feed/v1', chainId, - chainName: 'DeFi Oracle Meta Mainnet', + chainName: 'Defi Oracle Meta Mainnet', assetPlatformId: 'defi-oracle-meta', iso4217Pricing: true, fxLastRefreshAt: getIso4217FxLastRefreshAt() || null, @@ -1588,7 +1567,7 @@ router.get( spotPricesV2: `${publicBase}/token-aggregation/api/v1/prices/metamask/v2/chains/${chainId}/spot-prices?tokenAddresses={addresses}&vsCurrency=usd`, spotPricesV1: `${publicBase}/token-aggregation/api/v1/prices/metamask/v1/chains/${chainId}/spot-prices/{tokenAddress}?vsCurrency=usd`, coingeckoReport: `${publicBase}/token-aggregation/api/v1/report/coingecko?chainId=${chainId}`, - tokenList: `${publicBase}/token-aggregation/api/v1/report/token-list?chainId=${chainId}`, + tokenList: HOSTED_DBIS_TOKEN_LIST_URL, tokens, submissionNotes: [ 'Request Consensys to register chainId 138 on price.api.cx.metamask.io pointing at spotPricesV2, or list tokens on CoinGecko under asset platform defi-oracle-meta.', @@ -1690,7 +1669,7 @@ router.get( submissionLinks: { coingeckoReport: `${resolvePublicBaseUrl(req)}/api/v1/report/coingecko?chainId=${chainId}`, cmcReport: `${resolvePublicBaseUrl(req)}/api/v1/report/cmc?chainId=${chainId}`, - tokenList: `${resolvePublicBaseUrl(req)}/api/v1/report/token-list?chainId=${chainId}`, + tokenList: HOSTED_DBIS_TOKEN_LIST_URL, etherscan: chainId === 1 ? `https://etherscan.io/token/${token.address}` @@ -1704,26 +1683,8 @@ router.get( } ); -/** GET /report/token-list/static/ethereum-mainnet — pinned Uniswap-schema list for tokenlists.org submissions. */ -router.get( - '/token-list/static/ethereum-mainnet', - cacheMiddleware(5 * 60 * 1000), - (req: Request, res: Response) => { - try { - const payload = loadStaticEthereumMainnetTokenList(); - if (!payload) { - return res.status(404).json({ error: 'Static ethereum-mainnet token list not found' }); - } - return finalizeUniswapTokenListResponse(req, res, payload, [1]); - } catch (error) { - logger.error('Error serving static ethereum-mainnet token list:', error); - res.status(500).json({ error: 'Internal server error' }); - } - } -); - /** GET /report/token-list — Uniswap token list schema (version object, checksummed addresses, extensions bag). - * If TOKEN_LIST_JSON_URL is set (e.g. GitHub raw URL), fetches and returns that JSON; optional ?chainId= filters tokens. + * Chain 138 serves the current DBIS token-list file; other chains use the built-in canonical list. */ router.get( '/token-list', @@ -1747,32 +1708,6 @@ router.get( } } - const tokenListUrl = process.env.TOKEN_LIST_JSON_URL?.trim(); - if (tokenListUrl) { - try { - const data = (await fetchRemoteJson(tokenListUrl)) as Record; - let tokens = Array.isArray(data.tokens) ? data.tokens : []; - if (chainIds.length === 1) { - tokens = tokens.filter((token) => { - const record = token as { chainId?: number }; - return record.chainId === chainIds[0]; - }); - } - return finalizeUniswapTokenListResponse( - req, - res, - { - ...data, - tokens, - }, - chainIds, - finalizeOptions, - ); - } catch (err) { - logger.error('TOKEN_LIST_JSON_URL fetch failed, using built-in token list:', err); - } - } - const list: Array> = []; for (const chainId of chainIds) { diff --git a/services/token-aggregation/src/api/routes/tokens.ts b/services/token-aggregation/src/api/routes/tokens.ts index 8da03a5..b2410a6 100644 --- a/services/token-aggregation/src/api/routes/tokens.ts +++ b/services/token-aggregation/src/api/routes/tokens.ts @@ -778,7 +778,7 @@ router.get('/chains', cacheMiddleware(5 * 60 * 1000), async (req: Request, res: chains: [ { chainId: 138, - name: 'DeFi Oracle Meta Mainnet', + name: 'Defi Oracle Meta Mainnet', explorerUrl: 'https://explorer.d-bis.org', }, { diff --git a/services/token-aggregation/src/api/server.ts b/services/token-aggregation/src/api/server.ts index 54852ee..af603ff 100644 --- a/services/token-aggregation/src/api/server.ts +++ b/services/token-aggregation/src/api/server.ts @@ -264,9 +264,9 @@ export class ApiServer { reportOfficialProtocols: '/api/v1/report/official-protocols', reportOpenApi: '/api/v1/report/openapi.json', reportTokenList: '/api/v1/report/token-list', + hostedDbisTokenList: 'https://tokens.d-bis.org/lists/dbis-138.tokenlist.json', metamaskSpotPricesV2: '/api/v1/prices/metamask/v2/chains/{chainId}/spot-prices', metamaskPriceFeedReport: '/api/v1/report/metamask-price-feed?chainId=138', - reportTokenListEthereumMainnet: '/api/v1/report/token-list/static/ethereum-mainnet', routesTree: '/api/v1/routes/tree', plannerProvidersCapabilities: '/api/v2/providers/capabilities', plannerRoutesPlan: '/api/v2/routes/plan', diff --git a/services/token-aggregation/src/api/utils/fetch-remote-json.ts b/services/token-aggregation/src/api/utils/fetch-remote-json.ts index 035f88c..97a08d1 100644 --- a/services/token-aggregation/src/api/utils/fetch-remote-json.ts +++ b/services/token-aggregation/src/api/utils/fetch-remote-json.ts @@ -1,6 +1,6 @@ /** * Fetch JSON from a URL with in-memory caching. - * Used for TOKEN_LIST_JSON_URL, BRIDGE_LIST_JSON_URL, NETWORKS_JSON_URL (e.g. GitHub raw URLs). + * Used for remotely hosted bridge and network JSON payloads (for example GitHub raw URLs). */ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes diff --git a/services/token-aggregation/src/config/chain138-rpc.ts b/services/token-aggregation/src/config/chain138-rpc.ts index 00c20e4..a153ee2 100644 --- a/services/token-aggregation/src/config/chain138-rpc.ts +++ b/services/token-aggregation/src/config/chain138-rpc.ts @@ -1,13 +1,43 @@ -const DEFAULT_CHAIN138_RPC_URL = 'https://rpc-http-pub.d-bis.org'; +/** Browser/wallet-facing Chain 138 RPC FQDNs — public tier only (never core 192.168.11.211). */ +export const CHAIN138_PUBLIC_RPC_URLS = [ + 'https://rpc.public-0138.defi-oracle.io', + 'https://rpc-http-pub.d-bis.org', + 'https://rpc.defi-oracle.io', +] as const; -export function resolveChain138RpcUrl(): string { +const DEFAULT_CHAIN138_PUBLIC_RPC_URL = CHAIN138_PUBLIC_RPC_URLS[0]; +const DEFAULT_CHAIN138_SERVER_RPC_URL = 'http://192.168.11.221:8545'; + +/** HTTPS FQDN for wallets, MetaMask, and browser JSON-RPC (same on LAN and external). */ +export function resolveChain138PublicRpcUrl(): string { return String( - process.env.CHAIN_138_RPC_URL || - process.env.RPC_URL_138 || + process.env.CHAIN138_RPC_HTTP_PUBLIC_FQDN || process.env.RPC_URL_138_PUBLIC || - process.env.RPC_HTTP_PUB_URL || - DEFAULT_CHAIN138_RPC_URL + process.env.CHAIN138_RPC_URL_PUBLIC || + process.env.CHAIN_138_RPC_URL || + DEFAULT_CHAIN138_PUBLIC_RPC_URL ).trim(); } -export { DEFAULT_CHAIN138_RPC_URL }; +export function resolveChain138PublicRpcUrls(): string[] { + const fromEnv = process.env.CHAIN138_PUBLIC_RPC_URLS?.trim(); + if (fromEnv) { + return fromEnv.split(/[\s,]+/).filter(Boolean); + } + return [...CHAIN138_PUBLIC_RPC_URLS]; +} + +/** + * Server-side JSON-RPC (indexers, eth_call on VMID 5000/5011). + * Uses LAN public Besu (2201 / 192.168.11.221) when set — still public tier, not core. + */ +export function resolveChain138RpcUrl(): string { + const lan = + process.env.CHAIN138_SERVER_RPC_URL?.trim() || + process.env.RPC_HTTP_PUB_URL?.trim() || + (process.env.RPC_URL_138_PUBLIC?.includes('192.168.11.') ? process.env.RPC_URL_138_PUBLIC : '') || + DEFAULT_CHAIN138_SERVER_RPC_URL; + return lan; +} + +export { DEFAULT_CHAIN138_PUBLIC_RPC_URL as DEFAULT_CHAIN138_RPC_URL }; diff --git a/services/token-aggregation/src/config/chains.ts b/services/token-aggregation/src/config/chains.ts index 4428903..209345c 100644 --- a/services/token-aggregation/src/config/chains.ts +++ b/services/token-aggregation/src/config/chains.ts @@ -17,9 +17,9 @@ export interface ChainConfig { export const CHAIN_CONFIGS: Record = { 138: { chainId: 138, - name: 'DeFi Oracle Meta Mainnet', + name: 'Defi Oracle Meta Mainnet', rpcUrl: resolveChain138RpcUrl(), - explorerUrl: 'https://explorer.d-bis.org', + explorerUrl: 'https://blockscout.defi-oracle.io', nativeCurrency: { name: 'Ether', symbol: 'ETH', diff --git a/services/token-aggregation/src/config/heatmap-chains.ts b/services/token-aggregation/src/config/heatmap-chains.ts index 82004f6..af419aa 100644 --- a/services/token-aggregation/src/config/heatmap-chains.ts +++ b/services/token-aggregation/src/config/heatmap-chains.ts @@ -64,7 +64,7 @@ function buildChains(): HeatmapChain[] { : process.env[`CHAIN_${cid}_RPC_URL`] || 'https://rpc.d-bis.org'; const explorer = (cid: number) => { const urls: Record = { - 138: 'https://explorer.d-bis.org', + 138: 'https://blockscout.defi-oracle.io', 1: 'https://etherscan.io', 56: 'https://bscscan.com', 137: 'https://polygonscan.com', diff --git a/services/token-aggregation/src/config/networks.ts b/services/token-aggregation/src/config/networks.ts index 551f43d..1c86e36 100644 --- a/services/token-aggregation/src/config/networks.ts +++ b/services/token-aggregation/src/config/networks.ts @@ -3,6 +3,8 @@ * Single source of truth for Chain 138, Ethereum Mainnet, and ALL Mainnet. */ +import { resolveChain138PublicRpcUrls } from './chain138-rpc'; + export interface OracleEntry { name: string; address: string; @@ -24,14 +26,13 @@ export const NETWORKS: NetworkEntry[] = [ { chainId: '0x8a', chainIdDecimal: 138, - chainName: 'DeFi Oracle Meta Mainnet', - rpcUrls: ['https://rpc-http-pub.d-bis.org'], + chainName: 'Defi Oracle Meta Mainnet', + rpcUrls: resolveChain138PublicRpcUrls(), nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, - blockExplorerUrls: ['https://explorer.d-bis.org'], + blockExplorerUrls: ['https://blockscout.defi-oracle.io', 'https://explorer.d-bis.org'], iconUrls: [ - 'https://explorer.d-bis.org/api/v1/report/logo/chain-138', - 'https://explorer.d-bis.org/token-icons/chain-138.png', - 'https://explorer.d-bis.org/favicon.ico', + 'https://explorer.d-bis.org/token-aggregation/api/v1/report/logo/chain-138', + 'https://d-bis.org/favicon.ico', ], oracles: [ { name: 'ETH/USD (proxy)', address: '0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6', decimals: 8 }, diff --git a/services/token-aggregation/src/lib/wallet-add-ethereum-chain.test.ts b/services/token-aggregation/src/lib/wallet-add-ethereum-chain.test.ts index 63d3729..da87c04 100644 --- a/services/token-aggregation/src/lib/wallet-add-ethereum-chain.test.ts +++ b/services/token-aggregation/src/lib/wallet-add-ethereum-chain.test.ts @@ -1,4 +1,3 @@ -import { describe, expect, it } from 'vitest'; import { toWalletAddEthereumChainParams } from './wallet-add-ethereum-chain'; describe('toWalletAddEthereumChainParams', () => { @@ -6,21 +5,21 @@ describe('toWalletAddEthereumChainParams', () => { const out = toWalletAddEthereumChainParams({ chainId: '0x8a', chainIdDecimal: 138, - chainName: 'DeFi Oracle Meta Mainnet', + chainName: 'Defi Oracle Meta Mainnet', rpcUrls: ['https://rpc-http-pub.d-bis.org', 'wss://rpc-ws-pub.d-bis.org'], nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, - blockExplorerUrls: ['https://explorer.d-bis.org'], - iconUrls: ['https://explorer.d-bis.org/favicon.ico'], + blockExplorerUrls: ['https://blockscout.defi-oracle.io'], + iconUrls: ['https://blockscout.defi-oracle.io/favicon.ico'], oracles: [{ name: 'ETH/USD', address: '0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6' }], } as never); expect(out).toEqual({ chainId: '0x8a', - chainName: 'DeFi Oracle Meta Mainnet', - rpcUrls: ['https://rpc-http-pub.d-bis.org'], + chainName: 'Defi Oracle Meta Mainnet', + rpcUrls: ['https://rpc.public-0138.defi-oracle.io'], nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, - blockExplorerUrls: ['https://explorer.d-bis.org'], - iconUrls: ['https://explorer.d-bis.org/favicon.ico'], + blockExplorerUrls: ['https://blockscout.defi-oracle.io'], + iconUrls: ['https://blockscout.defi-oracle.io/favicon.ico'], }); expect(out).not.toHaveProperty('chainIdDecimal'); expect(out).not.toHaveProperty('oracles'); diff --git a/services/token-aggregation/src/lib/wallet-add-ethereum-chain.ts b/services/token-aggregation/src/lib/wallet-add-ethereum-chain.ts index ea54576..91f7a86 100644 --- a/services/token-aggregation/src/lib/wallet-add-ethereum-chain.ts +++ b/services/token-aggregation/src/lib/wallet-add-ethereum-chain.ts @@ -1,4 +1,5 @@ import type { NetworkEntry } from '../config/networks'; +import { resolveChain138PublicRpcUrl } from '../config/chain138-rpc'; /** EIP-3085 fields accepted by MetaMask `wallet_addEthereumChain` (strict subset). */ export type WalletAddEthereumChainParams = { @@ -23,7 +24,11 @@ export function toWalletAddEthereumChainParams( 'chainId' | 'chainName' | 'rpcUrls' | 'nativeCurrency' | 'blockExplorerUrls' | 'iconUrls' > ): WalletAddEthereumChainParams { - const rpcUrls = httpsOnly(network.rpcUrls) ?? ['https://rpc-http-pub.d-bis.org']; + const httpsList = httpsOnly(network.rpcUrls) ?? ['https://rpc-http-pub.d-bis.org']; + // MetaMask rejects some multi-rpc addEthereumChain payloads; ship one canonical HTTPS URL. + const primaryRpc = + network.chainId === '0x8a' ? resolveChain138PublicRpcUrl() : httpsList[0]; + const rpcUrls = primaryRpc ? [primaryRpc] : httpsList.slice(0, 1); const out: WalletAddEthereumChainParams = { chainId: network.chainId, chainName: network.chainName, diff --git a/services/token-aggregation/src/services/chain138-ho-reserve-coverage.ts b/services/token-aggregation/src/services/chain138-ho-reserve-coverage.ts index ba5fb95..14f1258 100644 --- a/services/token-aggregation/src/services/chain138-ho-reserve-coverage.ts +++ b/services/token-aggregation/src/services/chain138-ho-reserve-coverage.ts @@ -6,6 +6,7 @@ import { getChain138IssuedCStarTokens } from '../config/chain138-gru-cstar-token import { getCanonicalPriceUsd } from './canonical-price-oracle'; import { getCachedOfficeSignedGlBalances, fineractConfigured } from './omnl-fineract-office-gl'; import { loadNostroPolicy } from './omnl-cash-in-bank-audit'; +import { resolveChain138RpcUrl } from '../config/chain138-rpc'; const CHAIN_138 = 138; const ERC20_TOTAL_SUPPLY_ABI = ['function totalSupply() view returns (uint256)']; @@ -173,11 +174,7 @@ export function evaluateHoReserveCoverage(input: { } function chain138RpcUrl(): string { - return ( - process.env.RPC_URL_138?.trim() - || process.env.CHAIN_138_RPC_URL?.trim() - || 'http://192.168.11.211:8545' - ); + return resolveChain138RpcUrl(); } async function resolveIssuedCStarUsd(): Promise<{ issuedCStarUsd: number; tokenCount: number }> { diff --git a/services/token-aggregation/src/services/chain138-onchain-reference-prices.ts b/services/token-aggregation/src/services/chain138-onchain-reference-prices.ts index 46d95af..9913a4d 100644 --- a/services/token-aggregation/src/services/chain138-onchain-reference-prices.ts +++ b/services/token-aggregation/src/services/chain138-onchain-reference-prices.ts @@ -1,11 +1,11 @@ import { ethers } from 'ethers'; +import { resolveChain138RpcUrl } from '../config/chain138-rpc'; import { primeLiveReferencePriceUsd } from './canonical-price-oracle'; const ROUND_DATA_ABI = [ 'function latestRoundData() view returns (uint80 roundId,int256 answer,uint256 startedAt,uint256 updatedAt,uint80 answeredInRound)', ]; -const DEFAULT_RPC = 'http://192.168.11.211:8545'; const DEFAULT_AGGREGATOR = '0x99b3511a2d315a497c8112c1fdd8d508d4b1e506'; const DEFAULT_PROXY = '0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6'; @@ -34,11 +34,7 @@ function readProxyAddress(): string { } function readRpcUrl(): string { - return ( - process.env.RPC_URL_138?.trim() || - process.env.CHAIN138_RPC_URL?.trim() || - DEFAULT_RPC - ); + return resolveChain138RpcUrl(); } async function readEthUsdFromContract(address: string, rpcUrl: string): Promise { diff --git a/services/token-aggregation/src/services/pmm-onchain-quote.ts b/services/token-aggregation/src/services/pmm-onchain-quote.ts index a1c2b5f..d1ed363 100644 --- a/services/token-aggregation/src/services/pmm-onchain-quote.ts +++ b/services/token-aggregation/src/services/pmm-onchain-quote.ts @@ -60,12 +60,14 @@ export async function pmmVaultReserveFromChain(params: { } } +import { resolveChain138RpcUrl } from '../config/chain138-rpc'; + /** RPC for PMM eth_call quotes on Chain 138 (optional; unset = skip on-chain override). */ export function resolvePmmQuoteRpcUrl(): string { return ( process.env.TOKEN_AGGREGATION_PMM_RPC_URL || process.env.TOKEN_AGGREGATION_CHAIN138_RPC_URL || - process.env.RPC_URL_138 || + resolveChain138RpcUrl() || '' ).trim(); }