mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-03 02:50:59 +08:00
234 lines
7.1 KiB
Go
234 lines
7.1 KiB
Go
// 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)
|
|
}
|