View Source Hyperliquid

Hex.pm License: MIT

Elixir SDK for the Hyperliquid decentralized exchange with DSL-based API endpoints, WebSocket subscriptions, and optional Postgres/Phoenix integration.

Overview

Hyperliquid provides a comprehensive, type-safe interface to the Hyperliquid DEX. The DSL-based architecture eliminates boilerplate while providing response validation, automatic caching, and optional database persistence. Endpoint coverage tracks the nktkas TypeScript SDK, including HIP-4 prediction markets.

Features

  • DSL-based endpoint definitions - Clean, declarative API with automatic function generation
  • 165+ typed endpoints - 78 Info endpoints, 60 Exchange actions, 31 WebSocket subscriptions
  • Ecto schema validation - Built-in response validation and type safety
  • WebSocket connection pooling - Efficient connection management with automatic reconnection
  • Cachex-based caching - Fast in-memory asset metadata and mid price lookups
  • Optional Postgres persistence - Config-driven database storage for API data
  • Local node client - Low-latency access to local node Info and EVM RPC endpoints
  • Testnet/mainnet support - Easy chain switching with automatic database separation
  • Phoenix PubSub integration - Real-time event broadcasting

Installation

Add hyperliquid to your list of dependencies in mix.exs:

def deps do
  [
    {:hyperliquid, "~> 0.4.1"}
  ]
end

Configuration

Basic Configuration (No Database)

The minimal configuration requires only your private key:

# config/config.exs
config :hyperliquid,
  private_key: "YOUR_PRIVATE_KEY_HERE"

With Database Persistence

Enable database features by setting enable_db: true and adding the required dependencies:

# mix.exs
defp deps do
  [
    {:hyperliquid, "~> 0.4.1"},
    # Required when enable_db: true
    {:phoenix_ecto, "~> 4.5"},
    {:ecto_sql, "~> 3.10"},
    {:postgrex, ">= 0.0.0"}
  ]
end
# config/config.exs
config :hyperliquid,
  private_key: "YOUR_PRIVATE_KEY_HERE",
  enable_db: true

# Configure the Repo
config :hyperliquid, Hyperliquid.Repo,
  database: "hyperliquid_dev",
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  pool_size: 10

Testnet Configuration

Switch to testnet and optionally disable automatic cache initialization:

config :hyperliquid,
  chain: :testnet,
  private_key: "YOUR_TESTNET_KEY",
  autostart_cache: true  # Set to false to manually initialize cache

The database name automatically gets a _testnet suffix when using testnet.

Advanced Configuration

config :hyperliquid,
  # Chain selection
  chain: :mainnet,  # or :testnet

  # API endpoints (optional - defaults based on chain)
  http_url: "https://api.hyperliquid.xyz",
  ws_url: "wss://api.hyperliquid.xyz/ws",

  # Optional features
  enable_db: false,
  enable_web: false,
  autostart_cache: true,

  # Local node (for --serve-info and --serve-eth-rpc)
  enable_node_info: false,
  enable_node_rpc: false,
  node_url: "http://localhost:3001",

  # Debug logging
  debug: false,

  # Private key
  private_key: "YOUR_PRIVATE_KEY_HERE",

  # EIP-712 domain chainId for user-signed actions (withdrawals, transfers,
  # agent approvals). Must match the signatureChainId sent in the action body —
  # the exchange rebuilds the domain from it to recover the signer, so if the two
  # disagree it recovers the wrong address and rejects the action. Both are read
  # from here, so they cannot drift.
  #
  # Defaults to 421_614 ("0x66eee"), matching the official Python SDK and the
  # nktkas TypeScript SDK. The Hyperliquid frontend uses 42_161 ("0xa4b1");
  # either works, as long as it is used consistently.
  signature_chain_id: 421_614,

  # szDecimals for HIP-4 outcome assets. Outcome sizes are whole numbers —
  # confirmed on testnet, where 1000 was accepted and 1000.5 was rejected with
  # "Order has invalid size." No endpoint publishes this, so it stays
  # configurable in case it varies per outcome or changes on an upgrade.
  outcome_sz_decimals: 0

HIP-4 prediction markets

Outcome assets use their own encoding, derived from an outcome id plus a binary side as outcome * 10 + side:

representationformexample
spot coin#<encoding>#70020
token name+<encoding>+70020
asset ID100_000_000 + encoding100070020

They appear in neither spotMeta's universe nor its token list, so the cache resolves them from outcomeMeta:

Hyperliquid.Cache.outcome_coin(7002, 0)      # => "#70020"
Hyperliquid.Cache.outcome_asset(7002, 0)     # => 100070020
Hyperliquid.Cache.outcome_and_side("#70020") # => {:ok, {7002, 0}}
Hyperliquid.Cache.asset_from_coin("#70020")  # => 100070020

# Outcome coins work anywhere a coin is accepted
Hyperliquid.Api.Info.L2Book.request("#70020")
Hyperliquid.Api.Exchange.Order.limit_order("#70020", true, "0.5", "1")

Quick Start

Fetching Market Data

Use Info API endpoints to retrieve market data:

# Get mid prices for all assets
alias Hyperliquid.Api.Info.AllMids

{:ok, mids} = AllMids.request()
# Returns raw map: %{"BTC" => "43250.5", "ETH" => "2280.75", ...}

# Get account summary
alias Hyperliquid.Api.Info.ClearinghouseState

{:ok, state} = ClearinghouseState.request("0x1234...")
state.margin_summary.account_value
# => "10000.0"

# Get open orders
alias Hyperliquid.Api.Info.FrontendOpenOrders

{:ok, orders} = FrontendOpenOrders.request("0x1234...")
# => [%{coin: "BTC", limit_px: "43000.0", ...}]

# Get user fills
alias Hyperliquid.Api.Info.UserFills

{:ok, fills} = UserFills.request("0x1234...")
# => %{fills: [%{coin: "BTC", px: "43100.5", ...}]}

Placing Orders

Use Exchange API endpoints to trade. The private key defaults to the one in your config, or you can pass it explicitly via the :private_key option:

alias Hyperliquid.Api.Exchange.{Order, Cancel}

# Place a limit order (uses private_key from config)
{:ok, result} = Order.place_limit("BTC", true, "43000.0", "0.1")
# => %{status: "ok", response: %{data: %{statuses: [%{resting: %{oid: 12345}}]}}}

# Place a market order
{:ok, result} = Order.place_market("ETH", false, "1.5")

# Or build and place separately
order = Order.limit_order("BTC", true, "43000.0", "0.1")
{:ok, result} = Order.place(order)

# Override private key per-request
{:ok, result} = Order.place_limit("BTC", true, "43000.0", "0.1", private_key: other_key)

# Cancel an order by asset and order ID
{:ok, cancel_result} = Cancel.cancel(0, 12345)
# => %{status: "ok", response: %{data: %{statuses: ["success"]}}}

Exchange Action Signing

Hyperliquid exchange actions use two different signing schemes:

  • Agent-key compatible — Orders, cancels, leverage updates, and other trading actions use EIP-712 exchange domain signing. These can be signed with an agent key (approved via ApproveAgent) instead of your main private key. This is the recommended setup for trading bots.

  • L1-signed actions — Transfers (UsdClassTransfer, SubAccountTransfer), withdrawals, vault operations, sub-account creation, and other account-level actions require your actual private key. These cannot be delegated to an agent key.

# Agent-key compatible (trading actions)
# Configure your agent key in config and trade without exposing your main key
config :hyperliquid, private_key: "YOUR_AGENT_KEY"

Order.place_limit("BTC", true, "43000.0", "0.1")
Cancel.cancel(0, 12345)

# L1-signed actions (require main private key)
alias Hyperliquid.Api.Exchange.UsdClassTransfer
UsdClassTransfer.request(%{...}, private_key: "YOUR_MAIN_PRIVATE_KEY")

WebSocket Subscriptions

Subscribe to real-time data feeds:

alias Hyperliquid.WebSocket.Manager
alias Hyperliquid.Api.Subscription.{AllMids, Trades, UserFills}

# Subscribe to all mid prices (shared connection)
{:ok, sub_id} = Manager.subscribe(AllMids, %{})

# Subscribe to trades for BTC (shared connection)
{:ok, sub_id} = Manager.subscribe(Trades, %{coin: "BTC"})

# Subscribe to user fills (user-grouped connection)
{:ok, sub_id} = Manager.subscribe(UserFills, %{user: "0x1234..."})

# Unsubscribe
Manager.unsubscribe(sub_id)

# List active subscriptions
Manager.list_subscriptions()

Using the Cache

The cache provides fast access to asset metadata and mid prices:

alias Hyperliquid.Cache

# The cache auto-initializes on startup (unless autostart_cache: false)
# Manual initialization:
Cache.init()

# Get mid price for a coin
Cache.get_mid("BTC")
# => 43250.5

# Get asset index for a coin
Cache.asset_from_coin("BTC")
# => 0

Cache.asset_from_coin("HYPE/USDC")  # Spot pairs work too
# => 10107

# Get size decimals
Cache.decimals_from_coin("BTC")
# => 5

# Get token info
Cache.get_token_by_name("HFUN")
# => %{"name" => "HFUN", "index" => 2, "sz_decimals" => 2, ...}

# Subscribe to live mid price updates
{:ok, sub_id} = Cache.subscribe_to_mids()

API Reference

Info API (Market & Account Data)

The Info API provides read-only market and account information. All endpoints are located in Hyperliquid.Api.Info.*:

Market Data:

  • AllMids - Mid prices for all assets
  • AllPerpMetas - Perpetual market metadata
  • ActiveAssetData - Asset context data
  • CandleSnapshot - Historical candles
  • FundingHistory - Funding rate history
  • L2Book - Order book snapshot

Account Data:

  • ClearinghouseState - Perpetuals account summary
  • SpotClearinghouseState - Spot account summary
  • UserFills - Trade fill history
  • HistoricalOrders - Historical orders
  • FrontendOpenOrders - Current open orders
  • UserFunding - User funding payments

Vault & Delegation:

  • VaultDetails - Vault information
  • Delegations - User delegations
  • DelegatorRewards - Delegation rewards

See the HexDocs for the complete list of 78 Info endpoints.

Exchange API (Trading Operations)

The Exchange API handles all trading operations. All endpoints are located in Hyperliquid.Api.Exchange.*:

Order Management:

  • Modify - Place or modify orders
  • BatchModify - Batch order modifications
  • Cancel - Cancel orders
  • CancelByCloid - Cancel by client order ID

Account Operations:

  • UsdTransfer - Transfer USD between accounts
  • Withdraw3 - Withdraw to L1
  • CreateSubAccount - Create sub-accounts
  • UpdateLeverage - Adjust position leverage
  • UpdateIsolatedMargin - Modify isolated margin

Vault Operations:

  • CreateVault - Create a new vault
  • VaultTransfer - Vault deposits/withdrawals

See the HexDocs for the complete list of 60 Exchange actions.

Subscription API (Real-time Updates)

The Subscription API provides WebSocket channels for real-time data. All endpoints are located in Hyperliquid.Api.Subscription.*:

Market Subscriptions:

  • AllMids - All mid prices (shared connection)
  • Trades - Recent trades (shared connection)
  • L2Book - Order book updates (dedicated connection)
  • Candle - Real-time candles (shared connection)

User Subscriptions:

  • UserFills - User trade fills (user-grouped)
  • UserFundings - Funding payments (user-grouped)
  • OrderUpdates - Order status changes (user-grouped)
  • Notification - User notifications (user-grouped)

Explorer Subscriptions:

  • ExplorerBlock - New blocks (shared connection)
  • ExplorerTxs - Transactions (shared connection)

See the HexDocs for the complete list of 31 subscription channels.

Endpoint DSL

All API endpoints are defined using declarative macros that eliminate boilerplate:

Info/Exchange Endpoints

defmodule Hyperliquid.Api.Info.AllMids do
  use Hyperliquid.Api.Endpoint,
    type: :info,
    request_type: "allMids",
    optional_params: [:dex],
    rate_limit_cost: 2,
    raw_response: true

  embedded_schema do
    field(:mids, :map)
    field(:dex, :string)
  end

  def changeset(struct \\ %__MODULE__{}, attrs) do
    # Validation logic
  end
end

This automatically generates:

  • request/0, request/1 - Make API request, return {:ok, result} or {:error, reason}
  • request!/0, request!/1 - Bang variant that raises on error
  • build_request/1 - Build request parameters
  • parse_response/1 - Parse and validate response
  • rate_limit_cost/0 - Get rate limit cost

Subscription Endpoints

defmodule Hyperliquid.Api.Subscription.Trades do
  use Hyperliquid.Api.SubscriptionEndpoint,
    request_type: "trades",
    params: [:coin],
    connection_type: :shared,
    storage: [
      postgres: [enabled: true, table: "trades"],
      cache: [enabled: true, ttl: :timer.minutes(5)]
    ]

  embedded_schema do
    embeds_many :trades, Trade do
      field(:coin, :string)
      field(:px, :string)
      # ...
    end
  end

  def changeset(event \\ %__MODULE__{}, attrs) do
    # Validation logic
  end
end

This automatically generates:

  • build_request/1 - Build subscription request
  • __subscription_info__/0 - Metadata about the subscription
  • generate_subscription_key/1 - Unique key for connection routing

WebSocket Management

The Hyperliquid.WebSocket.Manager handles all WebSocket connections and subscriptions:

Connection Strategies

  • :shared - Multiple subscriptions share one connection (e.g., AllMids, Trades)
  • :dedicated - Each subscription gets its own connection (e.g., L2Book with params)
  • :user_grouped - All subscriptions for the same user share one connection (e.g., UserFills)

Subscribe with Callbacks

alias Hyperliquid.WebSocket.Manager
alias Hyperliquid.Api.Subscription.Trades

# Subscribe with callback function
callback = fn event ->
  IO.inspect(event, label: "Trade event")
end

{:ok, sub_id} = Manager.subscribe(Trades, %{coin: "BTC"}, callback)

Phoenix PubSub Integration

All WebSocket events are broadcast via Phoenix.PubSub:

# Subscribe to events in your LiveView or GenServer
Phoenix.PubSub.subscribe(Hyperliquid.PubSub, "ws_event")

# Or use the utility function
Hyperliquid.Utils.subscribe("ws_event")

# Handle events
def handle_info({:ws_event, event}, state) do
  # Process event
  {:noreply, state}
end

Caching

The cache module provides efficient access to frequently-used data:

Automatic Updates

When autostart_cache: true (default), the cache automatically:

  • Fetches exchange metadata on startup
  • Populates asset mappings and decimal precision
  • Updates mid prices from WebSocket subscriptions

Cache Functions

alias Hyperliquid.Cache

# Asset lookups
Cache.asset_from_coin("BTC")         # => 0
Cache.decimals_from_coin("BTC")      # => 5
Cache.get_mid("BTC")                 # => 43250.5

# Metadata
Cache.perps()                        # => [%{"name" => "BTC", ...}, ...]
Cache.spot_pairs()                   # => [%{"name" => "@0", ...}, ...]
Cache.tokens()                       # => [%{"name" => "USDC", ...}, ...]

# Token lookups
Cache.get_token_by_name("HFUN")      # => %{"index" => 2, ...}
Cache.get_token_key("HFUN")          # => "HFUN:0xbaf265..."

# Low-level cache access
Cache.get(:all_mids)                 # => %{"BTC" => "43250.5", ...}
Cache.put(:my_key, value)
Cache.exists?(:my_key)               # => true

Database Integration

When enable_db: true, the package provides Postgres persistence:

Setup

# Install database dependencies
mix deps.get

# Create and migrate database
mix ecto.create
mix ecto.migrate

Repo Configuration

# config/config.exs
config :hyperliquid, ecto_repos: [Hyperliquid.Repo]

config :hyperliquid, Hyperliquid.Repo,
  database: "hyperliquid_dev",
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  pool_size: 10

Storage Layer

Endpoints with storage configuration automatically persist data:

# This subscription will automatically store trades in Postgres and Cachex
alias Hyperliquid.Api.Subscription.Trades

{:ok, sub_id} = Manager.subscribe(Trades, %{coin: "BTC"})

# Query stored data
import Ecto.Query
alias Hyperliquid.Repo

query = from t in "trades",
  where: t.coin == "BTC",
  order_by: [desc: t.time],
  limit: 10

Repo.all(query)

Migrations

Database migrations are located in priv/repo/migrations/. The package includes migrations for:

  • trades, fills, orders, historical_orders
  • clearinghouse_states, user_snapshots
  • explorer_blocks, transactions
  • candles

Livebook

Use Hyperliquid in Livebook for interactive trading and analysis:

Mix.install([
  {:hyperliquid, "~> 0.4.1"}
],
config: [
  hyperliquid: [
    private_key: "YOUR_PRIVATE_KEY_HERE"
  ]
])

# Start working with the API
alias Hyperliquid.Api.Info.AllMids
{:ok, mids} = AllMids.request()

Testnet in Livebook

Mix.install([
  {:hyperliquid, "~> 0.4.1"}
],
config: [
  hyperliquid: [
    chain: :testnet,
    private_key: "YOUR_TESTNET_KEY"
  ]
])

Local Node

When running a Hyperliquid node with --serve-info and/or --serve-eth-rpc, the Hyperliquid.Node module provides low-latency access without rate limits.

Running the node

Start hl-node with the flags for whichever surfaces you want. Both are served on the same port (3001 by default):

# Info server only
./hl-node --serve-info

# Info server + EVM JSON-RPC
./hl-node --serve-info --serve-eth-rpc

Confirm each is up before pointing the client at it:

# Info server
curl -s -X POST http://localhost:3001/info \
  -H 'Content-Type: application/json' \
  -d '{"type":"exchangeStatus"}'
# => {"specialStatuses":null,"time":1786299106680}

# EVM RPC
curl -s -X POST http://localhost:3001/evm \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'
# => {"jsonrpc":"2.0","id":1,"result":"0x3e7"}

If the node runs on another host, tunnel the port rather than exposing it — the info server binds 0.0.0.0 and is unauthenticated:

ssh -N -L 3001:localhost:3001 your-node-host

Configuration

Info and RPC endpoints can be enabled independently:

config :hyperliquid,
  node_url: "http://localhost:3001",
  enable_node_info: true,  # enables Node info convenience functions
  enable_node_rpc: true    # registers :node named RPC at startup

Info Endpoints

Convenience functions are generated for all verified local info endpoints, with automatic struct parsing:

alias Hyperliquid.Node

# No-param endpoints
{:ok, meta} = Node.meta()
{:ok, status} = Node.exchange_status()
{:ok, metas} = Node.all_perp_metas()
{:ok, reserves} = Node.all_borrow_lend_reserve_states()
{:ok, spot} = Node.spot_meta()
{:ok, auction} = Node.gossip_priority_auction_status()
{:ok, ann} = Node.perp_concise_annotations()

# User-param endpoints
{:ok, state} = Node.clearinghouse_state("0x...")
{:ok, orders} = Node.open_orders("0x...")
{:ok, fees} = Node.user_fees("0x...")
{:ok, accounts} = Node.sub_accounts2("0x...")
{:ok, abstraction} = Node.user_dex_abstraction("0x...")

# HIP-4 prediction markets
{:ok, meta} = Node.outcome_meta()
{:ok, templates} = Node.outcome_templates()
{:ok, settled} = Node.settled_outcome(1)

# Other single-param endpoints
{:ok, table} = Node.margin_table(56)
{:ok, limits} = Node.perp_dex_limits("some_dex")
{:ok, status} = Node.perp_dex_status("")
{:ok, reserve} = Node.borrow_lend_reserve_state(0)
{:ok, ann} = Node.perp_annotation("BTC")

# Endpoints with optional dex: keyword arg
{:ok, meta} = Node.meta(dex: "some_dex")
{:ok, state} = Node.clearinghouse_state("0x...", dex: "some_dex")
{:ok, orders} = Node.open_orders("0x...", dex: "some_dex")
{:ok, caps} = Node.perps_at_open_interest_cap(dex: "some_dex")

# Generic fallback for any info request (returns raw map)
{:ok, data} = Node.info_request(%{type: "someEndpoint", user: "0x..."})

# Health check
{:ok, _} = Node.ping()
Supported local node info endpoints (48 verified) **No-param:** `meta`*, `spotMeta`, `allPerpMetas`, `allBorrowLendReserveStates`, `exchangeStatus`, `liquidatable`, `vaultSummaries`, `leadingVaults`, `perpDexs`, `perpCategories`, `perpDeployAuctionStatus`, `perpsAtOpenInterestCap`*, `spotDeployState`, `spotPairDeployAuctionStatus`, `validatorL1Votes`, `maxMarketOrderNtls`, `gossipPriorityAuctionStatus`, `perpConciseAnnotations`, `outcomeMeta`, `outcomeTemplates` **User-param:** `clearinghouseState`*, `spotClearinghouseState`, `openOrders`*, `frontendOpenOrders`*, `extraAgents`, `subAccounts`, `subAccounts2`, `userFees`, `userRateLimit`, `userVaultEquities`, `userDexAbstraction`, `userToMultiSigSigners`, `userRole`, `userAbstraction`, `approvedBuilders`, `borrowLendUserState`, `delegations`, `delegatorSummary`, `maxBuilderFee`, `webData2` **User+coin:** `activeAssetData` **Other params:** `marginTable` (id), `borrowLendReserveState` (token, **integer**), `perpAnnotation` (coin), `perpDexLimits` (dex), `perpDexStatus` (dex), `settledOutcome` (outcome) \* Supports optional `dex:` keyword arg **Not served by the node.** These fall back to the public API. The node holds state, not indexed history or aggregated market data, which is what this split reflects: `allMids`, `metaAndAssetCtxs`, `spotMetaAndAssetCtxs`, `predictedFundings`, `l2Book`, `recentTrades`, `candleSnapshot`, `fundingHistory`, `userFills`, `userFillsByTime`, `userFunding`, `userBorrowLendInterest`, `userNonFundingLedgerUpdates`, `historicalOrders`, `orderStatus`, `vaultDetails`, `tokenDetails`, `validatorSummaries`, `gossipRootIps`, `usdcRouting`, `portfolio`, `referral`, `isVip`, `legalCheck`, `preTransferCheck`, `twapHistory`, `delegatorHistory`, `delegatorRewards`, `userTwapSliceFills`, `userTwapSliceFillsByTime`, `alignedQuoteTokenInfo` `Node.aligned_quote_token_info/1` is still generated, but the node rejects it — probed with both string and integer token values. Verified by probing a live node on 2026-08-09. The node returns the same deserialization error for an unknown request type and a malformed one, so a type absent here may simply need a different request shape.

File Snapshots

The local info server supports fileSnapshot requests that write large data to files on the node's filesystem:

# Generic file snapshot
Node.file_snapshot(%{type: "referrerStates"}, "/tmp/out.json")

# Convenience helpers
Node.referrer_states_snapshot("/tmp/referrer.json")
Node.l4_snapshots("/tmp/l4.json", include_users: true, include_trigger_orders: true)

# Include block height in output
Node.file_snapshot(%{type: "referrerStates"}, "/tmp/out.json", include_height: true)

EVM RPC

When enable_node_rpc: true, a :node named RPC is registered at startup. Use it through the existing RPC modules or the Node helpers:

# Via existing RPC modules
alias Hyperliquid.Rpc.Eth
Eth.block_number(rpc_name: :node)

# Via Node helpers
Node.rpc_call("eth_blockNumber")
Node.rpc_call("eth_getBalance", ["0x...", "latest"])

Explorer API

Query the Hyperliquid explorer for block and transaction details:

alias Hyperliquid.Api.Explorer.{BlockDetails, TxDetails, UserDetails}

{:ok, block} = BlockDetails.request(block_height)
{:ok, tx} = TxDetails.request(tx_hash)
{:ok, user} = UserDetails.request("0x1234...")

RPC Transport

Make JSON-RPC calls to the Hyperliquid EVM:

alias Hyperliquid.Transport.Rpc

{:ok, block_number} = Rpc.call("eth_blockNumber", [])
{:ok, [block, chain]} = Rpc.batch([{"eth_blockNumber", []}, {"eth_chainId", []}])

Telemetry

Hyperliquid emits :telemetry events for API requests, WebSocket connections, cache operations, RPC calls, and storage flushes. See Hyperliquid.Telemetry for the full event reference.

Quick Debug Setup

Hyperliquid.Telemetry.attach_default_logger()

Telemetry.Metrics Example

defmodule MyApp.Telemetry do
  import Telemetry.Metrics

  def metrics do
    [
      summary("hyperliquid.api.request.stop.duration", unit: {:native, :millisecond}),
      summary("hyperliquid.api.exchange.stop.duration", unit: {:native, :millisecond}),
      counter("hyperliquid.ws.message.received.count"),
      summary("hyperliquid.rpc.request.stop.duration", unit: {:native, :millisecond}),
      last_value("hyperliquid.storage.flush.stop.record_count")
    ]
  end
end

Development

# Get dependencies
mix deps.get

# Run tests
mix test

# Run tests with database
mix test

# Format code
mix format

# Generate docs
mix docs

Documentation

Full documentation is available on HexDocs.

License

This project is licensed under the MIT License. See LICENSE.md for details.