From f2ebe824bd8f865cf7499d959434230277aa3a52 Mon Sep 17 00:00:00 2001 From: defiQUG Date: Fri, 22 May 2026 21:55:42 -0700 Subject: [PATCH] Add WalletConnect stub, track surfaces, legacy SPA retirement, and dual-domain checks. Publish walletconnect config endpoints, Track 3/4 notes on analytics/operator pages, legacy SPA at /legacy/index.html with root redirect, and a parity verifier for explorer.d-bis.org vs blockscout.defi-oracle.io. Co-authored-by: Cursor --- backend/api/rest/routes.go | 2 + backend/api/rest/walletconnect.go | 107 + .../api/rest/walletconnect_internal_test.go | 79 + backend/wallet/walletconnect.go | 123 +- .../common/nginx-next-frontend-proxy.conf | 6 + frontend/public/explorer-spa.js | 3 + frontend/public/index.html | 1993 +---------------- frontend/public/legacy/index.html | 1964 ++++++++++++++++ .../explorer/OperationsPageShell.tsx | 5 + .../explorer/OperationsTrackNote.tsx | 20 + frontend/src/data/explorerOperations.ts | 23 + frontend/src/services/api/walletConnect.ts | 24 + scripts/deploy-next-frontend-to-vmid5000.sh | 1 + scripts/e2e-sprint-smoke.spec.ts | 18 + .../check-explorer-dual-domain-parity.sh | 49 + 15 files changed, 2442 insertions(+), 1975 deletions(-) create mode 100644 backend/api/rest/walletconnect.go create mode 100644 backend/api/rest/walletconnect_internal_test.go create mode 100644 frontend/public/legacy/index.html create mode 100644 frontend/src/components/explorer/OperationsTrackNote.tsx create mode 100644 frontend/src/services/api/walletConnect.ts create mode 100755 scripts/verify/check-explorer-dual-domain-parity.sh diff --git a/backend/api/rest/routes.go b/backend/api/rest/routes.go index 8fac635..760b37b 100644 --- a/backend/api/rest/routes.go +++ b/backend/api/rest/routes.go @@ -54,6 +54,8 @@ func (s *Server) SetupRoutes(mux *http.ServeMux) { mux.HandleFunc("/api/v1/auth/wallet", s.handleAuthWallet) mux.HandleFunc("/api/v1/auth/refresh", s.handleAuthRefresh) mux.HandleFunc("/api/v1/auth/logout", s.handleAuthLogout) + mux.HandleFunc("/api/v1/walletconnect/", s.handleWalletConnectRoot) + mux.HandleFunc("/api/v1/walletconnect", s.handleWalletConnectRoot) mux.HandleFunc("/api/v1/auth/register", s.handleAuthRegister) mux.HandleFunc("/api/v1/auth/login", s.handleAuthLogin) mux.HandleFunc("/api/v1/access/me", s.handleAccessMe) diff --git a/backend/api/rest/walletconnect.go b/backend/api/rest/walletconnect.go new file mode 100644 index 0000000..a69ecb1 --- /dev/null +++ b/backend/api/rest/walletconnect.go @@ -0,0 +1,107 @@ +package rest + +import ( + "net/http" + "strings" + + "github.com/explorer/backend/wallet" +) + +func (s *Server) walletConnectHandler() *wallet.WalletConnect { + return wallet.NewWalletConnect(s.chainID) +} + +// handleWalletConnectConfig handles GET /api/v1/walletconnect/config +func (s *Server) handleWalletConnectConfig(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") + return + } + writeJSON(w, http.StatusOK, s.walletConnectHandler().PublicConfig()) +} + +// handleWalletConnectMetadata handles GET /api/v1/walletconnect/metadata +func (s *Server) handleWalletConnectMetadata(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") + return + } + writeJSON(w, http.StatusOK, map[string]interface{}{ + "name": "DBIS Explorer", + "description": "Chain 138 explorer by DBIS", + "url": "https://explorer.d-bis.org", + "icons": []string{"https://explorer.d-bis.org/favicon.ico"}, + }) +} + +// handleWalletConnectConnect handles POST /api/v1/walletconnect/connect +func (s *Server) handleWalletConnectConnect(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") + return + } + + response, err := s.walletConnectHandler().Connect(r.Context()) + if err != nil { + writeJSON(w, http.StatusNotImplemented, response) + return + } + writeJSON(w, http.StatusOK, response) +} + +// handleWalletConnectSession handles GET /api/v1/walletconnect/session/{id} +func (s *Server) handleWalletConnectSession(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") + return + } + + sessionID := strings.TrimPrefix(r.URL.Path, "/api/v1/walletconnect/session/") + sessionID = strings.Trim(sessionID, "/") + if sessionID == "" || strings.Contains(sessionID, "/") { + writeError(w, http.StatusBadRequest, "bad_request", "session id is required") + return + } + + session, err := s.walletConnectHandler().GetSession(r.Context(), sessionID) + if err != nil { + writeJSON(w, http.StatusNotImplemented, session) + return + } + writeJSON(w, http.StatusOK, session) +} + +// handleWalletConnectRoot dispatches walletconnect subroutes. +func (s *Server) handleWalletConnectRoot(w http.ResponseWriter, r *http.Request) { + path := strings.TrimPrefix(r.URL.Path, "/api/v1/walletconnect") + path = strings.Trim(path, "/") + + switch path { + case "config": + s.handleWalletConnectConfig(w, r) + case "metadata": + s.handleWalletConnectMetadata(w, r) + case "connect": + s.handleWalletConnectConnect(w, r) + case "": + if r.Method != http.MethodGet { + writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed") + return + } + writeJSON(w, http.StatusOK, map[string]interface{}{ + "endpoints": []string{ + "/api/v1/walletconnect/config", + "/api/v1/walletconnect/metadata", + "/api/v1/walletconnect/connect", + "/api/v1/walletconnect/session/{sessionId}", + }, + "fallbackAuth": "/api/v1/auth/wallet", + }) + default: + if strings.HasPrefix(path, "session/") { + s.handleWalletConnectSession(w, r) + return + } + writeError(w, http.StatusNotFound, "not_found", "walletconnect route not found") + } +} diff --git a/backend/api/rest/walletconnect_internal_test.go b/backend/api/rest/walletconnect_internal_test.go new file mode 100644 index 0000000..3dbaf46 --- /dev/null +++ b/backend/api/rest/walletconnect_internal_test.go @@ -0,0 +1,79 @@ +package rest + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" +) + +func TestHandleWalletConnectConfig(t *testing.T) { + t.Setenv("WALLETCONNECT_PROJECT_ID", "test-project-id") + server := NewServer(nil, 138) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/walletconnect/config", nil) + rec := httptest.NewRecorder() + server.handleWalletConnectConfig(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", rec.Code) + } + + var payload map[string]interface{} + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { + t.Fatalf("decode response: %v", err) + } + if payload["projectId"] != "test-project-id" { + t.Fatalf("expected project id, got %#v", payload["projectId"]) + } + if payload["fallbackAuth"] != "/api/v1/auth/wallet" { + t.Fatalf("expected fallback auth path, got %#v", payload["fallbackAuth"]) + } +} + +func TestHandleWalletConnectConnectStub(t *testing.T) { + server := NewServer(nil, 138) + + req := httptest.NewRequest(http.MethodPost, "/api/v1/walletconnect/connect", strings.NewReader("{}")) + rec := httptest.NewRecorder() + server.handleWalletConnectConnect(rec, req) + + if rec.Code != http.StatusNotImplemented { + t.Fatalf("expected 501, got %d", rec.Code) + } + + var payload map[string]interface{} + if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil { + t.Fatalf("decode response: %v", err) + } + if payload["status"] != "stub" { + t.Fatalf("expected stub status, got %#v", payload["status"]) + } +} + +func TestHandleWalletConnectSessionStub(t *testing.T) { + server := NewServer(nil, 138) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/walletconnect/session/demo-session", nil) + rec := httptest.NewRecorder() + server.handleWalletConnectSession(rec, req) + + if rec.Code != http.StatusNotImplemented { + t.Fatalf("expected 501, got %d", rec.Code) + } +} + +func TestHandleWalletConnectRootIndex(t *testing.T) { + _ = os.Setenv("WALLETCONNECT_PROJECT_ID", "") + server := NewServer(nil, 138) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/walletconnect", nil) + rec := httptest.NewRecorder() + server.handleWalletConnectRoot(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", rec.Code) + } +} diff --git a/backend/wallet/walletconnect.go b/backend/wallet/walletconnect.go index ae087a3..f93a673 100644 --- a/backend/wallet/walletconnect.go +++ b/backend/wallet/walletconnect.go @@ -3,35 +3,122 @@ package wallet import ( "context" "fmt" + "os" + "strings" + "time" ) -// WalletConnect handles WalletConnect v2 integration +const ( + WalletConnectStatusStub = "stub" + WalletConnectStatusDisabled = "disabled" +) + +// Config describes the public WalletConnect v2 posture exposed to clients. +type Config struct { + Status string `json:"status"` + Enabled bool `json:"enabled"` + ProjectID string `json:"projectId"` + RelayURL string `json:"relayUrl"` + MetadataURL string `json:"metadataUrl"` + RequiredNamespaces []string `json:"requiredNamespaces"` + SupportedChains []int `json:"supportedChains"` + FallbackAuth string `json:"fallbackAuth"` + Message string `json:"message"` + UpdatedAt string `json:"updatedAt"` +} + +// ConnectResponse is returned while WalletConnect session bridging remains a stub. +type ConnectResponse struct { + Status string `json:"status"` + Enabled bool `json:"enabled"` + URI string `json:"uri,omitempty"` + SessionID string `json:"sessionId,omitempty"` + ExpiresAt string `json:"expiresAt,omitempty"` + FallbackAuth string `json:"fallbackAuth"` + Message string `json:"message"` +} + +// Session represents a wallet session snapshot for future WalletConnect integration. +type Session struct { + SessionID string `json:"sessionId"` + Address string `json:"address,omitempty"` + ChainID int `json:"chainId,omitempty"` + Connected bool `json:"connected"` + Status string `json:"status"` + Message string `json:"message"` +} + +// WalletConnect handles WalletConnect v2 integration posture for the explorer API. type WalletConnect struct { projectID string + relayURL string + chainID int } -// NewWalletConnect creates a new WalletConnect handler -func NewWalletConnect(projectID string) *WalletConnect { - return &WalletConnect{projectID: projectID} +// NewWalletConnect creates a WalletConnect handler using deployment env vars. +func NewWalletConnect(chainID int) *WalletConnect { + projectID := strings.TrimSpace(os.Getenv("WALLETCONNECT_PROJECT_ID")) + relayURL := strings.TrimSpace(os.Getenv("WALLETCONNECT_RELAY_URL")) + if relayURL == "" { + relayURL = "wss://relay.walletconnect.org" + } + return &WalletConnect{ + projectID: projectID, + relayURL: relayURL, + chainID: chainID, + } } -// Connect initiates a wallet connection -func (wc *WalletConnect) Connect(ctx context.Context) (string, error) { - // Implementation would use WalletConnect v2 SDK - // Returns connection URI for QR code display - return "", fmt.Errorf("not implemented - requires WalletConnect SDK") +func (wc *WalletConnect) enabled() bool { + return wc.projectID != "" } -// Session represents a wallet session -type Session struct { - Address string - ChainID int - Connected bool +// PublicConfig returns the read-only WalletConnect config surface for clients. +func (wc *WalletConnect) PublicConfig() Config { + status := WalletConnectStatusStub + if !wc.enabled() { + status = WalletConnectStatusDisabled + } + return Config{ + Status: status, + Enabled: wc.enabled(), + ProjectID: wc.projectID, + RelayURL: wc.relayURL, + MetadataURL: "/api/v1/walletconnect/metadata", + RequiredNamespaces: []string{"eip155"}, + SupportedChains: []int{wc.chainID, 1}, + FallbackAuth: "/api/v1/auth/wallet", + Message: wc.publicMessage(), + UpdatedAt: time.Now().UTC().Format(time.RFC3339), + } } -// GetSession gets current wallet session -func (wc *WalletConnect) GetSession(ctx context.Context, sessionID string) (*Session, error) { - // Implementation would retrieve session from WalletConnect - return nil, fmt.Errorf("not implemented") +func (wc *WalletConnect) publicMessage() string { + if wc.enabled() { + return "WalletConnect v2 config is published, but session bridging is still stubbed. Use browser wallet auth at /api/v1/auth/wallet until mobile QR sessions ship." + } + return "WalletConnect v2 is not configured. Set WALLETCONNECT_PROJECT_ID to publish relay config; browser wallet auth remains available at /api/v1/auth/wallet." } +// Connect initiates a wallet connection. Live QR sessions are not implemented yet. +func (wc *WalletConnect) Connect(_ context.Context) (*ConnectResponse, error) { + return &ConnectResponse{ + Status: WalletConnectStatusStub, + Enabled: wc.enabled(), + FallbackAuth: "/api/v1/auth/wallet", + Message: "WalletConnect session creation is stubbed. Use browser extension wallet auth until the relay bridge is enabled.", + }, fmt.Errorf("walletconnect session bridge not implemented") +} + +// GetSession gets a wallet session snapshot. Storage is not implemented yet. +func (wc *WalletConnect) GetSession(_ context.Context, sessionID string) (*Session, error) { + if strings.TrimSpace(sessionID) == "" { + return nil, fmt.Errorf("session id is required") + } + return &Session{ + SessionID: sessionID, + Connected: false, + Status: WalletConnectStatusStub, + Message: "WalletConnect session lookup is stubbed.", + }, fmt.Errorf("walletconnect session storage not implemented") +} diff --git a/deployment/common/nginx-next-frontend-proxy.conf b/deployment/common/nginx-next-frontend-proxy.conf index cad8fd1..94d5cbe 100644 --- a/deployment/common/nginx-next-frontend-proxy.conf +++ b/deployment/common/nginx-next-frontend-proxy.conf @@ -12,6 +12,12 @@ # Include these locations after those API/static locations and before any legacy # catch-all that serves /var/www/html/index.html directly. +location ^~ /legacy/ { + alias /var/www/html/legacy/; + try_files $uri $uri/ /legacy/index.html; + add_header Cache-Control "no-store, no-cache, must-revalidate" always; +} + location ^~ /_next/ { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; diff --git a/frontend/public/explorer-spa.js b/frontend/public/explorer-spa.js index 6a22d1e..67212d6 100644 --- a/frontend/public/explorer-spa.js +++ b/frontend/public/explorer-spa.js @@ -1,4 +1,7 @@ const API_BASE = '/api'; + if (typeof console !== 'undefined' && console.warn) { + console.warn('[DBIS Explorer] Legacy SPA bundle loaded from /legacy/. Use / for the current Next.js explorer UI.'); + } const EXPLORER_API_BASE = '/explorer-api'; const EXPLORER_API_V1_BASE = EXPLORER_API_BASE + '/v1'; const EXPLORER_TRACK1_BASE = EXPLORER_API_V1_BASE + '/track1'; diff --git a/frontend/public/index.html b/frontend/public/index.html index 7dbce06..1d0712a 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -1,1964 +1,43 @@ - - - - - - - - DBIS Explorer | Chain 138 Explorer by DBIS - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + DBIS Explorer + + - - - - - - - - - - - -
-
- -
-
- -
- -
-
-
-

Gas & Network

- -
-
-
-
Base fee
-
Block time
-
TPS
-
Failed
-
-
-
-
History (10 blocks)
-
-
-
Live chain health for Chain 138.
-
-
-
-
- -
-
-

Latest Blocks

- -
-
-
Loading blocks...
-
-
- -
-
-

Latest Transactions

- -
-
-
Loading transactions...
-
-
-
- - -
-
-
-

WETH9 & WETH10 Utilities

- -
- - -
- - MetaMask not connected - -
- -
- - - -
- - -
-
-
WETH9 Token
- - -
-
- ETH Balance: - - -
-
- WETH9 Balance: - - -
-
- -
-

Wrap ETH → WETH9

-
- -
- - -
-
- -
- -
-

Unwrap WETH9 → ETH

-
- -
- - -
-
- -
-
-
- - - - - - -
-
- - -
-
-
-

Bridge Monitoring

- -
-
-
Loading bridge data...
-
-
-
- - -
-
-
-

All Blocks

- -
-
-
Loading blocks...
-
-
-
- -
-
-
-

All Transactions

- -
-
-
Loading transactions...
-
-
-
- -
- -
-
- -

Block Details

-
-
-
-
- -
- -
-
- -

Transaction Details

-
-
-
-
- -
-
-
-

All Addresses

-
-
-
Loading addresses...
-
-
-
- -
- -
-
- -

Address Details

-
-
-
-
- -
- -
-
- -

Token

-
-
-
-
- -
- -
-
- -

NFT

-
-
-
-
- -
-
-
- -

Search results

-
-
-
-
- -
-
-
- -

Tokens

-
-
-
Loading...
-
-
-
- -
- -
-
- -

Watchlist

-
-
-

Add addresses from their detail page to track them here.

-
-
-
- -
- -
-
- -

Pools

-
- - -
-
-
-
Loading pools...
-
-
-
- -
- -
-
- -

Liquidity Access

-
- -
-
-
-
Loading liquidity access...
-
-
-
- -
- -
-
- -

Routes

-
- -
-
-
-
Loading routes...
-
-
-
- -
- -
-
- -

More

-
-
-
Loading more pages...
-
-
-
- -
-
-
- -

Analytics Dashboard

-
-
-
Loading analytics access...
-
-
-
- -
-
-
- -

Operator Panel

-
-
-
Loading operator access...
-
-
-
- -
- -
-
- -

System topology

-
- Command center - -
-
-
-
Loading graph…
-
-
-
- -
- -
-
- -

Institution console

-
-
-
Loading…
-
-
-
- -
- -
-
- -

Compare addresses

-
-
-
Loading…
-
-
-
-
- - - - +
+

DBIS Explorer

+

Redirecting to the current Chain 138 explorer…

+

Continue to explorer

+

Open legacy SPA fallback

+
+ diff --git a/frontend/public/legacy/index.html b/frontend/public/legacy/index.html new file mode 100644 index 0000000..7dbce06 --- /dev/null +++ b/frontend/public/legacy/index.html @@ -0,0 +1,1964 @@ + + + + + + + + + + + DBIS Explorer | Chain 138 Explorer by DBIS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+ +
+
+
+

Gas & Network

+ +
+
+
+
Base fee
+
Block time
+
TPS
+
Failed
+
+
+
+
History (10 blocks)
+
+
+
Live chain health for Chain 138.
+
+
+
+
+ +
+
+

Latest Blocks

+ +
+
+
Loading blocks...
+
+
+ +
+
+

Latest Transactions

+ +
+
+
Loading transactions...
+
+
+
+ + +
+
+
+

WETH9 & WETH10 Utilities

+ +
+ + +
+ + MetaMask not connected + +
+ +
+ + + +
+ + +
+
+
WETH9 Token
+ + +
+
+ ETH Balance: + - +
+
+ WETH9 Balance: + - +
+
+ +
+

Wrap ETH → WETH9

+
+ +
+ + +
+
+ +
+ +
+

Unwrap WETH9 → ETH

+
+ +
+ + +
+
+ +
+
+
+ + + + + + +
+
+ + +
+
+
+

Bridge Monitoring

+ +
+
+
Loading bridge data...
+
+
+
+ + +
+
+
+

All Blocks

+ +
+
+
Loading blocks...
+
+
+
+ +
+
+
+

All Transactions

+ +
+
+
Loading transactions...
+
+
+
+ +
+ +
+
+ +

Block Details

+
+
+
+
+ +
+ +
+
+ +

Transaction Details

+
+
+
+
+ +
+
+
+

All Addresses

+
+
+
Loading addresses...
+
+
+
+ +
+ +
+
+ +

Address Details

+
+
+
+
+ +
+ +
+
+ +

Token

+
+
+
+
+ +
+ +
+
+ +

NFT

+
+
+
+
+ +
+
+
+ +

Search results

+
+
+
+
+ +
+
+
+ +

Tokens

+
+
+
Loading...
+
+
+
+ +
+ +
+
+ +

Watchlist

+
+
+

Add addresses from their detail page to track them here.

+
+
+
+ +
+ +
+
+ +

Pools

+
+ + +
+
+
+
Loading pools...
+
+
+
+ +
+ +
+
+ +

Liquidity Access

+
+ +
+
+
+
Loading liquidity access...
+
+
+
+ +
+ +
+
+ +

Routes

+
+ +
+
+
+
Loading routes...
+
+
+
+ +
+ +
+
+ +

More

+
+
+
Loading more pages...
+
+
+
+ +
+
+
+ +

Analytics Dashboard

+
+
+
Loading analytics access...
+
+
+
+ +
+
+
+ +

Operator Panel

+
+
+
Loading operator access...
+
+
+
+ +
+ +
+
+ +

System topology

+
+ Command center + +
+
+
+
Loading graph…
+
+
+
+ +
+ +
+
+ +

Institution console

+
+
+
Loading…
+
+
+
+ +
+ +
+
+ +

Compare addresses

+
+
+
Loading…
+
+
+
+
+ + + + + + diff --git a/frontend/src/components/explorer/OperationsPageShell.tsx b/frontend/src/components/explorer/OperationsPageShell.tsx index c022e8d..39111d3 100644 --- a/frontend/src/components/explorer/OperationsPageShell.tsx +++ b/frontend/src/components/explorer/OperationsPageShell.tsx @@ -3,6 +3,7 @@ import { Card } from '@/libs/frontend-ui-primitives' import type { ExplorerFeaturePage } from '@/data/explorerOperations' import OperationsSurfaceNav from './OperationsSurfaceNav' import OperationsActionGrid from './OperationsActionGrid' +import OperationsTrackNote from './OperationsTrackNote' export type StatusTone = 'normal' | 'warning' | 'danger' @@ -108,6 +109,10 @@ export default function OperationsPageShell({ ) : null} + {page.accessTrack && page.accessNote ? ( + + ) : null} + {children} diff --git a/frontend/src/components/explorer/OperationsTrackNote.tsx b/frontend/src/components/explorer/OperationsTrackNote.tsx new file mode 100644 index 0000000..b5cb91d --- /dev/null +++ b/frontend/src/components/explorer/OperationsTrackNote.tsx @@ -0,0 +1,20 @@ +import { Card } from '@/libs/frontend-ui-primitives' + +export default function OperationsTrackNote({ + track, + note, + className, +}: { + track: number + note: string + className?: string +}) { + return ( + +
+ Track {track} public surface +
+

{note}

+
+ ) +} diff --git a/frontend/src/data/explorerOperations.ts b/frontend/src/data/explorerOperations.ts index 02bf44e..54ab0eb 100644 --- a/frontend/src/data/explorerOperations.ts +++ b/frontend/src/data/explorerOperations.ts @@ -11,6 +11,8 @@ export interface ExplorerFeaturePage { title: string description: string note?: string + accessTrack?: number + accessNote?: string actions: ExplorerFeatureAction[] } @@ -141,6 +143,9 @@ export const explorerFeaturePages = { description: 'Use the public explorer pages and live monitoring endpoints as the visible analytics surface for chain activity, recent blocks, and transaction flow.', note: sharedOperationsNote, + accessTrack: 3, + accessNote: + 'This page is the public Track 3 analytics surface. Wallet-authenticated Track 3 APIs remain available after browser wallet sign-in.', actions: [ { title: 'Blocks', @@ -175,6 +180,9 @@ export const explorerFeaturePages = { description: 'Expose the public operator surface for bridge checks, route validation, planner providers, liquidity entry points, and documentation.', note: sharedOperationsNote, + accessTrack: 4, + accessNote: + 'This page is the public Track 4 operator surface. Sensitive operator write APIs remain gated behind wallet auth and operator policy.', actions: [ { title: 'Bridge monitoring', @@ -334,6 +342,16 @@ export const explorerOperationsSurfaces: ExplorerOperationsSurface[] = [ label: 'System', description: 'Networks, RPC methods, and topology inventory.', }, + { + href: '/analytics', + label: 'Analytics', + description: 'Track 3 activity summaries, trends, and freshness context.', + }, + { + href: '/operator', + label: 'Operator', + description: 'Track 4 relay, route, and planner shortcuts.', + }, ] export const explorerPublicApiLinks = [ @@ -357,4 +375,9 @@ export const explorerPublicApiLinks = [ label: 'Wallet networks', description: 'Published chain metadata for wallet onboarding.', }, + { + href: '/explorer-api/v1/walletconnect/config', + label: 'WalletConnect config', + description: 'Published WalletConnect v2 posture and browser-auth fallback.', + }, ] as const diff --git a/frontend/src/services/api/walletConnect.ts b/frontend/src/services/api/walletConnect.ts new file mode 100644 index 0000000..d62de5e --- /dev/null +++ b/frontend/src/services/api/walletConnect.ts @@ -0,0 +1,24 @@ +import { fetchPublicJson } from '@/utils/publicExplorer' + +const WALLETCONNECT_CONFIG_PATH = '/explorer-api/v1/walletconnect/config' + +export interface WalletConnectConfigResponse { + status: 'stub' | 'disabled' | string + enabled: boolean + projectId: string + relayUrl: string + metadataUrl: string + requiredNamespaces: string[] + supportedChains: number[] + fallbackAuth: string + message: string + updatedAt: string +} + +export async function getWalletConnectConfig(): Promise { + try { + return await fetchPublicJson(WALLETCONNECT_CONFIG_PATH) + } catch { + return null + } +} diff --git a/scripts/deploy-next-frontend-to-vmid5000.sh b/scripts/deploy-next-frontend-to-vmid5000.sh index 2efb418..99ed1a0 100755 --- a/scripts/deploy-next-frontend-to-vmid5000.sh +++ b/scripts/deploy-next-frontend-to-vmid5000.sh @@ -35,6 +35,7 @@ STATIC_SYNC_FILES=( "favicon.ico" "apple-touch-icon.png" "explorer-spa.js" + "legacy/index.html" ) cleanup() { diff --git a/scripts/e2e-sprint-smoke.spec.ts b/scripts/e2e-sprint-smoke.spec.ts index 6f91666..8376a2a 100644 --- a/scripts/e2e-sprint-smoke.spec.ts +++ b/scripts/e2e-sprint-smoke.spec.ts @@ -50,4 +50,22 @@ test.describe('Explorer sprint smoke', () => { await expect(page.getByRole('contentinfo').getByText(/Public APIs/i)).toBeVisible({ timeout: 10000 }) await expect(page.getByRole('contentinfo').getByRole('link', { name: /Blockscout stats/i })).toBeVisible({ timeout: 10000 }) }) + + test('analytics page shows track 3 surface note', async ({ page }) => { + await page.goto(`${EXPLORER_URL}/analytics`, { waitUntil: 'domcontentloaded', timeout: 30000 }) + await expect(page.getByRole('heading', { name: /Analytics & Network Activity/i })).toBeVisible({ timeout: 15000 }) + await expect(page.getByText(/Track 3 public surface/i).first()).toBeVisible({ timeout: 10000 }) + }) + + test('operator page shows track 4 surface note', async ({ page }) => { + await page.goto(`${EXPLORER_URL}/operator`, { waitUntil: 'domcontentloaded', timeout: 30000 }) + await expect(page.getByRole('heading', { name: /^Operator Surface$/i })).toBeVisible({ timeout: 15000 }) + await expect(page.getByText(/Track 4 public surface/i).first()).toBeVisible({ timeout: 10000 }) + }) + + test('legacy SPA fallback loads', async ({ page }) => { + await page.goto(`${EXPLORER_URL}/legacy/index.html`, { waitUntil: 'domcontentloaded', timeout: 30000 }) + await expect(page).toHaveTitle(/DBIS Explorer/i) + await expect(page.getByRole('heading', { name: /Latest Blocks/i })).toBeVisible({ timeout: 15000 }) + }) }) diff --git a/scripts/verify/check-explorer-dual-domain-parity.sh b/scripts/verify/check-explorer-dual-domain-parity.sh new file mode 100755 index 0000000..150dfeb --- /dev/null +++ b/scripts/verify/check-explorer-dual-domain-parity.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +PRIMARY_DOMAIN="${PRIMARY_DOMAIN:-https://explorer.d-bis.org}" +COMPANION_DOMAIN="${COMPANION_DOMAIN:-https://blockscout.defi-oracle.io}" + +paths=( + "/" + "/wallet" + "/operations" + "/bridge" + "/api/v2/stats" + "/explorer-api/v1/track1/bridge/status" + "/token-aggregation/api/v1/routes/matrix?includeNonLive=true" + "/explorer-api/v1/walletconnect/config" +) + +failures=0 + +check_path() { + local domain="$1" + local path="$2" + local code + code="$(curl -ksS -o /dev/null -w '%{http_code}' --max-time 20 "${domain}${path}")" + if [[ "$code" =~ ^(200|301|302|307|308)$ ]]; then + printf 'OK %3s %s%s\n' "$code" "$domain" "$path" + else + printf 'FAIL %3s %s%s\n' "$code" "$domain" "$path" + failures=$((failures + 1)) + fi +} + +echo "Checking explorer dual-domain parity" +echo "Primary: $PRIMARY_DOMAIN" +echo "Companion: $COMPANION_DOMAIN" +echo + +for path in "${paths[@]}"; do + check_path "$PRIMARY_DOMAIN" "$path" + check_path "$COMPANION_DOMAIN" "$path" +done + +echo +if (( failures > 0 )); then + echo "Dual-domain parity check failed ($failures mismatches/non-200)." >&2 + exit 1 +fi + +echo "Dual-domain parity check passed."