feat: explorer API, wallet, CCIP scripts, and config refresh
- Backend REST/gateway/track routes, analytics, Blockscout proxy paths. - Frontend wallet and liquidity surfaces; MetaMask token list alignment. - Deployment docs, verification scripts, address inventory updates. Check: go build ./... under backend/ (pass). Made-with: Cursor
This commit is contained in:
218
backend/api/rest/mission_control_test.go
Normal file
218
backend/api/rest/mission_control_test.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func resetMissionControlTestGlobals() {
|
||||
liquidityPoolsCache = sync.Map{}
|
||||
registryOnce = sync.Once{}
|
||||
registryAddrToKey = nil
|
||||
registryLoadErr = nil
|
||||
}
|
||||
|
||||
func TestHandleMissionControlLiquidityTokenPathRequiresEnv(t *testing.T) {
|
||||
resetMissionControlTestGlobals()
|
||||
t.Setenv("TOKEN_AGGREGATION_BASE_URL", "")
|
||||
t.Setenv("TOKEN_AGGREGATION_URL", "")
|
||||
|
||||
s := NewServer(nil, 138)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/mission-control/liquidity/token/0x93E66202A11B1772E55407B32B44e5Cd8eda7f22/pools", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.handleMissionControlLiquidityTokenPath(w, req)
|
||||
|
||||
require.Equal(t, http.StatusServiceUnavailable, w.Code)
|
||||
require.Contains(t, w.Body.String(), "TOKEN_AGGREGATION_BASE_URL not configured")
|
||||
}
|
||||
|
||||
func TestHandleMissionControlLiquidityTokenPathCachesSuccess(t *testing.T) {
|
||||
resetMissionControlTestGlobals()
|
||||
|
||||
var hitCount int
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
hitCount++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"data":{"count":1,"pools":[]}}`))
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
t.Setenv("TOKEN_AGGREGATION_BASE_URL", upstream.URL)
|
||||
t.Setenv("CHAIN_ID", "138")
|
||||
|
||||
s := NewServer(nil, 138)
|
||||
path := "/api/v1/mission-control/liquidity/token/0x93E66202A11B1772E55407B32B44e5Cd8eda7f22/pools"
|
||||
|
||||
w1 := httptest.NewRecorder()
|
||||
s.handleMissionControlLiquidityTokenPath(w1, httptest.NewRequest(http.MethodGet, path, nil))
|
||||
require.Equal(t, http.StatusOK, w1.Code)
|
||||
require.Equal(t, "miss", w1.Header().Get("X-Mission-Control-Cache"))
|
||||
|
||||
w2 := httptest.NewRecorder()
|
||||
s.handleMissionControlLiquidityTokenPath(w2, httptest.NewRequest(http.MethodGet, path, nil))
|
||||
require.Equal(t, http.StatusOK, w2.Code)
|
||||
require.Equal(t, "hit", w2.Header().Get("X-Mission-Control-Cache"))
|
||||
|
||||
require.Equal(t, 1, hitCount, "second request should be served from the in-memory cache")
|
||||
require.JSONEq(t, w1.Body.String(), w2.Body.String())
|
||||
}
|
||||
|
||||
func TestHandleMissionControlLiquidityTokenPathBypassesCacheWhenRequested(t *testing.T) {
|
||||
resetMissionControlTestGlobals()
|
||||
|
||||
var hitCount int
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
hitCount++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"data":{"count":1,"pools":[]}}`))
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
t.Setenv("TOKEN_AGGREGATION_BASE_URL", upstream.URL)
|
||||
t.Setenv("CHAIN_ID", "138")
|
||||
|
||||
s := NewServer(nil, 138)
|
||||
path := "/api/v1/mission-control/liquidity/token/0x93E66202A11B1772E55407B32B44e5Cd8eda7f22/pools"
|
||||
|
||||
w1 := httptest.NewRecorder()
|
||||
s.handleMissionControlLiquidityTokenPath(w1, httptest.NewRequest(http.MethodGet, path, nil))
|
||||
require.Equal(t, http.StatusOK, w1.Code)
|
||||
require.Equal(t, "miss", w1.Header().Get("X-Mission-Control-Cache"))
|
||||
|
||||
req2 := httptest.NewRequest(http.MethodGet, path+"?refresh=1", nil)
|
||||
req2.Header.Set("Cache-Control", "no-cache")
|
||||
w2 := httptest.NewRecorder()
|
||||
s.handleMissionControlLiquidityTokenPath(w2, req2)
|
||||
require.Equal(t, http.StatusOK, w2.Code)
|
||||
require.Equal(t, "bypass", w2.Header().Get("X-Mission-Control-Cache"))
|
||||
|
||||
require.Equal(t, 2, hitCount, "refresh=1 should force a fresh upstream read")
|
||||
}
|
||||
|
||||
func TestHandleMissionControlBridgeTraceLabelsFromRegistry(t *testing.T) {
|
||||
resetMissionControlTestGlobals()
|
||||
|
||||
fromAddr := "0x1111111111111111111111111111111111111111"
|
||||
toAddr := "0x2222222222222222222222222222222222222222"
|
||||
|
||||
registryJSON := `{
|
||||
"chains": {
|
||||
"138": {
|
||||
"contracts": {
|
||||
"CHAIN138_SOURCE_BRIDGE": "` + fromAddr + `",
|
||||
"CHAIN138_DEST_BRIDGE": "` + toAddr + `"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
registryPath := filepath.Join(t.TempDir(), "smart-contracts-master.json")
|
||||
require.NoError(t, os.WriteFile(registryPath, []byte(registryJSON), 0o644))
|
||||
|
||||
blockscout := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/api/v2/transactions/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", r.URL.Path)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{
|
||||
"from": {"hash":"` + fromAddr + `"},
|
||||
"to": {"hash":"` + toAddr + `"}
|
||||
}`))
|
||||
}))
|
||||
defer blockscout.Close()
|
||||
|
||||
t.Setenv("SMART_CONTRACTS_MASTER_JSON", registryPath)
|
||||
t.Setenv("BLOCKSCOUT_INTERNAL_URL", blockscout.URL)
|
||||
t.Setenv("EXPLORER_PUBLIC_BASE", "https://explorer.example.org")
|
||||
|
||||
s := NewServer(nil, 138)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/mission-control/bridge/trace?tx=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.HandleMissionControlBridgeTrace(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
var out struct {
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out))
|
||||
require.Equal(t, strings.ToLower(fromAddr), out.Data["from"])
|
||||
require.Equal(t, strings.ToLower(toAddr), out.Data["to"])
|
||||
require.Equal(t, "CHAIN138_SOURCE_BRIDGE", out.Data["from_registry"])
|
||||
require.Equal(t, "CHAIN138_DEST_BRIDGE", out.Data["to_registry"])
|
||||
require.Equal(t, "https://explorer.example.org/tx/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", out.Data["blockscout_url"])
|
||||
}
|
||||
|
||||
func TestHandleMissionControlBridgeTraceFallsBackToAddressInventoryLabels(t *testing.T) {
|
||||
resetMissionControlTestGlobals()
|
||||
|
||||
fromAddr := "0x4A666F96fC8764181194447A7dFdb7d471b301C8"
|
||||
toAddr := "0x152ed3e9912161b76bdfd368d0c84b7c31c10de7"
|
||||
|
||||
tempDir := t.TempDir()
|
||||
registryPath := filepath.Join(tempDir, "smart-contracts-master.json")
|
||||
inventoryPath := filepath.Join(tempDir, "address-inventory.json")
|
||||
|
||||
require.NoError(t, os.WriteFile(registryPath, []byte(`{
|
||||
"chains": {
|
||||
"138": {
|
||||
"contracts": {
|
||||
"CCIP_Router": "0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`), 0o644))
|
||||
require.NoError(t, os.WriteFile(inventoryPath, []byte(`{
|
||||
"inventory": {
|
||||
"DEPLOYER_ADMIN_138": "`+fromAddr+`",
|
||||
"CW_L1_BRIDGE_CHAIN138": "`+toAddr+`"
|
||||
}
|
||||
}`), 0o644))
|
||||
|
||||
blockscout := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{
|
||||
"from": {"hash":"` + fromAddr + `"},
|
||||
"to": {"hash":"` + toAddr + `"}
|
||||
}`))
|
||||
}))
|
||||
defer blockscout.Close()
|
||||
|
||||
t.Setenv("SMART_CONTRACTS_MASTER_JSON", registryPath)
|
||||
t.Setenv("EXPLORER_ADDRESS_INVENTORY_FILE", inventoryPath)
|
||||
t.Setenv("BLOCKSCOUT_INTERNAL_URL", blockscout.URL)
|
||||
|
||||
s := NewServer(nil, 138)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/mission-control/bridge/trace?tx=0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.HandleMissionControlBridgeTrace(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
var out struct {
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out))
|
||||
require.Equal(t, strings.ToLower(fromAddr), out.Data["from"])
|
||||
require.Equal(t, strings.ToLower(toAddr), out.Data["to"])
|
||||
require.Equal(t, "DEPLOYER_ADMIN_138", out.Data["from_registry"])
|
||||
require.Equal(t, "CW_L1_BRIDGE_CHAIN138", out.Data["to_registry"])
|
||||
}
|
||||
|
||||
func TestHandleMissionControlBridgeTraceRejectsBadHash(t *testing.T) {
|
||||
resetMissionControlTestGlobals()
|
||||
s := NewServer(nil, 138)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/mission-control/bridge/trace?tx=not-a-tx", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.HandleMissionControlBridgeTrace(w, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
require.Contains(t, w.Body.String(), "invalid transaction hash")
|
||||
}
|
||||
Reference in New Issue
Block a user