Metadata-Version: 2.4
Name: hogswap-py-sdk
Version: 1.2.0
Summary: Official Python SDK for the HOGSWAP v1 router API — best-route swaps across every integrated Algorand DEX, non-custodial unsigned-transaction flow, x402 payments. Zero dependencies.
Project-URL: Homepage, https://hogswap-v1.liquihog.dev
Project-URL: Documentation, https://hogswap-v1.liquihog.dev/reference
Project-URL: Repository, https://github.com/LiquiHog/hogswap-py-sdk
Project-URL: Issues, https://github.com/LiquiHog/hogswap-py-sdk/issues
Author: LiquiHog
License-Expression: MIT
License-File: LICENSE
Keywords: algorand,amm,defi,dex,hogswap,payments,router,stamm,swap,x402
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: signing
Requires-Dist: py-algorand-sdk>=2.0.0; extra == 'signing'
Description-Content-Type: text/markdown

# HOGSWAP SDK (Python)

The official Python SDK for the **HOGSWAP v1 router API** — one call to
quote the best route across every integrated Algorand DEX (STAMM, Tinyman,
Pact, Humble, AlgoFi, Folks, liquid-staking mints), one call to get unsigned
transactions your user's wallet signs. The API never touches keys and never
broadcasts — your app stays in full control.

- **Zero dependencies.** Pure standard library (`urllib`), Python 3.9+.
- **Sync and async.** `HogswapClient` and `AsyncHogswapClient` expose the
  identical surface; the async client runs requests in worker threads via
  `asyncio.to_thread` — no extra deps, real concurrency.
- **Honest numbers.** `expected_out` and `min_out_at_slippage` are what the
  contract actually delivers — routing fee included. Attach the user's wallet
  address and their HOG-holdings fee discount is priced in exactly.
- **Everything is atomic.** A swap either delivers at least the quoted
  minimum or the whole transaction group reverts. No partial fills, no
  stuck funds.
- **Typed.** Ships `py.typed` with full type hints.

```
Base URL: https://hogswap-v1.liquihog.dev
```

## 60-second start

```python
from hogswap import HogswapClient, from_base_units

hogswap = HogswapClient()

# How much USDC for 10 ALGO?  (amounts are integer base units: 1 ALGO = 1_000_000)
quote = hogswap.swap_quote(
    asset_in=0,              # ALGO
    asset_out=31566704,      # USDC
    amount_in=10_000_000,
    slippage_bps=50,         # 0.5% tolerance
)

print("expected:", from_base_units(quote["expected_out"], 6), "USDC")
print("worst case:", from_base_units(quote["min_out_at_slippage"], 6), "USDC")
print("route:", " + ".join(leg["dex_name"] for leg in quote["legs"]))
```

Async, same surface:

```python
import asyncio
from hogswap import AsyncHogswapClient

async def main():
    hogswap = AsyncHogswapClient()
    quote = await hogswap.swap_quote(asset_in=0, asset_out=31566704,
                                     amount_in=10_000_000)
    print(quote["expected_out"])

asyncio.run(main())
```

## The full trade loop

```python
import base64
from algosdk import encoding                # signing is YOUR side; any wallet works
from hogswap import HogswapClient

hogswap = HogswapClient(sender=user_address)   # fee discount priced in

# 1. Quote
quote = hogswap.swap_quote(asset_in=0, asset_out=31566704, amount_in=10_000_000)

# 2. Build unsigned transactions (call this right before signing)
built = hogswap.execute(quote_id=quote["quote_id"], user_address=user_address)

# 3. Sign with the user's wallet / key (py-algorand-sdk shown)
signed = [encoding.msgpack_decode(b64).sign(private_key) for b64 in built["txns_b64"]]

# 4. Submit the group to any algod node
txid = algod.send_transactions(signed)
```

Or, for a key you manage yourself, the optional helper (the ONLY
key-touching code in the SDK, and it needs an explicit
`pip install py-algorand-sdk`):

```python
from hogswap import account_signer
sign = account_signer(private_key)             # -> sign(txns_b64, purpose)
signed_b64 = sign(built["txns_b64"], "swap")
```

## Install

```
pip install hogswap-py-sdk
```

The import name is `hogswap`. Add local-signing support (optional) with
`pip install hogswap-py-sdk[signing]`.

## What you can quote

| Method | What it does |
|---|---|
| `swap_quote` | best route for `amount_in` of A → B |
| `exact_out_quote` | minimum input that guarantees an exact output |
| `multi_input_quote` | 2-4 assets → one output, single atomic group (dust consolidation) |
| `basket_quote` | one input → primary + up to 3 exact-amount side outputs |
| `lp_mint_quote` | add liquidity to a STAMM pool tier — with pool assets **or any asset** (auto-converted) |
| `lp_redeem_quote` | burn LP tokens into any asset |
| `execute` | quote → unsigned transaction group |
| `swap` | quote + execute in one call |

Market data (all edge-cached ~5s): `assets`, `asset`, `asset_pools`, `pools`,
`pool`, `stamm_pools`, `lp`, `pair`, `price`, `prices`,
`price_anchors`, `staking_assets`, `tvl`, `assets_tvl`, `health`.

**LP positions (1.2.0):** `lp(asset_id, amount=None)` values a
liquidity-provider token — issuing pool and STAMM tier, what one WHOLE
token is backed by, USD value, and the redeemable amount of each
underlying for a holding (`amount` in LP base units). Find ids on
`pools()[...]["lp_asset_id"]` or each
`stamm_pools()[...]["tier_breakdown"][...]["lp_asset_id"]`. Values are
a proportional-share redemption at analytics prices — no slippage, no
exit fee, null rather than guessed when a price or supply is missing.

Batch lookups: `assets(ids=[...])`, `prices([...])`, `assets_tvl([...])` —
up to 100 asset ids per call.

**Bounded-complexity routes (1.1.0):** pass `max_legs` (1-16) to
`swap_quote` to cap the TOTAL leg count, parallel pool splits
included — `max_hops` bounds depth only. Built for callers that
replay the session under their own resource budget (contract vaults,
composed groups): you get the best route *that fits*, at a slightly
worse price, instead of a rejection.

## Watches: server-side arming over SSE (1.1.0)

Stop polling for prices: register a standing condition once and get
pushed an event the block it arms. Free with any self-issued API key.

```python
client = HogswapClient(api_key="hsk_...")

# "Tell me when swapping 100 ALGO would deliver >= 8.65 USDC."
client.put_watch(
    client_key="my-bot:algo-usdc", kind="target",
    asset_in=0, asset_out=31566704,
    amount_in=100_000_000, min_out=8_650_000,
)

for ev in client.watch_events():          # blocking generator
    if ev["type"] == "fired":
        ...  # numbers only — re-quote NOW, then execute

# async client: same surface, `async for` over watch_events()
```

Edge-triggered with per-watch re-arm hysteresis and cooldown; TTL
auto-expiry; at-least-once delivery with seq-numbered replay. Events
are hints — fills stay gated by your quote's on-chain floor. Details:
[`docs/API.md`](docs/API.md).

**Live block stream** — the one endpoint without a wrapper method: it's
plain Server-Sent Events at `GET /stream/blocks`; use any SSE client.

Full request/response reference: [`docs/API.md`](docs/API.md).

## Amounts, fees, and floors — the three things to know

1. **Everything is integer base units.** 1 ALGO = 1,000,000 µALGO; each
   ASA's `decimals` comes from `assets()`. Use `to_base_units("1.5", 6)` /
   `from_base_units(1500000, 6)` to convert safely (exact `decimal.Decimal`
   math, no float drift).
2. **Quotes are delivery-exact.** The router's fee (5 bps, discounted by the
   sender's HOG holdings, free at 100+ HOG) is already subtracted from
   `expected_out` and `min_out_at_slippage`. Don't subtract anything
   client-side.
3. **`min_out_at_slippage` is enforced on-chain.** If market movement makes
   delivery fall below it, the group reverts atomically and the user only
   spends network fees.

## x402: paid tier + pay any invoice with any asset

The SDK speaks [x402](https://hogswap-v1.liquihog.dev/reference) end to
end, strictly non-custodially — you provide `sign` and `submit`
callables; the SDK never sees keys.

```python
from hogswap import HogswapClient, PaymentRequiredError

hogswap = HogswapClient(api_key="hsk_...")

# Self-issue a key (no signup, no human): sign a challenge locally.
ch = hogswap.register(address=address)
res = hogswap.register_verify(
    address=address, challenge=ch["challenge"],
    signature_b64=my_sign_bytes(ch["challenge"]),   # e.g. algosdk.util.sign_bytes
)
hogswap.set_api_key(res["api_key"])

# Out of credits? Any call raises PaymentRequiredError whose `.offer`
# IS the x402 payment instruction. Top up with ANY 1-4 assets you
# hold (here: minimum ALGO, solved by the exact-out router):
try:
    hogswap.swap_quote(...)
except PaymentRequiredError:
    hogswap.topup_with_assets(
        usdc_micro=1_000_000, user_address=address,
        inputs=[{"asset_id": 0}],            # pay 1 USDC worth of ALGO
        sign=lambda txns_b64, purpose: wallet.sign_group(txns_b64),
        submit=lambda signed, purpose: algod.send_transactions(signed),
    )                                        # returns when credits land
    # retry the original call

# Or pay ANY third-party Algorand x402 invoice the same way:
hogswap.pay_invoice(invoice=offer["accepts"][0], user_address=address,
                    inputs=[{"asset_id": 0}], sign=sign, submit=submit)
```

Groups are submitted **in order** (swap first — its on-chain floor
guarantees the payment is funded). On the async client, `sign` and
`submit` may be coroutine functions. AI agents get the same tools over
MCP: [`hogswap-mcp`](https://github.com/LiquiHog/hogswap-mcp); JavaScript
apps get [`hogswap-js-sdk`](https://github.com/LiquiHog/hogswap-js-sdk).

## Being a good API citizen (limits)

| Limit | Value | On violation |
|---|---|---|
| Quote rate | 30 requests / 10s / IP | HTTP 429 for ~10s |
| Concurrency | 4 in-flight requests / IP | immediate HTTP 429 |
| Execute budget | 5 builds per `quote_id` | HTTP 429 — get a fresh quote |
| Quote lifetime | ~30 seconds | HTTP 404 on execute — get a fresh quote |

The SDK raises typed errors (`RateLimitError`, `QuoteExpiredError`,
`ExecuteBudgetError`, …) so handling these is an `isinstance` check.
Identical anonymous quotes within 5s are served from cache — repeats are
nearly free, so don't build your own quote cache.

**Tips:** debounce type-to-quote inputs (~300ms); request a quote when the
user is ready to see a price, execute right before signing; never hardcode
pool/app ids — everything you need is in the API responses. (The router's
on-chain app id may change between releases; transactions returned by
`execute` always target the current one.)

## FAQ

**Do I need an API key?** No. Public, rate-limited per IP.

**Which wallets work?** Any Algorand wallet that signs standard transaction
groups (Pera, Defly, Exodus, KMD, …). The API returns plain unsigned
transactions; nothing wallet-specific.

**Is my seed phrase ever involved?** Never. The API builds unsigned
transactions; signing happens entirely in your app/wallet, and you submit
to algod yourself. The SDK core takes no keys anywhere; the optional
`account_signer` helper is the single, explicit opt-in for local keys.

**Sync or async?** Same surface, your call. `AsyncHogswapClient` wraps the
sync core with `asyncio.to_thread`, so there's exactly one implementation
to trust.

## License

MIT — see [LICENSE](LICENSE).
