Minimal async Python library for executing orders on Hyperliquid perps and Uniswap V3 / Sushiswap / Pancakeswap on Arbitrum One.
The official hyperliquid-python-sdk is synchronous, untyped, and awkward to integrate into async applications. No clean Python library exists for V3 AMM execution either. This library fills both gaps with a small, well-typed async API.
Scope: execution only. No signal logic, no strategy framework, no risk management. If you need real-time market data to power your signals, see mackinac — it provides a WebSocket feed with Hawkes process metrics, order book imbalance, and cross-venue arb signals that pairs naturally with this library (see examples/07_mackinac_feed.py).
pip install dex-execRequires Python 3.10+.
Go to app.hyperliquid.xyz and create an account. Your master wallet is the MetaMask address you connect with. Fund it with USDC on Arbitrum One (bridge via Arbitrum Bridge), then deposit into HL from the app.
HL uses an agent wallet to sign orders on your behalf. Your master key never touches the API after setup.
import asyncio
from dexec import create_agent
async def setup():
creds = await create_agent(
master_private_key = "0x...", # your master key, used once
agent_name = "my-bot",
testnet = False,
)
print(f"Agent address: {creds.address}")
print(f"Agent private key: {creds.private_key}")
# Store creds.private_key securely — use it for all HLClient calls
asyncio.run(setup())See examples/04_hl_agent_setup.py for the full flow. After this step, the master key can go back offline.
import asyncio
from dexec import HLClient
AGENT_KEY = "0x..." # from create_agent above
MASTER_ADDRESS = "0x..." # your master wallet address
async def main():
async with HLClient(AGENT_KEY, MASTER_ADDRESS) as hl:
bal = await hl.get_balance()
print(f"Account value: ${bal.account_value:,.2f}")
result = await hl.place_order("ETH", "buy", size=0.01, price=2000.0)
print(f"Order: {result.status} cloid={result.cloid}")
asyncio.run(main())Testnet: pass
testnet=TruetoHLClientandcreate_agent. Get a testnet account at app.hyperliquid-testnet.xyz — faucet USDC is available on the testnet site.
- Minimum notional: $10 per order (size × price).
- Price tick size: varies by asset. Use the current mark price as a reference — HL rejects prices more than 80% away from it.
- IOC at aggressive price: for market-like fills, use
order_type='ioc'with a price slightly above (buy) or below (sell) the current mark.
You need:
- ETH on Arbitrum for gas (~$0.01–$0.10 per transaction).
- The token you want to swap on Arbitrum One. For WETH/USDC pairs, you need WETH — not ETH. Wrap it first (see below).
Bridge ETH or tokens from Ethereum mainnet via Arbitrum Bridge or buy directly on Arbitrum via a centralised exchange that supports Arbitrum withdrawals.
Most pairs trade against WETH, not native ETH.
import asyncio
from dexec import AMMClient
async def main():
async with AMMClient(private_key="0x...") as amm:
tx = await amm.wrap_eth(0.05) # wrap 0.05 ETH → WETH
print(f"Wrapped: {tx}")
asyncio.run(main())Each venue (Uniswap V3, Sushiswap, Pancakeswap) runs pools at different fee tiers. The most liquid pools for common pairs on Arbitrum One:
| Pair | Venue | Fee tier | Notes |
|---|---|---|---|
| WETH/USDC | uni_v3 |
500 (0.05%) | Deepest WETH/USDC pool |
| WETH/USDC | uni_v3 |
3000 (0.3%) | Higher fee, less used |
| WETH/USDC | sushi |
3000 (0.3%) | Sushi has no 0.05% WETH/USDC pool |
| WETH/USDT | uni_v3 |
500 (0.05%) | |
| USDC/USDT | pancake |
100 (0.01%) | Stablecoin pair, tightest fee |
| WBTC/WETH | uni_v3 |
3000 (0.3%) |
You can also check pool depth live on mackinac — the AMM widget shows per-fee-tier liquidity and spread in real time.
If you're unsure which pool has liquidity for a non-standard pair, try get_quote() with each fee tier — it will raise ValueError if the pool doesn't exist.
Always quote before swapping to compute min_amount_out:
import asyncio
from dexec import AMMClient
async def main():
async with AMMClient(private_key="0x...") as amm:
# Step 1: get a quote (read-only, no gas)
quote = await amm.get_quote("WETH", "USDC", amount_in=0.1,
venue="uni_v3", fee_tier=500)
print(f"Quote: {quote.amount_out:.2f} USDC at ${quote.amount_out/0.1:,.2f}/WETH")
# Step 2: execute with 0.5% slippage tolerance
min_out = quote.amount_out * 0.995
result = await amm.swap("WETH", "USDC", amount_in=0.1,
venue="uni_v3", fee_tier=500,
min_amount_out=min_out)
if result.success:
print(f"Swap tx: {result.tx_hash}")
else:
print(f"Failed: {result.error}")
asyncio.run(main())Token approval (ERC-20 approve) is handled automatically on the first swap for each token/venue pair.
async with HLClient(private_key, address, testnet=False) as hl:
# Orders
await hl.place_order(symbol, side, size, price, order_type='gtc', reduce_only=False, cloid=None)
await hl.cancel_order(symbol, cloid)
await hl.cancel_all(symbol=None) # returns count cancelled
# Account
await hl.get_balance() # → AccountBalance
await hl.get_positions() # → list[Position]
await hl.get_open_orders() # → list[Order]
await hl.get_fills() # → list[Fill]
# Streams
async with hl.fills_stream() as fills:
async for fill in fills: ...
async with hl.order_updates_stream() as updates:
async for update in updates: ...
# On-chain
await hl.deposit_usdc(amount_usd, arb_private_key) # → tx_hash
await hl.withdraw_usdc(amount_usd) # → boolorder_type options: 'gtc' (resting limit), 'ioc' (fill or cancel), 'alo' (add liquidity only).
from dexec import create_agent, approve_agent
# Generate fresh keypair + approve on HL (one-time setup)
creds = await create_agent(master_private_key, agent_name="my-bot", testnet=False)
# → AgentCredentials(address, private_key)
# Approve an existing address as an agent (e.g. key generated externally)
ok = await approve_agent(master_private_key, agent_address, testnet=False)async with AMMClient(private_key=None, rpc_url=None) as amm:
# Wrap ETH → WETH
await amm.wrap_eth(amount_eth) # → tx_hash
# Quote (read-only, no gas)
quote = await amm.get_quote(token_in, token_out, amount_in, venue, fee_tier)
# Swap (auto-approves if needed)
result = await amm.swap(token_in, token_out, amount_in, venue, fee_tier,
min_amount_out, deadline=60)
# Manual approval
await amm.approve_token(token, spender) # → tx_hashvenue options: 'uni_v3', 'sushi', 'pancake'.
Token symbols supported out of the box: WETH, USDC, USDC.e, USDT, WBTC, ARB, LINK, DAI, GMX.
Pass a raw 0x address for any other token (decimals assumed 18 if not in the registry).
@dataclass class AccountBalance:
account_value: float; margin_used: float
free_collateral: float; withdrawable: float
@dataclass class Position:
symbol: str; side: Literal['long','short']; size: float
entry_price: float; unrealized_pnl: float
margin_used: float; leverage: float; liquidation_price: float | None
@dataclass class OrderResult:
success: bool; cloid: str
status: Literal['resting','filled','error']
oid: int | None; error: str | None
@dataclass class SwapQuote:
token_in: str; token_out: str
amount_in: float; amount_out: float
price_impact_pct: float; venue: str; fee_tier: int
@dataclass class SwapResult:
success: bool; tx_hash: str | None
amount_in: float; amount_out: float
venue: str; error: str | None| File | What it shows |
|---|---|
examples/01_hl_place_cancel.py |
HL order round-trip on testnet |
examples/02_hl_positions.py |
Poll positions + PnL |
examples/03_hl_fill_stream.py |
Async fill event loop |
examples/04_hl_agent_setup.py |
create_agent() full flow |
examples/05_hl_deposit.py |
USDC deposit to HL |
examples/06_amm_swap.py |
Wrap ETH, quote, and swap on Uniswap V3 |
examples/07_mackinac_feed.py |
mackinac WS signal → HL + AMM basis trade |
- Arbitrum RPC: defaults to the public
arb1.arbitrum.io/rpcendpoint. For production use Alchemy or Infura — the public endpoint is rate-limited and can drop connections under load. - ERC-20 approvals:
swap()checks allowance and callsapprove(max)automatically on the first swap for each token/router pair. No permit2 in V1. - Single-hop swaps only:
exactInputSinglefor V1 simplicity. Multi-hop routing (e.g. USDT → WETH → ARB) is not yet supported. - Uniswap V4: stubbed, not implemented. V4 uses a materially different router interface.
MIT