Metadata-Version: 2.4
Name: dex-exec
Version: 0.1.0
Summary: Minimal async execution library for Hyperliquid perps and Uniswap V3 / Sushiswap / Pancakeswap on Arbitrum One
Project-URL: Homepage, https://github.com/mackinac/dex-exec
Project-URL: Issues, https://github.com/mackinac/dex-exec/issues
Author: mackinac
License: MIT License
        
        Copyright (c) 2026 mackinac
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: arbitrum,async,defi,hyperliquid,trading,uniswap
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.10
Requires-Dist: eth-account>=0.12
Requires-Dist: httpx>=0.27
Requires-Dist: msgpack>=1.0
Requires-Dist: web3>=6
Requires-Dist: websockets>=12
Provides-Extra: dev
Requires-Dist: jupyter>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# dex-exec

Minimal async Python library for executing orders on **Hyperliquid** perps and **Uniswap V3 / Sushiswap / Pancakeswap** on Arbitrum One.

## Why

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](https://mackinac.io) — 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`).

## Install

```bash
pip install dex-exec
```

Requires Python 3.10+.

---

## Hyperliquid — getting started

### Step 1 — Create an HL account and fund it

Go to [app.hyperliquid.xyz](https://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](https://bridge.arbitrum.io)), then deposit into HL from the app.

### Step 2 — Create an agent key

HL uses an **agent wallet** to sign orders on your behalf. Your master key never touches the API after setup.

```python
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.

### Step 3 — Place orders

```python
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=True` to `HLClient` and `create_agent`. Get a testnet account at [app.hyperliquid-testnet.xyz](https://app.hyperliquid-testnet.xyz) — faucet USDC is available on the testnet site.

### HL order constraints

- **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.

---

## AMM — getting started

### Step 1 — Fund your Arbitrum wallet

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](https://bridge.arbitrum.io) or buy directly on Arbitrum via a centralised exchange that supports Arbitrum withdrawals.

### Step 2 — Wrap ETH → WETH if needed

Most pairs trade against WETH, not native ETH.

```python
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())
```

### Step 3 — Pick a venue and fee tier

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](https://mackinac.io) — 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.

### Step 4 — Quote then swap

Always quote before swapping to compute `min_amount_out`:

```python
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.

---

## `HLClient` reference

```python
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)                    # → bool
```

`order_type` options: `'gtc'` (resting limit), `'ioc'` (fill or cancel), `'alo'` (add liquidity only).

### Agent key functions

```python
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)
```

---

## `AMMClient` reference

```python
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_hash
```

`venue` 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).

---

## Typed return values

```python
@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
```

---

## Examples

| 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 |

---

## Notes

- **Arbitrum RPC**: defaults to the public `arb1.arbitrum.io/rpc` endpoint. 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 calls `approve(max)` automatically on the first swap for each token/router pair. No permit2 in V1.
- **Single-hop swaps only**: `exactInputSingle` for 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.

## License

MIT
