Harden explorer MetaMask data and navigation coverage
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"name": "MetaMask Multi-Chain Networks (13 chains)",
|
||||
"version": {"major": 1, "minor": 2, "patch": 0},
|
||||
"defaultChainId": 138,
|
||||
"explorerUrl": "https://explorer.d-bis.org",
|
||||
"tokenListUrl": "https://explorer.d-bis.org/api/config/token-list",
|
||||
"generatedBy": "SolaceScanScout",
|
||||
"chains": [
|
||||
{"chainId":"0x8a","chainIdDecimal":138,"chainName":"DeFi Oracle Meta Mainnet","rpcUrls":["https://rpc-http-pub.d-bis.org","https://rpc.d-bis.org","https://rpc2.d-bis.org","https://rpc.defi-oracle.io"],"nativeCurrency":{"name":"Ether","symbol":"ETH","decimals":18},"blockExplorerUrls":["https://explorer.d-bis.org"],"iconUrls":["https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png"]},
|
||||
{"chainId":"0x1","chainIdDecimal":1,"chainName":"Ethereum Mainnet","rpcUrls":["https://eth.llamarpc.com","https://rpc.ankr.com/eth","https://ethereum.publicnode.com","https://1rpc.io/eth"],"nativeCurrency":{"name":"Ether","symbol":"ETH","decimals":18},"blockExplorerUrls":["https://etherscan.io"],"iconUrls":["https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png"]},
|
||||
{"chainId":"0x9f2c4","chainIdDecimal":651940,"chainName":"ALL Mainnet","rpcUrls":["https://mainnet-rpc.alltra.global"],"nativeCurrency":{"name":"Ether","symbol":"ETH","decimals":18},"blockExplorerUrls":["https://alltra.global"],"iconUrls":["https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png"]},
|
||||
{"chainId":"0x8a","chainIdDecimal":138,"chainName":"DeFi Oracle Meta Mainnet","shortName":"dbis","rpcUrls":["https://rpc-http-pub.d-bis.org","https://rpc.d-bis.org","https://rpc2.d-bis.org","https://rpc.defi-oracle.io"],"nativeCurrency":{"name":"Ether","symbol":"ETH","decimals":18},"blockExplorerUrls":["https://explorer.d-bis.org"],"iconUrls":["https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png"],"infoURL":"https://explorer.d-bis.org","explorerApiUrl":"https://explorer.d-bis.org/api/v2","testnet":false},
|
||||
{"chainId":"0x1","chainIdDecimal":1,"chainName":"Ethereum Mainnet","shortName":"eth","rpcUrls":["https://eth.llamarpc.com","https://rpc.ankr.com/eth","https://ethereum.publicnode.com","https://1rpc.io/eth"],"nativeCurrency":{"name":"Ether","symbol":"ETH","decimals":18},"blockExplorerUrls":["https://etherscan.io"],"iconUrls":["https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png"],"infoURL":"https://ethereum.org","testnet":false},
|
||||
{"chainId":"0x9f2c4","chainIdDecimal":651940,"chainName":"ALL Mainnet","shortName":"all","rpcUrls":["https://mainnet-rpc.alltra.global"],"nativeCurrency":{"name":"Ether","symbol":"ETH","decimals":18},"blockExplorerUrls":["https://alltra.global"],"iconUrls":["https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png"],"infoURL":"https://alltra.global","testnet":false},
|
||||
{"chainId":"0x19","chainIdDecimal":25,"chainName":"Cronos Mainnet","rpcUrls":["https://evm.cronos.org","https://cronos-rpc.publicnode.com"],"nativeCurrency":{"name":"CRO","symbol":"CRO","decimals":18},"blockExplorerUrls":["https://cronos.org/explorer"],"iconUrls":["https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong"]},
|
||||
{"chainId":"0x38","chainIdDecimal":56,"chainName":"BNB Smart Chain","rpcUrls":["https://bsc-dataseed.binance.org","https://bsc-dataseed1.defibit.io","https://bsc-dataseed1.ninicoin.io"],"nativeCurrency":{"name":"BNB","symbol":"BNB","decimals":18},"blockExplorerUrls":["https://bscscan.com"],"iconUrls":["https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png"]},
|
||||
{"chainId":"0x64","chainIdDecimal":100,"chainName":"Gnosis Chain","rpcUrls":["https://rpc.gnosischain.com","https://gnosis-rpc.publicnode.com","https://1rpc.io/gnosis"],"nativeCurrency":{"name":"xDAI","symbol":"xDAI","decimals":18},"blockExplorerUrls":["https://gnosisscan.io"],"iconUrls":["https://raw.githubusercontent.com/ethereum/ethereum.org/main/static/images/eth-diamond-black.png"]},
|
||||
|
||||
@@ -7,6 +7,44 @@
|
||||
},
|
||||
"timestamp": "2026-03-26T09:17:26.866Z",
|
||||
"logoURI": "https://ipfs.io/ipfs/Qma3FKtLce9MjgJgWbtyCxBiPjJ6xi8jGWUSKNS5Jc2ong",
|
||||
"keywords": [
|
||||
"chain138",
|
||||
"defi-oracle-meta",
|
||||
"multichain",
|
||||
"metamask",
|
||||
"wallet"
|
||||
],
|
||||
"tags": {
|
||||
"stablecoin": {
|
||||
"name": "Stablecoin",
|
||||
"description": "Fiat-pegged and fiat-mirrored assets published for explorer and wallet discovery."
|
||||
},
|
||||
"defi": {
|
||||
"name": "DeFi",
|
||||
"description": "Assets surfaced across the explorer and DEX route matrix."
|
||||
},
|
||||
"compliant": {
|
||||
"name": "Compliant",
|
||||
"description": "Compliance-oriented assets deployed on Chain 138."
|
||||
},
|
||||
"oracle": {
|
||||
"name": "Oracle",
|
||||
"description": "Oracle or oracle-adjacent assets and price feed entries."
|
||||
},
|
||||
"wrapped": {
|
||||
"name": "Wrapped",
|
||||
"description": "Wrapped representations of native or bridged assets."
|
||||
},
|
||||
"ccip": {
|
||||
"name": "CCIP",
|
||||
"description": "Assets related to CCIP and bridge infrastructure."
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"defaultChainId": 138,
|
||||
"explorerUrl": "https://explorer.d-bis.org",
|
||||
"networksConfigUrl": "https://explorer.d-bis.org/api/config/networks"
|
||||
},
|
||||
"tokens": [
|
||||
{
|
||||
"chainId": 138,
|
||||
|
||||
163
backend/api/rest/config_test.go
Normal file
163
backend/api/rest/config_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testNetworksCatalog struct {
|
||||
Name string `json:"name"`
|
||||
DefaultChainID int `json:"defaultChainId"`
|
||||
ExplorerURL string `json:"explorerUrl"`
|
||||
TokenListURL string `json:"tokenListUrl"`
|
||||
GeneratedBy string `json:"generatedBy"`
|
||||
Chains []struct {
|
||||
ChainID string `json:"chainId"`
|
||||
ChainIDDecimal int `json:"chainIdDecimal"`
|
||||
ChainName string `json:"chainName"`
|
||||
ShortName string `json:"shortName"`
|
||||
RPCURLs []string `json:"rpcUrls"`
|
||||
BlockExplorerURL []string `json:"blockExplorerUrls"`
|
||||
InfoURL string `json:"infoURL"`
|
||||
ExplorerAPIURL string `json:"explorerApiUrl"`
|
||||
Testnet bool `json:"testnet"`
|
||||
} `json:"chains"`
|
||||
}
|
||||
|
||||
type testTokenList struct {
|
||||
Name string `json:"name"`
|
||||
Keywords []string
|
||||
Extensions struct {
|
||||
DefaultChainID int `json:"defaultChainId"`
|
||||
ExplorerURL string `json:"explorerUrl"`
|
||||
NetworksConfigURL string `json:"networksConfigUrl"`
|
||||
} `json:"extensions"`
|
||||
Tokens []struct {
|
||||
ChainID int `json:"chainId"`
|
||||
Address string `json:"address"`
|
||||
Symbol string `json:"symbol"`
|
||||
Decimals int `json:"decimals"`
|
||||
Extensions struct {
|
||||
UnitOfAccount string `json:"unitOfAccount"`
|
||||
UnitDescription string `json:"unitDescription"`
|
||||
} `json:"extensions"`
|
||||
} `json:"tokens"`
|
||||
}
|
||||
|
||||
func setupConfigHandler() http.Handler {
|
||||
server := NewServer(nil, 138)
|
||||
mux := http.NewServeMux()
|
||||
server.SetupRoutes(mux)
|
||||
return server.addMiddleware(mux)
|
||||
}
|
||||
|
||||
func TestConfigNetworksEndpointProvidesWalletMetadata(t *testing.T) {
|
||||
handler := setupConfigHandler()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/config/networks", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
if got := w.Header().Get("Access-Control-Allow-Origin"); got == "" {
|
||||
t.Fatal("expected CORS header on config endpoint")
|
||||
}
|
||||
|
||||
var payload testNetworksCatalog
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("failed to parse networks payload: %v", err)
|
||||
}
|
||||
|
||||
if payload.DefaultChainID != 138 {
|
||||
t.Fatalf("expected defaultChainId 138, got %d", payload.DefaultChainID)
|
||||
}
|
||||
if payload.ExplorerURL == "" || payload.TokenListURL == "" || payload.GeneratedBy == "" {
|
||||
t.Fatal("expected root metadata fields to be populated")
|
||||
}
|
||||
if len(payload.Chains) < 3 {
|
||||
t.Fatalf("expected multiple chain entries, got %d", len(payload.Chains))
|
||||
}
|
||||
|
||||
var foundChain138 bool
|
||||
for _, chain := range payload.Chains {
|
||||
if chain.ChainIDDecimal != 138 {
|
||||
continue
|
||||
}
|
||||
foundChain138 = true
|
||||
if chain.ShortName == "" || chain.InfoURL == "" || chain.ExplorerAPIURL == "" {
|
||||
t.Fatal("expected Chain 138 optional metadata to be populated")
|
||||
}
|
||||
if len(chain.RPCURLs) == 0 || len(chain.BlockExplorerURL) == 0 {
|
||||
t.Fatal("expected Chain 138 RPC and explorer URLs")
|
||||
}
|
||||
if chain.Testnet {
|
||||
t.Fatal("expected Chain 138 to be marked as mainnet")
|
||||
}
|
||||
}
|
||||
|
||||
if !foundChain138 {
|
||||
t.Fatal("expected Chain 138 entry in networks catalog")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigTokenListEndpointProvidesOptionalMetadata(t *testing.T) {
|
||||
handler := setupConfigHandler()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/config/token-list", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var payload testTokenList
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("failed to parse token list payload: %v", err)
|
||||
}
|
||||
|
||||
if len(payload.Keywords) == 0 {
|
||||
t.Fatal("expected token list keywords")
|
||||
}
|
||||
if payload.Extensions.DefaultChainID != 138 || payload.Extensions.ExplorerURL == "" || payload.Extensions.NetworksConfigURL == "" {
|
||||
t.Fatal("expected root-level token list extensions")
|
||||
}
|
||||
|
||||
var foundCXAUC bool
|
||||
var foundCUSDT bool
|
||||
for _, token := range payload.Tokens {
|
||||
switch token.Symbol {
|
||||
case "cXAUC":
|
||||
foundCXAUC = true
|
||||
if token.Extensions.UnitOfAccount == "" || token.Extensions.UnitDescription == "" {
|
||||
t.Fatal("expected cXAUC optional unit metadata")
|
||||
}
|
||||
case "cUSDT":
|
||||
foundCUSDT = true
|
||||
if token.Decimals != 6 {
|
||||
t.Fatalf("expected cUSDT decimals 6, got %d", token.Decimals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !foundCXAUC || !foundCUSDT {
|
||||
t.Fatal("expected cXAUC and cUSDT in token list")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigEndpointsSupportOptionsPreflight(t *testing.T) {
|
||||
handler := setupConfigHandler()
|
||||
req := httptest.NewRequest(http.MethodOptions, "/api/config/token-list", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200 preflight response, got %d", w.Code)
|
||||
}
|
||||
if got := w.Header().Get("Access-Control-Allow-Methods"); got == "" {
|
||||
t.Fatal("expected Access-Control-Allow-Methods header")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user