test: add Lighter API authentication tests and diagnostic tools

This commit is contained in:
tinkle-community
2026-01-13 14:03:28 +08:00
parent 2bc45827f3
commit 937527281e
3 changed files with 822 additions and 0 deletions

233
cmd/lighter_test/main.go Normal file
View File

@@ -0,0 +1,233 @@
// Lighter API Authentication Test Tool
// Usage: go run cmd/lighter_test/main.go -wallet=0x... -apikey=... [-testnet]
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
lighterClient "github.com/elliottech/lighter-go/client"
lighterHTTP "github.com/elliottech/lighter-go/client/http"
)
func main() {
// Parse command line flags
walletAddr := flag.String("wallet", "", "Ethereum wallet address")
apiKeyPrivateKey := flag.String("apikey", "", "API key private key (40 bytes hex)")
apiKeyIndex := flag.Int("apikeyindex", 0, "API key index (0-255)")
testnet := flag.Bool("testnet", false, "Use testnet instead of mainnet")
flag.Parse()
if *walletAddr == "" || *apiKeyPrivateKey == "" {
fmt.Println("Usage: go run cmd/lighter_test/main.go -wallet=0x... -apikey=...")
fmt.Println("Options:")
fmt.Println(" -wallet Ethereum wallet address (required)")
fmt.Println(" -apikey API key private key, 40 bytes hex (required)")
fmt.Println(" -apikeyindex API key index, 0-255 (default: 0)")
fmt.Println(" -testnet Use testnet instead of mainnet")
os.Exit(1)
}
fmt.Println("=== Lighter API Authentication Test ===")
fmt.Printf("Wallet: %s\n", *walletAddr)
fmt.Printf("API Key Index: %d\n", *apiKeyIndex)
fmt.Printf("Testnet: %v\n", *testnet)
fmt.Println()
// Determine base URL
baseURL := "https://mainnet.zklighter.elliot.ai"
chainID := uint32(304)
if *testnet {
baseURL = "https://testnet.zklighter.elliot.ai"
chainID = uint32(300)
}
// Create HTTP client
httpClient := lighterHTTP.NewClient(baseURL)
client := &http.Client{Timeout: 30 * time.Second}
// Step 1: Get account info
fmt.Println("Step 1: Getting account info...")
accountInfo, err := getAccountByL1Address(client, baseURL, *walletAddr)
if err != nil {
fmt.Printf("ERROR: Failed to get account info: %v\n", err)
os.Exit(1)
}
fmt.Printf("SUCCESS: Account index = %d\n\n", accountInfo.AccountIndex)
// Step 2: Create TxClient
fmt.Println("Step 2: Creating TxClient...")
txClient, err := lighterClient.NewTxClient(
httpClient,
*apiKeyPrivateKey,
accountInfo.AccountIndex,
uint8(*apiKeyIndex),
chainID,
)
if err != nil {
fmt.Printf("ERROR: Failed to create TxClient: %v\n", err)
os.Exit(1)
}
fmt.Println("SUCCESS: TxClient created\n")
// Step 3: Generate auth token
fmt.Println("Step 3: Generating auth token...")
deadline := time.Now().Add(1 * time.Hour)
authToken, err := txClient.GetAuthToken(deadline)
if err != nil {
fmt.Printf("ERROR: Failed to generate auth token: %v\n", err)
os.Exit(1)
}
fmt.Printf("SUCCESS: Auth token generated\n")
fmt.Printf("Token: %s...\n", authToken[:min(50, len(authToken))])
fmt.Printf("Valid until: %s\n\n", deadline.Format(time.RFC3339))
// Step 4: Test GetActiveOrders API with auth query parameter
fmt.Println("Step 4: Testing GetActiveOrders API...")
encodedAuth := url.QueryEscape(authToken)
endpoint := fmt.Sprintf("%s/api/v1/accountActiveOrders?account_index=%d&market_id=0&auth=%s",
baseURL, accountInfo.AccountIndex, encodedAuth)
fmt.Printf("Endpoint: %s...\n", endpoint[:min(120, len(endpoint))])
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
fmt.Printf("ERROR: Failed to create request: %v\n", err)
os.Exit(1)
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
fmt.Printf("ERROR: Request failed: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Status: %d\n", resp.StatusCode)
fmt.Printf("Response: %s\n\n", string(body))
// Parse response
var apiResp struct {
Code int `json:"code"`
Message string `json:"message"`
Orders []struct {
OrderID string `json:"order_id"`
Side string `json:"side"`
Type string `json:"type"`
Price string `json:"price"`
} `json:"orders"`
}
if err := json.Unmarshal(body, &apiResp); err != nil {
fmt.Printf("ERROR: Failed to parse response: %v\n", err)
os.Exit(1)
}
if apiResp.Code != 200 {
fmt.Printf("API ERROR: code=%d, message=%s\n", apiResp.Code, apiResp.Message)
fmt.Println("\n=== DIAGNOSTIC INFO ===")
fmt.Println("If you see 'invalid signature', possible causes:")
fmt.Println("1. API key is not registered on-chain")
fmt.Println("2. API key private key is incorrect")
fmt.Println("3. API key index is wrong")
fmt.Println("4. Account index mismatch")
fmt.Println("\nTo fix:")
fmt.Println("- Go to app.lighter.xyz and register/verify your API key")
fmt.Println("- Make sure you're using the correct API key private key")
os.Exit(1)
}
fmt.Printf("SUCCESS: Retrieved %d orders\n", len(apiResp.Orders))
for i, order := range apiResp.Orders {
if i >= 5 {
fmt.Printf("... and %d more orders\n", len(apiResp.Orders)-5)
break
}
fmt.Printf(" Order %s: %s %s @ %s\n", order.OrderID, order.Side, order.Type, order.Price)
}
// Step 5: Test GetTrades API (also needs auth)
fmt.Println("\nStep 5: Testing GetTrades API...")
tradesEndpoint := fmt.Sprintf("%s/api/v1/trades?account_index=%d&sort_by=timestamp&sort_dir=desc&limit=5&auth=%s",
baseURL, accountInfo.AccountIndex, encodedAuth)
tradesReq, _ := http.NewRequest("GET", tradesEndpoint, nil)
tradesResp, err := client.Do(tradesReq)
if err != nil {
fmt.Printf("ERROR: Trades request failed: %v\n", err)
} else {
defer tradesResp.Body.Close()
tradesBody, _ := io.ReadAll(tradesResp.Body)
fmt.Printf("Status: %d\n", tradesResp.StatusCode)
if tradesResp.StatusCode == 200 {
fmt.Println("SUCCESS: GetTrades API working")
} else {
fmt.Printf("Response: %s\n", string(tradesBody))
}
}
fmt.Println("\n=== ALL TESTS PASSED ===")
}
// AccountInfo represents Lighter account information
type AccountInfo struct {
AccountIndex int64 `json:"account_index"`
L1Address string `json:"l1_address"`
}
// getAccountByL1Address gets account info by L1 wallet address
func getAccountByL1Address(client *http.Client, baseURL, walletAddr string) (*AccountInfo, error) {
endpoint := fmt.Sprintf("%s/api/v1/account?by=l1_address&value=%s", baseURL, walletAddr)
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
req = req.WithContext(ctx)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// Parse response - can be in "accounts" or "sub_accounts" field
var apiResp struct {
Code int `json:"code"`
Message string `json:"message"`
Accounts []AccountInfo `json:"accounts"`
SubAccounts []AccountInfo `json:"sub_accounts"`
}
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("failed to parse response: %w, body: %s", err, string(body))
}
// Check main accounts first
if len(apiResp.Accounts) > 0 {
return &apiResp.Accounts[0], nil
}
// Check sub-accounts
if len(apiResp.SubAccounts) > 0 {
return &apiResp.SubAccounts[0], nil
}
return nil, fmt.Errorf("no account found for address: %s", walletAddr)
}

View File

@@ -0,0 +1,168 @@
//go:build ignore
// Test script to verify Lighter API authentication
// Run: go run scripts/test_lighter_orders.go
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
lighterClient "github.com/elliottech/lighter-go/client"
lighterHTTP "github.com/elliottech/lighter-go/client/http"
)
func main() {
// Configuration - update these values
walletAddr := os.Getenv("LIGHTER_WALLET")
apiKeyPrivateKey := os.Getenv("LIGHTER_API_KEY")
if walletAddr == "" || apiKeyPrivateKey == "" {
fmt.Println("Usage: LIGHTER_WALLET=0x... LIGHTER_API_KEY=... go run scripts/test_lighter_orders.go")
fmt.Println("Environment variables required:")
fmt.Println(" LIGHTER_WALLET - Ethereum wallet address")
fmt.Println(" LIGHTER_API_KEY - API key private key (40 bytes hex)")
os.Exit(1)
}
fmt.Println("=== Lighter API Test ===")
fmt.Printf("Wallet: %s\n\n", walletAddr)
baseURL := "https://mainnet.zklighter.elliot.ai"
chainID := uint32(304)
client := &http.Client{Timeout: 30 * time.Second}
// Step 1: Get account info (no auth required)
fmt.Println("1. Getting account info...")
accountIndex, err := getAccountIndex(client, baseURL, walletAddr)
if err != nil {
fmt.Printf(" FAILED: %v\n", err)
os.Exit(1)
}
fmt.Printf(" OK: account_index = %d\n\n", accountIndex)
// Step 2: Create TxClient and generate auth token
fmt.Println("2. Creating TxClient and generating auth token...")
httpClient := lighterHTTP.NewClient(baseURL)
txClient, err := lighterClient.NewTxClient(httpClient, apiKeyPrivateKey, accountIndex, 0, chainID)
if err != nil {
fmt.Printf(" FAILED: %v\n", err)
os.Exit(1)
}
authToken, err := txClient.GetAuthToken(time.Now().Add(1 * time.Hour))
if err != nil {
fmt.Printf(" FAILED: %v\n", err)
os.Exit(1)
}
fmt.Printf(" OK: auth token generated\n\n")
// Step 3: Test GetActiveOrders with auth query parameter (NEW method)
fmt.Println("3. Testing GetActiveOrders with auth query parameter (FIXED)...")
encodedAuth := url.QueryEscape(authToken)
endpoint := fmt.Sprintf("%s/api/v1/accountActiveOrders?account_index=%d&market_id=0&auth=%s",
baseURL, accountIndex, encodedAuth)
resp, err := client.Get(endpoint)
if err != nil {
fmt.Printf(" FAILED: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result map[string]interface{}
json.Unmarshal(body, &result)
if code, ok := result["code"].(float64); ok && code == 200 {
orders := result["orders"].([]interface{})
fmt.Printf(" OK: Retrieved %d orders\n", len(orders))
if len(orders) > 0 {
fmt.Println(" Sample orders:")
for i, o := range orders {
if i >= 3 {
fmt.Printf(" ... and %d more\n", len(orders)-3)
break
}
order := o.(map[string]interface{})
fmt.Printf(" - ID: %v, Price: %v, Side: %v\n",
order["order_id"], order["price"], order["is_ask"])
}
}
} else {
fmt.Printf(" FAILED: %s\n", string(body))
fmt.Println("\n Possible causes:")
fmt.Println(" - API key not registered on-chain")
fmt.Println(" - API key private key incorrect")
fmt.Println(" - Account index mismatch")
os.Exit(1)
}
// Step 4: Test GetActiveOrders with Authorization header (OLD method - for comparison)
fmt.Println("\n4. Testing GetActiveOrders with Authorization header (OLD method)...")
endpoint2 := fmt.Sprintf("%s/api/v1/accountActiveOrders?account_index=%d&market_id=0",
baseURL, accountIndex)
req, _ := http.NewRequest("GET", endpoint2, nil)
req.Header.Set("Authorization", authToken)
req.Header.Set("Content-Type", "application/json")
resp2, err := client.Do(req)
if err != nil {
fmt.Printf(" FAILED: %v\n", err)
} else {
defer resp2.Body.Close()
body2, _ := io.ReadAll(resp2.Body)
var result2 map[string]interface{}
json.Unmarshal(body2, &result2)
if code, ok := result2["code"].(float64); ok && code == 200 {
orders := result2["orders"].([]interface{})
fmt.Printf(" OK: Retrieved %d orders (both methods work!)\n", len(orders))
} else {
fmt.Printf(" FAILED: %s\n", string(body2))
fmt.Println(" ^ This is expected - Authorization header doesn't work consistently")
}
}
fmt.Println("\n=== TEST COMPLETE ===")
fmt.Println("If test 3 passed, the fix is working correctly.")
}
func getAccountIndex(client *http.Client, baseURL, walletAddr string) (int64, error) {
endpoint := fmt.Sprintf("%s/api/v1/account?by=l1_address&value=%s", baseURL, walletAddr)
resp, err := client.Get(endpoint)
if err != nil {
return 0, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result struct {
Code int `json:"code"`
Accounts []struct {
AccountIndex int64 `json:"account_index"`
} `json:"accounts"`
SubAccounts []struct {
AccountIndex int64 `json:"account_index"`
} `json:"sub_accounts"`
}
if err := json.Unmarshal(body, &result); err != nil {
return 0, fmt.Errorf("failed to parse: %w", err)
}
if len(result.Accounts) > 0 {
return result.Accounts[0].AccountIndex, nil
}
if len(result.SubAccounts) > 0 {
return result.SubAccounts[0].AccountIndex, nil
}
return 0, fmt.Errorf("no account found")
}

View File

@@ -0,0 +1,421 @@
package trader
import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestGetActiveOrders_ParseResponse tests parsing of Lighter API response
func TestGetActiveOrders_ParseResponse(t *testing.T) {
// Mock response from Lighter API
mockResponse := `{
"code": 200,
"message": "success",
"orders": [
{
"order_id": "123456",
"order_index": 123456,
"market_index": 0,
"side": "ask",
"type": "limit",
"is_ask": true,
"price": "3150.50",
"initial_base_amount": "1.5",
"remaining_base_amount": "1.5",
"filled_base_amount": "0",
"status": "open",
"trigger_price": "",
"reduce_only": false,
"timestamp": 1736745600000,
"created_at": 1736745600000
},
{
"order_id": "123457",
"order_index": 123457,
"market_index": 0,
"side": "bid",
"type": "limit",
"is_ask": false,
"price": "3100.00",
"initial_base_amount": "2.0",
"remaining_base_amount": "2.0",
"filled_base_amount": "0",
"status": "open",
"trigger_price": "",
"reduce_only": false,
"timestamp": 1736745601000,
"created_at": 1736745601000
},
{
"order_id": "123458",
"order_index": 123458,
"market_index": 0,
"side": "ask",
"type": "stop_loss",
"is_ask": true,
"price": "0",
"initial_base_amount": "1.0",
"remaining_base_amount": "1.0",
"filled_base_amount": "0",
"status": "open",
"trigger_price": "3000.00",
"reduce_only": true,
"timestamp": 1736745602000,
"created_at": 1736745602000
}
]
}`
// Parse the response
var apiResp struct {
Code int `json:"code"`
Message string `json:"message"`
Orders []OrderResponse `json:"orders"`
}
err := json.Unmarshal([]byte(mockResponse), &apiResp)
require.NoError(t, err, "Should parse response without error")
// Verify parsed data
assert.Equal(t, 200, apiResp.Code)
assert.Equal(t, 3, len(apiResp.Orders))
// Test first order (sell limit)
order1 := apiResp.Orders[0]
assert.Equal(t, "123456", order1.OrderID)
assert.True(t, order1.IsAsk, "First order should be ask (sell)")
assert.Equal(t, "3150.50", order1.Price)
assert.Equal(t, "1.5", order1.RemainingBaseAmount)
assert.False(t, order1.ReduceOnly)
// Test second order (buy limit)
order2 := apiResp.Orders[1]
assert.Equal(t, "123457", order2.OrderID)
assert.False(t, order2.IsAsk, "Second order should be bid (buy)")
assert.Equal(t, "3100.00", order2.Price)
// Test third order (stop-loss)
order3 := apiResp.Orders[2]
assert.Equal(t, "123458", order3.OrderID)
assert.Equal(t, "stop_loss", order3.Type)
assert.Equal(t, "3000.00", order3.TriggerPrice)
assert.True(t, order3.ReduceOnly)
}
// TestGetActiveOrders_EmptyResponse tests handling of empty orders
func TestGetActiveOrders_EmptyResponse(t *testing.T) {
mockResponse := `{
"code": 200,
"message": "success",
"orders": []
}`
var apiResp struct {
Code int `json:"code"`
Message string `json:"message"`
Orders []OrderResponse `json:"orders"`
}
err := json.Unmarshal([]byte(mockResponse), &apiResp)
require.NoError(t, err)
assert.Equal(t, 200, apiResp.Code)
assert.Equal(t, 0, len(apiResp.Orders))
}
// TestGetActiveOrders_ErrorResponse tests handling of API error
func TestGetActiveOrders_ErrorResponse(t *testing.T) {
mockResponse := `{
"code": 29500,
"message": "internal server error: invalid signature"
}`
var apiResp struct {
Code int `json:"code"`
Message string `json:"message"`
Orders []OrderResponse `json:"orders"`
}
err := json.Unmarshal([]byte(mockResponse), &apiResp)
require.NoError(t, err)
assert.Equal(t, 29500, apiResp.Code)
assert.Contains(t, apiResp.Message, "invalid signature")
}
// TestConvertOrderResponseToOpenOrder tests conversion logic
func TestConvertOrderResponseToOpenOrder(t *testing.T) {
testCases := []struct {
name string
order OrderResponse
expectedSide string
expectedType string
expectedPosSide string
}{
{
name: "Sell limit order (opening short)",
order: OrderResponse{
OrderID: "1",
IsAsk: true,
Type: "limit",
Price: "3150.00",
RemainingBaseAmount: "1.0",
ReduceOnly: false,
},
expectedSide: "SELL",
expectedType: "LIMIT",
expectedPosSide: "SHORT",
},
{
name: "Buy limit order (opening long)",
order: OrderResponse{
OrderID: "2",
IsAsk: false,
Type: "limit",
Price: "3100.00",
RemainingBaseAmount: "1.0",
ReduceOnly: false,
},
expectedSide: "BUY",
expectedType: "LIMIT",
expectedPosSide: "LONG",
},
{
name: "Sell stop-loss (closing long)",
order: OrderResponse{
OrderID: "3",
IsAsk: true,
Type: "stop_loss",
TriggerPrice: "3000.00",
RemainingBaseAmount: "1.0",
ReduceOnly: true,
},
expectedSide: "SELL",
expectedType: "STOP_MARKET",
expectedPosSide: "LONG",
},
{
name: "Buy stop-loss (closing short)",
order: OrderResponse{
OrderID: "4",
IsAsk: false,
Type: "stop_loss",
TriggerPrice: "3200.00",
RemainingBaseAmount: "1.0",
ReduceOnly: true,
},
expectedSide: "BUY",
expectedType: "STOP_MARKET",
expectedPosSide: "SHORT",
},
{
name: "Take profit (closing long)",
order: OrderResponse{
OrderID: "5",
IsAsk: true,
Type: "take_profit",
TriggerPrice: "3500.00",
RemainingBaseAmount: "1.0",
ReduceOnly: true,
},
expectedSide: "SELL",
expectedType: "TAKE_PROFIT_MARKET",
expectedPosSide: "LONG",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Convert side
side := "BUY"
if tc.order.IsAsk {
side = "SELL"
}
assert.Equal(t, tc.expectedSide, side)
// Convert order type
orderType := "LIMIT"
if tc.order.Type == "market" {
orderType = "MARKET"
} else if tc.order.Type == "stop_loss" || tc.order.Type == "stop" {
orderType = "STOP_MARKET"
} else if tc.order.Type == "take_profit" {
orderType = "TAKE_PROFIT_MARKET"
}
assert.Equal(t, tc.expectedType, orderType)
// Convert position side
positionSide := "LONG"
if tc.order.ReduceOnly {
if side == "BUY" {
positionSide = "SHORT"
} else {
positionSide = "LONG"
}
} else {
if side == "SELL" {
positionSide = "SHORT"
}
}
assert.Equal(t, tc.expectedPosSide, positionSide)
})
}
}
// TestGetActiveOrders_MockServer tests the full HTTP flow with a mock server
func TestGetActiveOrders_MockServer(t *testing.T) {
// Create mock server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify request path and auth parameter
assert.Contains(t, r.URL.Path, "/api/v1/accountActiveOrders")
// Check that auth query parameter is present
authParam := r.URL.Query().Get("auth")
if authParam == "" {
// Return error if no auth parameter
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]interface{}{
"code": 29500,
"message": "internal server error: invalid signature",
})
return
}
// Return success response
response := map[string]interface{}{
"code": 200,
"message": "success",
"orders": []map[string]interface{}{
{
"order_id": "123456",
"order_index": 123456,
"market_index": 0,
"side": "ask",
"type": "limit",
"is_ask": true,
"price": "3150.50",
"initial_base_amount": "1.5",
"remaining_base_amount": "1.5",
"filled_base_amount": "0",
"status": "open",
"trigger_price": "",
"reduce_only": false,
},
},
}
json.NewEncoder(w).Encode(response)
}))
defer server.Close()
// Test request without auth - should fail
resp, err := http.Get(server.URL + "/api/v1/accountActiveOrders?account_index=123&market_id=0")
require.NoError(t, err)
defer resp.Body.Close()
var errorResp struct {
Code int `json:"code"`
Message string `json:"message"`
}
json.NewDecoder(resp.Body).Decode(&errorResp)
assert.Equal(t, 29500, errorResp.Code)
// Test request with auth - should succeed
resp2, err := http.Get(server.URL + "/api/v1/accountActiveOrders?account_index=123&market_id=0&auth=test_token")
require.NoError(t, err)
defer resp2.Body.Close()
var successResp struct {
Code int `json:"code"`
Message string `json:"message"`
Orders []OrderResponse `json:"orders"`
}
json.NewDecoder(resp2.Body).Decode(&successResp)
assert.Equal(t, 200, successResp.Code)
assert.Equal(t, 1, len(successResp.Orders))
}
// TestAuthTokenFormat tests the auth token format
func TestAuthTokenFormat(t *testing.T) {
// Auth token format: timestamp:account_index:api_key_index:signature
// Example: 1768308847:687247:0:742e02...
sampleToken := "1768308847:687247:0:742e02abc123"
// The token should be URL encoded when used as query parameter
// Colons become %3A
expectedEncoded := "1768308847%3A687247%3A0%3A742e02abc123"
// URL encode the token
encoded := url.QueryEscape(sampleToken)
assert.Equal(t, expectedEncoded, encoded)
}
// TestOrderResponseStruct tests that OrderResponse struct matches API response
func TestOrderResponseStruct(t *testing.T) {
// Real API response sample (from logs)
realResponse := `{
"order_id": "4609885",
"order_index": 4609885,
"market_index": 0,
"side": "ask",
"type": "limit",
"is_ask": true,
"price": "3150.00",
"initial_base_amount": "0.0300",
"remaining_base_amount": "0.0300",
"filled_base_amount": "0",
"status": "open",
"trigger_price": "",
"reduce_only": false,
"timestamp": 1736745600000,
"created_at": 1736745600000
}`
var order OrderResponse
err := json.Unmarshal([]byte(realResponse), &order)
require.NoError(t, err)
assert.Equal(t, "4609885", order.OrderID)
assert.Equal(t, int64(4609885), order.OrderIndex)
assert.Equal(t, 0, order.MarketIndex)
assert.Equal(t, "ask", order.Side)
assert.Equal(t, "limit", order.Type)
assert.True(t, order.IsAsk)
assert.Equal(t, "3150.00", order.Price)
assert.Equal(t, "0.0300", order.InitialBaseAmount)
assert.Equal(t, "0.0300", order.RemainingBaseAmount)
assert.Equal(t, "0", order.FilledBaseAmount)
assert.Equal(t, "open", order.Status)
assert.Equal(t, "", order.TriggerPrice)
assert.False(t, order.ReduceOnly)
assert.Equal(t, int64(1736745600000), order.Timestamp)
assert.Equal(t, int64(1736745600000), order.CreatedAt)
}
// BenchmarkParseOrderResponse benchmarks response parsing
func BenchmarkParseOrderResponse(b *testing.B) {
mockResponse := `{
"code": 200,
"message": "success",
"orders": [
{"order_id": "1", "is_ask": true, "price": "3150.50", "remaining_base_amount": "1.5"},
{"order_id": "2", "is_ask": false, "price": "3100.00", "remaining_base_amount": "2.0"},
{"order_id": "3", "is_ask": true, "price": "3200.00", "remaining_base_amount": "0.5"}
]
}`
b.ResetTimer()
for i := 0; i < b.N; i++ {
var apiResp struct {
Code int `json:"code"`
Message string `json:"message"`
Orders []OrderResponse `json:"orders"`
}
json.Unmarshal([]byte(mockResponse), &apiResp)
}
}