Packages

Elixir SDK for Hyperliquid DEX with DSL-based API endpoints, WebSocket subscriptions, and optional Postgres/Phoenix integration

Current section

Files

Jump to
hyperliquid lib hyperliquid utils encoder.ex
Raw

lib/hyperliquid/utils/encoder.ex

defmodule Hyperliquid.Encoder do
@moduledoc """
Provides encoding functionality for Hyperliquid API actions.
This module contains various functions to encode structured data for different
Hyperliquid API actions. It supports encoding of orders, cancellations, modifications,
and various other action types.
## Key Features
- Encodes different action types (order, cancel, modify, etc.)
- Packs structured data for API requests
- Supports various field encodings
- Handles special cases like triggers and transfers
- Provides utilities for binary data manipulation
## Usage
Most functions in this module are intended for internal use within the Hyperliquid
API client. However, you may use some of the public functions if you need to
manually encode data for custom API interactions. Refer to the Signer module.
Example:
iex> action = %{type: "order", orders: [...], grouping: "na"}
iex> nonce = 12345
iex> vault_address = "0x1234..."
iex> Hyperliquid.Encoder.pack_action(action, nonce, vault_address)
Note: Ensure you understand the Hyperliquid API specifications when using
these encoding functions directly.
"""
alias Hyperliquid.Utils
import Msgpax
@action_type pack!("type")
@action_types %{
order: pack!("order"),
cancel: pack!("cancel"),
cancelByCloid: pack!("cancelByCloid"),
modify: pack!("modify"),
batchModify: pack!("batchModify"),
updateLeverage: pack!("updateLeverage"),
updateIsolatedMargin: pack!("updateIsolatedMargin"),
spotUser: pack!("spotUser"),
setReferrer: pack!("setReferrer"),
vaultTransfer: pack!("vaultTransfer"),
createSubAccount: pack!("createSubAccount"),
subAccountTransfer: pack!("subAccountTransfer"),
subAccountSpotTransfer: pack!("subAccountSpotTransfer")
}
@orders pack!("orders")
@cancels pack!("cancels")
@modifies pack!("modifies")
@oid pack!("oid")
@order pack!("order")
@class_transfer pack!("classTransfer")
@fields %{
a: pack!("a"),
b: pack!("b"),
p: pack!("p"),
s: pack!("s"),
r: pack!("r"),
t: pack!("t"),
o: pack!("o"),
c: pack!("c"),
asset: pack!("asset"),
cloid: pack!("cloid"),
isMarket: pack!("isMarket"),
triggerPx: pack!("triggerPx"),
tpsl: pack!("tpsl"),
trigger: pack!("trigger"),
limit: pack!("limit"),
usdc: pack!("usdc"),
toPerp: pack!("toPerp"),
vaultAddress: pack!("vaultAddress"),
isDeposit: pack!("isDeposit"),
usd: pack!("usd"),
hyperliquidChain: pack!("hyperliquidChain"),
signatureChainId: pack!("signatureChainId"),
token: pack!("token"),
amount: pack!("amount"),
time: pack!("time"),
chain: pack!("chain"),
isBuy: pack!("isBuy"),
ntli: pack!("ntli"),
isCross: pack!("isCross"),
leverage: pack!("leverage"),
subAccountUser: pack!("subAccountUser"),
name: pack!("name"),
code: pack!("code")
}
@grouping pack!("grouping")
@groupings %{
"na" => pack!("na"),
"normalTpsl" => pack!("normalTpsl"),
"positionTpsl" => pack!("positionTpsl")
}
def type(key), do: [@action_type | @action_types[key]]
def grouping(key), do: [@grouping | @groupings[key]]
def field(:t, %{t: %{trigger: _}} = value), do: [@fields[:t] | pack_trigger(value[:t])]
def field(key, value), do: [@fields[key] | pack!(value[key])]
def fields([_|_] = keys, value), do: Enum.map(keys, &field(&1, value))
def pack_orders(orders) do
Enum.reduce(orders, [@orders, first_byte(orders)], &(&2 ++ pack_order(&1)))
end
def pack_order(order) do
bytes = [first_byte(order) | fields([:a, :b, :p, :s, :r, :t], order)]
case Map.has_key?(order, :c) do
true -> [bytes ++ field(:c, order)]
_ -> bytes
end
end
def pack_trigger(t) do
[
first_byte(t),
@fields[:trigger],
first_byte(t[:trigger]),
fields([:isMarket, :triggerPx, :tpsl], t[:trigger])
]
end
def pack_cancel(%{cloid: _} = cancel) do
[first_byte(cancel), fields([:asset, :cloid], cancel)]
end
def pack_cancel(cancel) do
[first_byte(cancel), fields([:a, :o], cancel)]
end
def pack_cancels(cancels) do
Enum.reduce(cancels, [@cancels, first_byte(cancels)], &(&2 ++ pack_cancel(&1)))
end
def pack_modifies(modifies) do
Enum.reduce(modifies, [@modifies, first_byte(modifies)], &(&2 ++ [first_byte(&1) | pack_modify(&1)]))
end
def pack_modify(mod) do
[@oid, pack!(mod[:oid]), @order] ++ pack_order(mod[:order])
end
def pack_transfer(transfer) do
[
@class_transfer,
first_byte(transfer),
fields([:usdc, :toPerp], transfer)
]
end
def pack_action(%{type: "setReferrer"} = action, nonce, vault_address) do
[
first_byte(action),
type(:setReferrer),
fields([:code], action),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "updateLeverage"} = action, nonce, vault_address) do
[
first_byte(action),
type(:updateLeverage),
fields([:asset, :isCross, :leverage], action),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "createSubAccount"} = action, nonce, vault_address) do
[
first_byte(action),
type(:createSubAccount),
field(:name, action),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "subAccountTransfer"} = action, nonce, vault_address) do
[
first_byte(action),
type(:subAccountTransfer),
fields([:subAccountUser, :isDeposit, :usd], action),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "subAccountSpotTransfer"} = action, nonce, vault_address) do
[
first_byte(action),
type(:subAccountSpotTransfer),
fields([:subAccountUser, :isDeposit, :token, :amount], action),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "updateIsolatedMargin"} = action, nonce, vault_address) do
[
first_byte(action),
type(:updateIsolatedMargin),
fields([:asset, :isBuy, :ntli], action),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "vaultTransfer"} = action, nonce, vault_address) do
[
first_byte(action),
type(:vaultTransfer),
fields([:vaultAddress, :isDeposit, :usd], action),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "order"} = action, nonce, vault_address) do
[
first_byte(action),
type(:order),
pack_orders(action[:orders]),
grouping(action[:grouping]),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "cancel"} = action, nonce, vault_address) do
[
first_byte(action),
type(:cancel),
pack_cancels(action[:cancels]),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "cancelByCloid"} = action, nonce, vault_address) do
[
first_byte(action),
type(:cancelByCloid),
pack_cancels(action[:cancels]),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "modify"} = action, nonce, vault_address) do
[
first_byte(action),
type(:modify),
pack_modify(action),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "batchModify"} = action, nonce, vault_address) do
[
first_byte(action),
type(:batchModify),
pack_modifies(action[:modifies]),
add_additional_bytes(nonce, vault_address)
]
end
def pack_action(%{type: "spotUser"} = action, nonce, vault_address) do
[
first_byte(action),
type(:spotUser),
pack_transfer(action[:classTransfer]),
add_additional_bytes(nonce, vault_address)
]
end
def first_byte(action), do: pack!(action) |> Enum.take(1)
def first_byte(action, false), do: first_byte(action) |> to_binary()
def to_binary(data), do: IO.iodata_to_binary(data)
def address_to_bytes(address) do
Utils.trim_0x(address)
|> Base.decode16(case: :lower)
|> case do
{:ok, binary} -> binary
{:error, _reason} -> raise "Invalid hexadecimal string"
end
end
def add_additional_bytes(nonce, nil) do
nonce_position = byte_size(<<>>)
<<>> <> <<0::size(8 * 9)>>
|> put_big_uint64(nonce, nonce_position)
|> put_uint8(0, nonce_position + 8)
end
def add_additional_bytes(nonce, vault_address) do
address_bytes = address_to_bytes(vault_address)
nonce_position = byte_size(<<>>)
<<>> <> <<0::size(8 * 29)>>
|> put_big_uint64(nonce, nonce_position)
|> put_uint8(1, nonce_position + 8)
|> put_bytes(address_bytes, nonce_position + 9)
end
def put_big_uint64(data, value, position) do
<<head::binary-size(position), _::binary-size(8), tail::binary>> = data
<<head::binary, value::big-integer-size(64), tail::binary>>
end
def put_uint8(data, value, position) do
<<head::binary-size(position), _::size(8), tail::binary>> = data
<<head::binary, value::integer-size(8), tail::binary>>
end
def put_bytes(data, bytes, position) do
<<head::binary-size(position), _::binary-size(byte_size(bytes)), tail::binary>> = data
<<head::binary, bytes::binary, tail::binary>>
end
end