> ## Documentation Index
> Fetch the complete documentation index at: https://docs.allium.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Usage Examples

> This provides examples of how to use the Hyperliquid APIs on Allium.

## Calculating Spot Volume

Calculate total trading volume for spot coins on Hyperliquid using the Allium API.

Spot coins are identified as coins whose name starts with `@`.

### Features

* Fetches all fills for a user using the `userFillsByTime` endpoint
* Handles pagination (max 2000 results per call)
* Deduplicates fills using (time, coin, tid) as unique key
* Filters for spot coins (starting with `@`)
* Calculates trading volume as `price * size`
* Shows progress during execution

### Usage

<Tabs>
  <Tab title="Go">
    ```bash theme={null}
    go run main.go --api-key YOUR_API_KEY

    # With custom user
    go run main.go --api-key YOUR_API_KEY --user 0x...

    # Build binary
    go build -o spot_volume
    ./spot_volume --api-key YOUR_API_KEY
    ```
  </Tab>

  <Tab title="Python">
    ```bash theme={null}
    uv run spot_volume.py --api-key YOUR_API_KEY

    # With custom user
    uv run spot_volume.py --api-key YOUR_API_KEY --user 0x...
    ```
  </Tab>

  <Tab title="JavaScript">
    ```bash theme={null}
    node spot_volume.js --api-key YOUR_API_KEY

    # With custom user
    node spot_volume.js --api-key YOUR_API_KEY --user 0x...

    # Make executable
    chmod +x spot_volume.js
    ./spot_volume.js --api-key YOUR_API_KEY
    ```
  </Tab>
</Tabs>

### Arguments

| Argument    | Required | Description                                                                 |
| ----------- | -------- | --------------------------------------------------------------------------- |
| `--api-key` | Yes      | Allium API key                                                              |
| `--user`    | No       | User wallet address (default: `0xc0142fc8aa609f324b44e414816ea549322afbe8`) |

### Output

The script displays:

* Progress for each page fetched
* Number of fills received and processed
* Spot coin fills and volume per page
* Final summary with total statistics

### Source Code

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    #!/usr/bin/env python3

    # /// script
    # dependencies = [
    #   "requests",
    # ]
    # ///
    """
    Fetch all fills for a Hyperliquid user and calculate total trading volume for spot coins.
    Spot coins are identified by names starting with '@'.
    """
    import argparse
    import sys
    from datetime import datetime
    from typing import Set, Tuple
    import requests

    # API Configuration
    API_URL = "https://api.allium.so/api/v1/developer/trading/hyperliquid/info/fills"
    DEFAULT_USER = "0xc0142fc8aa609f324b44e414816ea549322afbe8"

    # Start from a historical date (e.g., Hyperliquid launch)
    START_TIME_MS = 1609459200000  # Jan 1, 2021

    def fetch_fills(user: str, start_time: int, api_key: str, end_time: int | None = None) -> list:
        """Fetch fills from the API for a given time range."""
        headers = {
            "Content-Type": "application/json",
            "X-API-KEY": api_key,
        }
        
        payload = {
            "user": user,
            "type": "userFillsByTime",
            "startTime": start_time,
        }
        
        if end_time:
            payload["endTime"] = end_time
        
        print("  Making API request...", flush=True)
        try:
            response = requests.post(API_URL, json=payload, headers=headers, timeout=30)
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            print(f"Error fetching fills: {e}", file=sys.stderr)
            if hasattr(e, 'response') and e.response is not None:
                print(f"Response: {e.response.text}", file=sys.stderr)
            sys.exit(1)

    def calculate_spot_volume(user: str, api_key: str) -> None:
        """Calculate total trading volume for spot coins (coins starting with '@')."""
        
        seen_fills: Set[Tuple[int, str, int]] = set()  # (time, coin, tid)
        total_volume = 0.0
        spot_fill_count = 0
        total_fill_count = 0
        current_start_time = START_TIME_MS
        page_count = 0
        
        print(f"Fetching fills for user: {user}", flush=True)
        print(f"Starting from: {datetime.fromtimestamp(START_TIME_MS / 1000).isoformat()}", flush=True)
        print("-" * 80, flush=True)
        
        while True:
            page_count += 1
            print(f"\nPage {page_count}: Fetching fills from {datetime.fromtimestamp(current_start_time / 1000).isoformat()}...", flush=True)
            
            fills = fetch_fills(user, current_start_time, api_key)
            
            if not fills:
                print("No more fills found.", flush=True)
                break
            
            print(f"  Received {len(fills)} fills", flush=True)
            
            # Track new fills in this batch
            new_fills = 0
            new_spot_fills = 0
            page_volume = 0.0
            
            for fill in fills:
                # Create unique key for this fill
                fill_key = (fill.get("time"), fill.get("coin"), fill.get("tid"))
                
                # Skip if we've already seen this fill
                if fill_key in seen_fills:
                    continue
                
                seen_fills.add(fill_key)
                new_fills += 1
                total_fill_count += 1
                
                coin = fill.get("coin", "")
                
                # Check if it's a spot coin (starts with '@')
                if coin.startswith("@"):
                    sz = fill.get("sz", "0")
                    px = fill.get("px", "0")
                    try:
                        size = float(sz)
                        price = float(px)
                        volume = size * price  # Trading volume = size * price
                        total_volume += volume
                        page_volume += volume
                        spot_fill_count += 1
                        new_spot_fills += 1
                    except (ValueError, TypeError):
                        print(f"  Warning: Could not parse size '{sz}' or price '{px}' for coin {coin}", file=sys.stderr)
            
            print(f"  New unique fills: {new_fills} (spot: {new_spot_fills}, volume: {page_volume:.4f})", flush=True)
            
            # Check if we need to continue paging
            if len(fills) < 2000:
                print("\nReceived less than 2000 fills, pagination complete.", flush=True)
                break
            
            if new_fills == 0:
                print("\nNo new fills found, all remaining fills already seen. Pagination complete.", flush=True)
                break
            
            # Use the timestamp of the last fill for the next page
            last_timestamp = fills[-1].get("time")
            current_start_time = last_timestamp
            
            print(f"  Continuing from timestamp: {datetime.fromtimestamp(last_timestamp / 1000).isoformat()}", flush=True)
        
        # Print results
        print("\n" + "=" * 80)
        print("RESULTS")
        print("=" * 80)
        print(f"Total fills processed: {total_fill_count:,}")
        print(f"Spot coin fills: {spot_fill_count:,}")
        print(f"Total spot coin trading volume: {total_volume:,.4f}")
        print(f"Pages fetched: {page_count}")
        print("=" * 80)

    def main():
        parser = argparse.ArgumentParser(
            description="Calculate total trading volume for spot coins on Hyperliquid"
        )
        parser.add_argument(
            "--user",
            type=str,
            default=DEFAULT_USER,
            help=f"User wallet address (default: {DEFAULT_USER})"
        )
        parser.add_argument(
            "--api-key",
            type=str,
            required=True,
            help="Allium API key"
        )
        
        args = parser.parse_args()
        
        # Validate user address format (basic check)
        if not args.user.startswith("0x") or len(args.user) != 42:
            print("Error: Invalid user address format. Expected 0x followed by 40 hex characters.", file=sys.stderr)
            sys.exit(1)
        
        calculate_spot_volume(args.user, args.api_key)

    if __name__ == "__main__":
        main()
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    package main

    import (
    	"bytes"
    	"encoding/json"
    	"flag"
    	"fmt"
    	"io"
    	"net/http"
    	"os"
    	"regexp"
    	"strings"
    	"time"
    )

    const (
    	apiURL         = "https://api.allium.so/api/v1/developer/trading/hyperliquid/info/fills"
    	defaultUser    = "0xc0142fc8aa609f324b44e414816ea549322afbe8"
    	startTimeMS    = int64(1609459200000) // Jan 1, 2021
    	maxResults     = 2000
    	requestTimeout = 30 * time.Second
    )

    // Fill represents a single fill from the API
    type Fill struct {
    	ClosedPnl      *string `json:"closedPnl"`
    	Coin           *string `json:"coin"`
    	Crossed        *bool   `json:"crossed"`
    	Dir            *string `json:"dir"`
    	Hash           *string `json:"hash"`
    	Oid            *int64  `json:"oid"`
    	Px             *string `json:"px"`
    	Side           *string `json:"side"`
    	StartPosition  *string `json:"startPosition"`
    	Sz             *string `json:"sz"`
    	Time           *int64  `json:"time"`
    	Fee            *string `json:"fee"`
    	FeeToken       *string `json:"feeToken"`
    	Tid            *int64  `json:"tid"`
    	BuilderFee     *string `json:"builderFee"`
    	BuilderAddress *string `json:"builderAddress"`
    	TwapId         *int64  `json:"twapId"`
    }

    // FillRequest represents the API request payload
    type FillRequest struct {
    	Type      string `json:"type"`
    	User      string `json:"user"`
    	StartTime int64  `json:"startTime"`
    	EndTime   *int64 `json:"endTime,omitempty"`
    }

    // FillKey uniquely identifies a fill
    type FillKey struct {
    	Time int64
    	Coin string
    	Tid  int64
    }

    func main() {
    	user := flag.String("user", defaultUser, "User wallet address")
    	apiKey := flag.String("api-key", "", "Allium API key")
    	flag.Parse()

    	if *apiKey == "" {
    		fmt.Fprintln(os.Stderr, "Error: --api-key is required")
    		flag.Usage()
    		os.Exit(1)
    	}

    	// Validate user address format
    	if !isValidAddress(*user) {
    		fmt.Fprintln(os.Stderr, "Error: Invalid user address format. Expected 0x followed by 40 hex characters.")
    		os.Exit(1)
    	}

    	calculateSpotVolume(*user, *apiKey)
    }

    func isValidAddress(address string) bool {
    	matched, _ := regexp.MatchString(`^0x[0-9a-fA-F]{40}$`, address)
    	return matched
    }

    func calculateSpotVolume(user, apiKey string) {
    	seenFills := make(map[FillKey]bool)
    	var totalVolume float64
    	var spotFillCount int
    	var totalFillCount int
    	currentStartTime := startTimeMS
    	pageCount := 0

    	fmt.Printf("Fetching fills for user: %s\n", user)
    	fmt.Printf("Starting from: %s\n", time.UnixMilli(startTimeMS).Format(time.RFC3339))
    	fmt.Println(strings.Repeat("-", 80))

    	for {
    		pageCount++
    		fmt.Printf("\nPage %d: Fetching fills from %s...\n", pageCount, time.UnixMilli(currentStartTime).Format(time.RFC3339))

    		fills, err := fetchFills(user, currentStartTime, apiKey)
    		if err != nil {
    			fmt.Fprintf(os.Stderr, "Error fetching fills: %v\n", err)
    			os.Exit(1)
    		}

    		if len(fills) == 0 {
    			fmt.Println("No more fills found.")
    			break
    		}

    		fmt.Printf("  Received %d fills\n", len(fills))

    		// Track new fills in this batch
    		newFills := 0
    		newSpotFills := 0
    		var pageVolume float64

    		for _, fill := range fills {
    			// Create unique key for this fill
    			if fill.Time == nil || fill.Coin == nil || fill.Tid == nil {
    				continue
    			}

    			fillKey := FillKey{
    				Time: *fill.Time,
    				Coin: *fill.Coin,
    				Tid:  *fill.Tid,
    			}

    			// Skip if we've already seen this fill
    			if seenFills[fillKey] {
    				continue
    			}

    			seenFills[fillKey] = true
    			newFills++
    			totalFillCount++

    			coin := *fill.Coin

    			// Check if it's a spot coin (starts with '@')
    			if strings.HasPrefix(coin, "@") {
    				if fill.Sz == nil || fill.Px == nil {
    					fmt.Fprintf(os.Stderr, "  Warning: Missing size or price for coin %s\n", coin)
    					continue
    				}

    				var size, price float64
    				if _, err := fmt.Sscanf(*fill.Sz, "%f", &size); err != nil {
    					fmt.Fprintf(os.Stderr, "  Warning: Could not parse size '%s' for coin %s\n", *fill.Sz, coin)
    					continue
    				}
    				if _, err := fmt.Sscanf(*fill.Px, "%f", &price); err != nil {
    					fmt.Fprintf(os.Stderr, "  Warning: Could not parse price '%s' for coin %s\n", *fill.Px, coin)
    					continue
    				}

    				volume := size * price // Trading volume = size * price
    				totalVolume += volume
    				pageVolume += volume
    				spotFillCount++
    				newSpotFills++
    			}
    		}

    		fmt.Printf("  New unique fills: %d (spot: %d, volume: %.4f)\n", newFills, newSpotFills, pageVolume)

    		// Check if we need to continue paging
    		if len(fills) < maxResults {
    			fmt.Println("\nReceived less than 2000 fills, pagination complete.")
    			break
    		}

    		if newFills == 0 {
    			fmt.Println("\nNo new fills found, all remaining fills already seen. Pagination complete.")
    			break
    		}

    		// Use the timestamp of the last fill for the next page
    		lastFill := fills[len(fills)-1]
    		if lastFill.Time == nil {
    			fmt.Fprintln(os.Stderr, "Error: Last fill has no timestamp")
    			os.Exit(1)
    		}
    		currentStartTime = *lastFill.Time
    		fmt.Printf("  Continuing from timestamp: %s\n", time.UnixMilli(currentStartTime).Format(time.RFC3339))
    	}

    	// Print results
    	fmt.Println("\n" + strings.Repeat("=", 80))
    	fmt.Println("RESULTS")
    	fmt.Println(strings.Repeat("=", 80))
    	fmt.Printf("Total fills processed: %s\n", formatNumber(totalFillCount))
    	fmt.Printf("Spot coin fills: %s\n", formatNumber(spotFillCount))
    	fmt.Printf("Total spot coin trading volume: %s\n", formatFloat(totalVolume))
    	fmt.Printf("Pages fetched: %d\n", pageCount)
    	fmt.Println(strings.Repeat("=", 80))
    }

    func fetchFills(user string, startTime int64, apiKey string) ([]Fill, error) {
    	fmt.Println("  Making API request...")

    	request := FillRequest{
    		Type:      "userFillsByTime",
    		User:      user,
    		StartTime: startTime,
    	}

    	jsonData, err := json.Marshal(request)
    	if err != nil {
    		return nil, fmt.Errorf("failed to marshal request: %w", err)
    	}

    	req, err := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
    	if err != nil {
    		return nil, fmt.Errorf("failed to create request: %w", err)
    	}

    	req.Header.Set("Content-Type", "application/json")
    	req.Header.Set("X-API-KEY", apiKey)

    	client := &http.Client{
    		Timeout: requestTimeout,
    	}

    	resp, err := client.Do(req)
    	if err != nil {
    		return nil, fmt.Errorf("failed to make request: %w", err)
    	}
    	defer resp.Body.Close()

    	if resp.StatusCode != http.StatusOK {
    		body, _ := io.ReadAll(resp.Body)
    		return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body))
    	}

    	body, err := io.ReadAll(resp.Body)
    	if err != nil {
    		return nil, fmt.Errorf("failed to read response body: %w", err)
    	}

    	var fills []Fill
    	if err := json.Unmarshal(body, &fills); err != nil {
    		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
    	}

    	return fills, nil
    }

    func formatNumber(n int) string {
    	str := fmt.Sprintf("%d", n)
    	var result []rune
    	for i, r := range str {
    		if i > 0 && (len(str)-i)%3 == 0 {
    			result = append(result, ',')
    		}
    		result = append(result, r)
    	}
    	return string(result)
    }

    func formatFloat(f float64) string {
    	str := fmt.Sprintf("%.4f", f)
    	parts := strings.Split(str, ".")
    	intPart := parts[0]
    	decPart := parts[1]

    	var result []rune
    	for i, r := range intPart {
    		if i > 0 && (len(intPart)-i)%3 == 0 {
    			result = append(result, ',')
    		}
    		result = append(result, r)
    	}

    	return string(result) + "." + decPart
    }
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    #!/usr/bin/env node

    /**
     * Fetch all fills for a Hyperliquid user and calculate total trading volume for spot coins.
     * Spot coins are identified by names starting with '@'.
     */

    const API_URL = 'https://api.allium.so/api/v1/developer/trading/hyperliquid/info/fills';
    const DEFAULT_USER = '0xc0142fc8aa609f324b44e414816ea549322afbe8';
    const START_TIME_MS = 1609459200000; // Jan 1, 2021
    const MAX_RESULTS = 2000;
    const REQUEST_TIMEOUT = 30000;

    /**
     * Fetch fills from the API for a given time range
     */
    async function fetchFills(user, startTime, apiKey, endTime = null) {
        const payload = {
            user,
            type: 'userFillsByTime',
            startTime,
        };

        if (endTime) {
            payload.endTime = endTime;
        }

        console.log('  Making API request...');

        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);

        try {
            const response = await fetch(API_URL, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-API-KEY': apiKey,
                },
                body: JSON.stringify(payload),
                signal: controller.signal,
            });

            clearTimeout(timeoutId);

            if (!response.ok) {
                const text = await response.text();
                throw new Error(`API returned status ${response.status}: ${text}`);
            }

            return await response.json();
        } catch (error) {
            clearTimeout(timeoutId);
            if (error.name === 'AbortError') {
                throw new Error('Request timeout');
            }
            throw error;
        }
    }

    /**
     * Calculate total trading volume for spot coins
     */
    async function calculateSpotVolume(user, apiKey) {
        const seenFills = new Set(); // Set of "time:coin:tid" strings
        let totalVolume = 0.0;
        let spotFillCount = 0;
        let totalFillCount = 0;
        let currentStartTime = START_TIME_MS;
        let pageCount = 0;

        console.log(`Fetching fills for user: ${user}`);
        console.log(`Starting from: ${new Date(START_TIME_MS).toISOString()}`);
        console.log('-'.repeat(80));

        while (true) {
            pageCount++;
            console.log(`\nPage ${pageCount}: Fetching fills from ${new Date(currentStartTime).toISOString()}...`);

            let fills;
            try {
                fills = await fetchFills(user, currentStartTime, apiKey);
            } catch (error) {
                console.error(`Error fetching fills: ${error.message}`);
                process.exit(1);
            }

            if (!fills || fills.length === 0) {
                console.log('No more fills found.');
                break;
            }

            console.log(`  Received ${fills.length} fills`);

            // Track new fills in this batch
            let newFills = 0;
            let newSpotFills = 0;
            let pageVolume = 0.0;

            for (const fill of fills) {
                // Create unique key for this fill
                const fillKey = `${fill.time}:${fill.coin}:${fill.tid}`;

                // Skip if we've already seen this fill
                if (seenFills.has(fillKey)) {
                    continue;
                }

                seenFills.add(fillKey);
                newFills++;
                totalFillCount++;

                const coin = fill.coin || '';

                // Check if it's a spot coin (starts with '@')
                if (coin.startsWith('@')) {
                    const sz = fill.sz || '0';
                    const px = fill.px || '0';

                    try {
                        const size = parseFloat(sz);
                        const price = parseFloat(px);

                        if (isNaN(size) || isNaN(price)) {
                            console.error(`  Warning: Could not parse size '${sz}' or price '${px}' for coin ${coin}`);
                            continue;
                        }

                        const volume = size * price; // Trading volume = size * price
                        totalVolume += volume;
                        pageVolume += volume;
                        spotFillCount++;
                        newSpotFills++;
                    } catch (error) {
                        console.error(`  Warning: Error processing coin ${coin}: ${error.message}`);
                    }
                }
            }

            console.log(`  New unique fills: ${newFills} (spot: ${newSpotFills}, volume: ${pageVolume.toFixed(4)})`);

            // Check if we need to continue paging
            if (fills.length < MAX_RESULTS) {
                console.log('\nReceived less than 2000 fills, pagination complete.');
                break;
            }

            if (newFills === 0) {
                console.log('\nNo new fills found, all remaining fills already seen. Pagination complete.');
                break;
            }

            // Use the timestamp of the last fill for the next page
            const lastFill = fills[fills.length - 1];
            currentStartTime = lastFill.time;
            console.log(`  Continuing from timestamp: ${new Date(currentStartTime).toISOString()}`);
        }

        // Print results
        console.log('\n' + '='.repeat(80));
        console.log('RESULTS');
        console.log('='.repeat(80));
        console.log(`Total fills processed: ${formatNumber(totalFillCount)}`);
        console.log(`Spot coin fills: ${formatNumber(spotFillCount)}`);
        console.log(`Total spot coin trading volume: ${formatFloat(totalVolume)}`);
        console.log(`Pages fetched: ${pageCount}`);
        console.log('='.repeat(80));
    }

    /**
     * Format number with comma separators
     */
    function formatNumber(num) {
        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    }

    /**
     * Format float with comma separators and 4 decimal places
     */
    function formatFloat(num) {
        const [intPart, decPart] = num.toFixed(4).split('.');
        return formatNumber(intPart) + '.' + decPart;
    }

    /**
     * Validate Ethereum address format
     */
    function isValidAddress(address) {
        return /^0x[0-9a-fA-F]{40}$/.test(address);
    }

    /**
     * Parse command line arguments
     */
    function parseArgs() {
        const args = process.argv.slice(2);
        const parsed = {
            user: DEFAULT_USER,
            apiKey: null,
        };

        for (let i = 0; i < args.length; i++) {
            const arg = args[i];
            
            if (arg === '--user' && i + 1 < args.length) {
                parsed.user = args[++i];
            } else if (arg === '--api-key' && i + 1 < args.length) {
                parsed.apiKey = args[++i];
            } else if (arg === '--help' || arg === '-h') {
                console.log(`
    Usage: node spot_volume.js [options]

    Calculate total trading volume for spot coins on Hyperliquid

    Options:
      --user <address>    User wallet address (default: ${DEFAULT_USER})
      --api-key <key>     Allium API key (required)
      --help, -h          Show this help message
    `);
                process.exit(0);
            }
        }

        return parsed;
    }

    /**
     * Main entry point
     */
    async function main() {
        const args = parseArgs();

        if (!args.apiKey) {
            console.error('Error: --api-key is required');
            console.error('Use --help for usage information');
            process.exit(1);
        }

        if (!isValidAddress(args.user)) {
            console.error('Error: Invalid user address format. Expected 0x followed by 40 hex characters.');
            process.exit(1);
        }

        await calculateSpotVolume(args.user, args.apiKey);
    }

    // Run main function
    main().catch((error) => {
        console.error(`Fatal error: ${error.message}`);
        process.exit(1);
    });
    ```
  </Tab>
</Tabs>
