Skip to main content

mackinac-client

Python client for the Mackinac market-data API — live quotes, trades, funding rates, yield rates, and lending markets across 16 DeFi venues including Hyperliquid, dYdX V4, Uniswap V3/V4, Pendle, Spectra, GMX, Vertex, Ostium, Aave V3, Compound V3, Morpho Blue, and more.

pip install mackinac-client

Quick start

Synchronous (no asyncio required)

No credentials required for free-tier access (3 concurrent symbols):

from mackinac import Mackinac, QuoteMessage, PrintMessage

with Mackinac() as m:
    # What symbols are on HL?
    print(m.live_symbols("hl"))

    # Stream only quotes — types= filters the feed, no isinstance needed
    with m.subscribe("hl:ETH", types=QuoteMessage) as feed:
        for quote in feed:
            bid = quote.bids[0].price if quote.bids else None
            ask = quote.asks[0].price if quote.asks else None
            print(f"ETH  bid {bid}  ask {ask}")
            break  # just the first one

    # Stream only trades
    with m.subscribe("hl:ETH", types=PrintMessage) as feed:
        for trade in feed:
            side = "buy" if trade.side == 1 else "sell"
            print(f"ETH  {side}  {trade.price}  ×  {trade.size}")
            break

Async (for existing asyncio code)

import asyncio
from mackinac import AsyncClient, QuoteMessage

async def main():
    async with AsyncClient() as client:
        print(await client.live_symbols("hl"))

        async with client.subscribe("hl:ETH", types=QuoteMessage) as feed:
            async for quote in feed:
                print(f"ETH  bid {quote.bids[0].price}  ask {quote.asks[0].price}")
                break

asyncio.run(main())

Authentication

Both Mackinac (sync) and AsyncClient (async) share the same constructors:

m      = Mackinac.from_api_key("mk_live_...")       # sync
client = AsyncClient.from_api_key("mk_live_...")    # async

API keys are long-lived and authenticate WebSocket connections only. Obtain them from the dashboard (requires tier api).

JWT (browser / short-lived sessions)

m      = Mackinac.from_jwt("eyJhbGciOi...")
client = AsyncClient.from_jwt("eyJhbGciOi...")

JWTs expire after 7 days. Refresh via client.refresh_token() / await client.refresh_token().

Wallet sign-in (EIP-191)

pip install 'mackinac-client[wallet]'
m      = Mackinac.from_wallet(private_key="0xdeadbeef...", address="0xYourWalletAddress")
client = await AsyncClient.from_wallet(
    private_key="0xdeadbeef...",
    address="0xYourWalletAddress",
)

Subscribing to live data

async with client.subscribe("hl:ETH", "uni:WETH/USDC", "rates:all") as feed:
    async for msg in feed:
        match msg.type:
            case "quote":
                print(msg.exchange, msg.symbol, msg.bids[0].price)
            case "print":
                print(msg.price, msg.size, msg.side)  # side: 0=sell 1=buy
            case "rate_market":
                print(msg.symbol, f"{msg.impliedApy * 100:.2f}% APY")
            case "funding":
                print(msg.symbol, f"{msg.ratePct:.4f}% ann.")

All messages are typed Pydantic v2 models. Use isinstance() or match on msg.type to branch per message type.

Subscribe key format

Venue Key format Example
Hyperliquid perp hl:<SYMBOL> hl:ETH
dYdX V4 perp dydx:<BASE>-USD dydx:ETH-USD
Uniswap V3 (Arbitrum) uni:<BASE>/<QUOTE> uni:WETH/USDC
Uniswap V4 (Arbitrum) univ4:<BASE>/<QUOTE> univ4:WETH/USDC
SushiSwap V3 sushi:<BASE>/<QUOTE> sushi:WETH/USDC
PancakeSwap V3 pancake:<BASE>/<QUOTE> pancake:WBTC/WETH
GMX perp gmx:<SYMBOL> gmx:BTC
Vertex perp vertex:<SYMBOL> vertex:SOL
Ostium (RWA) ostium:<BASE>/<QUOTE> ostium:XAU/USD
Aave V3 lending aave:<ASSET> or aave:all aave:USDC
Compound V3 lending compound:<ASSET> or compound:all compound:USDC
Morpho Blue lending morpho:<ASSET> or morpho:all morpho:USDC
Lending actions (all venues) lending:actions lending:actions
Pendle/Spectra (all) rates:all —
Pendle/Spectra (trades) rates:swaps —
Specific yield market rates:<symbol-or-address> rates:PT-weETH-25JUN2026
AMM consolidated NBBO ammbook:<BASE>/<QUOTE> ammbook:WETH/USDC (professional+)

Use mackinac.symbols helpers to construct keys safely:

from mackinac import symbols

symbols.amm_pair("WETH", "USDC")        # "WETH/USDC"
symbols.ostium_pair("XAU")              # "XAU/USD"
symbols.pendle_address("0xC62D...")     # "0xc62d..."  (lowercased)
symbols.rates_all()                     # "rates:all"

Unsubscribing mid-session

await feed.unsubscribe("hl:ETH")

Auto-reconnect

The library reconnects automatically on drops (1s → 2s → 4s → … → 30s cap) and re-subscribes all active symbols. The async for loop continues seamlessly — no user-side retry logic needed.

Raw websockets note

If you connect with the websockets library directly (rather than via AsyncClient), set max_size to at least 10 MB. The initial trade snapshot sent on subscribe can exceed the library's 1 MB default:

async with websockets.connect(url, max_size=10 * 1024 * 1024) as ws:
    ...

AsyncClient already sets this internally.


REST methods

Symbol discovery

# Which exchanges are live?
await client.markets()                        # dict[exchange, MarketStatus]

# All subscribable symbols on one venue
await client.live_symbols("hl")              # list[str]
await client.live_symbols("pendle")          # list[str]

# All venues at once
await client.live_symbols()                  # dict[exchange, list[str]]

# Symbols with persisted history
await client.historical_symbols("hl")        # list[str]

# Which venues quote ETH?
await client.instrument("ETH")               # InstrumentVenues

Historical data

History iterators paginate automatically — no cursor handling needed:

# Trades
async for trade in client.history_trades("hl", "ETH",
                                          start="2026-04-01",
                                          end="2026-04-08"):
    print(trade.time, trade.price, trade.size, trade.side)

# 5-second quote snapshots
async for snap in client.history_quotes("uni", "WETH/USDC",
                                         start="2026-04-01"):
    print(snap.time, snap.bids[0].price if snap.bids else None)

# Funding rates
async for fr in client.history_funding("hl", "ETH"):
    print(fr.ratePct, fr.intervalHrs)

# Yield-market snapshots
async for rate in client.history_rates("0xc62d...", start="2026-03-01"):
    print(rate.impliedApy, rate.tvl)

All methods accept start/end as ISO 8601 strings or epoch milliseconds. Default range: last 24 hours. Maximum lookback: 1 day (free tier) or 90 days (professional/api).

Account

await client.me()                  # JWT claims
await client.subscription_status() # current tier + expiry (re-reads on-chain)
await client.refresh_token()       # new 7-day JWT with updated tier claims

Message types

Type Description Venues
QuoteMessage Top-of-book snapshot All
PrintMessage Trade execution CLOB, AMM, Oracle*, Yield
FundingMessage Perpetual funding rate HL, dYdX, GMX, Vertex, Ostium
DepthMessage Concentrated-liquidity tick snapshot + market impact AMM
LiquidityMessage LP Mint/Burn event AMM
RateMarketMessage Yield-market or lending-market snapshot Pendle, Spectra, Aave, Compound, Morpho
RateDepthMessage Depth-at-size APY quote Pendle only
LendingActionMessage On-chain lending event (Supply/Borrow/Liquidate/...) Aave, Compound, Morpho
RateModelParamsMessage IRM curve parameters Aave, Compound, Morpho
AmmBookMessage Consolidated AMM NBBO (professional+) Virtual
AmmLiquiditySnapshotMessage LP event backfill on subscribe (professional+) Virtual
ArbFlagMessage Cross-venue arb gap signal (super_admin) Virtual
SpreadMessage Same-venue cross-tier spread (professional+) Uni, Univ4
FeedStaleMessage Feed health: stale Broadcast
FeedLiveMessage Feed health: recovered Broadcast
ServerClosingMessage Graceful shutdown notice Broadcast
ErrorMessage Error frame Server response

*GMX and Vertex do not emit PrintMessage (oracle settle on net positions).

Key field conventions

  • time — always epoch milliseconds (int)
  • side — 0 = aggressive sell (bid hit), 1 = aggressive buy (ask lift), 2 = unknown
  • ratePct (FundingMessage) — annualized percentage, e.g. 10.95 = 10.95%/yr
  • impliedApy (RateMarketMessage) — decimal fraction, e.g. 0.058 = 5.8%
  • ptPrice — Pendle: USD price; Spectra: fraction of IBT
  • tvl — Pendle: USD; Spectra v1: underlying-asset units (no USD oracle)
  • amount0/amount1/amount (LiquidityMessage) — decimal strings in raw ERC-20 units

Tiers & limits

Tier WS symbol cap ammbook / spread Historical data
Free (anonymous) 3 — 1 day
Professional 50 ✓ 90 days
API 100 ✓ 90 days

Error handling

from mackinac.exceptions import (
    AuthError,      # 401 / WS auth_error
    TierError,      # 403 / WS subscription_required
    RateLimitError, # 429 / WS rate_limited — has .retry_after
    SymbolLimitError,
    InvalidSymbolError,
    ServerError,    # 5xx / WS internal_error
)

try:
    async with client.subscribe("ammbook:WETH/USDC") as feed:
        ...
except TierError:
    print("Upgrade to professional tier for ammbook access")
  • REST 4xx → typed exception immediately (no retry)
  • REST 5xx → retried 3× with exponential backoff, then ServerError
  • WS auth_error / subscription_required → raised, reconnect stops
  • Other WS errors → delivered as ErrorMessage to the iterator

Installing extras

pip install 'mackinac-client[wallet]'   # EIP-191 wallet sign-in
pip install 'mackinac-client[tables]'   # pandas, for amm_fee_table + rate_table examples
pip install 'mackinac-client[dev]'      # testing + model codegen

Examples

Examples with both API flavours use a _sync / _async filename suffix. Older single-flavour examples keep their original names.

File What it shows
examples/subscribe_quotes.py HL ETH live top-of-book
examples/historical_trades.py 7-day Ostium XAU/USD → CSV
examples/yield_rates.py Pendle + Spectra live APY table
examples/reconnect_pattern.py Iterator survives forced disconnect
examples/multi_venue_basis.py Cross-venue ETH basis across CLOB, AMM, and oracle venues
examples/amm_fee_table.py AMM depth → pandas fee-tier impact table
examples/rate_table.py rate_market stream → pandas DataFrame
examples/funding_carry.py CME vs HL vs Ostium carry cost comparison
examples/lending_rates_sync.py Aave + Compound + Morpho supply/borrow APY table (sync Mackinac)
examples/lending_rates_async.py Same lending-rate table using AsyncClient
examples/liquidation_watch_sync.py Live liquidation feed across all lending venues (sync)
examples/liquidation_watch_async.py Same liquidation feed using AsyncClient

Versioning

mackinac.__version__ follows the library release. mackinac.__protocol_version__ matches the API schema version; a major bump means a breaking wire-format change.


License

MIT

Metadata

Release files for mackinac-client 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mackinac-client 0.2.0
File Size Uploaded Provenance
mackinac_client-0.2.0.tar.gz 51.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mackinac-client 0.2.0
File Interpreter ABI Platform
mackinac_client-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 87.4 kB

Release files / mackinac_client-0.2.0.tar.gz

Download URL mackinac_client-0.2.0.tar.gz
Size 51.6 kB
Tags Source
SHA-256 checksum
How to use checksums
7d715bb12615021818db851f57bd706216070910b60035356f4d878254f8c072
BLAKE2b-256 checksum
How to use checksums
9812478bc792b7786eed9ecc1e41a75aed9fe43a1f6ed7829519d7a690bd2157
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via Hatch/1.16.5 cpython/3.12.9 HTTPX/0.28.1

Release files / mackinac_client-0.2.0-py3-none-any.whl

Download URL mackinac_client-0.2.0-py3-none-any.whl
Size 35.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bc702144b6ef534e274db6259782ce38ea14bc021dcc8fcb308f86fce57b4930
BLAKE2b-256 checksum
How to use checksums
ec84d15e2ce95ff319fdc0f60c46dc2c0e2d03f504fb65112942a3e4c264abd1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via Hatch/1.16.5 cpython/3.12.9 HTTPX/0.28.1

Security for mackinac-client 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page