Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions responses.jsonl

Large diffs are not rendered by default.

23 changes: 17 additions & 6 deletions server/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -641,15 +641,22 @@

// handleSpotMetaAndAssetCtxs returns mock spot trading metadata
func (h *Handler) handleSpotMetaAndAssetCtxs() SpotMetaAndAssetCtxs {
dogeFullName := "Dogecoin"

return SpotMetaAndAssetCtxs{
Tokens: []SpotToken{
{Name: "USDC", SzDecimals: 6, WeiDecimals: 6, Index: 0, TokenId: "0x1", IsCanonical: true},
{Name: "BTC", SzDecimals: 8, WeiDecimals: 8, Index: 1, TokenId: "0x2", IsCanonical: true},
{Name: "ETH", SzDecimals: 18, WeiDecimals: 18, Index: 2, TokenId: "0x3", IsCanonical: true},
{Name: "USDC", SzDecimals: 6, WeiDecimals: 6, Index: 0, TokenId: "0x1", IsCanonical: true, DeployerTradingFeeShare: "0.0"},
{Name: "BTC", SzDecimals: 8, WeiDecimals: 8, Index: 1, TokenId: "0x2", IsCanonical: true, DeployerTradingFeeShare: "0.0"},
{Name: "ETH", SzDecimals: 18, WeiDecimals: 18, Index: 2, TokenId: "0x3", IsCanonical: true, DeployerTradingFeeShare: "0.0"},
{Name: "DOGE", SzDecimals: 1, WeiDecimals: 8, Index: 342, TokenId: "0xd0ged0ged0ged0ged0ged0ged0ged0ge", IsCanonical: true, FullName: &dogeFullName, DeployerTradingFeeShare: "0.0"},
},
Universe: []SpotUniverse{
{Tokens: []int{1, 0}, Name: "BTC/USDC", Index: 0},
{Tokens: []int{2, 0}, Name: "ETH/USDC", Index: 1},
{Tokens: []int{342, 0}, Name: "DOGE/USDC", Index: 210, IsCanonical: true},

Check failure on line 656 in server/handlers.go

View workflow job for this annotation

GitHub Actions / test

unknown field IsCanonical in struct literal of type SpotUniverse
},
AssetCtxs: []SpotAssetCtx{
{PrevDayPx: "0.2168", DayNtlVlm: "12500000.0", MarkPx: "0.2168", MidPx: "0.2168", CirculatingSupply: "143000000000.0", Coin: "DOGE", TotalSupply: "143000000000.0", DayBaseVlm: "57500000.0"},
},
}
}
Expand Down Expand Up @@ -687,15 +694,19 @@

// handleSpotMeta returns mock spot trading metadata (same structure as spotMetaAndAssetCtxs)
func (h *Handler) handleSpotMeta() SpotMeta {
dogeFullName := "Dogecoin"

return SpotMeta{
Tokens: []SpotToken{
{Name: "USDC", SzDecimals: 6, WeiDecimals: 6, Index: 0, TokenId: "0x1", IsCanonical: true},
{Name: "BTC", SzDecimals: 8, WeiDecimals: 8, Index: 1, TokenId: "0x2", IsCanonical: true},
{Name: "ETH", SzDecimals: 18, WeiDecimals: 18, Index: 2, TokenId: "0x3", IsCanonical: true},
{Name: "USDC", SzDecimals: 6, WeiDecimals: 6, Index: 0, TokenId: "0x1", IsCanonical: true, DeployerTradingFeeShare: "0.0"},
{Name: "BTC", SzDecimals: 8, WeiDecimals: 8, Index: 1, TokenId: "0x2", IsCanonical: true, DeployerTradingFeeShare: "0.0"},
{Name: "ETH", SzDecimals: 18, WeiDecimals: 18, Index: 2, TokenId: "0x3", IsCanonical: true, DeployerTradingFeeShare: "0.0"},
{Name: "DOGE", SzDecimals: 1, WeiDecimals: 8, Index: 342, TokenId: "0xd0ged0ged0ged0ged0ged0ged0ged0ge", IsCanonical: true, FullName: &dogeFullName, DeployerTradingFeeShare: "0.0"},
},
Universe: []SpotUniverse{
{Tokens: []int{1, 0}, Name: "BTC/USDC", Index: 0},
{Tokens: []int{2, 0}, Name: "ETH/USDC", Index: 1},
{Tokens: []int{342, 0}, Name: "DOGE/USDC", Index: 210, IsCanonical: true},

Check failure on line 709 in server/handlers.go

View workflow job for this annotation

GitHub Actions / test

unknown field IsCanonical in struct literal of type SpotUniverse
},
}
}
Expand Down
69 changes: 69 additions & 0 deletions server/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,22 +306,91 @@ func TestHandleInfo_SpotMeta(t *testing.T) {

// Check for expected tokens
foundUSDC := false
foundDOGE := false
for _, token := range response.Tokens {
if token.Name == "USDC" {
foundUSDC = true
if token.SzDecimals != 6 {
t.Errorf("USDC: expected szDecimals=6, got %d", token.SzDecimals)
}
}
if token.Name == "DOGE" {
foundDOGE = true
if token.SzDecimals != 1 {
t.Errorf("DOGE: expected szDecimals=1, got %d", token.SzDecimals)
}
}
}

if !foundUSDC {
t.Error("Expected to find USDC in tokens")
}

if !foundDOGE {
t.Error("Expected to find DOGE in tokens")
}

t.Logf("✓ SpotMeta response: %d tokens, %d trading pairs", len(response.Tokens), len(response.Universe))
}

// TestHandleInfo_SpotMetaAndAssetCtxs validates the combined spot metadata and asset contexts
func TestHandleInfo_SpotMetaAndAssetCtxs(t *testing.T) {
handler := NewHandler()

reqBody := InfoRequest{Type: "spotMetaAndAssetCtxs"}
bodyBytes, err := json.Marshal(reqBody)
if err != nil {
t.Fatalf("Failed to marshal request: %v", err)
}

req := httptest.NewRequest(http.MethodPost, "/info", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()

handler.HandleInfo(w, req)

if w.Code != http.StatusOK {
t.Fatalf("Expected status 200, got %d (body: %s)", w.Code, w.Body.String())
}

var response SpotMetaAndAssetCtxs
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
t.Fatalf("Failed to unmarshal response: %v (body: %s)", err, w.Body.String())
}

if len(response.Tokens) == 0 {
t.Fatal("Expected tokens to be present")
}

if len(response.AssetCtxs) == 0 {
t.Fatal("Expected assetCtxs to be present")
}

foundDOGE := false
for _, token := range response.Tokens {
if token.Name == "DOGE" {
foundDOGE = true
break
}
}

if !foundDOGE {
t.Fatal("Expected DOGE token in spot metadata")
}

foundDOGECtx := false
for _, ctx := range response.AssetCtxs {
if ctx.Coin == "DOGE" {
foundDOGECtx = true
break
}
}

if !foundDOGECtx {
t.Fatal("Expected DOGE asset context in response")
}
}

// TestHandleInfo_MetaAndAssetCtxs tests the existing metaAndAssetCtxs endpoint
func TestHandleInfo_MetaAndAssetCtxs(t *testing.T) {
handler := NewHandler()
Expand Down
87 changes: 79 additions & 8 deletions server/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,15 @@ func (m *MetaAndAssetCtxs) UnmarshalJSON(data []byte) error {

// SpotToken represents a spot trading token
type SpotToken struct {
Name string `json:"name"`
SzDecimals int `json:"szDecimals"`
WeiDecimals int `json:"weiDecimals"`
Index int `json:"index"`
TokenId string `json:"tokenId"`
IsCanonical bool `json:"isCanonical"`
Name string `json:"name"`
SzDecimals int `json:"szDecimals"`
WeiDecimals int `json:"weiDecimals"`
Index int `json:"index"`
TokenId string `json:"tokenId"`
IsCanonical bool `json:"isCanonical"`
EvmContract *string `json:"evmContract,omitempty"`
FullName *string `json:"fullName,omitempty"`
DeployerTradingFeeShare string `json:"deployerTradingFeeShare,omitempty"`
}

// SpotUniverse represents a spot trading pair
Expand All @@ -234,8 +237,76 @@ type SpotUniverse struct {

// SpotMetaAndAssetCtxs is the response for spot metadata queries
type SpotMetaAndAssetCtxs struct {
Tokens []SpotToken `json:"tokens"`
Universe []SpotUniverse `json:"universe"`
Tokens []SpotToken `json:"tokens"`
Universe []SpotUniverse `json:"universe"`
AssetCtxs []SpotAssetCtx `json:"assetCtxs"`
}

// SpotAssetCtx represents asset context data for spot tokens
type SpotAssetCtx struct {
PrevDayPx string `json:"prevDayPx"`
DayNtlVlm string `json:"dayNtlVlm"`
MarkPx string `json:"markPx"`
MidPx string `json:"midPx"`
CirculatingSupply string `json:"circulatingSupply"`
Coin string `json:"coin"`
TotalSupply string `json:"totalSupply"`
DayBaseVlm string `json:"dayBaseVlm"`
}

// MarshalJSON implements custom JSON marshaling for SpotMetaAndAssetCtxs to
// match the array response returned by the real API: [meta, assetCtxs].
func (m SpotMetaAndAssetCtxs) MarshalJSON() ([]byte, error) {
meta := struct {
Tokens []SpotToken `json:"tokens"`
Universe []SpotUniverse `json:"universe"`
}{Tokens: m.Tokens, Universe: m.Universe}

return json.Marshal([]interface{}{meta, m.AssetCtxs})
}

// UnmarshalJSON accepts both the array representation returned by the real API
// and a map representation for flexibility in tests.
func (m *SpotMetaAndAssetCtxs) UnmarshalJSON(data []byte) error {
var arrayForm []json.RawMessage
if err := json.Unmarshal(data, &arrayForm); err == nil {
if len(arrayForm) != 2 {
return fmt.Errorf("expected 2 elements in spotMetaAndAssetCtxs response, got %d", len(arrayForm))
}

var meta struct {
Tokens []SpotToken `json:"tokens"`
Universe []SpotUniverse `json:"universe"`
}
if err := json.Unmarshal(arrayForm[0], &meta); err != nil {
return err
}

var assetCtxs []SpotAssetCtx
if err := json.Unmarshal(arrayForm[1], &assetCtxs); err != nil {
return err
}

m.Tokens = meta.Tokens
m.Universe = meta.Universe
m.AssetCtxs = assetCtxs
return nil
}

// Fallback: try object form
var objectForm struct {
Tokens []SpotToken `json:"tokens"`
Universe []SpotUniverse `json:"universe"`
AssetCtxs []SpotAssetCtx `json:"assetCtxs"`
}
if err := json.Unmarshal(data, &objectForm); err != nil {
return err
}

m.Tokens = objectForm.Tokens
m.Universe = objectForm.Universe
m.AssetCtxs = objectForm.AssetCtxs
return nil
}

// Meta is the response for the "meta" info type (simpler than metaAndAssetCtxs)
Expand Down
Loading