From 00faa021b3290e85dbfbeb76703b11e3838b0e0c Mon Sep 17 00:00:00 2001 From: Flavian Date: Tue, 10 Dec 2024 18:45:45 +0100 Subject: [PATCH 01/11] initial commit --- .vscode/settings.json | 3 + cspell.json | 27 +++ images/lnmarkets-logo.svg | 78 ++++++++ lnmarkets/__init__.py | 3 + lnmarkets/futures/__init__.py | 40 ++++ lnmarkets/futures/add_margin.py | 16 ++ lnmarkets/futures/cancel_all_trades.py | 15 ++ lnmarkets/futures/cancel_trade.py | 15 ++ lnmarkets/futures/cash_in.py | 16 ++ lnmarkets/futures/close_all_trades.py | 16 ++ lnmarkets/futures/close_trade.py | 15 ++ lnmarkets/futures/get_carry_fees.py | 28 +++ lnmarkets/futures/get_fixing_history.py | 28 +++ lnmarkets/futures/get_index_history.py | 25 +++ lnmarkets/futures/get_leaderboard.py | 10 + lnmarkets/futures/get_market_details.py | 10 + lnmarkets/futures/get_ohlc_history.py | 18 ++ lnmarkets/futures/get_price_history.py | 17 ++ lnmarkets/futures/get_ticker.py | 11 ++ lnmarkets/futures/get_trade.py | 15 ++ lnmarkets/futures/get_trades.py | 18 ++ lnmarkets/futures/new_trade.py | 26 +++ lnmarkets/futures/types.py | 155 ++++++++++++++++ lnmarkets/futures/update_trade.py | 17 ++ lnmarkets/notifications/__init__.py | 7 + lnmarkets/notifications/get_notifications.py | 11 ++ lnmarkets/notifications/mark_all_as_read.py | 8 + lnmarkets/notifications/types.py | 9 + lnmarkets/options/__init__.py | 39 ++++ lnmarkets/options/close_all_trades.py | 11 ++ lnmarkets/options/close_trade.py | 15 ++ lnmarkets/options/get_instrument.py | 15 ++ lnmarkets/options/get_instruments.py | 14 ++ lnmarkets/options/get_market_details.py | 10 + lnmarkets/options/get_trade.py | 15 ++ lnmarkets/options/get_trades.py | 18 ++ lnmarkets/options/get_volatility_index.py | 10 + lnmarkets/options/new_trade.py | 18 ++ lnmarkets/options/types.py | 123 +++++++++++++ lnmarkets/options/update_trade.py | 16 ++ lnmarkets/oracle/__init__.py | 2 + lnmarkets/oracle/get_index.py | 21 +++ lnmarkets/oracle/get_last_price.py | 21 +++ lnmarkets/rest.py | 107 +++++++++++ lnmarkets/swaps/__init__.py | 10 + lnmarkets/swaps/get_swap.py | 15 ++ lnmarkets/swaps/get_swap_by_source_id.py | 16 ++ lnmarkets/swaps/get_swaps.py | 17 ++ lnmarkets/swaps/new_swap.py | 17 ++ lnmarkets/swaps/types.py | 25 +++ lnmarkets/types.py | 1 + lnmarkets/user/__init__.py | 41 +++++ lnmarkets/user/deposit.py | 21 +++ lnmarkets/user/deposit_synthetic_usd.py | 23 +++ lnmarkets/user/get_bitcoin_addresses.py | 20 ++ lnmarkets/user/get_deposit.py | 15 ++ lnmarkets/user/get_user.py | 10 + lnmarkets/user/get_withdrawal.py | 15 ++ lnmarkets/user/get_withdrawals.py | 10 + lnmarkets/user/new_bitcoin_address.py | 19 ++ lnmarkets/user/transfer.py | 20 ++ lnmarkets/user/types.py | 181 ++++++++++++++++++ lnmarkets/user/update_user.py | 20 ++ lnmarkets/user/withdraw.py | 24 +++ lnmarkets/user/withdraw_synthetic_usd.py | 25 +++ poetry.lock | 183 +++++++++++++++++++ scripts/spell-check.sh | 3 + 67 files changed, 1843 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 cspell.json create mode 100644 images/lnmarkets-logo.svg create mode 100644 lnmarkets/__init__.py create mode 100644 lnmarkets/futures/__init__.py create mode 100644 lnmarkets/futures/add_margin.py create mode 100644 lnmarkets/futures/cancel_all_trades.py create mode 100644 lnmarkets/futures/cancel_trade.py create mode 100644 lnmarkets/futures/cash_in.py create mode 100644 lnmarkets/futures/close_all_trades.py create mode 100644 lnmarkets/futures/close_trade.py create mode 100644 lnmarkets/futures/get_carry_fees.py create mode 100644 lnmarkets/futures/get_fixing_history.py create mode 100644 lnmarkets/futures/get_index_history.py create mode 100644 lnmarkets/futures/get_leaderboard.py create mode 100644 lnmarkets/futures/get_market_details.py create mode 100644 lnmarkets/futures/get_ohlc_history.py create mode 100644 lnmarkets/futures/get_price_history.py create mode 100644 lnmarkets/futures/get_ticker.py create mode 100644 lnmarkets/futures/get_trade.py create mode 100644 lnmarkets/futures/get_trades.py create mode 100644 lnmarkets/futures/new_trade.py create mode 100644 lnmarkets/futures/types.py create mode 100644 lnmarkets/futures/update_trade.py create mode 100644 lnmarkets/notifications/__init__.py create mode 100644 lnmarkets/notifications/get_notifications.py create mode 100644 lnmarkets/notifications/mark_all_as_read.py create mode 100644 lnmarkets/notifications/types.py create mode 100644 lnmarkets/options/__init__.py create mode 100644 lnmarkets/options/close_all_trades.py create mode 100644 lnmarkets/options/close_trade.py create mode 100644 lnmarkets/options/get_instrument.py create mode 100644 lnmarkets/options/get_instruments.py create mode 100644 lnmarkets/options/get_market_details.py create mode 100644 lnmarkets/options/get_trade.py create mode 100644 lnmarkets/options/get_trades.py create mode 100644 lnmarkets/options/get_volatility_index.py create mode 100644 lnmarkets/options/new_trade.py create mode 100644 lnmarkets/options/types.py create mode 100644 lnmarkets/options/update_trade.py create mode 100644 lnmarkets/oracle/__init__.py create mode 100644 lnmarkets/oracle/get_index.py create mode 100644 lnmarkets/oracle/get_last_price.py create mode 100644 lnmarkets/rest.py create mode 100644 lnmarkets/swaps/__init__.py create mode 100644 lnmarkets/swaps/get_swap.py create mode 100644 lnmarkets/swaps/get_swap_by_source_id.py create mode 100644 lnmarkets/swaps/get_swaps.py create mode 100644 lnmarkets/swaps/new_swap.py create mode 100644 lnmarkets/swaps/types.py create mode 100644 lnmarkets/types.py create mode 100644 lnmarkets/user/__init__.py create mode 100644 lnmarkets/user/deposit.py create mode 100644 lnmarkets/user/deposit_synthetic_usd.py create mode 100644 lnmarkets/user/get_bitcoin_addresses.py create mode 100644 lnmarkets/user/get_deposit.py create mode 100644 lnmarkets/user/get_user.py create mode 100644 lnmarkets/user/get_withdrawal.py create mode 100644 lnmarkets/user/get_withdrawals.py create mode 100644 lnmarkets/user/new_bitcoin_address.py create mode 100644 lnmarkets/user/transfer.py create mode 100644 lnmarkets/user/types.py create mode 100644 lnmarkets/user/update_user.py create mode 100644 lnmarkets/user/withdraw.py create mode 100644 lnmarkets/user/withdraw_synthetic_usd.py create mode 100644 poetry.lock create mode 100755 scripts/spell-check.sh diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d623851 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.tabSize": 2 +} \ No newline at end of file diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..beb6458 --- /dev/null +++ b/cspell.json @@ -0,0 +1,27 @@ +{ + "dictionaries": [ + "python", + "en-gb" + ], + "ignorePaths": [ + ".venv", + ".gitignore", + "pyproject.toml", + "*.svg", + "poetry.lock" + ], + "words": [ + "cashin", + "linkingpublickey", + "lnmarkets", + "lnurl", + "nostr", + "ohlcs", + "pipreqs", + "stoploss", + "takeprofit", + "topt", + "Totp", + "webauthn" + ] +} diff --git a/images/lnmarkets-logo.svg b/images/lnmarkets-logo.svg new file mode 100644 index 0000000..3e11618 --- /dev/null +++ b/images/lnmarkets-logo.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lnmarkets/__init__.py b/lnmarkets/__init__.py new file mode 100644 index 0000000..226c0d2 --- /dev/null +++ b/lnmarkets/__init__.py @@ -0,0 +1,3 @@ +from .rest import LNMClient + +from .types import UUID diff --git a/lnmarkets/futures/__init__.py b/lnmarkets/futures/__init__.py new file mode 100644 index 0000000..5aebfdf --- /dev/null +++ b/lnmarkets/futures/__init__.py @@ -0,0 +1,40 @@ +from .add_margin import add_margin, AddMarginParams +from .cancel_all_trades import cancel_all_trades, CancelAllTradesResponse +from .cancel_trade import cancel_trade, CancelTradeParams +from .cash_in import cash_in, CashInParams +from .close_all_trades import close_all_trades, CloseAllTradesResponse +from .close_trade import close_trade, CloseTradeParams +from .get_carry_fees import get_carry_fees_history, CarryFeesHistoryParams, CarryFeesHistoryResponse +from .get_fixing_history import get_fixing_history, FixingHistoryResponse +from .get_index_history import get_index_history +from .get_leaderboard import get_leaderboard +from .get_market_details import get_market_details +from .get_ohlc_history import get_ohlc_history +from .get_price_history import get_price_history +from .get_ticker import get_ticker +from .get_trades import get_trades + +from .types import ( + FuturesCanceledTrade, + FuturesClosedTrade, + FuturesTrade, + FuturesTradeStatus, + FuturesMarketDetails, + FuturesMarketDetailsCarry, + FuturesMarketDetailsCount, + FuturesMarketDetailsFees, + FuturesMarketDetailsLeverage, + FuturesMarketDetailsLimits, + FuturesMarketDetailsQuantity, + FuturesMarketDetailsTier, + FuturesMarketDetailsTrading, + FuturesOpenOrRunningTrade, + FuturesOpenTrade, + FuturesRunningTrade, + FuturesTicker, + FuturesTradeSide, + FuturesTradeType, + OHLC, + OHLCRange, + UUID, +) diff --git a/lnmarkets/futures/add_margin.py b/lnmarkets/futures/add_margin.py new file mode 100644 index 0000000..7c53911 --- /dev/null +++ b/lnmarkets/futures/add_margin.py @@ -0,0 +1,16 @@ +import json +from typing import NotRequired, Required, TypedDict +from lnmarkets import LNMClient +from lnmarkets.futures.types import FuturesRunningTrade, UUID + + +class AddMarginParams(TypedDict): + amount: Required[float] + id: NotRequired[UUID] + + +def add_margin(client: LNMClient, params: AddMarginParams) -> FuturesRunningTrade: + """ + @see https://docs.lnmarkets.com/api/operations/futuresaddmargin + """ + return json.loads(client.request_api('POST', '/futures/margin', params, True)) diff --git a/lnmarkets/futures/cancel_all_trades.py b/lnmarkets/futures/cancel_all_trades.py new file mode 100644 index 0000000..ae47a0e --- /dev/null +++ b/lnmarkets/futures/cancel_all_trades.py @@ -0,0 +1,15 @@ +import json +from lnmarkets import LNMClient +from typing import List, TypedDict +from lnmarkets.futures.types import FuturesCanceledTrade + + +class CancelAllTradesResponse(TypedDict): + trades: List[FuturesCanceledTrade] + + +def cancel_all_trades(client: LNMClient) -> CancelAllTradesResponse: + """ + @see https://docs.lnmarkets.com/api/operations/futurescancelalltrades + """ + return json.loads(client.request_api('DELETE', '/futures/all/cancel', {}, True)) diff --git a/lnmarkets/futures/cancel_trade.py b/lnmarkets/futures/cancel_trade.py new file mode 100644 index 0000000..a920bde --- /dev/null +++ b/lnmarkets/futures/cancel_trade.py @@ -0,0 +1,15 @@ +import json +from lnmarkets import LNMClient, UUID +from typing import TypedDict, Required +from lnmarkets.futures.types import FuturesCanceledTrade + + +class CancelTradeParams(TypedDict): + id: Required[UUID] + + +def cancel_trade(client: LNMClient, params: CancelTradeParams) -> FuturesCanceledTrade: + """ + @see https://docs.lnmarkets.com/api/operations/futurescanceltrade + """ + return json.loads(client.request_api('POST', '/futures/cancel', params, True)) \ No newline at end of file diff --git a/lnmarkets/futures/cash_in.py b/lnmarkets/futures/cash_in.py new file mode 100644 index 0000000..acc7255 --- /dev/null +++ b/lnmarkets/futures/cash_in.py @@ -0,0 +1,16 @@ +import json +from lnmarkets import LNMClient, UUID +from typing import TypedDict, Required +from lnmarkets.futures.types import FuturesRunningTrade + + +class CashInParams(TypedDict): + amount: Required[float] + id: Required[UUID] + + +def cash_in(client: LNMClient, params: CashInParams) -> FuturesRunningTrade: + """ + @see https://docs.lnmarkets.com/api/operations/futurescashin + """ + return json.loads(client.request_api('POST', '/futures/cash-in', params, True)) diff --git a/lnmarkets/futures/close_all_trades.py b/lnmarkets/futures/close_all_trades.py new file mode 100644 index 0000000..41c5623 --- /dev/null +++ b/lnmarkets/futures/close_all_trades.py @@ -0,0 +1,16 @@ +import json +from lnmarkets import LNMClient +from typing import List, TypedDict +from lnmarkets.futures.types import FuturesClosedTrade + + +class CloseAllTradesResponse(TypedDict): + trades: List[FuturesClosedTrade] + + +def close_all_trades(client: LNMClient) -> CloseAllTradesResponse: + """ + @see https://docs.lnmarkets.com/api/operations/futuresclosealltrades + """ + return json.loads(client.request_api('DELETE', '/futures/all/close', {}, True)) + diff --git a/lnmarkets/futures/close_trade.py b/lnmarkets/futures/close_trade.py new file mode 100644 index 0000000..ae8291d --- /dev/null +++ b/lnmarkets/futures/close_trade.py @@ -0,0 +1,15 @@ +import json +from lnmarkets import LNMClient, UUID +from typing import TypedDict, Required +from lnmarkets.futures.types import FuturesClosedTrade + + +class CloseTradeParams(TypedDict): + id: Required[UUID] + + +def close_trade(client: LNMClient, params: CloseTradeParams) -> FuturesClosedTrade: + """ + @see https://docs.lnmarkets.com/api/operations/futuresclosetrade + """ + return json.loads(client.request_api('DELETE', '/futures', params, True)) diff --git a/lnmarkets/futures/get_carry_fees.py b/lnmarkets/futures/get_carry_fees.py new file mode 100644 index 0000000..b9f463c --- /dev/null +++ b/lnmarkets/futures/get_carry_fees.py @@ -0,0 +1,28 @@ +import json +from lnmarkets.futures.types import UUID +from lnmarkets import LNMClient +from typing import List, TypedDict, Required, NotRequired + + +class CarryFeesHistoryParams(TypedDict): + from_ts: Required[int] + to: Required[int] + limit: NotRequired[int] + + +class CarryFee(TypedDict): + fee: float + fixing_id: UUID + id: UUID + ts: int + + +class CarryFeesHistoryResponse(TypedDict): + carryFees: List[CarryFee] + + +def get_carry_fees_history(client: LNMClient, params: CarryFeesHistoryParams) -> CarryFeesHistoryResponse: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgetcarryfees + """ + return json.loads(client.request_api('GET', '/futures/carry-fees', params, False)) diff --git a/lnmarkets/futures/get_fixing_history.py b/lnmarkets/futures/get_fixing_history.py new file mode 100644 index 0000000..5014948 --- /dev/null +++ b/lnmarkets/futures/get_fixing_history.py @@ -0,0 +1,28 @@ +import json +from lnmarkets.futures.types import UUID +from lnmarkets import LNMClient +from typing import List, TypedDict, Optional, NotRequired + + +class FixingHistoryParams(TypedDict): + from_ts: NotRequired[int] + to: NotRequired[int] + limit: NotRequired[int] + + +class FixingHistory(TypedDict): + fee: float + fixing_id: UUID + id: UUID + ts: int + + +class FixingHistoryResponse(TypedDict): + fixingHistory: List[FixingHistory] + + +def get_fixing_history(client: LNMClient, params: Optional[FixingHistoryParams] = None) -> FixingHistoryResponse: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgetfixinghistory + """ + return json.loads(client.request_api('GET', '/futures/history/fixing', params or {}, False)) diff --git a/lnmarkets/futures/get_index_history.py b/lnmarkets/futures/get_index_history.py new file mode 100644 index 0000000..919bfea --- /dev/null +++ b/lnmarkets/futures/get_index_history.py @@ -0,0 +1,25 @@ +import json +from lnmarkets import LNMClient +from typing import List, TypedDict, Optional, NotRequired + + +class HistoryParams(TypedDict): + from_ts: NotRequired[int] + to: NotRequired[int] + limit: NotRequired[int] + + +class IndexHistory(TypedDict): + time: int + value: float + + +class IndexHistoryResponse(TypedDict): + indexHistory: List[IndexHistory] + + +def get_index_history(client: LNMClient, params: Optional[HistoryParams] = None) -> IndexHistoryResponse: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgetindexhistory + """ + return json.loads(client.request_api('GET', '/futures/history/index', params or {}, False)) diff --git a/lnmarkets/futures/get_leaderboard.py b/lnmarkets/futures/get_leaderboard.py new file mode 100644 index 0000000..d4bbded --- /dev/null +++ b/lnmarkets/futures/get_leaderboard.py @@ -0,0 +1,10 @@ +import json +from lnmarkets.user.types import Leaderboard +from lnmarkets import LNMClient + + +def get_leaderboard(client: LNMClient) -> Leaderboard: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgetleaderboard + """ + return json.loads(client.request_api('GET', '/futures/leaderboard', {}, False)) diff --git a/lnmarkets/futures/get_market_details.py b/lnmarkets/futures/get_market_details.py new file mode 100644 index 0000000..2933bdc --- /dev/null +++ b/lnmarkets/futures/get_market_details.py @@ -0,0 +1,10 @@ +import json +from lnmarkets import LNMClient +from lnmarkets.futures.types import FuturesMarketDetails + + +def get_market_details(client: LNMClient) -> FuturesMarketDetails: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgetfuturesmarket + """ + return json.loads(client.request_api('GET', '/futures/market', {}, False)) \ No newline at end of file diff --git a/lnmarkets/futures/get_ohlc_history.py b/lnmarkets/futures/get_ohlc_history.py new file mode 100644 index 0000000..dbf369d --- /dev/null +++ b/lnmarkets/futures/get_ohlc_history.py @@ -0,0 +1,18 @@ +import json +from lnmarkets.futures.types import OHLC, OHLCRange +from typing import List, TypedDict, Required, NotRequired +from lnmarkets import LNMClient + + +class OHLCHistoryParams(TypedDict): + from_ts: Required[int] + to: Required[int] + range: Required[OHLCRange] + limit: NotRequired[int] + + +def get_ohlc_history(client: LNMClient, params: OHLCHistoryParams) -> List[OHLC]: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgetohlcs + """ + return json.loads(client.request_api('GET', '/futures/ohlcs', params, False)) diff --git a/lnmarkets/futures/get_price_history.py b/lnmarkets/futures/get_price_history.py new file mode 100644 index 0000000..e3fb0b3 --- /dev/null +++ b/lnmarkets/futures/get_price_history.py @@ -0,0 +1,17 @@ +import json +from typing import List, TypedDict, Required, NotRequired +from lnmarkets import LNMClient + + +class HistoryParams(TypedDict): + from_ts: Required[int] + to: Required[int] + limit: NotRequired[int] + + +def get_price_history(client: LNMClient, params: HistoryParams) -> List[dict]: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgetpricehistory + """ + return json.loads(client.request_api('GET', '/futures/history/price', params, False)) + diff --git a/lnmarkets/futures/get_ticker.py b/lnmarkets/futures/get_ticker.py new file mode 100644 index 0000000..83e8616 --- /dev/null +++ b/lnmarkets/futures/get_ticker.py @@ -0,0 +1,11 @@ +import json +from lnmarkets import LNMClient +from lnmarkets.futures.types import FuturesTicker + + +def get_ticker(client: LNMClient) -> FuturesTicker: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgetticker + """ + return json.loads(client.request_api('GET', '/futures/ticker', {}, False)) + diff --git a/lnmarkets/futures/get_trade.py b/lnmarkets/futures/get_trade.py new file mode 100644 index 0000000..c5faa6f --- /dev/null +++ b/lnmarkets/futures/get_trade.py @@ -0,0 +1,15 @@ +import json +from lnmarkets.futures.types import FuturesTrade +from lnmarkets import LNMClient +from typing import Required, UUID, TypedDict + + +class GetTradeParams(TypedDict): + id: Required[UUID] + + +def get_trade(client: LNMClient, params: GetTradeParams) -> FuturesTrade: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgettrade + """ + return json.loads(client.request_api('GET', f'/futures/trades/{params["id"]}', {}, False)) diff --git a/lnmarkets/futures/get_trades.py b/lnmarkets/futures/get_trades.py new file mode 100644 index 0000000..3955b18 --- /dev/null +++ b/lnmarkets/futures/get_trades.py @@ -0,0 +1,18 @@ +import json +from typing import List, TypedDict, Required, NotRequired +from lnmarkets import LNMClient +from lnmarkets.futures.types import FuturesTrade, FuturesTradeStatus + + +class GetTradesParams(TypedDict): + type: Required[FuturesTradeStatus] + from_ts: NotRequired[int] + to: NotRequired[int] + limit: NotRequired[int] + + +def get_trades(client: LNMClient, params: GetTradesParams) -> List[FuturesTrade]: + """ + @see https://docs.lnmarkets.com/api/operations/futuresgettrades + """ + return json.loads(client.request_api('GET', '/futures', params, True)) diff --git a/lnmarkets/futures/new_trade.py b/lnmarkets/futures/new_trade.py new file mode 100644 index 0000000..e27aaea --- /dev/null +++ b/lnmarkets/futures/new_trade.py @@ -0,0 +1,26 @@ +import json +from lnmarkets import LNMClient +from typing import TypedDict, Required, NotRequired +from lnmarkets.futures import ( + FuturesOpenOrRunningTrade, + FuturesTradeSide, + FuturesTradeType, +) + + +class NewTradeParams(TypedDict): + leverage: Required[float] + side: Required[FuturesTradeSide] + type: Required[FuturesTradeType] + margin: NotRequired[float] + price: NotRequired[float] + quantity: NotRequired[float] + stoploss: NotRequired[float] + takeprofit: NotRequired[float] + + +def new_trade(client: LNMClient, params: NewTradeParams) -> FuturesOpenOrRunningTrade: + """ + @see https://docs.lnmarkets.com/api/operations/futuresnewtrade + """ + return json.loads(client.request_api('POST', '/futures', params, True)) diff --git a/lnmarkets/futures/types.py b/lnmarkets/futures/types.py new file mode 100644 index 0000000..ae6af71 --- /dev/null +++ b/lnmarkets/futures/types.py @@ -0,0 +1,155 @@ +from typing import List, Literal, Optional, TypedDict, Union +from lnmarkets import UUID + + +class FuturesCanceledTrade(TypedDict): + canceled: Literal[True] + closed: Literal[False] + closed_ts: int + market_filled_ts: None + open: Literal[False] + running: Literal[False] + type: Literal['l'] + + +class FuturesClosedTrade(TypedDict): + canceled: Literal[False] + closed: Literal[True] + closed_ts: int + exit_price: float + market_filled_ts: int + open: Literal[False] + running: Literal[False] + + +class FuturesMarketDetailsTier(TypedDict): + fees: float + min_volume: float + + +class FuturesMarketDetailsCarry(TypedDict): + hours: List[int] + min: float + + +class FuturesMarketDetailsTrading(TypedDict): + tiers: List[FuturesMarketDetailsTier] + + +class FuturesMarketDetailsFees(TypedDict): + carry: FuturesMarketDetailsCarry + trading: FuturesMarketDetailsTrading + + +class FuturesMarketDetailsCount(TypedDict): + max: int + + +class FuturesMarketDetailsLeverage(TypedDict): + max: float + min: float + + +class FuturesMarketDetailsQuantity(TypedDict): + max: float + min: float + trade: float + + +class FuturesMarketDetailsLimits(TypedDict): + count: FuturesMarketDetailsCount + leverage: FuturesMarketDetailsLeverage + quantity: FuturesMarketDetailsQuantity + + +class FuturesMarketDetails(TypedDict): + active: bool + fees: FuturesMarketDetailsFees + limits: FuturesMarketDetailsLimits + + +class FuturesOpenTrade(TypedDict): + canceled: Literal[False] + closed: Literal[False] + closed_ts: None + market_filled_ts: None + running: Literal[False] + type: Literal['l'] + + +class FuturesRunningTrade(TypedDict): + canceled: Literal[False] + closed: Literal[False] + closed_ts: None + market_filled_ts: int + running: Literal[True] + + +type FuturesOpenOrRunningTrade = Union[FuturesOpenTrade, FuturesRunningTrade] + + +class FuturesTicker(TypedDict): + ask_price: float + bid_price: float + carry_fee_rate: float + carry_fee_timestamp: int + index: float + last_price: float + + +class FuturesTrade(TypedDict): + canceled: bool + closed: bool + closed_ts: Optional[int] + closing_fee: float + creation_ts: int + entry_margin: Optional[float] + entry_price: Optional[float] + exit_price: Optional[float] + id: UUID + last_update_ts: int + leverage: float + liquidation: float + maintenance_margin: float + margin: float + market_filled_ts: Optional[int] + open: bool + opening_fee: float + pl: float + price: float + quantity: float + running: bool + side: Literal['b', 's'] + sum_carry_fees: float + type: Literal['l', 'm'] + uid: UUID + + +type FuturesTradeSide = Literal['b', 's'] +type FuturesTradeStatus = Literal['closed', 'open', 'running'] +type FuturesTradeType = Literal['l', 'm'] + + +class OHLC(TypedDict): + close: float + high: float + low: float + open: float + time: int + volume: float + + +type OHLCRange = Literal[ + '1', + '1D', + '1M', + '1W', + '3', + '3M', + '5', + '15', + '30', + '60', + '120', + '240' +] diff --git a/lnmarkets/futures/update_trade.py b/lnmarkets/futures/update_trade.py new file mode 100644 index 0000000..7c790b7 --- /dev/null +++ b/lnmarkets/futures/update_trade.py @@ -0,0 +1,17 @@ +import json +from lnmarkets import LNMClient +from lnmarkets.futures import UUID, FuturesOpenOrRunningTrade +from typing import TypedDict, Required, Literal + + +class UpdateTradeParams(TypedDict): + id: Required[UUID] + type: Required[Literal['stoploss', 'takeprofit']] + value: Required[float] + + +def update_trade(client: LNMClient, params: UpdateTradeParams) -> FuturesOpenOrRunningTrade: + """ + @see https://docs.lnmarkets.com/api/operations/futuresupdatetrade + """ + return json.loads(client.request_api('PUT', '/futures', params, True)) diff --git a/lnmarkets/notifications/__init__.py b/lnmarkets/notifications/__init__.py new file mode 100644 index 0000000..4c1fb0a --- /dev/null +++ b/lnmarkets/notifications/__init__.py @@ -0,0 +1,7 @@ +from .get_notifications import get_all_notifications +from .mark_all_as_read import mark_all_notifications_as_read + + +from .types import ( + Notification, +) \ No newline at end of file diff --git a/lnmarkets/notifications/get_notifications.py b/lnmarkets/notifications/get_notifications.py new file mode 100644 index 0000000..d444585 --- /dev/null +++ b/lnmarkets/notifications/get_notifications.py @@ -0,0 +1,11 @@ +import json +from typing import List, TypedDict +from lnmarkets import LNMClient +from lnmarkets.notifications.types import Notification + + +def get_all_notifications(client: LNMClient) -> List[Notification]: + """ + @see https://docs.lnmarkets.com/api/operations/notificationsfetchnotifications + """ + return json.loads(client.request_api('GET', '/notifications', {}, True)) \ No newline at end of file diff --git a/lnmarkets/notifications/mark_all_as_read.py b/lnmarkets/notifications/mark_all_as_read.py new file mode 100644 index 0000000..ee63f1e --- /dev/null +++ b/lnmarkets/notifications/mark_all_as_read.py @@ -0,0 +1,8 @@ +from lnmarkets import LNMClient + + +def mark_all_notifications_as_read(client: LNMClient) -> None: + """ + @see https://docs.lnmarkets.com/api/operations/notificationsmarkallnotificationsasread + """ + client.request_api('DELETE', '/notifications/all', {}, True) diff --git a/lnmarkets/notifications/types.py b/lnmarkets/notifications/types.py new file mode 100644 index 0000000..9c514d0 --- /dev/null +++ b/lnmarkets/notifications/types.py @@ -0,0 +1,9 @@ +from lnmarkets import UUID +from typing import TypedDict, Any + + +class Notification(TypedDict): + creation_ts: int + data: Any + event: str + id: UUID diff --git a/lnmarkets/options/__init__.py b/lnmarkets/options/__init__.py new file mode 100644 index 0000000..8e3b611 --- /dev/null +++ b/lnmarkets/options/__init__.py @@ -0,0 +1,39 @@ +from .close_all_trades import close_all_trades +from .close_trade import close_trade, CloseTradeParams +from .get_instrument import get_instrument, GetInstrumentParams +from .get_instruments import get_instruments +from .get_market_details import get_market_details +from .get_trade import get_trade, GetTradeParams +from .get_trades import get_trades, GetTradesParams +from .get_volatility_index import get_volatility_index +from .new_trade import new_trade, NewTradeParams +from .update_trade import update_trade, UpdateTradeParams + + +from .types import ( + OptionsTradeRunningWithDelta, + OptionsInstrument, + OptionsMarketDetails, + OptionsMarketDetailsFees, + OptionsMarketDetailsLimits, + OptionsMarketDetailsLimitsCount, + OptionsMarketDetailsLimitsMargin, + OptionsMarketDetailsLimitsQuantity, + OptionsSettlement, + OptionsTrade, + OptionsSide, + OptionsTradeClosed, + OptionsTradeClosedCash, + OptionsTradeClosedPhysical, + OptionsTradeExpired, + OptionsTradeExpiredCash, + OptionsTradeExpiredPhysical, + OptionsTradeExpiredPhysicalDelivered, + OptionsTradeExpiredPhysicalNotDelivered, + OptionsTradeOrder, + OptionsTradeRunning, + OptionsTradeStatus, + OptionsTradeWithDelta, + OptionsType, + OptionsVolatilityIndex, +) \ No newline at end of file diff --git a/lnmarkets/options/close_all_trades.py b/lnmarkets/options/close_all_trades.py new file mode 100644 index 0000000..35087a9 --- /dev/null +++ b/lnmarkets/options/close_all_trades.py @@ -0,0 +1,11 @@ +import json +from typing import List +from lnmarkets.options.types import OptionsTradeClosed +from lnmarkets import LNMClient + + +def close_all_trades(client: LNMClient) -> List[OptionsTradeClosed]: + """ + @see https://docs.lnmarkets.com/api/operations/optionsclosealltrades + """ + return json.loads(client.request_api('DELETE', '/options/all/close', {}, True)) \ No newline at end of file diff --git a/lnmarkets/options/close_trade.py b/lnmarkets/options/close_trade.py new file mode 100644 index 0000000..92e265d --- /dev/null +++ b/lnmarkets/options/close_trade.py @@ -0,0 +1,15 @@ +import json +from typing import TypedDict, Required +from lnmarkets.options.types import UUID, OptionsTradeClosed +from lnmarkets import LNMClient + + +class CloseTradeParams(TypedDict): + id: Required[UUID] + + +def close_trade(client: LNMClient, params: CloseTradeParams) -> OptionsTradeClosed: + """ + @see https://docs.lnmarkets.com/api/operations/optionsclosetrade + """ + return json.loads(client.request_api('DELETE', '/options', params, True)) \ No newline at end of file diff --git a/lnmarkets/options/get_instrument.py b/lnmarkets/options/get_instrument.py new file mode 100644 index 0000000..f66f19f --- /dev/null +++ b/lnmarkets/options/get_instrument.py @@ -0,0 +1,15 @@ +import json +from typing import TypedDict, Required +from lnmarkets.options.types import OptionsInstrument +from lnmarkets import LNMClient + + +class GetInstrumentParams(TypedDict): + instrument_name: Required[str] + + +def get_instrument(client: LNMClient, params: GetInstrumentParams) -> OptionsInstrument: + """ + @see https://docs.lnmarkets.com/api/operations/optionsgetinstrument + """ + return json.loads(client.request_api('GET', '/options/instrument', params, False)) \ No newline at end of file diff --git a/lnmarkets/options/get_instruments.py b/lnmarkets/options/get_instruments.py new file mode 100644 index 0000000..d4974f7 --- /dev/null +++ b/lnmarkets/options/get_instruments.py @@ -0,0 +1,14 @@ +import json +from typing import List, TypedDict +from lnmarkets import LNMClient + + +class GetInstrumentsResponse(TypedDict): + instruments: List[str] + + +def get_instruments(client: LNMClient) -> GetInstrumentsResponse: + """ + @see https://docs.lnmarkets.com/api/operations/optionsgetinstruments + """ + return json.loads(client.request_api('GET', '/options/instruments', {}, False)) \ No newline at end of file diff --git a/lnmarkets/options/get_market_details.py b/lnmarkets/options/get_market_details.py new file mode 100644 index 0000000..ce2a1fc --- /dev/null +++ b/lnmarkets/options/get_market_details.py @@ -0,0 +1,10 @@ +import json +from lnmarkets.options.types import OptionsMarketDetails +from lnmarkets import LNMClient + + +def get_market_details(client: LNMClient) -> OptionsMarketDetails: + """ + @see https://docs.lnmarkets.com/api/operations/optionsgetoptionsmarket + """ + return json.loads(client.request_api('GET', '/options', {}, False)) \ No newline at end of file diff --git a/lnmarkets/options/get_trade.py b/lnmarkets/options/get_trade.py new file mode 100644 index 0000000..74353fc --- /dev/null +++ b/lnmarkets/options/get_trade.py @@ -0,0 +1,15 @@ +import json +from typing import TypedDict, Required +from lnmarkets.options.types import UUID, OptionsTrade +from lnmarkets import LNMClient + + +class GetTradeParams(TypedDict): + id: Required[UUID] + + +def get_trade(client: LNMClient, params: GetTradeParams) -> OptionsTrade: + """ + @see https://docs.lnmarkets.com/api/operations/optionsgettrade + """ + return json.loads(client.request_api('GET', f'/options/trades/{params["id"]}', {}, True)) \ No newline at end of file diff --git a/lnmarkets/options/get_trades.py b/lnmarkets/options/get_trades.py new file mode 100644 index 0000000..8909ffb --- /dev/null +++ b/lnmarkets/options/get_trades.py @@ -0,0 +1,18 @@ +import json +from typing import List, TypedDict, Required, NotRequired +from lnmarkets.options.types import OptionsTrade, OptionsTradeStatus +from lnmarkets import LNMClient + + +class GetTradesParams(TypedDict): + from_ts: Required[int] + to: Required[int] + status: Required[OptionsTradeStatus] + limit: NotRequired[int] + + +def get_trades(client: LNMClient, params: GetTradesParams) -> List[OptionsTrade]: + """ + @see https://docs.lnmarkets.com/api/operations/optionsgettrades + """ + return json.loads(client.request_api('GET', '/options/trades', params, True)) \ No newline at end of file diff --git a/lnmarkets/options/get_volatility_index.py b/lnmarkets/options/get_volatility_index.py new file mode 100644 index 0000000..5b659e4 --- /dev/null +++ b/lnmarkets/options/get_volatility_index.py @@ -0,0 +1,10 @@ +import json +from lnmarkets.options.types import OptionsVolatilityIndex +from lnmarkets import LNMClient + + +def get_volatility_index(client: LNMClient) -> OptionsVolatilityIndex: + """ + @see https://docs.lnmarkets.com/api/operations/optionsgetvolatilityindex + """ + return json.loads(client.request_api('GET', '/options/volatility-index', {}, False)) \ No newline at end of file diff --git a/lnmarkets/options/new_trade.py b/lnmarkets/options/new_trade.py new file mode 100644 index 0000000..bfbd72f --- /dev/null +++ b/lnmarkets/options/new_trade.py @@ -0,0 +1,18 @@ +import json +from typing import TypedDict, Required +from lnmarkets.options.types import OptionsSettlement, OptionsSide, OptionsTradeRunning +from lnmarkets import LNMClient + + +class NewTradeParams(TypedDict): + instrument_name: Required[str] + quantity: Required[int] + settlement: Required[OptionsSettlement] + side: Required[OptionsSide] + + +def new_trade(client: LNMClient, params: NewTradeParams) -> OptionsTradeRunning: + """ + @see https://docs.lnmarkets.com/api/operations/optionsnewtrade + """ + return json.loads(client.request_api('POST', '/options', params, True)) \ No newline at end of file diff --git a/lnmarkets/options/types.py b/lnmarkets/options/types.py new file mode 100644 index 0000000..a5f6a41 --- /dev/null +++ b/lnmarkets/options/types.py @@ -0,0 +1,123 @@ +from typing import TypedDict, Union, Literal +from lnmarkets import UUID + +class OptionsInstrument(TypedDict): + volatility: float + +class OptionsMarketDetailsFees(TypedDict): + trading: float + +class OptionsMarketDetailsLimitsCount(TypedDict): + max: float + +class OptionsMarketDetailsLimitsMargin(TypedDict): + max: float + min: float + +class OptionsMarketDetailsLimitsQuantity(TypedDict): + max: float + min: float + +class OptionsMarketDetailsLimits(TypedDict): + count: OptionsMarketDetailsLimitsCount + margin: OptionsMarketDetailsLimitsMargin + quantity: OptionsMarketDetailsLimitsQuantity + +class OptionsMarketDetails(TypedDict): + active: bool + fees: OptionsMarketDetailsFees + limits: OptionsMarketDetailsLimits + +type OptionsSettlement = Literal['cash', 'physical'] +type OptionsSide = Literal['b', 's'] +type OptionsType = Literal['c', 'p'] + +class OptionsTrade(TypedDict): + closed: bool + closed_ts: float | None + closing_fee: float + creation_ts: float + domestic: str + exercised: bool + expired: bool + expiry_ts: float + fixing_price: float | None + forward: float + forward_point: float + id: UUID + leg_id: UUID + maintenance_margin: float + margin: float + opening_fee: float + physical_delivery_id: str | None + pl: float + quantity: float + running: bool + settlement: OptionsSettlement + side: OptionsSide + strike: float + type: OptionsType + uid: UUID + volatility: float + +class OptionsTradeClosedCash(OptionsTrade, TypedDict): + closed: Literal[True] + closed_ts: float + expired: Literal[False] + fixing_price: float + physical_delivery_id: None + +class OptionsTradeClosedPhysical(OptionsTrade, TypedDict): + closed: Literal[True] + closed_ts: float + fixing_price: float + physical_delivery_id: str + +OptionsTradeClosed = Union[OptionsTradeClosedCash, OptionsTradeClosedPhysical] + +class OptionsTradeExpiredCash(OptionsTrade, TypedDict): + closed: Literal[False] + closed_ts: float + expired: Literal[True] + fixing_price: float + physical_delivery_id: None + +class OptionsTradeExpiredPhysical(OptionsTrade, TypedDict): + closed: Literal[False] + closed_ts: float + expired: Literal[True] + fixing_price: float + +class OptionsTradeExpiredPhysicalDelivered(OptionsTradeExpiredPhysical, TypedDict): + physical_delivery_id: str + +class OptionsTradeExpiredPhysicalNotDelivered(OptionsTradeExpiredPhysical, TypedDict): + physical_delivery_id: None + +OptionsTradeExpired = Union[ + OptionsTradeExpiredCash, + OptionsTradeExpiredPhysicalDelivered, + OptionsTradeExpiredPhysicalNotDelivered +] + +class OptionsTradeOrder(TypedDict): + instrument_name: str + quantity: float + settlement: OptionsSettlement + side: OptionsSide + +class OptionsTradeRunning(OptionsTrade, TypedDict): + closed: Literal[False] + closed_ts: None + physical_delivery_id: None + +class OptionsTradeRunningWithDelta(OptionsTradeRunning, TypedDict): + delta: float + +type OptionsTradeStatus = Literal['closed', 'running'] + +class OptionsTradeWithDelta(OptionsTrade, TypedDict): + delta: float | None + +class OptionsVolatilityIndex(TypedDict): + volatility_index: float diff --git a/lnmarkets/options/update_trade.py b/lnmarkets/options/update_trade.py new file mode 100644 index 0000000..7f72f64 --- /dev/null +++ b/lnmarkets/options/update_trade.py @@ -0,0 +1,16 @@ +import json +from typing import TypedDict, Required +from lnmarkets.options.types import UUID, OptionsSettlement, OptionsTradeRunningWithDelta +from lnmarkets import LNMClient + + +class UpdateTradeParams(TypedDict): + id: Required[UUID] + settlement: Required[OptionsSettlement] + + +def update_trade(client: LNMClient, params: UpdateTradeParams) -> OptionsTradeRunningWithDelta: + """ + @see https://docs.lnmarkets.com/api/operations/optionsupdatetrade + """ + return json.loads(client.request_api('PUT', '/options', params, True)) \ No newline at end of file diff --git a/lnmarkets/oracle/__init__.py b/lnmarkets/oracle/__init__.py new file mode 100644 index 0000000..b83fb15 --- /dev/null +++ b/lnmarkets/oracle/__init__.py @@ -0,0 +1,2 @@ +from .get_index import get_index, GetIndexParams +from .get_last_price import get_last_price, GetLastPriceParams, LastPriceResponse diff --git a/lnmarkets/oracle/get_index.py b/lnmarkets/oracle/get_index.py new file mode 100644 index 0000000..8f00d7e --- /dev/null +++ b/lnmarkets/oracle/get_index.py @@ -0,0 +1,21 @@ +import json +from typing import TypedDict, Required, NotRequired, List +from lnmarkets import LNMClient + + +class GetIndexParams(TypedDict): + from_ts: Required[int] + to: Required[int] + limit: NotRequired[int] + + +class IndexEntry(TypedDict): + index: float + time: int + + +def get_index(client: LNMClient, params: GetIndexParams) -> List[IndexEntry]: + """ + @see https://docs.lnmarkets.com/api/operations/oraclegetindex + """ + return json.loads(client.request_api('GET', '/oracle', params, False)) \ No newline at end of file diff --git a/lnmarkets/oracle/get_last_price.py b/lnmarkets/oracle/get_last_price.py new file mode 100644 index 0000000..18b4de3 --- /dev/null +++ b/lnmarkets/oracle/get_last_price.py @@ -0,0 +1,21 @@ +import json +from typing import TypedDict, Required, NotRequired +from lnmarkets import LNMClient + + +class GetLastPriceParams(TypedDict): + from_ts: Required[int] + to: Required[int] + limit: NotRequired[int] + + +class LastPriceResponse(TypedDict): + last_price: float + time: int + + +def get_last_price(client: LNMClient, params: GetLastPriceParams) -> LastPriceResponse: + """ + @see https://docs.lnmarkets.com/api/operations/oraclegetlastprice + """ + return json.loads(client.request_api('GET', '/oracle/last-price', params, False)) \ No newline at end of file diff --git a/lnmarkets/rest.py b/lnmarkets/rest.py new file mode 100644 index 0000000..e079445 --- /dev/null +++ b/lnmarkets/rest.py @@ -0,0 +1,107 @@ +import os + +from typing import Literal, TypedDict +from urllib.parse import urlencode +from datetime import datetime +from base64 import b64encode +from requests import request + +import hashlib +import hmac +import json + +type Network = Literal['mainnet', 'testnet'] +type Method = Literal['GET', 'POST', 'PUT', 'DELETE'] + +def _get_hostname(network: Network) -> str: + hostname = os.getenv('LNMARKETS_API_HOSTNAME') + + if hostname: + return hostname + elif network == 'testnet': + return 'api.testnet.lnmarkets.com' + else: + return 'api.lnmarkets.com' + +class _LNMOptions(TypedDict): + key: str + secret: str + passphrase: str + network: Network + hostname: str + custom_headers: dict[str, str] + skip_api_key: bool + +class LNMClient(): + def __init__(self, options: _LNMOptions): + self.key = options.get('key', os.getenv('LNMARKETS_API_KEY')) + self.secret = options.get('secret', os.getenv('LNM_API_SECRET')) + self.passphrase = options.get('passphrase', os.getenv('LNM_API_PASSPHRASE')) + self.network = options.get('network', os.getenv('LNM_API_NETWORK', 'mainnet')) + self.hostname = _get_hostname(self.network) + self.custom_headers = options.get('custom_headers') + self.skip_api_key = options.get('skip_api_key', False) + + def _request_options(self, **options: dict[str, str | dict | bool]) -> dict: + credentials: str = options.get('credentials') + method = options.get('method') + path = options.get('path') + params = options.get('params') + opts = { 'headers': {} } + + if method != 'DELETE': + opts['headers']['Content-Type'] = 'application/json' + + if self.custom_headers: + opts['headers'].update(**self.custom_headers) + + if method in ['GET', 'DELETE']: + data = urlencode(params) + elif method in ['POST', 'PUT']: + data = json.dumps(params, separators=(',', ':')) + + if credentials and not self.skip_api_key: + if not self.key: + raise Exception('You need an API key to use an authenticated route') + elif not self.secret: + raise Exception('You need an API secret to use an authenticated route') + elif not self.passphrase: + raise Exception('You need an API passphrase to use an authenticated route') + + ts = str(int(datetime.now().timestamp() * 1000)) + + payload = ts + method + '/v2' + path + data + hashed = hmac.new(bytes(self.secret, 'utf-8'), bytes(payload, 'utf-8'), hashlib.sha256).digest() + signature = b64encode(hashed) + + opts['headers']['LNM-ACCESS-KEY'] = self.key + opts['headers']['LNM-ACCESS-PASSPHRASE'] = self.passphrase + opts['headers']['LNM-ACCESS-TIMESTAMP'] = ts + opts['headers']['LNM-ACCESS-SIGNATURE'] = signature + + opts['resource'] = 'https://' + self.hostname + '/v2' + path + + if method in ['GET', 'DELETE'] and params: + opts['resource'] += '?' + data + + return opts + + def request_api(self, method: Method, path: str, params: dict, credentials: bool = False): + options = { + 'method': method, + 'path': path, + 'params': params, + 'credentials': credentials + } + + opts = self._request_options(**options) + resource = opts.get('resource') + headers = opts.get('headers') + + if method in ['GET', 'DELETE']: + response = request(method, resource, headers = headers) + elif method in ['POST', 'PUT']: + response = request(method, resource, data = json.dumps(params, separators=(',', ':')), headers = headers) + + return response.text + \ No newline at end of file diff --git a/lnmarkets/swaps/__init__.py b/lnmarkets/swaps/__init__.py new file mode 100644 index 0000000..5d97e41 --- /dev/null +++ b/lnmarkets/swaps/__init__.py @@ -0,0 +1,10 @@ +from .get_swap_by_source_id import get_swap_by_source_id, GetSwapBySourceIdParams +from .get_swap import get_swap, GetSwapParams +from .get_swaps import get_swaps, GetSwapsParams +from .new_swap import new_swap, NewSwapParams + +from .types import ( + Swap, + SwapAsset, + SwapSource, +) \ No newline at end of file diff --git a/lnmarkets/swaps/get_swap.py b/lnmarkets/swaps/get_swap.py new file mode 100644 index 0000000..3a47faf --- /dev/null +++ b/lnmarkets/swaps/get_swap.py @@ -0,0 +1,15 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient +from lnmarkets.swaps.types import UUID, Swap + + +class GetSwapParams(TypedDict): + swap_id: Required[UUID] + + +def get_swap(client: LNMClient, params: GetSwapParams) -> Swap: + """ + @see https://docs.lnmarkets.com/api/operations/swapsgetswap + """ + return json.loads(client.request_api('GET', f'/swap/{params["swap_id"]}', {}, True)) \ No newline at end of file diff --git a/lnmarkets/swaps/get_swap_by_source_id.py b/lnmarkets/swaps/get_swap_by_source_id.py new file mode 100644 index 0000000..a43f15c --- /dev/null +++ b/lnmarkets/swaps/get_swap_by_source_id.py @@ -0,0 +1,16 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient +from lnmarkets.swaps.types import UUID, Swap, SwapSource + + +class GetSwapBySourceIdParams(TypedDict): + source_id: Required[UUID] + source: Required[SwapSource] + + +def get_swap_by_source_id(client: LNMClient, params: GetSwapBySourceIdParams) -> Swap: + """ + @see https://docs.lnmarkets.com/api/operations/swapsgetswapbysourceid + """ + return json.loads(client.request_api('GET', f'/swap/source/{params["source_id"]}', {'source': params['source']}, True)) \ No newline at end of file diff --git a/lnmarkets/swaps/get_swaps.py b/lnmarkets/swaps/get_swaps.py new file mode 100644 index 0000000..851ec25 --- /dev/null +++ b/lnmarkets/swaps/get_swaps.py @@ -0,0 +1,17 @@ +import json +from typing import TypedDict, NotRequired +from lnmarkets import LNMClient +from lnmarkets.swaps.types import Swap + + +class GetSwapsParams(TypedDict): + from_ts: NotRequired[int] + to: NotRequired[int] + limit: NotRequired[int] + + +def get_swaps(client: LNMClient, params: GetSwapsParams = None) -> list[Swap]: + """ + @see https://docs.lnmarkets.com/api/operations/swapsgetswaps + """ + return json.loads(client.request_api('GET', '/swap', params, True)) \ No newline at end of file diff --git a/lnmarkets/swaps/new_swap.py b/lnmarkets/swaps/new_swap.py new file mode 100644 index 0000000..275779f --- /dev/null +++ b/lnmarkets/swaps/new_swap.py @@ -0,0 +1,17 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient +from lnmarkets.swaps.types import Swap, SwapAsset + + +class NewSwapParams(TypedDict): + in_amount: Required[int] + in_asset: Required[SwapAsset] + out_asset: Required[SwapAsset] + + +def new_swap(client: LNMClient, params: NewSwapParams) -> Swap: + """ + @see https://docs.lnmarkets.com/api/operations/swapsnewswap + """ + return json.loads(client.request_api('POST', '/swap', params, True)) \ No newline at end of file diff --git a/lnmarkets/swaps/types.py b/lnmarkets/swaps/types.py new file mode 100644 index 0000000..52df959 --- /dev/null +++ b/lnmarkets/swaps/types.py @@ -0,0 +1,25 @@ +from typing import TypedDict, NotRequired, Literal +from lnmarkets.swaps.types import UUID + +type SwapAsset = Literal['BTC', 'USD'] + +type SwapSource = Literal[ + 'deposit', + 'fee-refund', + 'swap', + 'withdrawal', + 'withdrawal-failed' +] + +class Swap(TypedDict): + creation_ts: int + id: UUID + in_amount: int + in_asset: SwapAsset + out_amount: int + out_asset: SwapAsset + source: NotRequired[SwapSource] + source_id: NotRequired[UUID] + uid: UUID + + diff --git a/lnmarkets/types.py b/lnmarkets/types.py new file mode 100644 index 0000000..ac49bca --- /dev/null +++ b/lnmarkets/types.py @@ -0,0 +1 @@ +type UUID = str \ No newline at end of file diff --git a/lnmarkets/user/__init__.py b/lnmarkets/user/__init__.py new file mode 100644 index 0000000..f743b17 --- /dev/null +++ b/lnmarkets/user/__init__.py @@ -0,0 +1,41 @@ +from .deposit_synthetic_usd import deposit_synthetic_usd, DepositSyntheticUsdParams, DepositSyntheticUsdResponse +from .deposit import deposit, DepositParams, DepositResponse +from .get_bitcoin_addresses import get_bitcoin_addresses, GetBitcoinAddressesParams +from .get_deposit import get_deposit, GetDepositParams +from .get_user import get_user +from .get_withdrawal import get_withdrawal, GetWithdrawalParams +from .get_withdrawals import get_withdrawals +from .new_bitcoin_address import new_bitcoin_address, NewBitcoinAddressParams, NewBitcoinAddressResponse +from .transfer import transfer, TransferParams, TransferResponse +from .update_user import update_user, UpdateUserParams +from .withdraw_synthetic_usd import withdraw_synthetic_usd, WithdrawSyntheticUsdParams, WithdrawSyntheticUsdResponse + +from .types import ( + ApiKeyCreation, + Currency, + BitcoinDeposit, + Deposit, + DepositType, + FeeTier, + GenericDeposit, + GenericDepositBase, + GenericDepositError, + GenericDepositSuccess, + GenericOnChainWithdrawal, + GenericWithdrawal, + FetchTransactionsRequest, + GenericWithdrawalBase, + GenericWithdrawalError, + GenericWithdrawalSuccess, + InternalTransfer, + InternalWithdrawalCondensed, + Leaderboard, + LeaderboardEntry, + LightningDeposit, + LightningWithdrawalCondensed, + NewApiKey, + OnChainWithdrawalCondensed, + OnChainWithdrawalStatus, + TotpSetup, + WithdrawalCondensed, +) \ No newline at end of file diff --git a/lnmarkets/user/deposit.py b/lnmarkets/user/deposit.py new file mode 100644 index 0000000..62e9cd0 --- /dev/null +++ b/lnmarkets/user/deposit.py @@ -0,0 +1,21 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient +from lnmarkets.user.types import UUID + + +class DepositParams(TypedDict): + amount: Required[float] + + +class DepositResponse(TypedDict): + deposit_id: UUID + expiry: int + payment_request: str + + +def deposit(client: LNMClient, params: DepositParams) -> DepositResponse: + """ + @see https://docs.lnmarkets.com/api/operations/userdeposit + """ + return json.loads(client.request_api('POST', '/user/deposit', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/deposit_synthetic_usd.py b/lnmarkets/user/deposit_synthetic_usd.py new file mode 100644 index 0000000..829e2a7 --- /dev/null +++ b/lnmarkets/user/deposit_synthetic_usd.py @@ -0,0 +1,23 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient +from lnmarkets.user.types import UUID, Currency + + +class DepositSyntheticUsdParams(TypedDict): + amount: Required[float] + currency: Required[Currency] + + +class DepositSyntheticUsdResponse(TypedDict): + deposit_id: UUID + expiry: int + payment_request: str + synthetic_usd_amount: float + + +def deposit_synthetic_usd(client: LNMClient, params: DepositSyntheticUsdParams) -> DepositSyntheticUsdResponse: + """ + @see https://docs.lnmarkets.com/api/operations/userdepositsyntheticusd + """ + return json.loads(client.request_api('POST', '/user/deposit/susd', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/get_bitcoin_addresses.py b/lnmarkets/user/get_bitcoin_addresses.py new file mode 100644 index 0000000..4c38965 --- /dev/null +++ b/lnmarkets/user/get_bitcoin_addresses.py @@ -0,0 +1,20 @@ +import json +from typing import TypedDict, NotRequired +from lnmarkets import LNMClient + + +class GetBitcoinAddressesParams(TypedDict): + current: NotRequired[bool] + + +class BitcoinAddress(TypedDict): + address: str + creation_ts: int + is_used: bool + + +def get_bitcoin_addresses(client: LNMClient, params: GetBitcoinAddressesParams = None) -> list[BitcoinAddress]: + """ + @see https://docs.lnmarkets.com/api/operations/usergetbitcoinaddresses + """ + return json.loads(client.request_api('GET', '/user/bitcoin/addresses', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/get_deposit.py b/lnmarkets/user/get_deposit.py new file mode 100644 index 0000000..bc57592 --- /dev/null +++ b/lnmarkets/user/get_deposit.py @@ -0,0 +1,15 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient +from lnmarkets.user.types import UUID, Deposit + + +class GetDepositParams(TypedDict): + deposit_id: Required[UUID] + + +def get_deposit(client: LNMClient, params: GetDepositParams) -> Deposit: + """ + @see https://docs.lnmarkets.com/api/operations/usergetdeposit + """ + return json.loads(client.request_api('GET', f'/user/deposit/{params["deposit_id"]}', {}, True)) \ No newline at end of file diff --git a/lnmarkets/user/get_user.py b/lnmarkets/user/get_user.py new file mode 100644 index 0000000..c455661 --- /dev/null +++ b/lnmarkets/user/get_user.py @@ -0,0 +1,10 @@ +import json +from lnmarkets import LNMClient +from lnmarkets.user.types import User + + +def get_user(client: LNMClient) -> User: + """ + @see https://docs.lnmarkets.com/api/operations/usergetuser + """ + return json.loads(client.request_api('GET', '/user', {}, True)) diff --git a/lnmarkets/user/get_withdrawal.py b/lnmarkets/user/get_withdrawal.py new file mode 100644 index 0000000..c80a441 --- /dev/null +++ b/lnmarkets/user/get_withdrawal.py @@ -0,0 +1,15 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient +from lnmarkets.user.types import UUID, WithdrawalCondensed + + +class GetWithdrawalParams(TypedDict): + id: Required[UUID] + + +def get_withdrawal(client: LNMClient, params: GetWithdrawalParams) -> WithdrawalCondensed: + """ + @see https://docs.lnmarkets.com/api/operations/usergetwithdrawal + """ + return json.loads(client.request_api('GET', f'/user/withdrawals/{params["id"]}', {}, True)) \ No newline at end of file diff --git a/lnmarkets/user/get_withdrawals.py b/lnmarkets/user/get_withdrawals.py new file mode 100644 index 0000000..c0fbcfe --- /dev/null +++ b/lnmarkets/user/get_withdrawals.py @@ -0,0 +1,10 @@ +import json +from lnmarkets import LNMClient +from lnmarkets.user.types import WithdrawalCondensed + + +def get_withdrawals(client: LNMClient) -> list[WithdrawalCondensed]: + """ + @see https://docs.lnmarkets.com/api/operations/usergetwithdrawals + """ + return json.loads(client.request_api('GET', '/user/withdraw', {}, True)) \ No newline at end of file diff --git a/lnmarkets/user/new_bitcoin_address.py b/lnmarkets/user/new_bitcoin_address.py new file mode 100644 index 0000000..c3eb22e --- /dev/null +++ b/lnmarkets/user/new_bitcoin_address.py @@ -0,0 +1,19 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient + + +class NewBitcoinAddressParams(TypedDict): + format: Required[str] # Literal['p2tr', 'p2wpkh'] + + +class NewBitcoinAddressResponse(TypedDict): + address: str + creation_ts: int + + +def new_bitcoin_address(client: LNMClient, params: NewBitcoinAddressParams) -> NewBitcoinAddressResponse: + """ + @see https://docs.lnmarkets.com/api/operations/usernewbitcoinaddress + """ + return json.loads(client.request_api('POST', '/user/bitcoin/address', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/transfer.py b/lnmarkets/user/transfer.py new file mode 100644 index 0000000..065706b --- /dev/null +++ b/lnmarkets/user/transfer.py @@ -0,0 +1,20 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient + + +class TransferParams(TypedDict): + amount: Required[float] + to_username: Required[str] + + +class TransferResponse(TypedDict): + amount: float + to: str + + +def transfer(client: LNMClient, params: TransferParams) -> TransferResponse: + """ + @see https://docs.lnmarkets.com/api/operations/usertransfer + """ + return json.loads(client.request_api('POST', '/user/transfer', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/types.py b/lnmarkets/user/types.py new file mode 100644 index 0000000..408ae74 --- /dev/null +++ b/lnmarkets/user/types.py @@ -0,0 +1,181 @@ +from typing import TypedDict, List, Optional, Union, Literal, Any +from lnmarkets import UUID + +class ApiKeyCreation(TypedDict): + name: str + passphrase: str + permissions: List[str] + +class BitcoinDeposit(TypedDict): + amount: float + block_id: Optional[str] + confirmation_height: Optional[int] + confirmed_ts: Optional[int] + id: str + is_confirmed: bool + transaction_id: str + ts: int + +type Currency = Literal['btc', 'usd'] + +type Deposit = Union['BitcoinDeposit', 'InternalTransfer', 'LightningDeposit'] + +type DepositType = Literal['bitcoin', 'internal', 'lightning'] + +type FeeTier = Literal[0, 1, 2, 3] + +class FetchTransactionsRequest(TypedDict, total=False): + cursor: int + from_: int + limit: int + to: int + types: str + +type GenericDeposit = Union['GenericDepositError', 'GenericDepositSuccess'] + +class GenericDepositBase(TypedDict): + amount: float + comment: Optional[str] + id: str + success: bool + transaction_id_or_hash: str + ts: int + type: 'DepositType' + +class GenericDepositError(TypedDict): + amount: float + comment: Optional[str] + id: str + success: Literal[False] + transaction_id_or_hash: Optional[str] + ts: int + type: 'DepositType' + +class GenericDepositSuccess(GenericDepositBase): + success: Literal[True] + +class GenericOnChainWithdrawal(TypedDict): + amount: float + fee: float + id: str + ts: int + status: 'OnChainWithdrawalStatus' + transaction_id_or_hash: Optional[str] + type: Literal['bitcoin'] + +type GenericWithdrawal = Union['GenericWithdrawalError', 'GenericWithdrawalSuccess'] + +class GenericWithdrawalBase(TypedDict): + amount: float + fee: float + id: str + success: bool + transaction_id_or_hash: str + ts: int + type: Literal['internal', 'lightning'] + +class GenericWithdrawalError(TypedDict): + amount: float + id: str + success: Literal[False] + transaction_id_or_hash: str + ts: int + type: Literal['internal', 'lightning'] + fee: Optional[float] + +class GenericWithdrawalSuccess(GenericWithdrawalBase): + success: Literal[True] + +class InternalTransfer(TypedDict): + amount: float + from_username: str + id: str + success: bool + to_username: str + ts: int + +class InternalWithdrawalCondensed(TypedDict): + amount: float + id: str + success: bool + to_username: str + ts: int + type: Literal['internal'] + +class LeaderboardEntry(TypedDict): + direction: int + pl: float + username: str + +class Leaderboard(TypedDict): + all_time: List[LeaderboardEntry] + daily: List[LeaderboardEntry] + monthly: List[LeaderboardEntry] + weekly: List[LeaderboardEntry] + +class LightningDeposit(TypedDict): + amount: float + comment: Optional[str] + id: str + payment_hash: str + success: bool + success_ts: Optional[int] + ts: int + +class LightningWithdrawalCondensed(TypedDict): + amount: float + destination: Optional[str] + fee: float + id: str + payment_hash: str + success: bool + ts: int + type: Literal['lightning'] + +class NewApiKey(TypedDict): + creation_ts: int + id: str + key: str + last_modified: int + name: Optional[str] + permissions: List[str] + secret: str + +class OnChainWithdrawalCondensed(TypedDict): + address: str + amount: float + fee: float + id: str + status: 'OnChainWithdrawalStatus' + transaction_id: str + ts: int + type: Literal['bitcoin'] + +type OnChainWithdrawalStatus = Literal['confirmed', 'failed', 'pending'] + +class TotpSetup(TypedDict): + backup_codes: List[str] + secret: str + url: str + +class User(TypedDict): + account_type: str + auto_withdraw_enabled: bool + auto_withdraw_lightning_address: Optional[str] + balance: float + email: Optional[str] + email_confirmed: bool + fee_tier: FeeTier + linking_public_key: Optional[str] + metrics: Any + nostr_pubkey: Optional[str] + role: str + show_leaderboard: bool + synthetic_usd_balance: float + totp_enabled: bool + uid: UUID + username: str + use_taproot_addresses: bool + webauthn_enabled: bool + +type WithdrawalCondensed = Union[InternalWithdrawalCondensed, LightningWithdrawalCondensed, OnChainWithdrawalCondensed] diff --git a/lnmarkets/user/update_user.py b/lnmarkets/user/update_user.py new file mode 100644 index 0000000..ae8c385 --- /dev/null +++ b/lnmarkets/user/update_user.py @@ -0,0 +1,20 @@ +import json +from typing import TypedDict, NotRequired +from lnmarkets import LNMClient +from lnmarkets.user.types import User + + +class UpdateUserParams(TypedDict): + auto_withdraw_enabled: NotRequired[bool] + auto_withdraw_lightning_address: NotRequired[bool] + nostr_pubkey: NotRequired[str] + show_leaderboard: NotRequired[bool] + username: NotRequired[str] + use_taproot_addresses: NotRequired[bool] + + +def update_user(client: LNMClient, params: UpdateUserParams) -> User: + """ + @see https://docs.lnmarkets.com/api/operations/userupdate + """ + return json.loads(client.request_api('PUT', '/user', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/withdraw.py b/lnmarkets/user/withdraw.py new file mode 100644 index 0000000..338d790 --- /dev/null +++ b/lnmarkets/user/withdraw.py @@ -0,0 +1,24 @@ +import json +from typing import TypedDict, Required, NotRequired +from lnmarkets import LNMClient +from lnmarkets.user.types import UUID + + +class WithdrawParams(TypedDict): + invoice: Required[str] + quote_id: NotRequired[UUID] + + +class WithdrawResponse(TypedDict): + amount: NotRequired[float] + fee: NotRequired[float] + id: UUID + payment_hash: NotRequired[str] + success_time: NotRequired[int] + + +def withdraw(client: LNMClient, params: WithdrawParams) -> WithdrawResponse: + """ + @see https://docs.lnmarkets.com/api/operations/userwithdraw + """ + return json.loads(client.request_api('POST', '/user/withdraw', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/withdraw_synthetic_usd.py b/lnmarkets/user/withdraw_synthetic_usd.py new file mode 100644 index 0000000..f612862 --- /dev/null +++ b/lnmarkets/user/withdraw_synthetic_usd.py @@ -0,0 +1,25 @@ +import json +from typing import TypedDict, Required +from lnmarkets import LNMClient +from lnmarkets.user.types import UUID, Currency + + +class WithdrawSyntheticUsdParams(TypedDict): + amount: Required[float] + currency: Required[Currency] + + +class WithdrawSyntheticUsdResponse(TypedDict): + amount: float + currency: Currency + fee_reserve: float + min_balance_after: float + quote_id: UUID + valid_until: int + + +def withdraw_synthetic_usd(client: LNMClient, params: WithdrawSyntheticUsdParams) -> WithdrawSyntheticUsdResponse: + """ + @see https://docs.lnmarkets.com/api/operations/userwithdrawalsyntheticusd + """ + return json.loads(client.request_api('POST', '/user/withdraw/susd', params, True)) \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..428a70e --- /dev/null +++ b/poetry.lock @@ -0,0 +1,183 @@ +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "8f6b6c24f619f4b3c83feba88e6f3e8b31d33569993e9e520ccb70cf6c4799c5" diff --git a/scripts/spell-check.sh b/scripts/spell-check.sh new file mode 100755 index 0000000..63111cb --- /dev/null +++ b/scripts/spell-check.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cspell --show-context --no-progress ** \ No newline at end of file From 882938ff72e523076be16be68f85cd5c178c15ea Mon Sep 17 00:00:00 2001 From: Flavian Date: Tue, 10 Dec 2024 18:50:28 +0100 Subject: [PATCH 02/11] refactor: fix indentation --- lnmarkets/rest.py | 175 ++++++++++++++++++------------------- lnmarkets/user/get_user.py | 8 +- 2 files changed, 91 insertions(+), 92 deletions(-) diff --git a/lnmarkets/rest.py b/lnmarkets/rest.py index e079445..8a5c1dc 100644 --- a/lnmarkets/rest.py +++ b/lnmarkets/rest.py @@ -14,94 +14,93 @@ type Method = Literal['GET', 'POST', 'PUT', 'DELETE'] def _get_hostname(network: Network) -> str: - hostname = os.getenv('LNMARKETS_API_HOSTNAME') - - if hostname: - return hostname - elif network == 'testnet': - return 'api.testnet.lnmarkets.com' - else: - return 'api.lnmarkets.com' - + hostname = os.getenv('LNMARKETS_API_HOSTNAME') + + if hostname: + return hostname + elif network == 'testnet': + return 'api.testnet.lnmarkets.com' + else: + return 'api.lnmarkets.com' + class _LNMOptions(TypedDict): - key: str - secret: str - passphrase: str - network: Network - hostname: str - custom_headers: dict[str, str] - skip_api_key: bool + key: str + secret: str + passphrase: str + network: Network + hostname: str + custom_headers: dict[str, str] + skip_api_key: bool class LNMClient(): - def __init__(self, options: _LNMOptions): - self.key = options.get('key', os.getenv('LNMARKETS_API_KEY')) - self.secret = options.get('secret', os.getenv('LNM_API_SECRET')) - self.passphrase = options.get('passphrase', os.getenv('LNM_API_PASSPHRASE')) - self.network = options.get('network', os.getenv('LNM_API_NETWORK', 'mainnet')) - self.hostname = _get_hostname(self.network) - self.custom_headers = options.get('custom_headers') - self.skip_api_key = options.get('skip_api_key', False) - - def _request_options(self, **options: dict[str, str | dict | bool]) -> dict: - credentials: str = options.get('credentials') - method = options.get('method') - path = options.get('path') - params = options.get('params') - opts = { 'headers': {} } - - if method != 'DELETE': - opts['headers']['Content-Type'] = 'application/json' - - if self.custom_headers: - opts['headers'].update(**self.custom_headers) - - if method in ['GET', 'DELETE']: - data = urlencode(params) - elif method in ['POST', 'PUT']: - data = json.dumps(params, separators=(',', ':')) - - if credentials and not self.skip_api_key: - if not self.key: - raise Exception('You need an API key to use an authenticated route') - elif not self.secret: - raise Exception('You need an API secret to use an authenticated route') - elif not self.passphrase: - raise Exception('You need an API passphrase to use an authenticated route') - - ts = str(int(datetime.now().timestamp() * 1000)) - - payload = ts + method + '/v2' + path + data - hashed = hmac.new(bytes(self.secret, 'utf-8'), bytes(payload, 'utf-8'), hashlib.sha256).digest() - signature = b64encode(hashed) - - opts['headers']['LNM-ACCESS-KEY'] = self.key - opts['headers']['LNM-ACCESS-PASSPHRASE'] = self.passphrase - opts['headers']['LNM-ACCESS-TIMESTAMP'] = ts - opts['headers']['LNM-ACCESS-SIGNATURE'] = signature - - opts['resource'] = 'https://' + self.hostname + '/v2' + path - - if method in ['GET', 'DELETE'] and params: - opts['resource'] += '?' + data - - return opts - - def request_api(self, method: Method, path: str, params: dict, credentials: bool = False): - options = { - 'method': method, - 'path': path, - 'params': params, - 'credentials': credentials - } - - opts = self._request_options(**options) - resource = opts.get('resource') - headers = opts.get('headers') - - if method in ['GET', 'DELETE']: - response = request(method, resource, headers = headers) - elif method in ['POST', 'PUT']: - response = request(method, resource, data = json.dumps(params, separators=(',', ':')), headers = headers) - - return response.text - \ No newline at end of file + def __init__(self, options: _LNMOptions): + self.key = options.get('key', os.getenv('LNMARKETS_API_KEY')) + self.secret = options.get('secret', os.getenv('LNM_API_SECRET')) + self.passphrase = options.get('passphrase', os.getenv('LNM_API_PASSPHRASE')) + self.network = options.get('network', os.getenv('LNM_API_NETWORK', 'mainnet')) + self.hostname = _get_hostname(self.network) + self.custom_headers = options.get('custom_headers') + self.skip_api_key = options.get('skip_api_key', False) + + def _request_options(self, **options: dict[str, str | dict | bool]) -> dict: + credentials: str = options.get('credentials') + method = options.get('method') + path = options.get('path') + params = options.get('params') + opts = { 'headers': {} } + + if method != 'DELETE': + opts['headers']['Content-Type'] = 'application/json' + + if self.custom_headers: + opts['headers'].update(**self.custom_headers) + + if method in ['GET', 'DELETE']: + data = urlencode(params) + elif method in ['POST', 'PUT']: + data = json.dumps(params, separators=(',', ':')) + + if credentials and not self.skip_api_key: + if not self.key: + raise Exception('You need an API key to use an authenticated route') + elif not self.secret: + raise Exception('You need an API secret to use an authenticated route') + elif not self.passphrase: + raise Exception('You need an API passphrase to use an authenticated route') + + ts = str(int(datetime.now().timestamp() * 1000)) + + payload = ts + method + '/v2' + path + data + hashed = hmac.new(bytes(self.secret, 'utf-8'), bytes(payload, 'utf-8'), hashlib.sha256).digest() + signature = b64encode(hashed) + + opts['headers']['LNM-ACCESS-KEY'] = self.key + opts['headers']['LNM-ACCESS-PASSPHRASE'] = self.passphrase + opts['headers']['LNM-ACCESS-TIMESTAMP'] = ts + opts['headers']['LNM-ACCESS-SIGNATURE'] = signature + + opts['resource'] = 'https://' + self.hostname + '/v2' + path + + if method in ['GET', 'DELETE'] and params: + opts['resource'] += '?' + data + + return opts + + def request_api(self, method: Method, path: str, params: dict, credentials: bool = False): + options = { + 'method': method, + 'path': path, + 'params': params, + 'credentials': credentials + } + + opts = self._request_options(**options) + resource = opts.get('resource') + headers = opts.get('headers') + + if method in ['GET', 'DELETE']: + response = request(method, resource, headers = headers) + elif method in ['POST', 'PUT']: + response = request(method, resource, data = json.dumps(params, separators=(',', ':')), headers = headers) + + return response.text \ No newline at end of file diff --git a/lnmarkets/user/get_user.py b/lnmarkets/user/get_user.py index c455661..f1f415b 100644 --- a/lnmarkets/user/get_user.py +++ b/lnmarkets/user/get_user.py @@ -4,7 +4,7 @@ def get_user(client: LNMClient) -> User: - """ - @see https://docs.lnmarkets.com/api/operations/usergetuser - """ - return json.loads(client.request_api('GET', '/user', {}, True)) + """ + @see https://docs.lnmarkets.com/api/operations/usergetuser + """ + return json.loads(client.request_api('GET', '/user', {}, True)) From d0e05412d283e19b870849740388d06a4f685368 Mon Sep 17 00:00:00 2001 From: Flavian Date: Mon, 16 Dec 2024 16:15:50 +0100 Subject: [PATCH 03/11] fix: wrong env variable loaded for api key --- lnmarkets/rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnmarkets/rest.py b/lnmarkets/rest.py index 8a5c1dc..e1f67e8 100644 --- a/lnmarkets/rest.py +++ b/lnmarkets/rest.py @@ -34,7 +34,7 @@ class _LNMOptions(TypedDict): class LNMClient(): def __init__(self, options: _LNMOptions): - self.key = options.get('key', os.getenv('LNMARKETS_API_KEY')) + self.key = options.get('key', os.getenv('LNM_API_KEY')) self.secret = options.get('secret', os.getenv('LNM_API_SECRET')) self.passphrase = options.get('passphrase', os.getenv('LNM_API_PASSPHRASE')) self.network = options.get('network', os.getenv('LNM_API_NETWORK', 'mainnet')) From f5ed2b5a1f94560fd0b0c9c2347fdabffd95b5f3 Mon Sep 17 00:00:00 2001 From: Flavian Date: Mon, 16 Dec 2024 16:17:07 +0100 Subject: [PATCH 04/11] fix: wrong env variable loaded for api hostname --- lnmarkets/rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnmarkets/rest.py b/lnmarkets/rest.py index e1f67e8..45c7553 100644 --- a/lnmarkets/rest.py +++ b/lnmarkets/rest.py @@ -14,7 +14,7 @@ type Method = Literal['GET', 'POST', 'PUT', 'DELETE'] def _get_hostname(network: Network) -> str: - hostname = os.getenv('LNMARKETS_API_HOSTNAME') + hostname = os.getenv('LNM_API_HOSTNAME') if hostname: return hostname From c1e149d654341d221d22111d9b8f564f792152a0 Mon Sep 17 00:00:00 2001 From: Flavian Date: Tue, 17 Dec 2024 11:17:44 +0100 Subject: [PATCH 05/11] test: add tests (#1) --- cspell.json | 2 ++ poetry.lock | 16 +++++++++++++++- tests/__init__.py | 0 tests/test_rest.py | 25 +++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/__init__.py create mode 100644 tests/test_rest.py diff --git a/cspell.json b/cspell.json index beb6458..d6249d1 100644 --- a/cspell.json +++ b/cspell.json @@ -12,12 +12,14 @@ ], "words": [ "cashin", + "dotenv", "linkingpublickey", "lnmarkets", "lnurl", "nostr", "ohlcs", "pipreqs", + "snok", "stoploss", "takeprofit", "topt", diff --git a/poetry.lock b/poetry.lock index 428a70e..80033ef 100644 --- a/poetry.lock +++ b/poetry.lock @@ -139,6 +139,20 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "requests" version = "2.32.3" @@ -180,4 +194,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "8f6b6c24f619f4b3c83feba88e6f3e8b31d33569993e9e520ccb70cf6c4799c5" +content-hash = "9a600d0ca7a2c9819e3a3132c23e371a12791288834e9e6cbdbaaac95fbfc12a" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_rest.py b/tests/test_rest.py new file mode 100644 index 0000000..102f7e3 --- /dev/null +++ b/tests/test_rest.py @@ -0,0 +1,25 @@ +import unittest +import os +from dotenv import load_dotenv + +from lnmarkets import LNMClient +from lnmarkets.user import get_user + +load_dotenv() + +class TestRest(unittest.TestCase): + def test_rest(self): + client = LNMClient({ + 'network': 'testnet', + 'key': os.getenv('LNM_API_KEY'), + 'secret': os.getenv('LNM_API_SECRET'), + 'passphrase': os.getenv('LNM_API_PASSPHRASE'), + }) + + user_info = get_user(client) + + self.assertRegex(user_info['uid'], r'^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$') + + +if __name__ == '__main__': + unittest.main() From 0fb21497ba5420c1c28bf3beacdc5b70862a66ee Mon Sep 17 00:00:00 2001 From: Flavian Date: Thu, 19 Dec 2024 10:29:43 +0100 Subject: [PATCH 06/11] chore: remove vscode settings --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d623851..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "editor.tabSize": 2 -} \ No newline at end of file From 4416540f4430d9f800b8067de4cf473d33b75b09 Mon Sep 17 00:00:00 2001 From: CaoKha Date: Thu, 16 Oct 2025 11:52:29 +0200 Subject: [PATCH 07/11] feat: v3 --- cspell.json | 29 --- examples/basic.py | 161 +++++++++++++++ images/lnmarkets-logo.svg | 78 -------- lnmarkets/__init__.py | 3 - lnmarkets/futures/__init__.py | 40 ---- lnmarkets/futures/add_margin.py | 16 -- lnmarkets/futures/cancel_all_trades.py | 15 -- lnmarkets/futures/cancel_trade.py | 15 -- lnmarkets/futures/cash_in.py | 16 -- lnmarkets/futures/close_all_trades.py | 16 -- lnmarkets/futures/close_trade.py | 15 -- lnmarkets/futures/get_carry_fees.py | 28 --- lnmarkets/futures/get_fixing_history.py | 28 --- lnmarkets/futures/get_index_history.py | 25 --- lnmarkets/futures/get_leaderboard.py | 10 - lnmarkets/futures/get_market_details.py | 10 - lnmarkets/futures/get_ohlc_history.py | 18 -- lnmarkets/futures/get_price_history.py | 17 -- lnmarkets/futures/get_ticker.py | 11 -- lnmarkets/futures/get_trade.py | 15 -- lnmarkets/futures/get_trades.py | 18 -- lnmarkets/futures/new_trade.py | 26 --- lnmarkets/futures/types.py | 155 --------------- lnmarkets/futures/update_trade.py | 17 -- lnmarkets/notifications/__init__.py | 7 - lnmarkets/notifications/get_notifications.py | 11 -- lnmarkets/notifications/mark_all_as_read.py | 8 - lnmarkets/notifications/types.py | 9 - lnmarkets/options/__init__.py | 39 ---- lnmarkets/options/close_all_trades.py | 11 -- lnmarkets/options/close_trade.py | 15 -- lnmarkets/options/get_instrument.py | 15 -- lnmarkets/options/get_instruments.py | 14 -- lnmarkets/options/get_market_details.py | 10 - lnmarkets/options/get_trade.py | 15 -- lnmarkets/options/get_trades.py | 18 -- lnmarkets/options/get_volatility_index.py | 10 - lnmarkets/options/new_trade.py | 18 -- lnmarkets/options/types.py | 123 ------------ lnmarkets/options/update_trade.py | 16 -- lnmarkets/oracle/__init__.py | 2 - lnmarkets/oracle/get_index.py | 21 -- lnmarkets/oracle/get_last_price.py | 21 -- lnmarkets/rest.py | 106 ---------- lnmarkets/swaps/__init__.py | 10 - lnmarkets/swaps/get_swap.py | 15 -- lnmarkets/swaps/get_swap_by_source_id.py | 16 -- lnmarkets/swaps/get_swaps.py | 17 -- lnmarkets/swaps/new_swap.py | 17 -- lnmarkets/swaps/types.py | 25 --- lnmarkets/types.py | 1 - lnmarkets/user/__init__.py | 41 ---- lnmarkets/user/deposit.py | 21 -- lnmarkets/user/deposit_synthetic_usd.py | 23 --- lnmarkets/user/get_bitcoin_addresses.py | 20 -- lnmarkets/user/get_deposit.py | 15 -- lnmarkets/user/get_user.py | 10 - lnmarkets/user/get_withdrawal.py | 15 -- lnmarkets/user/get_withdrawals.py | 10 - lnmarkets/user/new_bitcoin_address.py | 19 -- lnmarkets/user/transfer.py | 20 -- lnmarkets/user/types.py | 181 ----------------- lnmarkets/user/update_user.py | 20 -- lnmarkets/user/withdraw.py | 24 --- lnmarkets/user/withdraw_synthetic_usd.py | 25 --- poetry.lock | 197 ------------------- scripts/spell-check.sh | 3 - tests/__init__.py | 0 tests/test_rest.py | 25 --- 69 files changed, 161 insertions(+), 1880 deletions(-) delete mode 100644 cspell.json create mode 100644 examples/basic.py delete mode 100644 images/lnmarkets-logo.svg delete mode 100644 lnmarkets/__init__.py delete mode 100644 lnmarkets/futures/__init__.py delete mode 100644 lnmarkets/futures/add_margin.py delete mode 100644 lnmarkets/futures/cancel_all_trades.py delete mode 100644 lnmarkets/futures/cancel_trade.py delete mode 100644 lnmarkets/futures/cash_in.py delete mode 100644 lnmarkets/futures/close_all_trades.py delete mode 100644 lnmarkets/futures/close_trade.py delete mode 100644 lnmarkets/futures/get_carry_fees.py delete mode 100644 lnmarkets/futures/get_fixing_history.py delete mode 100644 lnmarkets/futures/get_index_history.py delete mode 100644 lnmarkets/futures/get_leaderboard.py delete mode 100644 lnmarkets/futures/get_market_details.py delete mode 100644 lnmarkets/futures/get_ohlc_history.py delete mode 100644 lnmarkets/futures/get_price_history.py delete mode 100644 lnmarkets/futures/get_ticker.py delete mode 100644 lnmarkets/futures/get_trade.py delete mode 100644 lnmarkets/futures/get_trades.py delete mode 100644 lnmarkets/futures/new_trade.py delete mode 100644 lnmarkets/futures/types.py delete mode 100644 lnmarkets/futures/update_trade.py delete mode 100644 lnmarkets/notifications/__init__.py delete mode 100644 lnmarkets/notifications/get_notifications.py delete mode 100644 lnmarkets/notifications/mark_all_as_read.py delete mode 100644 lnmarkets/notifications/types.py delete mode 100644 lnmarkets/options/__init__.py delete mode 100644 lnmarkets/options/close_all_trades.py delete mode 100644 lnmarkets/options/close_trade.py delete mode 100644 lnmarkets/options/get_instrument.py delete mode 100644 lnmarkets/options/get_instruments.py delete mode 100644 lnmarkets/options/get_market_details.py delete mode 100644 lnmarkets/options/get_trade.py delete mode 100644 lnmarkets/options/get_trades.py delete mode 100644 lnmarkets/options/get_volatility_index.py delete mode 100644 lnmarkets/options/new_trade.py delete mode 100644 lnmarkets/options/types.py delete mode 100644 lnmarkets/options/update_trade.py delete mode 100644 lnmarkets/oracle/__init__.py delete mode 100644 lnmarkets/oracle/get_index.py delete mode 100644 lnmarkets/oracle/get_last_price.py delete mode 100644 lnmarkets/rest.py delete mode 100644 lnmarkets/swaps/__init__.py delete mode 100644 lnmarkets/swaps/get_swap.py delete mode 100644 lnmarkets/swaps/get_swap_by_source_id.py delete mode 100644 lnmarkets/swaps/get_swaps.py delete mode 100644 lnmarkets/swaps/new_swap.py delete mode 100644 lnmarkets/swaps/types.py delete mode 100644 lnmarkets/types.py delete mode 100644 lnmarkets/user/__init__.py delete mode 100644 lnmarkets/user/deposit.py delete mode 100644 lnmarkets/user/deposit_synthetic_usd.py delete mode 100644 lnmarkets/user/get_bitcoin_addresses.py delete mode 100644 lnmarkets/user/get_deposit.py delete mode 100644 lnmarkets/user/get_user.py delete mode 100644 lnmarkets/user/get_withdrawal.py delete mode 100644 lnmarkets/user/get_withdrawals.py delete mode 100644 lnmarkets/user/new_bitcoin_address.py delete mode 100644 lnmarkets/user/transfer.py delete mode 100644 lnmarkets/user/types.py delete mode 100644 lnmarkets/user/update_user.py delete mode 100644 lnmarkets/user/withdraw.py delete mode 100644 lnmarkets/user/withdraw_synthetic_usd.py delete mode 100644 poetry.lock delete mode 100755 scripts/spell-check.sh delete mode 100644 tests/__init__.py delete mode 100644 tests/test_rest.py diff --git a/cspell.json b/cspell.json deleted file mode 100644 index d6249d1..0000000 --- a/cspell.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "dictionaries": [ - "python", - "en-gb" - ], - "ignorePaths": [ - ".venv", - ".gitignore", - "pyproject.toml", - "*.svg", - "poetry.lock" - ], - "words": [ - "cashin", - "dotenv", - "linkingpublickey", - "lnmarkets", - "lnurl", - "nostr", - "ohlcs", - "pipreqs", - "snok", - "stoploss", - "takeprofit", - "topt", - "Totp", - "webauthn" - ] -} diff --git a/examples/basic.py b/examples/basic.py new file mode 100644 index 0000000..4b8b779 --- /dev/null +++ b/examples/basic.py @@ -0,0 +1,161 @@ +""" +Example usage of the v3 client-based API. +""" + +import asyncio +import os +from pprint import pprint + +from dotenv import load_dotenv + +from lnmarkets_sdk.client import APIAuthContext, APIClientConfig, LNMClient +from lnmarkets_sdk.models.account import GetLightningDepositsParams +from lnmarkets_sdk.models.futures_cross import ( + FuturesCrossOrderLimit, +) + +load_dotenv() + + +async def example_public_endpoints(): + """Example: Make public requests without authentication.""" + print("\n" + "=" * 80) + print("PUBLIC ENDPOINTS EXAMPLE") + print("=" * 80) + + # Create client without authentication for public endpoints + # The httpx.AsyncClient is created once and reuses connections + async with LNMClient() as client: + # All these requests share the same connection pool + print("\n🔄 Making multiple requests with connection reuse...") + + # Get futures ticker + ticker = await client.futures.get_ticker() + print("\n--- Futures Ticker ---") + pprint(ticker.model_dump(), indent=2, width=100) + + # Get leaderboard + await asyncio.sleep(1) + leaderboard = await client.futures.get_leaderboard() + print("\n--- Leaderboard (Top 3 Daily) ---") + pprint(leaderboard.daily[:3], indent=2, width=100) + + # Get oracle data + await asyncio.sleep(1) + oracle_index = await client.oracle.get_index() + print("\n--- Oracle Index (Latest) ---") + pprint(oracle_index[0].model_dump() if oracle_index else "No data", indent=2) + + # Get synthetic USD best price + await asyncio.sleep(1) + best_price = await client.synthetic_usd.get_best_price() + print("\n--- Synthetic USD Best Price ---") + pprint(best_price.model_dump(), indent=2) + + # Ping the API + await asyncio.sleep(1) + ping_response = await client.ping() + print("\n--- Ping ---") + print(f"Response: {ping_response}") + + +async def example_authenticated_endpoints(): + """Example: Use authenticated endpoints with credentials.""" + print("\n" + "=" * 80) + print("AUTHENTICATED ENDPOINTS EXAMPLE") + print("=" * 80) + + key = os.getenv("LNM_API_KEY_V3") + secret = os.getenv("LNM_API_SECRET_V3") + passphrase = os.getenv("LNM_API_PASSPHRASE_V3") + + if not (key and secret and passphrase): + print("\n⚠️ Skipping authenticated example:") + print( + " Please set LNM_API_KEY_V3, LNM_API_SECRET_V3, and LNM_API_PASSPHRASE_V3" + ) + return + + # Create config with authentication and custom timeout + config = APIClientConfig( + authentication=APIAuthContext( + key=key, + secret=secret, + passphrase=passphrase, + ), + network="mainnet", + timeout=60.0, # 60 second timeout (default is 30s) + ) + + async with LNMClient(config) as client: + print("\n🔐 Using authenticated endpoints with connection pooling...") + + # Get account info + account = await client.account.get_account() + print("\n--- Account Info ---") + print(f"Username: {account.username}") + print(f"Balance: {account.balance} sats") + print(f"Synthetic USD Balance: {account.synthetic_usd_balance}") + + # Get Bitcoin address + btc_address = await client.account.get_bitcoin_address() + print("\n--- Bitcoin Deposit Address ---") + print(f"Address: {btc_address.address}") + + # Get lightning deposits (last 5) + deposits = await client.account.get_lightning_deposits( + GetLightningDepositsParams(limit=5) + ) + print(f"\n--- Recent Lightning Deposits (Last {len(deposits)}) ---") + for deposit in deposits: + print(f"Deposits {deposit.amount} sats at {deposit.created_at}") + + # Get running trades + running_trades = await client.futures.isolated.get_running_trades() + print("\n--- Running Isolated Trades ---") + print(f"Count: {len(running_trades)}") + for trade in running_trades[:3]: # Show first 3 + side = "LONG" if trade.side == "b" else "SHORT" + print( + f" {side} - Margin: {trade.margin} sats, Leverage: {trade.leverage}x, PL: {trade.pl} sats" + ) + + # Get cross margin position + try: + position = await client.futures.cross.get_position() + print("\n--- Cross Margin Position ---") + print(f"Quantity: {position.quantity}") + print(f"Margin: {position.margin} sats") + print(f"Leverage: {position.leverage}x") + print(f"Total PL: {position.total_pl} sats") + except Exception as e: + print(f"Error: {e}") + + # Open a new cross order + try: + print("\n--- Try to open a new cross order ---") + order_params = FuturesCrossOrderLimit( + type="limit", price=1.5, quantity=1, side="b" + ) + new_order = await client.futures.cross.new_order(order_params) + print(f"New order: {new_order}") + except Exception as e: + print(f"Error: {e}") + + +async def main(): + """Run all examples.""" + print("\n" + "=" * 80) + print("LN MARKETS V3 CLIENT EXAMPLES") + print("=" * 80) + + await example_public_endpoints() + await example_authenticated_endpoints() + + print("\n" + "=" * 80) + print("EXAMPLES COMPLETE") + print("=" * 80 + "\n") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/images/lnmarkets-logo.svg b/images/lnmarkets-logo.svg deleted file mode 100644 index 3e11618..0000000 --- a/images/lnmarkets-logo.svg +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lnmarkets/__init__.py b/lnmarkets/__init__.py deleted file mode 100644 index 226c0d2..0000000 --- a/lnmarkets/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .rest import LNMClient - -from .types import UUID diff --git a/lnmarkets/futures/__init__.py b/lnmarkets/futures/__init__.py deleted file mode 100644 index 5aebfdf..0000000 --- a/lnmarkets/futures/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from .add_margin import add_margin, AddMarginParams -from .cancel_all_trades import cancel_all_trades, CancelAllTradesResponse -from .cancel_trade import cancel_trade, CancelTradeParams -from .cash_in import cash_in, CashInParams -from .close_all_trades import close_all_trades, CloseAllTradesResponse -from .close_trade import close_trade, CloseTradeParams -from .get_carry_fees import get_carry_fees_history, CarryFeesHistoryParams, CarryFeesHistoryResponse -from .get_fixing_history import get_fixing_history, FixingHistoryResponse -from .get_index_history import get_index_history -from .get_leaderboard import get_leaderboard -from .get_market_details import get_market_details -from .get_ohlc_history import get_ohlc_history -from .get_price_history import get_price_history -from .get_ticker import get_ticker -from .get_trades import get_trades - -from .types import ( - FuturesCanceledTrade, - FuturesClosedTrade, - FuturesTrade, - FuturesTradeStatus, - FuturesMarketDetails, - FuturesMarketDetailsCarry, - FuturesMarketDetailsCount, - FuturesMarketDetailsFees, - FuturesMarketDetailsLeverage, - FuturesMarketDetailsLimits, - FuturesMarketDetailsQuantity, - FuturesMarketDetailsTier, - FuturesMarketDetailsTrading, - FuturesOpenOrRunningTrade, - FuturesOpenTrade, - FuturesRunningTrade, - FuturesTicker, - FuturesTradeSide, - FuturesTradeType, - OHLC, - OHLCRange, - UUID, -) diff --git a/lnmarkets/futures/add_margin.py b/lnmarkets/futures/add_margin.py deleted file mode 100644 index 7c53911..0000000 --- a/lnmarkets/futures/add_margin.py +++ /dev/null @@ -1,16 +0,0 @@ -import json -from typing import NotRequired, Required, TypedDict -from lnmarkets import LNMClient -from lnmarkets.futures.types import FuturesRunningTrade, UUID - - -class AddMarginParams(TypedDict): - amount: Required[float] - id: NotRequired[UUID] - - -def add_margin(client: LNMClient, params: AddMarginParams) -> FuturesRunningTrade: - """ - @see https://docs.lnmarkets.com/api/operations/futuresaddmargin - """ - return json.loads(client.request_api('POST', '/futures/margin', params, True)) diff --git a/lnmarkets/futures/cancel_all_trades.py b/lnmarkets/futures/cancel_all_trades.py deleted file mode 100644 index ae47a0e..0000000 --- a/lnmarkets/futures/cancel_all_trades.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from lnmarkets import LNMClient -from typing import List, TypedDict -from lnmarkets.futures.types import FuturesCanceledTrade - - -class CancelAllTradesResponse(TypedDict): - trades: List[FuturesCanceledTrade] - - -def cancel_all_trades(client: LNMClient) -> CancelAllTradesResponse: - """ - @see https://docs.lnmarkets.com/api/operations/futurescancelalltrades - """ - return json.loads(client.request_api('DELETE', '/futures/all/cancel', {}, True)) diff --git a/lnmarkets/futures/cancel_trade.py b/lnmarkets/futures/cancel_trade.py deleted file mode 100644 index a920bde..0000000 --- a/lnmarkets/futures/cancel_trade.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from lnmarkets import LNMClient, UUID -from typing import TypedDict, Required -from lnmarkets.futures.types import FuturesCanceledTrade - - -class CancelTradeParams(TypedDict): - id: Required[UUID] - - -def cancel_trade(client: LNMClient, params: CancelTradeParams) -> FuturesCanceledTrade: - """ - @see https://docs.lnmarkets.com/api/operations/futurescanceltrade - """ - return json.loads(client.request_api('POST', '/futures/cancel', params, True)) \ No newline at end of file diff --git a/lnmarkets/futures/cash_in.py b/lnmarkets/futures/cash_in.py deleted file mode 100644 index acc7255..0000000 --- a/lnmarkets/futures/cash_in.py +++ /dev/null @@ -1,16 +0,0 @@ -import json -from lnmarkets import LNMClient, UUID -from typing import TypedDict, Required -from lnmarkets.futures.types import FuturesRunningTrade - - -class CashInParams(TypedDict): - amount: Required[float] - id: Required[UUID] - - -def cash_in(client: LNMClient, params: CashInParams) -> FuturesRunningTrade: - """ - @see https://docs.lnmarkets.com/api/operations/futurescashin - """ - return json.loads(client.request_api('POST', '/futures/cash-in', params, True)) diff --git a/lnmarkets/futures/close_all_trades.py b/lnmarkets/futures/close_all_trades.py deleted file mode 100644 index 41c5623..0000000 --- a/lnmarkets/futures/close_all_trades.py +++ /dev/null @@ -1,16 +0,0 @@ -import json -from lnmarkets import LNMClient -from typing import List, TypedDict -from lnmarkets.futures.types import FuturesClosedTrade - - -class CloseAllTradesResponse(TypedDict): - trades: List[FuturesClosedTrade] - - -def close_all_trades(client: LNMClient) -> CloseAllTradesResponse: - """ - @see https://docs.lnmarkets.com/api/operations/futuresclosealltrades - """ - return json.loads(client.request_api('DELETE', '/futures/all/close', {}, True)) - diff --git a/lnmarkets/futures/close_trade.py b/lnmarkets/futures/close_trade.py deleted file mode 100644 index ae8291d..0000000 --- a/lnmarkets/futures/close_trade.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from lnmarkets import LNMClient, UUID -from typing import TypedDict, Required -from lnmarkets.futures.types import FuturesClosedTrade - - -class CloseTradeParams(TypedDict): - id: Required[UUID] - - -def close_trade(client: LNMClient, params: CloseTradeParams) -> FuturesClosedTrade: - """ - @see https://docs.lnmarkets.com/api/operations/futuresclosetrade - """ - return json.loads(client.request_api('DELETE', '/futures', params, True)) diff --git a/lnmarkets/futures/get_carry_fees.py b/lnmarkets/futures/get_carry_fees.py deleted file mode 100644 index b9f463c..0000000 --- a/lnmarkets/futures/get_carry_fees.py +++ /dev/null @@ -1,28 +0,0 @@ -import json -from lnmarkets.futures.types import UUID -from lnmarkets import LNMClient -from typing import List, TypedDict, Required, NotRequired - - -class CarryFeesHistoryParams(TypedDict): - from_ts: Required[int] - to: Required[int] - limit: NotRequired[int] - - -class CarryFee(TypedDict): - fee: float - fixing_id: UUID - id: UUID - ts: int - - -class CarryFeesHistoryResponse(TypedDict): - carryFees: List[CarryFee] - - -def get_carry_fees_history(client: LNMClient, params: CarryFeesHistoryParams) -> CarryFeesHistoryResponse: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgetcarryfees - """ - return json.loads(client.request_api('GET', '/futures/carry-fees', params, False)) diff --git a/lnmarkets/futures/get_fixing_history.py b/lnmarkets/futures/get_fixing_history.py deleted file mode 100644 index 5014948..0000000 --- a/lnmarkets/futures/get_fixing_history.py +++ /dev/null @@ -1,28 +0,0 @@ -import json -from lnmarkets.futures.types import UUID -from lnmarkets import LNMClient -from typing import List, TypedDict, Optional, NotRequired - - -class FixingHistoryParams(TypedDict): - from_ts: NotRequired[int] - to: NotRequired[int] - limit: NotRequired[int] - - -class FixingHistory(TypedDict): - fee: float - fixing_id: UUID - id: UUID - ts: int - - -class FixingHistoryResponse(TypedDict): - fixingHistory: List[FixingHistory] - - -def get_fixing_history(client: LNMClient, params: Optional[FixingHistoryParams] = None) -> FixingHistoryResponse: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgetfixinghistory - """ - return json.loads(client.request_api('GET', '/futures/history/fixing', params or {}, False)) diff --git a/lnmarkets/futures/get_index_history.py b/lnmarkets/futures/get_index_history.py deleted file mode 100644 index 919bfea..0000000 --- a/lnmarkets/futures/get_index_history.py +++ /dev/null @@ -1,25 +0,0 @@ -import json -from lnmarkets import LNMClient -from typing import List, TypedDict, Optional, NotRequired - - -class HistoryParams(TypedDict): - from_ts: NotRequired[int] - to: NotRequired[int] - limit: NotRequired[int] - - -class IndexHistory(TypedDict): - time: int - value: float - - -class IndexHistoryResponse(TypedDict): - indexHistory: List[IndexHistory] - - -def get_index_history(client: LNMClient, params: Optional[HistoryParams] = None) -> IndexHistoryResponse: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgetindexhistory - """ - return json.loads(client.request_api('GET', '/futures/history/index', params or {}, False)) diff --git a/lnmarkets/futures/get_leaderboard.py b/lnmarkets/futures/get_leaderboard.py deleted file mode 100644 index d4bbded..0000000 --- a/lnmarkets/futures/get_leaderboard.py +++ /dev/null @@ -1,10 +0,0 @@ -import json -from lnmarkets.user.types import Leaderboard -from lnmarkets import LNMClient - - -def get_leaderboard(client: LNMClient) -> Leaderboard: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgetleaderboard - """ - return json.loads(client.request_api('GET', '/futures/leaderboard', {}, False)) diff --git a/lnmarkets/futures/get_market_details.py b/lnmarkets/futures/get_market_details.py deleted file mode 100644 index 2933bdc..0000000 --- a/lnmarkets/futures/get_market_details.py +++ /dev/null @@ -1,10 +0,0 @@ -import json -from lnmarkets import LNMClient -from lnmarkets.futures.types import FuturesMarketDetails - - -def get_market_details(client: LNMClient) -> FuturesMarketDetails: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgetfuturesmarket - """ - return json.loads(client.request_api('GET', '/futures/market', {}, False)) \ No newline at end of file diff --git a/lnmarkets/futures/get_ohlc_history.py b/lnmarkets/futures/get_ohlc_history.py deleted file mode 100644 index dbf369d..0000000 --- a/lnmarkets/futures/get_ohlc_history.py +++ /dev/null @@ -1,18 +0,0 @@ -import json -from lnmarkets.futures.types import OHLC, OHLCRange -from typing import List, TypedDict, Required, NotRequired -from lnmarkets import LNMClient - - -class OHLCHistoryParams(TypedDict): - from_ts: Required[int] - to: Required[int] - range: Required[OHLCRange] - limit: NotRequired[int] - - -def get_ohlc_history(client: LNMClient, params: OHLCHistoryParams) -> List[OHLC]: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgetohlcs - """ - return json.loads(client.request_api('GET', '/futures/ohlcs', params, False)) diff --git a/lnmarkets/futures/get_price_history.py b/lnmarkets/futures/get_price_history.py deleted file mode 100644 index e3fb0b3..0000000 --- a/lnmarkets/futures/get_price_history.py +++ /dev/null @@ -1,17 +0,0 @@ -import json -from typing import List, TypedDict, Required, NotRequired -from lnmarkets import LNMClient - - -class HistoryParams(TypedDict): - from_ts: Required[int] - to: Required[int] - limit: NotRequired[int] - - -def get_price_history(client: LNMClient, params: HistoryParams) -> List[dict]: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgetpricehistory - """ - return json.loads(client.request_api('GET', '/futures/history/price', params, False)) - diff --git a/lnmarkets/futures/get_ticker.py b/lnmarkets/futures/get_ticker.py deleted file mode 100644 index 83e8616..0000000 --- a/lnmarkets/futures/get_ticker.py +++ /dev/null @@ -1,11 +0,0 @@ -import json -from lnmarkets import LNMClient -from lnmarkets.futures.types import FuturesTicker - - -def get_ticker(client: LNMClient) -> FuturesTicker: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgetticker - """ - return json.loads(client.request_api('GET', '/futures/ticker', {}, False)) - diff --git a/lnmarkets/futures/get_trade.py b/lnmarkets/futures/get_trade.py deleted file mode 100644 index c5faa6f..0000000 --- a/lnmarkets/futures/get_trade.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from lnmarkets.futures.types import FuturesTrade -from lnmarkets import LNMClient -from typing import Required, UUID, TypedDict - - -class GetTradeParams(TypedDict): - id: Required[UUID] - - -def get_trade(client: LNMClient, params: GetTradeParams) -> FuturesTrade: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgettrade - """ - return json.loads(client.request_api('GET', f'/futures/trades/{params["id"]}', {}, False)) diff --git a/lnmarkets/futures/get_trades.py b/lnmarkets/futures/get_trades.py deleted file mode 100644 index 3955b18..0000000 --- a/lnmarkets/futures/get_trades.py +++ /dev/null @@ -1,18 +0,0 @@ -import json -from typing import List, TypedDict, Required, NotRequired -from lnmarkets import LNMClient -from lnmarkets.futures.types import FuturesTrade, FuturesTradeStatus - - -class GetTradesParams(TypedDict): - type: Required[FuturesTradeStatus] - from_ts: NotRequired[int] - to: NotRequired[int] - limit: NotRequired[int] - - -def get_trades(client: LNMClient, params: GetTradesParams) -> List[FuturesTrade]: - """ - @see https://docs.lnmarkets.com/api/operations/futuresgettrades - """ - return json.loads(client.request_api('GET', '/futures', params, True)) diff --git a/lnmarkets/futures/new_trade.py b/lnmarkets/futures/new_trade.py deleted file mode 100644 index e27aaea..0000000 --- a/lnmarkets/futures/new_trade.py +++ /dev/null @@ -1,26 +0,0 @@ -import json -from lnmarkets import LNMClient -from typing import TypedDict, Required, NotRequired -from lnmarkets.futures import ( - FuturesOpenOrRunningTrade, - FuturesTradeSide, - FuturesTradeType, -) - - -class NewTradeParams(TypedDict): - leverage: Required[float] - side: Required[FuturesTradeSide] - type: Required[FuturesTradeType] - margin: NotRequired[float] - price: NotRequired[float] - quantity: NotRequired[float] - stoploss: NotRequired[float] - takeprofit: NotRequired[float] - - -def new_trade(client: LNMClient, params: NewTradeParams) -> FuturesOpenOrRunningTrade: - """ - @see https://docs.lnmarkets.com/api/operations/futuresnewtrade - """ - return json.loads(client.request_api('POST', '/futures', params, True)) diff --git a/lnmarkets/futures/types.py b/lnmarkets/futures/types.py deleted file mode 100644 index ae6af71..0000000 --- a/lnmarkets/futures/types.py +++ /dev/null @@ -1,155 +0,0 @@ -from typing import List, Literal, Optional, TypedDict, Union -from lnmarkets import UUID - - -class FuturesCanceledTrade(TypedDict): - canceled: Literal[True] - closed: Literal[False] - closed_ts: int - market_filled_ts: None - open: Literal[False] - running: Literal[False] - type: Literal['l'] - - -class FuturesClosedTrade(TypedDict): - canceled: Literal[False] - closed: Literal[True] - closed_ts: int - exit_price: float - market_filled_ts: int - open: Literal[False] - running: Literal[False] - - -class FuturesMarketDetailsTier(TypedDict): - fees: float - min_volume: float - - -class FuturesMarketDetailsCarry(TypedDict): - hours: List[int] - min: float - - -class FuturesMarketDetailsTrading(TypedDict): - tiers: List[FuturesMarketDetailsTier] - - -class FuturesMarketDetailsFees(TypedDict): - carry: FuturesMarketDetailsCarry - trading: FuturesMarketDetailsTrading - - -class FuturesMarketDetailsCount(TypedDict): - max: int - - -class FuturesMarketDetailsLeverage(TypedDict): - max: float - min: float - - -class FuturesMarketDetailsQuantity(TypedDict): - max: float - min: float - trade: float - - -class FuturesMarketDetailsLimits(TypedDict): - count: FuturesMarketDetailsCount - leverage: FuturesMarketDetailsLeverage - quantity: FuturesMarketDetailsQuantity - - -class FuturesMarketDetails(TypedDict): - active: bool - fees: FuturesMarketDetailsFees - limits: FuturesMarketDetailsLimits - - -class FuturesOpenTrade(TypedDict): - canceled: Literal[False] - closed: Literal[False] - closed_ts: None - market_filled_ts: None - running: Literal[False] - type: Literal['l'] - - -class FuturesRunningTrade(TypedDict): - canceled: Literal[False] - closed: Literal[False] - closed_ts: None - market_filled_ts: int - running: Literal[True] - - -type FuturesOpenOrRunningTrade = Union[FuturesOpenTrade, FuturesRunningTrade] - - -class FuturesTicker(TypedDict): - ask_price: float - bid_price: float - carry_fee_rate: float - carry_fee_timestamp: int - index: float - last_price: float - - -class FuturesTrade(TypedDict): - canceled: bool - closed: bool - closed_ts: Optional[int] - closing_fee: float - creation_ts: int - entry_margin: Optional[float] - entry_price: Optional[float] - exit_price: Optional[float] - id: UUID - last_update_ts: int - leverage: float - liquidation: float - maintenance_margin: float - margin: float - market_filled_ts: Optional[int] - open: bool - opening_fee: float - pl: float - price: float - quantity: float - running: bool - side: Literal['b', 's'] - sum_carry_fees: float - type: Literal['l', 'm'] - uid: UUID - - -type FuturesTradeSide = Literal['b', 's'] -type FuturesTradeStatus = Literal['closed', 'open', 'running'] -type FuturesTradeType = Literal['l', 'm'] - - -class OHLC(TypedDict): - close: float - high: float - low: float - open: float - time: int - volume: float - - -type OHLCRange = Literal[ - '1', - '1D', - '1M', - '1W', - '3', - '3M', - '5', - '15', - '30', - '60', - '120', - '240' -] diff --git a/lnmarkets/futures/update_trade.py b/lnmarkets/futures/update_trade.py deleted file mode 100644 index 7c790b7..0000000 --- a/lnmarkets/futures/update_trade.py +++ /dev/null @@ -1,17 +0,0 @@ -import json -from lnmarkets import LNMClient -from lnmarkets.futures import UUID, FuturesOpenOrRunningTrade -from typing import TypedDict, Required, Literal - - -class UpdateTradeParams(TypedDict): - id: Required[UUID] - type: Required[Literal['stoploss', 'takeprofit']] - value: Required[float] - - -def update_trade(client: LNMClient, params: UpdateTradeParams) -> FuturesOpenOrRunningTrade: - """ - @see https://docs.lnmarkets.com/api/operations/futuresupdatetrade - """ - return json.loads(client.request_api('PUT', '/futures', params, True)) diff --git a/lnmarkets/notifications/__init__.py b/lnmarkets/notifications/__init__.py deleted file mode 100644 index 4c1fb0a..0000000 --- a/lnmarkets/notifications/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .get_notifications import get_all_notifications -from .mark_all_as_read import mark_all_notifications_as_read - - -from .types import ( - Notification, -) \ No newline at end of file diff --git a/lnmarkets/notifications/get_notifications.py b/lnmarkets/notifications/get_notifications.py deleted file mode 100644 index d444585..0000000 --- a/lnmarkets/notifications/get_notifications.py +++ /dev/null @@ -1,11 +0,0 @@ -import json -from typing import List, TypedDict -from lnmarkets import LNMClient -from lnmarkets.notifications.types import Notification - - -def get_all_notifications(client: LNMClient) -> List[Notification]: - """ - @see https://docs.lnmarkets.com/api/operations/notificationsfetchnotifications - """ - return json.loads(client.request_api('GET', '/notifications', {}, True)) \ No newline at end of file diff --git a/lnmarkets/notifications/mark_all_as_read.py b/lnmarkets/notifications/mark_all_as_read.py deleted file mode 100644 index ee63f1e..0000000 --- a/lnmarkets/notifications/mark_all_as_read.py +++ /dev/null @@ -1,8 +0,0 @@ -from lnmarkets import LNMClient - - -def mark_all_notifications_as_read(client: LNMClient) -> None: - """ - @see https://docs.lnmarkets.com/api/operations/notificationsmarkallnotificationsasread - """ - client.request_api('DELETE', '/notifications/all', {}, True) diff --git a/lnmarkets/notifications/types.py b/lnmarkets/notifications/types.py deleted file mode 100644 index 9c514d0..0000000 --- a/lnmarkets/notifications/types.py +++ /dev/null @@ -1,9 +0,0 @@ -from lnmarkets import UUID -from typing import TypedDict, Any - - -class Notification(TypedDict): - creation_ts: int - data: Any - event: str - id: UUID diff --git a/lnmarkets/options/__init__.py b/lnmarkets/options/__init__.py deleted file mode 100644 index 8e3b611..0000000 --- a/lnmarkets/options/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -from .close_all_trades import close_all_trades -from .close_trade import close_trade, CloseTradeParams -from .get_instrument import get_instrument, GetInstrumentParams -from .get_instruments import get_instruments -from .get_market_details import get_market_details -from .get_trade import get_trade, GetTradeParams -from .get_trades import get_trades, GetTradesParams -from .get_volatility_index import get_volatility_index -from .new_trade import new_trade, NewTradeParams -from .update_trade import update_trade, UpdateTradeParams - - -from .types import ( - OptionsTradeRunningWithDelta, - OptionsInstrument, - OptionsMarketDetails, - OptionsMarketDetailsFees, - OptionsMarketDetailsLimits, - OptionsMarketDetailsLimitsCount, - OptionsMarketDetailsLimitsMargin, - OptionsMarketDetailsLimitsQuantity, - OptionsSettlement, - OptionsTrade, - OptionsSide, - OptionsTradeClosed, - OptionsTradeClosedCash, - OptionsTradeClosedPhysical, - OptionsTradeExpired, - OptionsTradeExpiredCash, - OptionsTradeExpiredPhysical, - OptionsTradeExpiredPhysicalDelivered, - OptionsTradeExpiredPhysicalNotDelivered, - OptionsTradeOrder, - OptionsTradeRunning, - OptionsTradeStatus, - OptionsTradeWithDelta, - OptionsType, - OptionsVolatilityIndex, -) \ No newline at end of file diff --git a/lnmarkets/options/close_all_trades.py b/lnmarkets/options/close_all_trades.py deleted file mode 100644 index 35087a9..0000000 --- a/lnmarkets/options/close_all_trades.py +++ /dev/null @@ -1,11 +0,0 @@ -import json -from typing import List -from lnmarkets.options.types import OptionsTradeClosed -from lnmarkets import LNMClient - - -def close_all_trades(client: LNMClient) -> List[OptionsTradeClosed]: - """ - @see https://docs.lnmarkets.com/api/operations/optionsclosealltrades - """ - return json.loads(client.request_api('DELETE', '/options/all/close', {}, True)) \ No newline at end of file diff --git a/lnmarkets/options/close_trade.py b/lnmarkets/options/close_trade.py deleted file mode 100644 index 92e265d..0000000 --- a/lnmarkets/options/close_trade.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets.options.types import UUID, OptionsTradeClosed -from lnmarkets import LNMClient - - -class CloseTradeParams(TypedDict): - id: Required[UUID] - - -def close_trade(client: LNMClient, params: CloseTradeParams) -> OptionsTradeClosed: - """ - @see https://docs.lnmarkets.com/api/operations/optionsclosetrade - """ - return json.loads(client.request_api('DELETE', '/options', params, True)) \ No newline at end of file diff --git a/lnmarkets/options/get_instrument.py b/lnmarkets/options/get_instrument.py deleted file mode 100644 index f66f19f..0000000 --- a/lnmarkets/options/get_instrument.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets.options.types import OptionsInstrument -from lnmarkets import LNMClient - - -class GetInstrumentParams(TypedDict): - instrument_name: Required[str] - - -def get_instrument(client: LNMClient, params: GetInstrumentParams) -> OptionsInstrument: - """ - @see https://docs.lnmarkets.com/api/operations/optionsgetinstrument - """ - return json.loads(client.request_api('GET', '/options/instrument', params, False)) \ No newline at end of file diff --git a/lnmarkets/options/get_instruments.py b/lnmarkets/options/get_instruments.py deleted file mode 100644 index d4974f7..0000000 --- a/lnmarkets/options/get_instruments.py +++ /dev/null @@ -1,14 +0,0 @@ -import json -from typing import List, TypedDict -from lnmarkets import LNMClient - - -class GetInstrumentsResponse(TypedDict): - instruments: List[str] - - -def get_instruments(client: LNMClient) -> GetInstrumentsResponse: - """ - @see https://docs.lnmarkets.com/api/operations/optionsgetinstruments - """ - return json.loads(client.request_api('GET', '/options/instruments', {}, False)) \ No newline at end of file diff --git a/lnmarkets/options/get_market_details.py b/lnmarkets/options/get_market_details.py deleted file mode 100644 index ce2a1fc..0000000 --- a/lnmarkets/options/get_market_details.py +++ /dev/null @@ -1,10 +0,0 @@ -import json -from lnmarkets.options.types import OptionsMarketDetails -from lnmarkets import LNMClient - - -def get_market_details(client: LNMClient) -> OptionsMarketDetails: - """ - @see https://docs.lnmarkets.com/api/operations/optionsgetoptionsmarket - """ - return json.loads(client.request_api('GET', '/options', {}, False)) \ No newline at end of file diff --git a/lnmarkets/options/get_trade.py b/lnmarkets/options/get_trade.py deleted file mode 100644 index 74353fc..0000000 --- a/lnmarkets/options/get_trade.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets.options.types import UUID, OptionsTrade -from lnmarkets import LNMClient - - -class GetTradeParams(TypedDict): - id: Required[UUID] - - -def get_trade(client: LNMClient, params: GetTradeParams) -> OptionsTrade: - """ - @see https://docs.lnmarkets.com/api/operations/optionsgettrade - """ - return json.loads(client.request_api('GET', f'/options/trades/{params["id"]}', {}, True)) \ No newline at end of file diff --git a/lnmarkets/options/get_trades.py b/lnmarkets/options/get_trades.py deleted file mode 100644 index 8909ffb..0000000 --- a/lnmarkets/options/get_trades.py +++ /dev/null @@ -1,18 +0,0 @@ -import json -from typing import List, TypedDict, Required, NotRequired -from lnmarkets.options.types import OptionsTrade, OptionsTradeStatus -from lnmarkets import LNMClient - - -class GetTradesParams(TypedDict): - from_ts: Required[int] - to: Required[int] - status: Required[OptionsTradeStatus] - limit: NotRequired[int] - - -def get_trades(client: LNMClient, params: GetTradesParams) -> List[OptionsTrade]: - """ - @see https://docs.lnmarkets.com/api/operations/optionsgettrades - """ - return json.loads(client.request_api('GET', '/options/trades', params, True)) \ No newline at end of file diff --git a/lnmarkets/options/get_volatility_index.py b/lnmarkets/options/get_volatility_index.py deleted file mode 100644 index 5b659e4..0000000 --- a/lnmarkets/options/get_volatility_index.py +++ /dev/null @@ -1,10 +0,0 @@ -import json -from lnmarkets.options.types import OptionsVolatilityIndex -from lnmarkets import LNMClient - - -def get_volatility_index(client: LNMClient) -> OptionsVolatilityIndex: - """ - @see https://docs.lnmarkets.com/api/operations/optionsgetvolatilityindex - """ - return json.loads(client.request_api('GET', '/options/volatility-index', {}, False)) \ No newline at end of file diff --git a/lnmarkets/options/new_trade.py b/lnmarkets/options/new_trade.py deleted file mode 100644 index bfbd72f..0000000 --- a/lnmarkets/options/new_trade.py +++ /dev/null @@ -1,18 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets.options.types import OptionsSettlement, OptionsSide, OptionsTradeRunning -from lnmarkets import LNMClient - - -class NewTradeParams(TypedDict): - instrument_name: Required[str] - quantity: Required[int] - settlement: Required[OptionsSettlement] - side: Required[OptionsSide] - - -def new_trade(client: LNMClient, params: NewTradeParams) -> OptionsTradeRunning: - """ - @see https://docs.lnmarkets.com/api/operations/optionsnewtrade - """ - return json.loads(client.request_api('POST', '/options', params, True)) \ No newline at end of file diff --git a/lnmarkets/options/types.py b/lnmarkets/options/types.py deleted file mode 100644 index a5f6a41..0000000 --- a/lnmarkets/options/types.py +++ /dev/null @@ -1,123 +0,0 @@ -from typing import TypedDict, Union, Literal -from lnmarkets import UUID - -class OptionsInstrument(TypedDict): - volatility: float - -class OptionsMarketDetailsFees(TypedDict): - trading: float - -class OptionsMarketDetailsLimitsCount(TypedDict): - max: float - -class OptionsMarketDetailsLimitsMargin(TypedDict): - max: float - min: float - -class OptionsMarketDetailsLimitsQuantity(TypedDict): - max: float - min: float - -class OptionsMarketDetailsLimits(TypedDict): - count: OptionsMarketDetailsLimitsCount - margin: OptionsMarketDetailsLimitsMargin - quantity: OptionsMarketDetailsLimitsQuantity - -class OptionsMarketDetails(TypedDict): - active: bool - fees: OptionsMarketDetailsFees - limits: OptionsMarketDetailsLimits - -type OptionsSettlement = Literal['cash', 'physical'] -type OptionsSide = Literal['b', 's'] -type OptionsType = Literal['c', 'p'] - -class OptionsTrade(TypedDict): - closed: bool - closed_ts: float | None - closing_fee: float - creation_ts: float - domestic: str - exercised: bool - expired: bool - expiry_ts: float - fixing_price: float | None - forward: float - forward_point: float - id: UUID - leg_id: UUID - maintenance_margin: float - margin: float - opening_fee: float - physical_delivery_id: str | None - pl: float - quantity: float - running: bool - settlement: OptionsSettlement - side: OptionsSide - strike: float - type: OptionsType - uid: UUID - volatility: float - -class OptionsTradeClosedCash(OptionsTrade, TypedDict): - closed: Literal[True] - closed_ts: float - expired: Literal[False] - fixing_price: float - physical_delivery_id: None - -class OptionsTradeClosedPhysical(OptionsTrade, TypedDict): - closed: Literal[True] - closed_ts: float - fixing_price: float - physical_delivery_id: str - -OptionsTradeClosed = Union[OptionsTradeClosedCash, OptionsTradeClosedPhysical] - -class OptionsTradeExpiredCash(OptionsTrade, TypedDict): - closed: Literal[False] - closed_ts: float - expired: Literal[True] - fixing_price: float - physical_delivery_id: None - -class OptionsTradeExpiredPhysical(OptionsTrade, TypedDict): - closed: Literal[False] - closed_ts: float - expired: Literal[True] - fixing_price: float - -class OptionsTradeExpiredPhysicalDelivered(OptionsTradeExpiredPhysical, TypedDict): - physical_delivery_id: str - -class OptionsTradeExpiredPhysicalNotDelivered(OptionsTradeExpiredPhysical, TypedDict): - physical_delivery_id: None - -OptionsTradeExpired = Union[ - OptionsTradeExpiredCash, - OptionsTradeExpiredPhysicalDelivered, - OptionsTradeExpiredPhysicalNotDelivered -] - -class OptionsTradeOrder(TypedDict): - instrument_name: str - quantity: float - settlement: OptionsSettlement - side: OptionsSide - -class OptionsTradeRunning(OptionsTrade, TypedDict): - closed: Literal[False] - closed_ts: None - physical_delivery_id: None - -class OptionsTradeRunningWithDelta(OptionsTradeRunning, TypedDict): - delta: float - -type OptionsTradeStatus = Literal['closed', 'running'] - -class OptionsTradeWithDelta(OptionsTrade, TypedDict): - delta: float | None - -class OptionsVolatilityIndex(TypedDict): - volatility_index: float diff --git a/lnmarkets/options/update_trade.py b/lnmarkets/options/update_trade.py deleted file mode 100644 index 7f72f64..0000000 --- a/lnmarkets/options/update_trade.py +++ /dev/null @@ -1,16 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets.options.types import UUID, OptionsSettlement, OptionsTradeRunningWithDelta -from lnmarkets import LNMClient - - -class UpdateTradeParams(TypedDict): - id: Required[UUID] - settlement: Required[OptionsSettlement] - - -def update_trade(client: LNMClient, params: UpdateTradeParams) -> OptionsTradeRunningWithDelta: - """ - @see https://docs.lnmarkets.com/api/operations/optionsupdatetrade - """ - return json.loads(client.request_api('PUT', '/options', params, True)) \ No newline at end of file diff --git a/lnmarkets/oracle/__init__.py b/lnmarkets/oracle/__init__.py deleted file mode 100644 index b83fb15..0000000 --- a/lnmarkets/oracle/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .get_index import get_index, GetIndexParams -from .get_last_price import get_last_price, GetLastPriceParams, LastPriceResponse diff --git a/lnmarkets/oracle/get_index.py b/lnmarkets/oracle/get_index.py deleted file mode 100644 index 8f00d7e..0000000 --- a/lnmarkets/oracle/get_index.py +++ /dev/null @@ -1,21 +0,0 @@ -import json -from typing import TypedDict, Required, NotRequired, List -from lnmarkets import LNMClient - - -class GetIndexParams(TypedDict): - from_ts: Required[int] - to: Required[int] - limit: NotRequired[int] - - -class IndexEntry(TypedDict): - index: float - time: int - - -def get_index(client: LNMClient, params: GetIndexParams) -> List[IndexEntry]: - """ - @see https://docs.lnmarkets.com/api/operations/oraclegetindex - """ - return json.loads(client.request_api('GET', '/oracle', params, False)) \ No newline at end of file diff --git a/lnmarkets/oracle/get_last_price.py b/lnmarkets/oracle/get_last_price.py deleted file mode 100644 index 18b4de3..0000000 --- a/lnmarkets/oracle/get_last_price.py +++ /dev/null @@ -1,21 +0,0 @@ -import json -from typing import TypedDict, Required, NotRequired -from lnmarkets import LNMClient - - -class GetLastPriceParams(TypedDict): - from_ts: Required[int] - to: Required[int] - limit: NotRequired[int] - - -class LastPriceResponse(TypedDict): - last_price: float - time: int - - -def get_last_price(client: LNMClient, params: GetLastPriceParams) -> LastPriceResponse: - """ - @see https://docs.lnmarkets.com/api/operations/oraclegetlastprice - """ - return json.loads(client.request_api('GET', '/oracle/last-price', params, False)) \ No newline at end of file diff --git a/lnmarkets/rest.py b/lnmarkets/rest.py deleted file mode 100644 index 45c7553..0000000 --- a/lnmarkets/rest.py +++ /dev/null @@ -1,106 +0,0 @@ -import os - -from typing import Literal, TypedDict -from urllib.parse import urlencode -from datetime import datetime -from base64 import b64encode -from requests import request - -import hashlib -import hmac -import json - -type Network = Literal['mainnet', 'testnet'] -type Method = Literal['GET', 'POST', 'PUT', 'DELETE'] - -def _get_hostname(network: Network) -> str: - hostname = os.getenv('LNM_API_HOSTNAME') - - if hostname: - return hostname - elif network == 'testnet': - return 'api.testnet.lnmarkets.com' - else: - return 'api.lnmarkets.com' - -class _LNMOptions(TypedDict): - key: str - secret: str - passphrase: str - network: Network - hostname: str - custom_headers: dict[str, str] - skip_api_key: bool - -class LNMClient(): - def __init__(self, options: _LNMOptions): - self.key = options.get('key', os.getenv('LNM_API_KEY')) - self.secret = options.get('secret', os.getenv('LNM_API_SECRET')) - self.passphrase = options.get('passphrase', os.getenv('LNM_API_PASSPHRASE')) - self.network = options.get('network', os.getenv('LNM_API_NETWORK', 'mainnet')) - self.hostname = _get_hostname(self.network) - self.custom_headers = options.get('custom_headers') - self.skip_api_key = options.get('skip_api_key', False) - - def _request_options(self, **options: dict[str, str | dict | bool]) -> dict: - credentials: str = options.get('credentials') - method = options.get('method') - path = options.get('path') - params = options.get('params') - opts = { 'headers': {} } - - if method != 'DELETE': - opts['headers']['Content-Type'] = 'application/json' - - if self.custom_headers: - opts['headers'].update(**self.custom_headers) - - if method in ['GET', 'DELETE']: - data = urlencode(params) - elif method in ['POST', 'PUT']: - data = json.dumps(params, separators=(',', ':')) - - if credentials and not self.skip_api_key: - if not self.key: - raise Exception('You need an API key to use an authenticated route') - elif not self.secret: - raise Exception('You need an API secret to use an authenticated route') - elif not self.passphrase: - raise Exception('You need an API passphrase to use an authenticated route') - - ts = str(int(datetime.now().timestamp() * 1000)) - - payload = ts + method + '/v2' + path + data - hashed = hmac.new(bytes(self.secret, 'utf-8'), bytes(payload, 'utf-8'), hashlib.sha256).digest() - signature = b64encode(hashed) - - opts['headers']['LNM-ACCESS-KEY'] = self.key - opts['headers']['LNM-ACCESS-PASSPHRASE'] = self.passphrase - opts['headers']['LNM-ACCESS-TIMESTAMP'] = ts - opts['headers']['LNM-ACCESS-SIGNATURE'] = signature - - opts['resource'] = 'https://' + self.hostname + '/v2' + path - - if method in ['GET', 'DELETE'] and params: - opts['resource'] += '?' + data - - return opts - - def request_api(self, method: Method, path: str, params: dict, credentials: bool = False): - options = { - 'method': method, - 'path': path, - 'params': params, - 'credentials': credentials - } - - opts = self._request_options(**options) - resource = opts.get('resource') - headers = opts.get('headers') - - if method in ['GET', 'DELETE']: - response = request(method, resource, headers = headers) - elif method in ['POST', 'PUT']: - response = request(method, resource, data = json.dumps(params, separators=(',', ':')), headers = headers) - - return response.text \ No newline at end of file diff --git a/lnmarkets/swaps/__init__.py b/lnmarkets/swaps/__init__.py deleted file mode 100644 index 5d97e41..0000000 --- a/lnmarkets/swaps/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from .get_swap_by_source_id import get_swap_by_source_id, GetSwapBySourceIdParams -from .get_swap import get_swap, GetSwapParams -from .get_swaps import get_swaps, GetSwapsParams -from .new_swap import new_swap, NewSwapParams - -from .types import ( - Swap, - SwapAsset, - SwapSource, -) \ No newline at end of file diff --git a/lnmarkets/swaps/get_swap.py b/lnmarkets/swaps/get_swap.py deleted file mode 100644 index 3a47faf..0000000 --- a/lnmarkets/swaps/get_swap.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient -from lnmarkets.swaps.types import UUID, Swap - - -class GetSwapParams(TypedDict): - swap_id: Required[UUID] - - -def get_swap(client: LNMClient, params: GetSwapParams) -> Swap: - """ - @see https://docs.lnmarkets.com/api/operations/swapsgetswap - """ - return json.loads(client.request_api('GET', f'/swap/{params["swap_id"]}', {}, True)) \ No newline at end of file diff --git a/lnmarkets/swaps/get_swap_by_source_id.py b/lnmarkets/swaps/get_swap_by_source_id.py deleted file mode 100644 index a43f15c..0000000 --- a/lnmarkets/swaps/get_swap_by_source_id.py +++ /dev/null @@ -1,16 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient -from lnmarkets.swaps.types import UUID, Swap, SwapSource - - -class GetSwapBySourceIdParams(TypedDict): - source_id: Required[UUID] - source: Required[SwapSource] - - -def get_swap_by_source_id(client: LNMClient, params: GetSwapBySourceIdParams) -> Swap: - """ - @see https://docs.lnmarkets.com/api/operations/swapsgetswapbysourceid - """ - return json.loads(client.request_api('GET', f'/swap/source/{params["source_id"]}', {'source': params['source']}, True)) \ No newline at end of file diff --git a/lnmarkets/swaps/get_swaps.py b/lnmarkets/swaps/get_swaps.py deleted file mode 100644 index 851ec25..0000000 --- a/lnmarkets/swaps/get_swaps.py +++ /dev/null @@ -1,17 +0,0 @@ -import json -from typing import TypedDict, NotRequired -from lnmarkets import LNMClient -from lnmarkets.swaps.types import Swap - - -class GetSwapsParams(TypedDict): - from_ts: NotRequired[int] - to: NotRequired[int] - limit: NotRequired[int] - - -def get_swaps(client: LNMClient, params: GetSwapsParams = None) -> list[Swap]: - """ - @see https://docs.lnmarkets.com/api/operations/swapsgetswaps - """ - return json.loads(client.request_api('GET', '/swap', params, True)) \ No newline at end of file diff --git a/lnmarkets/swaps/new_swap.py b/lnmarkets/swaps/new_swap.py deleted file mode 100644 index 275779f..0000000 --- a/lnmarkets/swaps/new_swap.py +++ /dev/null @@ -1,17 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient -from lnmarkets.swaps.types import Swap, SwapAsset - - -class NewSwapParams(TypedDict): - in_amount: Required[int] - in_asset: Required[SwapAsset] - out_asset: Required[SwapAsset] - - -def new_swap(client: LNMClient, params: NewSwapParams) -> Swap: - """ - @see https://docs.lnmarkets.com/api/operations/swapsnewswap - """ - return json.loads(client.request_api('POST', '/swap', params, True)) \ No newline at end of file diff --git a/lnmarkets/swaps/types.py b/lnmarkets/swaps/types.py deleted file mode 100644 index 52df959..0000000 --- a/lnmarkets/swaps/types.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import TypedDict, NotRequired, Literal -from lnmarkets.swaps.types import UUID - -type SwapAsset = Literal['BTC', 'USD'] - -type SwapSource = Literal[ - 'deposit', - 'fee-refund', - 'swap', - 'withdrawal', - 'withdrawal-failed' -] - -class Swap(TypedDict): - creation_ts: int - id: UUID - in_amount: int - in_asset: SwapAsset - out_amount: int - out_asset: SwapAsset - source: NotRequired[SwapSource] - source_id: NotRequired[UUID] - uid: UUID - - diff --git a/lnmarkets/types.py b/lnmarkets/types.py deleted file mode 100644 index ac49bca..0000000 --- a/lnmarkets/types.py +++ /dev/null @@ -1 +0,0 @@ -type UUID = str \ No newline at end of file diff --git a/lnmarkets/user/__init__.py b/lnmarkets/user/__init__.py deleted file mode 100644 index f743b17..0000000 --- a/lnmarkets/user/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -from .deposit_synthetic_usd import deposit_synthetic_usd, DepositSyntheticUsdParams, DepositSyntheticUsdResponse -from .deposit import deposit, DepositParams, DepositResponse -from .get_bitcoin_addresses import get_bitcoin_addresses, GetBitcoinAddressesParams -from .get_deposit import get_deposit, GetDepositParams -from .get_user import get_user -from .get_withdrawal import get_withdrawal, GetWithdrawalParams -from .get_withdrawals import get_withdrawals -from .new_bitcoin_address import new_bitcoin_address, NewBitcoinAddressParams, NewBitcoinAddressResponse -from .transfer import transfer, TransferParams, TransferResponse -from .update_user import update_user, UpdateUserParams -from .withdraw_synthetic_usd import withdraw_synthetic_usd, WithdrawSyntheticUsdParams, WithdrawSyntheticUsdResponse - -from .types import ( - ApiKeyCreation, - Currency, - BitcoinDeposit, - Deposit, - DepositType, - FeeTier, - GenericDeposit, - GenericDepositBase, - GenericDepositError, - GenericDepositSuccess, - GenericOnChainWithdrawal, - GenericWithdrawal, - FetchTransactionsRequest, - GenericWithdrawalBase, - GenericWithdrawalError, - GenericWithdrawalSuccess, - InternalTransfer, - InternalWithdrawalCondensed, - Leaderboard, - LeaderboardEntry, - LightningDeposit, - LightningWithdrawalCondensed, - NewApiKey, - OnChainWithdrawalCondensed, - OnChainWithdrawalStatus, - TotpSetup, - WithdrawalCondensed, -) \ No newline at end of file diff --git a/lnmarkets/user/deposit.py b/lnmarkets/user/deposit.py deleted file mode 100644 index 62e9cd0..0000000 --- a/lnmarkets/user/deposit.py +++ /dev/null @@ -1,21 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient -from lnmarkets.user.types import UUID - - -class DepositParams(TypedDict): - amount: Required[float] - - -class DepositResponse(TypedDict): - deposit_id: UUID - expiry: int - payment_request: str - - -def deposit(client: LNMClient, params: DepositParams) -> DepositResponse: - """ - @see https://docs.lnmarkets.com/api/operations/userdeposit - """ - return json.loads(client.request_api('POST', '/user/deposit', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/deposit_synthetic_usd.py b/lnmarkets/user/deposit_synthetic_usd.py deleted file mode 100644 index 829e2a7..0000000 --- a/lnmarkets/user/deposit_synthetic_usd.py +++ /dev/null @@ -1,23 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient -from lnmarkets.user.types import UUID, Currency - - -class DepositSyntheticUsdParams(TypedDict): - amount: Required[float] - currency: Required[Currency] - - -class DepositSyntheticUsdResponse(TypedDict): - deposit_id: UUID - expiry: int - payment_request: str - synthetic_usd_amount: float - - -def deposit_synthetic_usd(client: LNMClient, params: DepositSyntheticUsdParams) -> DepositSyntheticUsdResponse: - """ - @see https://docs.lnmarkets.com/api/operations/userdepositsyntheticusd - """ - return json.loads(client.request_api('POST', '/user/deposit/susd', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/get_bitcoin_addresses.py b/lnmarkets/user/get_bitcoin_addresses.py deleted file mode 100644 index 4c38965..0000000 --- a/lnmarkets/user/get_bitcoin_addresses.py +++ /dev/null @@ -1,20 +0,0 @@ -import json -from typing import TypedDict, NotRequired -from lnmarkets import LNMClient - - -class GetBitcoinAddressesParams(TypedDict): - current: NotRequired[bool] - - -class BitcoinAddress(TypedDict): - address: str - creation_ts: int - is_used: bool - - -def get_bitcoin_addresses(client: LNMClient, params: GetBitcoinAddressesParams = None) -> list[BitcoinAddress]: - """ - @see https://docs.lnmarkets.com/api/operations/usergetbitcoinaddresses - """ - return json.loads(client.request_api('GET', '/user/bitcoin/addresses', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/get_deposit.py b/lnmarkets/user/get_deposit.py deleted file mode 100644 index bc57592..0000000 --- a/lnmarkets/user/get_deposit.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient -from lnmarkets.user.types import UUID, Deposit - - -class GetDepositParams(TypedDict): - deposit_id: Required[UUID] - - -def get_deposit(client: LNMClient, params: GetDepositParams) -> Deposit: - """ - @see https://docs.lnmarkets.com/api/operations/usergetdeposit - """ - return json.loads(client.request_api('GET', f'/user/deposit/{params["deposit_id"]}', {}, True)) \ No newline at end of file diff --git a/lnmarkets/user/get_user.py b/lnmarkets/user/get_user.py deleted file mode 100644 index f1f415b..0000000 --- a/lnmarkets/user/get_user.py +++ /dev/null @@ -1,10 +0,0 @@ -import json -from lnmarkets import LNMClient -from lnmarkets.user.types import User - - -def get_user(client: LNMClient) -> User: - """ - @see https://docs.lnmarkets.com/api/operations/usergetuser - """ - return json.loads(client.request_api('GET', '/user', {}, True)) diff --git a/lnmarkets/user/get_withdrawal.py b/lnmarkets/user/get_withdrawal.py deleted file mode 100644 index c80a441..0000000 --- a/lnmarkets/user/get_withdrawal.py +++ /dev/null @@ -1,15 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient -from lnmarkets.user.types import UUID, WithdrawalCondensed - - -class GetWithdrawalParams(TypedDict): - id: Required[UUID] - - -def get_withdrawal(client: LNMClient, params: GetWithdrawalParams) -> WithdrawalCondensed: - """ - @see https://docs.lnmarkets.com/api/operations/usergetwithdrawal - """ - return json.loads(client.request_api('GET', f'/user/withdrawals/{params["id"]}', {}, True)) \ No newline at end of file diff --git a/lnmarkets/user/get_withdrawals.py b/lnmarkets/user/get_withdrawals.py deleted file mode 100644 index c0fbcfe..0000000 --- a/lnmarkets/user/get_withdrawals.py +++ /dev/null @@ -1,10 +0,0 @@ -import json -from lnmarkets import LNMClient -from lnmarkets.user.types import WithdrawalCondensed - - -def get_withdrawals(client: LNMClient) -> list[WithdrawalCondensed]: - """ - @see https://docs.lnmarkets.com/api/operations/usergetwithdrawals - """ - return json.loads(client.request_api('GET', '/user/withdraw', {}, True)) \ No newline at end of file diff --git a/lnmarkets/user/new_bitcoin_address.py b/lnmarkets/user/new_bitcoin_address.py deleted file mode 100644 index c3eb22e..0000000 --- a/lnmarkets/user/new_bitcoin_address.py +++ /dev/null @@ -1,19 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient - - -class NewBitcoinAddressParams(TypedDict): - format: Required[str] # Literal['p2tr', 'p2wpkh'] - - -class NewBitcoinAddressResponse(TypedDict): - address: str - creation_ts: int - - -def new_bitcoin_address(client: LNMClient, params: NewBitcoinAddressParams) -> NewBitcoinAddressResponse: - """ - @see https://docs.lnmarkets.com/api/operations/usernewbitcoinaddress - """ - return json.loads(client.request_api('POST', '/user/bitcoin/address', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/transfer.py b/lnmarkets/user/transfer.py deleted file mode 100644 index 065706b..0000000 --- a/lnmarkets/user/transfer.py +++ /dev/null @@ -1,20 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient - - -class TransferParams(TypedDict): - amount: Required[float] - to_username: Required[str] - - -class TransferResponse(TypedDict): - amount: float - to: str - - -def transfer(client: LNMClient, params: TransferParams) -> TransferResponse: - """ - @see https://docs.lnmarkets.com/api/operations/usertransfer - """ - return json.loads(client.request_api('POST', '/user/transfer', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/types.py b/lnmarkets/user/types.py deleted file mode 100644 index 408ae74..0000000 --- a/lnmarkets/user/types.py +++ /dev/null @@ -1,181 +0,0 @@ -from typing import TypedDict, List, Optional, Union, Literal, Any -from lnmarkets import UUID - -class ApiKeyCreation(TypedDict): - name: str - passphrase: str - permissions: List[str] - -class BitcoinDeposit(TypedDict): - amount: float - block_id: Optional[str] - confirmation_height: Optional[int] - confirmed_ts: Optional[int] - id: str - is_confirmed: bool - transaction_id: str - ts: int - -type Currency = Literal['btc', 'usd'] - -type Deposit = Union['BitcoinDeposit', 'InternalTransfer', 'LightningDeposit'] - -type DepositType = Literal['bitcoin', 'internal', 'lightning'] - -type FeeTier = Literal[0, 1, 2, 3] - -class FetchTransactionsRequest(TypedDict, total=False): - cursor: int - from_: int - limit: int - to: int - types: str - -type GenericDeposit = Union['GenericDepositError', 'GenericDepositSuccess'] - -class GenericDepositBase(TypedDict): - amount: float - comment: Optional[str] - id: str - success: bool - transaction_id_or_hash: str - ts: int - type: 'DepositType' - -class GenericDepositError(TypedDict): - amount: float - comment: Optional[str] - id: str - success: Literal[False] - transaction_id_or_hash: Optional[str] - ts: int - type: 'DepositType' - -class GenericDepositSuccess(GenericDepositBase): - success: Literal[True] - -class GenericOnChainWithdrawal(TypedDict): - amount: float - fee: float - id: str - ts: int - status: 'OnChainWithdrawalStatus' - transaction_id_or_hash: Optional[str] - type: Literal['bitcoin'] - -type GenericWithdrawal = Union['GenericWithdrawalError', 'GenericWithdrawalSuccess'] - -class GenericWithdrawalBase(TypedDict): - amount: float - fee: float - id: str - success: bool - transaction_id_or_hash: str - ts: int - type: Literal['internal', 'lightning'] - -class GenericWithdrawalError(TypedDict): - amount: float - id: str - success: Literal[False] - transaction_id_or_hash: str - ts: int - type: Literal['internal', 'lightning'] - fee: Optional[float] - -class GenericWithdrawalSuccess(GenericWithdrawalBase): - success: Literal[True] - -class InternalTransfer(TypedDict): - amount: float - from_username: str - id: str - success: bool - to_username: str - ts: int - -class InternalWithdrawalCondensed(TypedDict): - amount: float - id: str - success: bool - to_username: str - ts: int - type: Literal['internal'] - -class LeaderboardEntry(TypedDict): - direction: int - pl: float - username: str - -class Leaderboard(TypedDict): - all_time: List[LeaderboardEntry] - daily: List[LeaderboardEntry] - monthly: List[LeaderboardEntry] - weekly: List[LeaderboardEntry] - -class LightningDeposit(TypedDict): - amount: float - comment: Optional[str] - id: str - payment_hash: str - success: bool - success_ts: Optional[int] - ts: int - -class LightningWithdrawalCondensed(TypedDict): - amount: float - destination: Optional[str] - fee: float - id: str - payment_hash: str - success: bool - ts: int - type: Literal['lightning'] - -class NewApiKey(TypedDict): - creation_ts: int - id: str - key: str - last_modified: int - name: Optional[str] - permissions: List[str] - secret: str - -class OnChainWithdrawalCondensed(TypedDict): - address: str - amount: float - fee: float - id: str - status: 'OnChainWithdrawalStatus' - transaction_id: str - ts: int - type: Literal['bitcoin'] - -type OnChainWithdrawalStatus = Literal['confirmed', 'failed', 'pending'] - -class TotpSetup(TypedDict): - backup_codes: List[str] - secret: str - url: str - -class User(TypedDict): - account_type: str - auto_withdraw_enabled: bool - auto_withdraw_lightning_address: Optional[str] - balance: float - email: Optional[str] - email_confirmed: bool - fee_tier: FeeTier - linking_public_key: Optional[str] - metrics: Any - nostr_pubkey: Optional[str] - role: str - show_leaderboard: bool - synthetic_usd_balance: float - totp_enabled: bool - uid: UUID - username: str - use_taproot_addresses: bool - webauthn_enabled: bool - -type WithdrawalCondensed = Union[InternalWithdrawalCondensed, LightningWithdrawalCondensed, OnChainWithdrawalCondensed] diff --git a/lnmarkets/user/update_user.py b/lnmarkets/user/update_user.py deleted file mode 100644 index ae8c385..0000000 --- a/lnmarkets/user/update_user.py +++ /dev/null @@ -1,20 +0,0 @@ -import json -from typing import TypedDict, NotRequired -from lnmarkets import LNMClient -from lnmarkets.user.types import User - - -class UpdateUserParams(TypedDict): - auto_withdraw_enabled: NotRequired[bool] - auto_withdraw_lightning_address: NotRequired[bool] - nostr_pubkey: NotRequired[str] - show_leaderboard: NotRequired[bool] - username: NotRequired[str] - use_taproot_addresses: NotRequired[bool] - - -def update_user(client: LNMClient, params: UpdateUserParams) -> User: - """ - @see https://docs.lnmarkets.com/api/operations/userupdate - """ - return json.loads(client.request_api('PUT', '/user', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/withdraw.py b/lnmarkets/user/withdraw.py deleted file mode 100644 index 338d790..0000000 --- a/lnmarkets/user/withdraw.py +++ /dev/null @@ -1,24 +0,0 @@ -import json -from typing import TypedDict, Required, NotRequired -from lnmarkets import LNMClient -from lnmarkets.user.types import UUID - - -class WithdrawParams(TypedDict): - invoice: Required[str] - quote_id: NotRequired[UUID] - - -class WithdrawResponse(TypedDict): - amount: NotRequired[float] - fee: NotRequired[float] - id: UUID - payment_hash: NotRequired[str] - success_time: NotRequired[int] - - -def withdraw(client: LNMClient, params: WithdrawParams) -> WithdrawResponse: - """ - @see https://docs.lnmarkets.com/api/operations/userwithdraw - """ - return json.loads(client.request_api('POST', '/user/withdraw', params, True)) \ No newline at end of file diff --git a/lnmarkets/user/withdraw_synthetic_usd.py b/lnmarkets/user/withdraw_synthetic_usd.py deleted file mode 100644 index f612862..0000000 --- a/lnmarkets/user/withdraw_synthetic_usd.py +++ /dev/null @@ -1,25 +0,0 @@ -import json -from typing import TypedDict, Required -from lnmarkets import LNMClient -from lnmarkets.user.types import UUID, Currency - - -class WithdrawSyntheticUsdParams(TypedDict): - amount: Required[float] - currency: Required[Currency] - - -class WithdrawSyntheticUsdResponse(TypedDict): - amount: float - currency: Currency - fee_reserve: float - min_balance_after: float - quote_id: UUID - valid_until: int - - -def withdraw_synthetic_usd(client: LNMClient, params: WithdrawSyntheticUsdParams) -> WithdrawSyntheticUsdResponse: - """ - @see https://docs.lnmarkets.com/api/operations/userwithdrawalsyntheticusd - """ - return json.loads(client.request_api('POST', '/user/withdraw/susd', params, True)) \ No newline at end of file diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 80033ef..0000000 --- a/poetry.lock +++ /dev/null @@ -1,197 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. - -[[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, -] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "python-dotenv" -version = "1.0.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, - {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "urllib3" -version = "2.2.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.11" -content-hash = "9a600d0ca7a2c9819e3a3132c23e371a12791288834e9e6cbdbaaac95fbfc12a" diff --git a/scripts/spell-check.sh b/scripts/spell-check.sh deleted file mode 100755 index 63111cb..0000000 --- a/scripts/spell-check.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -cspell --show-context --no-progress ** \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_rest.py b/tests/test_rest.py deleted file mode 100644 index 102f7e3..0000000 --- a/tests/test_rest.py +++ /dev/null @@ -1,25 +0,0 @@ -import unittest -import os -from dotenv import load_dotenv - -from lnmarkets import LNMClient -from lnmarkets.user import get_user - -load_dotenv() - -class TestRest(unittest.TestCase): - def test_rest(self): - client = LNMClient({ - 'network': 'testnet', - 'key': os.getenv('LNM_API_KEY'), - 'secret': os.getenv('LNM_API_SECRET'), - 'passphrase': os.getenv('LNM_API_PASSPHRASE'), - }) - - user_info = get_user(client) - - self.assertRegex(user_info['uid'], r'^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$') - - -if __name__ == '__main__': - unittest.main() From 983012f680f1437c3b4f695fca70ce2773765c21 Mon Sep 17 00:00:00 2001 From: CaoKha Date: Wed, 5 Nov 2025 18:45:55 +0100 Subject: [PATCH 08/11] chore: add tests --- .github/workflows/check.yml | 10 + README.md | 6 +- examples/basic.py | 21 +- pyproject.toml | 4 +- pytest.ini | 15 ++ src/lnmarkets_sdk/_internal/__init__.py | 18 +- src/lnmarkets_sdk/_internal/models.py | 7 +- src/lnmarkets_sdk/_internal/utils.py | 8 +- src/lnmarkets_sdk/http/__init__.py | 0 .../{ => http}/client/__init__.py | 0 .../{ => http}/client/account.py | 2 +- .../{ => http}/client/futures/__init__.py | 2 +- .../{ => http}/client/futures/cross.py | 2 +- .../{ => http}/client/futures/isolated.py | 2 +- src/lnmarkets_sdk/{ => http}/client/oracle.py | 2 +- .../{ => http}/client/synthetic_usd.py | 2 +- src/lnmarkets_sdk/models/account.py | 26 +- src/lnmarkets_sdk/models/funding_fees.py | 2 +- src/lnmarkets_sdk/models/futures_cross.py | 4 +- src/lnmarkets_sdk/models/futures_data.py | 17 +- src/lnmarkets_sdk/models/futures_isolated.py | 8 +- tests/test_integration.py | 255 ++++++++++++++++++ uv.lock | 111 +++++++- 23 files changed, 465 insertions(+), 59 deletions(-) create mode 100644 pytest.ini create mode 100644 src/lnmarkets_sdk/http/__init__.py rename src/lnmarkets_sdk/{ => http}/client/__init__.py (100%) rename src/lnmarkets_sdk/{ => http}/client/account.py (99%) rename src/lnmarkets_sdk/{ => http}/client/futures/__init__.py (97%) rename src/lnmarkets_sdk/{ => http}/client/futures/cross.py (98%) rename src/lnmarkets_sdk/{ => http}/client/futures/isolated.py (98%) rename src/lnmarkets_sdk/{ => http}/client/oracle.py (95%) rename src/lnmarkets_sdk/{ => http}/client/synthetic_usd.py (95%) create mode 100644 tests/test_integration.py diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b064a37..a6dfdc1 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -36,3 +36,13 @@ jobs: - name: Run type checker (pyright) run: uv run pyright + + - name: Run tests + run: uv run pytest + env: + V2_API_KEY: ${{ secrets.V2_API_KEY }} + V2_API_KEY_SECRET: ${{ secrets.V2_API_KEY_SECRET }} + V2_API_KEY_PASSPHRASE: ${{ secrets.V2_API_KEY_PASSPHRASE }} + V3_API_KEY: ${{ secrets.V3_API_KEY }} + V3_API_KEY_SECRET: ${{ secrets.V3_API_KEY_SECRET }} + V3_API_KEY_PASSPHRASE: ${{ secrets.V3_API_KEY_PASSPHRASE }} diff --git a/README.md b/README.md index 51aa483..ffb0015 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This is the Python version of the LN Markets API SDK. It provides a client-based For public endpoints, you can just do this: ```python -from lnmarkets_sdk.client import LNMClient +from lnmarkets_sdk.http.client import LNMClient import asyncio async with LNMClient() as client: @@ -21,7 +21,7 @@ Remember to sleep between requests, as the rate limit is 1 requests per second f For endpoints that need authentication, you need to create an instance of the `LNMClient` class and provide your API credentials: ```python -from lnmarkets_sdk.client import APIAuthContext, APIClientConfig, LNMClient +from lnmarkets_sdk.http.client import APIAuthContext, APIClientConfig, LNMClient config = APIClientConfig( authentication=APIAuthContext( @@ -41,7 +41,7 @@ For endpoints that requires input parameters, you can find the corresponding mod ```python -from lnmarkets_sdk.client import APIAuthContext, APIClientConfig, LNMClient +from lnmarkets_sdk.http.client import APIAuthContext, APIClientConfig, LNMClient from lnmarkets_sdk.models.account import GetLightningDepositsParams config = APIClientConfig( diff --git a/examples/basic.py b/examples/basic.py index 4b8b779..364252a 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -8,7 +8,7 @@ from dotenv import load_dotenv -from lnmarkets_sdk.client import APIAuthContext, APIClientConfig, LNMClient +from lnmarkets_sdk.http.client import APIAuthContext, APIClientConfig, LNMClient from lnmarkets_sdk.models.account import GetLightningDepositsParams from lnmarkets_sdk.models.futures_cross import ( FuturesCrossOrderLimit, @@ -25,7 +25,7 @@ async def example_public_endpoints(): # Create client without authentication for public endpoints # The httpx.AsyncClient is created once and reuses connections - async with LNMClient() as client: + async with LNMClient(APIClientConfig(network="testnet4")) as client: # All these requests share the same connection pool print("\n🔄 Making multiple requests with connection reuse...") @@ -65,15 +65,16 @@ async def example_authenticated_endpoints(): print("AUTHENTICATED ENDPOINTS EXAMPLE") print("=" * 80) - key = os.getenv("LNM_API_KEY_V3") - secret = os.getenv("LNM_API_SECRET_V3") - passphrase = os.getenv("LNM_API_PASSPHRASE_V3") + key = os.getenv("V3_API_KEY") + secret = os.getenv("V3_API_KEY_SECRET") + passphrase = os.getenv("V3_API_KEY_PASSPHRASE") + print(f"key: {key}") + print(f"secret: {secret}") + print(f"passphrase: {passphrase}") if not (key and secret and passphrase): print("\n⚠️ Skipping authenticated example:") - print( - " Please set LNM_API_KEY_V3, LNM_API_SECRET_V3, and LNM_API_PASSPHRASE_V3" - ) + print(" Please set V3_API_KEY, V3_API_KEY_SECRET, and V3_API_KEY_PASSPHRASE") return # Create config with authentication and custom timeout @@ -83,7 +84,7 @@ async def example_authenticated_endpoints(): secret=secret, passphrase=passphrase, ), - network="mainnet", + network="testnet4", timeout=60.0, # 60 second timeout (default is 30s) ) @@ -104,7 +105,7 @@ async def example_authenticated_endpoints(): # Get lightning deposits (last 5) deposits = await client.account.get_lightning_deposits( - GetLightningDepositsParams(limit=5) + GetLightningDepositsParams(from_="2022-01-01", limit=5) ) print(f"\n--- Recent Lightning Deposits (Last {len(deposits)}) ---") for deposit in deposits: diff --git a/pyproject.toml b/pyproject.toml index 2cef7bf..2c7741d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" urls = { "Homepage" = "https://github.com/ln-markets/sdk-python", "Repository" = "https://github.com/ln-markets/sdk-python", "Bug Tracker" = "https://github.com/ln-markets/sdk-python/issues" } name = "lnmarkets-sdk" -version = "0.0.3" +version = "0.0.5" description = "LN Markets API Python SDK" readme = "README.md" license = { text = "MIT" } @@ -44,7 +44,7 @@ dev = [ "playwright>=1.40.0", ] lint = ["ruff>=0.12.0", "pyright>=1.1.390"] -test = ["pytest", "pytest-asyncio"] +test = ["pytest", "pytest-asyncio", "pytest-httpx>=0.35.0", "pytest-cov>=6.0.0"] [tool.hatch.build.metadata] include = ["src/**", "README.md", "LICENSE"] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..5266fbc --- /dev/null +++ b/pytest.ini @@ -0,0 +1,15 @@ +[pytest] +testpaths = tests +python_files = test_*.py +python_classes = Test* +python_functions = test_* +asyncio_mode = auto +asyncio_default_fixture_loop_scope = function + +markers = + asyncio: mark test as an async test + integration: mark test as integration test (requires real API) + +filterwarnings = + ignore::DeprecationWarning + diff --git a/src/lnmarkets_sdk/_internal/__init__.py b/src/lnmarkets_sdk/_internal/__init__.py index 785b709..461ca9b 100644 --- a/src/lnmarkets_sdk/_internal/__init__.py +++ b/src/lnmarkets_sdk/_internal/__init__.py @@ -52,32 +52,30 @@ async def request( if not self._client: raise RuntimeError("Client must be used within async context manager") - data = prepare_params(params) + params_dict = prepare_params(params) headers = {} if credentials: if not self.auth: raise ValueError("Authentication required but no credentials provided") - body_str = "" - if data: + data = "" + if params_dict: if method == "GET": - path = f"{path}?{urlencode({k: str(v) for k, v in data.items()})}" + data = f"?{urlencode({k: str(v) for k, v in params_dict.items()})}" else: - body_str = json.dumps(data, separators=(",", ":")) + data = json.dumps(params_dict, separators=(",", ":")) headers.update({"Content-Type": "application/json"}) - auth_headers = create_auth_headers( - self.auth, method, f"/v3{path}", body_str - ) + auth_headers = create_auth_headers(self.auth, method, f"/v3{path}", data) headers.update(auth_headers) # Use httpx native parameter handling if method == "GET": return await self._client.request( - method, path, params=data, headers=headers if headers else None + method, path, params=params_dict, headers=headers if headers else None ) else: return await self._client.request( - method, path, json=data, headers=headers if headers else None + method, path, json=params_dict, headers=headers if headers else None ) diff --git a/src/lnmarkets_sdk/_internal/models.py b/src/lnmarkets_sdk/_internal/models.py index 8f4353c..455159c 100644 --- a/src/lnmarkets_sdk/_internal/models.py +++ b/src/lnmarkets_sdk/_internal/models.py @@ -5,7 +5,7 @@ from pydantic.alias_generators import to_camel from pydantic.types import UUID4 -type APINetwork = Literal["mainnet", "testnet"] +type APINetwork = Literal["mainnet", "testnet4"] type APIMethod = Literal["GET", "POST", "PUT"] type UUID = UUID4 @@ -19,7 +19,7 @@ class BaseConfig: str_strip_whitespace=True, use_enum_values=True, alias_generator=to_camel, - populate_by_name=True, # to make `from_` field becomes `from` + validate_by_name=True, # to make `from_` field becomes `from` ) @@ -79,7 +79,8 @@ def __init__(self, message: str, status_code: int, response: httpx.Response): class FromToLimitParams(BaseModel, BaseConfig): from_: str | None = Field( default=None, - alias="from", + serialization_alias="from", + validation_alias="from", description="Start date as a string value in ISO format", ) limit: int = Field( diff --git a/src/lnmarkets_sdk/_internal/utils.py b/src/lnmarkets_sdk/_internal/utils.py index e3794f8..c901159 100644 --- a/src/lnmarkets_sdk/_internal/utils.py +++ b/src/lnmarkets_sdk/_internal/utils.py @@ -3,7 +3,6 @@ import hashlib import hmac import json -import os from base64 import b64encode from collections.abc import Mapping from datetime import datetime @@ -71,10 +70,9 @@ def create_auth_headers( def get_hostname(network: APINetwork) -> str: """Get API hostname based on network.""" - hostname = os.getenv("LNM_API_HOSTNAME") - if hostname: - return hostname - return "api.testnet.lnmarkets.com" if network == "testnet" else "api.lnmarkets.com" + return ( + "api.testnet4.lnmarkets.com" if network == "testnet4" else "api.lnmarkets.com" + ) def parse_response[T]( diff --git a/src/lnmarkets_sdk/http/__init__.py b/src/lnmarkets_sdk/http/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/lnmarkets_sdk/client/__init__.py b/src/lnmarkets_sdk/http/client/__init__.py similarity index 100% rename from src/lnmarkets_sdk/client/__init__.py rename to src/lnmarkets_sdk/http/client/__init__.py diff --git a/src/lnmarkets_sdk/client/account.py b/src/lnmarkets_sdk/http/client/account.py similarity index 99% rename from src/lnmarkets_sdk/client/account.py rename to src/lnmarkets_sdk/http/client/account.py index 64db870..4b82416 100644 --- a/src/lnmarkets_sdk/client/account.py +++ b/src/lnmarkets_sdk/http/client/account.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from lnmarkets_sdk.client import LNMClient + from lnmarkets_sdk.http.client import LNMClient from lnmarkets_sdk.models.account import ( Account, diff --git a/src/lnmarkets_sdk/client/futures/__init__.py b/src/lnmarkets_sdk/http/client/futures/__init__.py similarity index 97% rename from src/lnmarkets_sdk/client/futures/__init__.py rename to src/lnmarkets_sdk/http/client/futures/__init__.py index 3d075f2..053528e 100644 --- a/src/lnmarkets_sdk/client/futures/__init__.py +++ b/src/lnmarkets_sdk/http/client/futures/__init__.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from lnmarkets_sdk.client import LNMClient + from lnmarkets_sdk.http.client import LNMClient from lnmarkets_sdk.models.funding_fees import FundingSettlementResponse from lnmarkets_sdk.models.futures_data import ( diff --git a/src/lnmarkets_sdk/client/futures/cross.py b/src/lnmarkets_sdk/http/client/futures/cross.py similarity index 98% rename from src/lnmarkets_sdk/client/futures/cross.py rename to src/lnmarkets_sdk/http/client/futures/cross.py index 9301ed9..cec7f11 100644 --- a/src/lnmarkets_sdk/client/futures/cross.py +++ b/src/lnmarkets_sdk/http/client/futures/cross.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from lnmarkets_sdk.client import LNMClient + from lnmarkets_sdk.http.client import LNMClient from lnmarkets_sdk.models.funding_fees import FundingFees from lnmarkets_sdk.models.futures_cross import ( diff --git a/src/lnmarkets_sdk/client/futures/isolated.py b/src/lnmarkets_sdk/http/client/futures/isolated.py similarity index 98% rename from src/lnmarkets_sdk/client/futures/isolated.py rename to src/lnmarkets_sdk/http/client/futures/isolated.py index a58fbb8..a7f8edc 100644 --- a/src/lnmarkets_sdk/client/futures/isolated.py +++ b/src/lnmarkets_sdk/http/client/futures/isolated.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from lnmarkets_sdk.client import LNMClient + from lnmarkets_sdk.http.client import LNMClient from lnmarkets_sdk.models.funding_fees import FundingFees from lnmarkets_sdk.models.futures_isolated import ( diff --git a/src/lnmarkets_sdk/client/oracle.py b/src/lnmarkets_sdk/http/client/oracle.py similarity index 95% rename from src/lnmarkets_sdk/client/oracle.py rename to src/lnmarkets_sdk/http/client/oracle.py index 8f5ad9c..deca195 100644 --- a/src/lnmarkets_sdk/client/oracle.py +++ b/src/lnmarkets_sdk/http/client/oracle.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from lnmarkets_sdk.client import LNMClient + from lnmarkets_sdk.http.client import LNMClient from lnmarkets_sdk.models.oracle import ( GetIndexParams, diff --git a/src/lnmarkets_sdk/client/synthetic_usd.py b/src/lnmarkets_sdk/http/client/synthetic_usd.py similarity index 95% rename from src/lnmarkets_sdk/client/synthetic_usd.py rename to src/lnmarkets_sdk/http/client/synthetic_usd.py index 8a263f5..d5105ff 100644 --- a/src/lnmarkets_sdk/client/synthetic_usd.py +++ b/src/lnmarkets_sdk/http/client/synthetic_usd.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from lnmarkets_sdk.client import LNMClient + from lnmarkets_sdk.http.client import LNMClient from lnmarkets_sdk.models.synthetic_usd import ( BestPriceResponse, diff --git a/src/lnmarkets_sdk/models/account.py b/src/lnmarkets_sdk/models/account.py index 8c88def..55f6b60 100644 --- a/src/lnmarkets_sdk/models/account.py +++ b/src/lnmarkets_sdk/models/account.py @@ -12,15 +12,17 @@ class Account(BaseModel, BaseConfig): ) balance: float = Field(..., description="Balance of the user (in satoshis)") fee_tier: int = Field(..., description="Fee tier of the user") - email: str | None = Field(None, description="Email of the user") + email: str | None = Field(default=None, description="Email of the user") id: UUID = Field(..., description="Unique identifier for this account") - linking_public_key: str | None = Field(None, description="Public key of the user") + linking_public_key: str | None = Field( + default=None, description="Public key of the user" + ) class BitcoinDepositCondensed(BaseModel, BaseConfig): amount: float = Field(..., description="The amount of the deposit") block_height: int | None = Field( - None, description="The block height of the deposit" + default=None, description="The block height of the deposit" ) confirmations: int = Field( ..., description="The number of confirmations of the deposit" @@ -51,12 +53,14 @@ class LightningDepositCondensed(BaseModel, BaseConfig): amount: float | None = Field( None, description="Amount of the deposit (in satoshis)" ) - comment: str | None = Field(None, description="Comment of the deposit") + comment: str | None = Field(default=None, description="Comment of the deposit") created_at: str = Field(..., description="Timestamp when the deposit was created") id: UUID = Field(..., description="Unique identifier for this deposit") - payment_hash: str | None = Field(None, description="Payment hash of the deposit") + payment_hash: str | None = Field( + default=None, description="Payment hash of the deposit" + ) settled_at: str | None = Field( - None, description="Timestamp when the deposit was settled" + default=None, description="Timestamp when the deposit was settled" ) @@ -79,12 +83,16 @@ class OnChainWithdrawalCondensed(BaseModel, BaseConfig): created_at: str = Field( ..., description="Timestamp when the withdrawal was created" ) - fee: float | None = Field(None, description="Fee of the withdrawal (in satoshis)") + fee: float | None = Field( + default=None, description="Fee of the withdrawal (in satoshis)" + ) id: UUID = Field(..., description="Unique identifier for the withdrawal") status: Literal["canceled", "pending", "processed", "processing", "rejected"] = ( Field(..., description="Status of the withdrawal") ) - tx_id: str | None = Field(None, description="Transaction ID of the withdrawal") + tx_id: str | None = Field( + default=None, description="Transaction ID of the withdrawal" + ) class InternalTransfer(BaseModel, BaseConfig): @@ -154,7 +162,7 @@ class AddBitcoinAddressParams(BaseModel, BaseConfig): class DepositLightningParams(BaseModel, BaseConfig): amount: int = Field(..., gt=0, description="Amount to deposit (in satoshis)") - comment: str | None = Field(None, description="Comment for the deposit") + comment: str | None = Field(default=None, description="Comment for the deposit") description_hash: str | None = Field( default=None, pattern=r"^[a-f0-9]{64}$", diff --git a/src/lnmarkets_sdk/models/funding_fees.py b/src/lnmarkets_sdk/models/funding_fees.py index 3f86e66..bece3a4 100644 --- a/src/lnmarkets_sdk/models/funding_fees.py +++ b/src/lnmarkets_sdk/models/funding_fees.py @@ -9,7 +9,7 @@ class FundingFees(BaseModel, BaseConfig): fee: float = Field(..., description="Funding fee amount") settlement_id: UUID = Field(..., description="Funding settlement ID") time: str = Field(..., description="Timestamp in ISO format") - trade_id: UUID | None = Field(None, description="Associated trade ID") + trade_id: UUID | None = Field(default=None, description="Associated trade ID") class FundingSettlement(BaseModel, BaseConfig): diff --git a/src/lnmarkets_sdk/models/futures_cross.py b/src/lnmarkets_sdk/models/futures_cross.py index fd3946f..35b8ed0 100644 --- a/src/lnmarkets_sdk/models/futures_cross.py +++ b/src/lnmarkets_sdk/models/futures_cross.py @@ -71,12 +71,12 @@ class FuturesCrossCanceledOrder(BaseModel, BaseConfig): class FuturesCrossPosition(BaseModel, BaseConfig): delta_pl: float = Field(..., description="Delta P&L") - entry_price: float | None = Field(None, description="Entry price") + entry_price: float | None = Field(default=None, description="Entry price") funding_fees: float = Field(..., description="Funding fees") id: UUID = Field(..., description="Position ID") initial_margin: float = Field(..., description="Initial margin") leverage: int = Field(..., gt=0, description="Leverage") - liquidation: float | None = Field(None, description="Liquidation price") + liquidation: float | None = Field(default=None, description="Liquidation price") maintenance_margin: float = Field(..., description="Maintenance margin") margin: float = Field(..., description="Current margin") quantity: float = Field(..., description="Position quantity") diff --git a/src/lnmarkets_sdk/models/futures_data.py b/src/lnmarkets_sdk/models/futures_data.py index 2df6aae..71230a7 100644 --- a/src/lnmarkets_sdk/models/futures_data.py +++ b/src/lnmarkets_sdk/models/futures_data.py @@ -27,10 +27,10 @@ class PriceBucket(BaseModel, BaseConfig): """Price bucket for ticker.""" ask_price: float | None = Field( - None, description="Current best ask/sell price available (in USD)" + default=None, description="Current best ask/sell price available (in USD)" ) bid_price: float | None = Field( - None, description="Current best bid price available (in USD)" + default=None, description="Current best bid price available (in USD)" ) max_size: int = Field(..., description="Maximum order size (in BTC)") min_size: int = Field(..., description="Minimum order size (in BTC)") @@ -78,7 +78,9 @@ class UserInfo(BaseModel, BaseConfig): class Leaderboard(BaseModel, BaseConfig): """Futures leaderboard data.""" - all_time: list[UserInfo] = Field(alias="all-time") + all_time: list[UserInfo] = Field( + validation_alias="all-time", serialization_alias="all-time" + ) daily: list[UserInfo] monthly: list[UserInfo] weekly: list[UserInfo] @@ -86,7 +88,10 @@ class Leaderboard(BaseModel, BaseConfig): class GetCandlesParams(BaseModel, BaseConfig): from_: str = Field( - ..., alias="from", description="Start date as a string value in ISO format" + ..., + validation_alias="from", + serialization_alias="from", + description="Start date as a string value in ISO format", ) range: CandleResolution = Field( default="1m", description="Resolution of the OHLC candle" @@ -94,7 +99,9 @@ class GetCandlesParams(BaseModel, BaseConfig): limit: int = Field( default=100, ge=1, le=1000, description="Number of entries to return" ) - to: str | None = Field(None, description="End date as a string value in ISO format") + to: str | None = Field( + default=None, description="End date as a string value in ISO format" + ) class GetFundingSettlementsParams(FromToLimitParams): ... diff --git a/src/lnmarkets_sdk/models/futures_isolated.py b/src/lnmarkets_sdk/models/futures_isolated.py index f86a3f7..87291c1 100644 --- a/src/lnmarkets_sdk/models/futures_isolated.py +++ b/src/lnmarkets_sdk/models/futures_isolated.py @@ -22,8 +22,12 @@ class FuturesOrder(BaseModel, BaseConfig): multiple_of=0.5, description="Take profit price level (0 if not set)", ) - margin: int | None = Field(None, description="Margin of the position (in satoshis)") - quantity: int | None = Field(None, description="Quantity of the position (in USD)") + margin: int | None = Field( + default=None, description="Margin of the position (in satoshis)" + ) + quantity: int | None = Field( + default=None, description="Quantity of the position (in USD)" + ) price: float | None = Field( default=None, gt=0, multiple_of=0.5, description="Price of the limit order" ) diff --git a/tests/test_integration.py b/tests/test_integration.py new file mode 100644 index 0000000..ced2a95 --- /dev/null +++ b/tests/test_integration.py @@ -0,0 +1,255 @@ +"""Integration tests for LNMarkets SDK v3 - tests against real API. + +WARNING: These tests make real API calls to testnet4.lnmarkets.com +- They may be rate limited if run too frequently +- Authenticated tests require valid API credentials +- Some tests create and cancel orders on the real exchange + +Run with: pytest tests/test_integration_v3.py -v -s +Run authenticated tests: V3_API_KEY=... pytest tests/test_integration_v3.py -v +""" + +import asyncio +import os + +import pytest +from dotenv import load_dotenv + +from lnmarkets_sdk.http.client import APIAuthContext, APIClientConfig, LNMClient +from lnmarkets_sdk.models.account import DepositLightningParams +from lnmarkets_sdk.models.futures_isolated import FuturesOrder + +load_dotenv() + + +# Add delay between tests to avoid rate limiting +@pytest.fixture(autouse=True) +async def rate_limit_delay(): + """Add delay between tests to avoid rate limiting.""" + yield + await asyncio.sleep(1) # 1s delay between tests + + +def create_public_config() -> APIClientConfig: + """Create config for testnet4.""" + return APIClientConfig(network="testnet4") + + +def create_auth_config() -> APIClientConfig: + """Create authenticated config for testnet4.""" + return APIClientConfig( + network="testnet4", + authentication=APIAuthContext( + key=os.environ.get("V3_API_KEY", "test-key"), + secret=os.environ.get("V3_API_KEY_SECRET", "test-secret"), + passphrase=os.environ.get("V3_API_KEY_PASSPHRASE", "test-passphrase"), + ), + ) + + +class TestBasicsIntegration: + """Integration tests for basic API endpoints.""" + + @pytest.mark.asyncio + async def test_ping(self): + """Test ping endpoint against real API.""" + async with LNMClient(create_public_config()) as client: + result = await client.ping() + assert "pong" in result + + @pytest.mark.asyncio + async def test_time(self): + """Test time endpoint against real API.""" + async with LNMClient(create_public_config()) as client: + result = await client.request("GET", "/time") + assert "time" in result + assert isinstance(result["time"], str) + + +class TestAccountIntegration: + """Integration tests for account endpoints (require authentication).""" + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + @pytest.mark.asyncio + async def test_get_account(self): + """Test getting account info from real API.""" + async with LNMClient(create_auth_config()) as client: + account = await client.account.get_account() + assert account.balance >= 0 + assert isinstance(account.email, str) + assert isinstance(account.username, str) + assert account.fee_tier >= 0 + assert account.id is not None + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + @pytest.mark.asyncio + async def test_deposit_lightning(self): + """Test creating a lightning deposit invoice on real API.""" + async with LNMClient(create_auth_config()) as client: + params = DepositLightningParams(amount=100_000) + result = await client.account.deposit_lightning(params) + assert result.deposit_id is not None + assert result.payment_request.startswith("ln") + + +class TestFuturesIntegration: + """Integration tests for futures endpoints.""" + + @pytest.mark.asyncio + async def test_get_ticker(self): + """Test getting futures ticker from real API.""" + async with LNMClient(create_public_config()) as client: + ticker = await client.futures.get_ticker() + assert ticker.index > 0 + assert ticker.last_price > 0 + + @pytest.mark.asyncio + async def test_get_leaderboard(self): + """Test getting leaderboard from real API.""" + async with LNMClient(create_public_config()) as client: + leaderboard = await client.futures.get_leaderboard() + assert isinstance(leaderboard.daily, list) + + @pytest.mark.asyncio + async def test_get_candles(self): + """Test getting candles from real API.""" + from lnmarkets_sdk.models.futures_data import GetCandlesParams + + async with LNMClient(create_public_config()) as client: + params = GetCandlesParams( + from_="2023-05-23T09:52:57.863Z", range="1m", limit=1 + ) + candles = await client.futures.get_candles(params) + assert isinstance(candles, list) + assert len(candles) > 0 + assert candles[0].open > 0 + assert candles[0].high > 0 + assert candles[0].low > 0 + assert candles[0].close > 0 + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + @pytest.mark.asyncio + async def test_futures_isolated(self): + """Test complete futures isolated workflow on real API.""" + async with LNMClient(create_auth_config()) as client: + # Create a new trade + params = FuturesOrder( + type="l", # limit order + side="b", # buy + price=100_000, + quantity=1, + leverage=100, + ) + trade = await client.futures.isolated.new_trade(params) + assert trade.id is not None + assert trade.side == "b" + assert trade.type == "l" + assert trade.leverage == 100 + + # Get open trades + open_trades = await client.futures.isolated.get_open_trades() + assert isinstance(open_trades, list) + # Our trade should be in the list + trade_ids = [t.id for t in open_trades] + assert trade.id in trade_ids + + # Cancel the trade + from lnmarkets_sdk.models.futures_isolated import CancelTradeParams + + cancel_params = CancelTradeParams(id=trade.id) + canceled = await client.futures.isolated.cancel(cancel_params) + assert canceled.id == trade.id + assert canceled.canceled is True + + +class TestFuturesCrossIntegration: + """Integration tests for cross margin futures.""" + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + @pytest.mark.asyncio + async def test_get_position(self): + """Test getting cross margin position from real API.""" + async with LNMClient(create_auth_config()) as client: + position = await client.futures.cross.get_position() + assert position.margin >= 0 + assert position.leverage > 0 + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + @pytest.mark.asyncio + async def test_cross_orders(self): + """Test getting cross margin orders from real API.""" + async with LNMClient(create_auth_config()) as client: + # Get open orders + open_orders = await client.futures.cross.get_open_orders() + assert isinstance(open_orders, list) + + # Get filled orders + from lnmarkets_sdk.models.futures_cross import GetFilledOrdersParams + + params = GetFilledOrdersParams(limit=5) + filled_orders = await client.futures.cross.get_filled_orders(params) + assert isinstance(filled_orders, list) + + +class TestOracleIntegration: + """Integration tests for oracle endpoints.""" + + @pytest.mark.asyncio + async def test_get_last_price(self): + """Test getting last price from real API.""" + async with LNMClient(create_public_config()) as client: + result = await client.oracle.get_last_price() + assert result[0].last_price > 0 + assert result[0].time is not None + + @pytest.mark.asyncio + async def test_get_index(self): + """Test getting index history from real API.""" + from lnmarkets_sdk.models.oracle import GetIndexParams + + async with LNMClient(create_public_config()) as client: + params = GetIndexParams(limit=5) + result = await client.oracle.get_index(params) + assert isinstance(result, list) + assert len(result) > 0 + assert result[0].index > 0 + + +class TestSyntheticUSDIntegration: + """Integration tests for synthetic USD endpoints.""" + + @pytest.mark.asyncio + async def test_get_best_price(self): + """Test getting best price from real API.""" + async with LNMClient(create_public_config()) as client: + result = await client.synthetic_usd.get_best_price() + assert result.ask_price + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + @pytest.mark.asyncio + async def test_get_swaps(self): + """Test getting swaps from real API.""" + from lnmarkets_sdk.models.synthetic_usd import GetSwapsParams + + async with LNMClient(create_auth_config()) as client: + params = GetSwapsParams(limit=5) + result = await client.synthetic_usd.get_swaps(params) + assert isinstance(result, list) diff --git a/uv.lock b/uv.lock index ead6a73..25e248d 100644 --- a/uv.lock +++ b/uv.lock @@ -100,6 +100,80 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905, upload-time = "2025-10-15T15:15:08.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", size = 216098, upload-time = "2025-10-15T15:13:02.916Z" }, + { url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", size = 216331, upload-time = "2025-10-15T15:13:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", size = 247825, upload-time = "2025-10-15T15:13:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", size = 250573, upload-time = "2025-10-15T15:13:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", size = 251706, upload-time = "2025-10-15T15:13:09.4Z" }, + { url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", size = 248221, upload-time = "2025-10-15T15:13:10.964Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", size = 249624, upload-time = "2025-10-15T15:13:12.5Z" }, + { url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", size = 247744, upload-time = "2025-10-15T15:13:14.554Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", size = 247325, upload-time = "2025-10-15T15:13:16.438Z" }, + { url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", size = 249180, upload-time = "2025-10-15T15:13:17.959Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", size = 218479, upload-time = "2025-10-15T15:13:19.608Z" }, + { url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", size = 219290, upload-time = "2025-10-15T15:13:21.593Z" }, + { url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", size = 217924, upload-time = "2025-10-15T15:13:23.39Z" }, + { url = "https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1", size = 216129, upload-time = "2025-10-15T15:13:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be", size = 216380, upload-time = "2025-10-15T15:13:26.976Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f5/3da9cc9596708273385189289c0e4d8197d37a386bdf17619013554b3447/coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d", size = 247375, upload-time = "2025-10-15T15:13:28.923Z" }, + { url = "https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82", size = 249978, upload-time = "2025-10-15T15:13:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8c/042dede2e23525e863bf1ccd2b92689692a148d8b5fd37c37899ba882645/coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52", size = 251253, upload-time = "2025-10-15T15:13:32.174Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/3c58df67bfa809a7bddd786356d9c5283e45d693edb5f3f55d0986dd905a/coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b", size = 247591, upload-time = "2025-10-15T15:13:34.147Z" }, + { url = "https://files.pythonhosted.org/packages/26/5b/c7f32efd862ee0477a18c41e4761305de6ddd2d49cdeda0c1116227570fd/coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4", size = 249411, upload-time = "2025-10-15T15:13:38.425Z" }, + { url = "https://files.pythonhosted.org/packages/76/b5/78cb4f1e86c1611431c990423ec0768122905b03837e1b4c6a6f388a858b/coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd", size = 247303, upload-time = "2025-10-15T15:13:40.464Z" }, + { url = "https://files.pythonhosted.org/packages/87/c9/23c753a8641a330f45f221286e707c427e46d0ffd1719b080cedc984ec40/coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc", size = 247157, upload-time = "2025-10-15T15:13:42.087Z" }, + { url = "https://files.pythonhosted.org/packages/c5/42/6e0cc71dc8a464486e944a4fa0d85bdec031cc2969e98ed41532a98336b9/coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48", size = 248921, upload-time = "2025-10-15T15:13:43.715Z" }, + { url = "https://files.pythonhosted.org/packages/e8/1c/743c2ef665e6858cccb0f84377dfe3a4c25add51e8c7ef19249be92465b6/coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040", size = 218526, upload-time = "2025-10-15T15:13:45.336Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05", size = 219317, upload-time = "2025-10-15T15:13:47.401Z" }, + { url = "https://files.pythonhosted.org/packages/97/54/47db81dcbe571a48a298f206183ba8a7ba79200a37cd0d9f4788fcd2af4a/coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a", size = 217948, upload-time = "2025-10-15T15:13:49.096Z" }, + { url = "https://files.pythonhosted.org/packages/e5/8b/cb68425420154e7e2a82fd779a8cc01549b6fa83c2ad3679cd6c088ebd07/coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b", size = 216837, upload-time = "2025-10-15T15:13:51.09Z" }, + { url = "https://files.pythonhosted.org/packages/33/55/9d61b5765a025685e14659c8d07037247de6383c0385757544ffe4606475/coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37", size = 217061, upload-time = "2025-10-15T15:13:52.747Z" }, + { url = "https://files.pythonhosted.org/packages/52/85/292459c9186d70dcec6538f06ea251bc968046922497377bf4a1dc9a71de/coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de", size = 258398, upload-time = "2025-10-15T15:13:54.45Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e2/46edd73fb8bf51446c41148d81944c54ed224854812b6ca549be25113ee0/coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f", size = 260574, upload-time = "2025-10-15T15:13:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/07/5e/1df469a19007ff82e2ca8fe509822820a31e251f80ee7344c34f6cd2ec43/coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c", size = 262797, upload-time = "2025-10-15T15:13:58.635Z" }, + { url = "https://files.pythonhosted.org/packages/f9/50/de216b31a1434b94d9b34a964c09943c6be45069ec704bfc379d8d89a649/coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa", size = 257361, upload-time = "2025-10-15T15:14:00.409Z" }, + { url = "https://files.pythonhosted.org/packages/82/1e/3f9f8344a48111e152e0fd495b6fff13cc743e771a6050abf1627a7ba918/coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740", size = 260349, upload-time = "2025-10-15T15:14:02.188Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/3f52741f9e7d82124272f3070bbe316006a7de1bad1093f88d59bfc6c548/coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef", size = 258114, upload-time = "2025-10-15T15:14:03.907Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8b/918f0e15f0365d50d3986bbd3338ca01178717ac5678301f3f547b6619e6/coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0", size = 256723, upload-time = "2025-10-15T15:14:06.324Z" }, + { url = "https://files.pythonhosted.org/packages/44/9e/7776829f82d3cf630878a7965a7d70cc6ca94f22c7d20ec4944f7148cb46/coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca", size = 259238, upload-time = "2025-10-15T15:14:08.002Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", size = 219180, upload-time = "2025-10-15T15:14:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", size = 220241, upload-time = "2025-10-15T15:14:11.471Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", size = 218510, upload-time = "2025-10-15T15:14:13.46Z" }, + { url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", size = 216110, upload-time = "2025-10-15T15:14:15.157Z" }, + { url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", size = 216395, upload-time = "2025-10-15T15:14:16.863Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", size = 247433, upload-time = "2025-10-15T15:14:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", size = 249970, upload-time = "2025-10-15T15:14:20.307Z" }, + { url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", size = 251324, upload-time = "2025-10-15T15:14:22.419Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", size = 247445, upload-time = "2025-10-15T15:14:24.205Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", size = 249324, upload-time = "2025-10-15T15:14:26.234Z" }, + { url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", size = 247261, upload-time = "2025-10-15T15:14:28.42Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", size = 247092, upload-time = "2025-10-15T15:14:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", size = 248755, upload-time = "2025-10-15T15:14:32.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", size = 218793, upload-time = "2025-10-15T15:14:34.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", size = 219587, upload-time = "2025-10-15T15:14:37.047Z" }, + { url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", size = 218168, upload-time = "2025-10-15T15:14:38.861Z" }, + { url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", size = 216850, upload-time = "2025-10-15T15:14:40.651Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", size = 217071, upload-time = "2025-10-15T15:14:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", size = 258570, upload-time = "2025-10-15T15:14:44.542Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", size = 260738, upload-time = "2025-10-15T15:14:46.436Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", size = 262994, upload-time = "2025-10-15T15:14:48.3Z" }, + { url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", size = 257282, upload-time = "2025-10-15T15:14:50.236Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", size = 260430, upload-time = "2025-10-15T15:14:52.413Z" }, + { url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", size = 258190, upload-time = "2025-10-15T15:14:54.268Z" }, + { url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", size = 256658, upload-time = "2025-10-15T15:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", size = 259342, upload-time = "2025-10-15T15:14:58.538Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", size = 219568, upload-time = "2025-10-15T15:15:00.382Z" }, + { url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", size = 220687, upload-time = "2025-10-15T15:15:02.322Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", size = 218711, upload-time = "2025-10-15T15:15:04.575Z" }, + { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" }, +] + [[package]] name = "greenlet" version = "3.2.4" @@ -196,7 +270,7 @@ wheels = [ [[package]] name = "lnmarkets-sdk" -version = "0.0.3" +version = "0.0.5" source = { editable = "." } dependencies = [ { name = "httpx" }, @@ -210,6 +284,8 @@ dev = [ { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-httpx" }, { name = "python-dotenv" }, { name = "ruff" }, ] @@ -220,6 +296,8 @@ lint = [ test = [ { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-httpx" }, ] [package.metadata] @@ -235,6 +313,8 @@ dev = [ { name = "pyright", specifier = ">=1.1.390" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-cov", specifier = ">=6.0.0" }, + { name = "pytest-httpx", specifier = ">=0.35.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "ruff", specifier = ">=0.12.0" }, ] @@ -245,6 +325,8 @@ lint = [ test = [ { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-cov", specifier = ">=6.0.0" }, + { name = "pytest-httpx", specifier = ">=0.35.0" }, ] [[package]] @@ -438,6 +520,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, ] +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "pytest-httpx" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/89/5b12b7b29e3d0af3a4b9c071ee92fa25a9017453731a38f08ba01c280f4c/pytest_httpx-0.35.0.tar.gz", hash = "sha256:d619ad5d2e67734abfbb224c3d9025d64795d4b8711116b1a13f72a251ae511f", size = 54146, upload-time = "2024-11-28T19:16:54.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/ed/026d467c1853dd83102411a78126b4842618e86c895f93528b0528c7a620/pytest_httpx-0.35.0-py3-none-any.whl", hash = "sha256:ee11a00ffcea94a5cbff47af2114d34c5b231c326902458deed73f9c459fd744", size = 19442, upload-time = "2024-11-28T19:16:52.787Z" }, +] + [[package]] name = "python-dotenv" version = "1.1.1" From bd2b40e94a8d14aaea264ead58caf08a1737a25c Mon Sep 17 00:00:00 2001 From: CaoKha Date: Wed, 5 Nov 2025 19:02:31 +0100 Subject: [PATCH 09/11] chore: remove useless comments --- pyproject.toml | 2 +- tests/test_integration.py | 25 +------------------------ uv.lock | 2 +- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2c7741d..e7cb9c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" urls = { "Homepage" = "https://github.com/ln-markets/sdk-python", "Repository" = "https://github.com/ln-markets/sdk-python", "Bug Tracker" = "https://github.com/ln-markets/sdk-python/issues" } name = "lnmarkets-sdk" -version = "0.0.5" +version = "0.0.6" description = "LN Markets API Python SDK" readme = "README.md" license = { text = "MIT" } diff --git a/tests/test_integration.py b/tests/test_integration.py index ced2a95..195b222 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,13 +1,4 @@ -"""Integration tests for LNMarkets SDK v3 - tests against real API. - -WARNING: These tests make real API calls to testnet4.lnmarkets.com -- They may be rate limited if run too frequently -- Authenticated tests require valid API credentials -- Some tests create and cancel orders on the real exchange - -Run with: pytest tests/test_integration_v3.py -v -s -Run authenticated tests: V3_API_KEY=... pytest tests/test_integration_v3.py -v -""" +"""Integration tests for LNMarkets SDK v3""" import asyncio import os @@ -52,14 +43,12 @@ class TestBasicsIntegration: @pytest.mark.asyncio async def test_ping(self): - """Test ping endpoint against real API.""" async with LNMClient(create_public_config()) as client: result = await client.ping() assert "pong" in result @pytest.mark.asyncio async def test_time(self): - """Test time endpoint against real API.""" async with LNMClient(create_public_config()) as client: result = await client.request("GET", "/time") assert "time" in result @@ -75,7 +64,6 @@ class TestAccountIntegration: ) @pytest.mark.asyncio async def test_get_account(self): - """Test getting account info from real API.""" async with LNMClient(create_auth_config()) as client: account = await client.account.get_account() assert account.balance >= 0 @@ -90,7 +78,6 @@ async def test_get_account(self): ) @pytest.mark.asyncio async def test_deposit_lightning(self): - """Test creating a lightning deposit invoice on real API.""" async with LNMClient(create_auth_config()) as client: params = DepositLightningParams(amount=100_000) result = await client.account.deposit_lightning(params) @@ -103,7 +90,6 @@ class TestFuturesIntegration: @pytest.mark.asyncio async def test_get_ticker(self): - """Test getting futures ticker from real API.""" async with LNMClient(create_public_config()) as client: ticker = await client.futures.get_ticker() assert ticker.index > 0 @@ -111,14 +97,12 @@ async def test_get_ticker(self): @pytest.mark.asyncio async def test_get_leaderboard(self): - """Test getting leaderboard from real API.""" async with LNMClient(create_public_config()) as client: leaderboard = await client.futures.get_leaderboard() assert isinstance(leaderboard.daily, list) @pytest.mark.asyncio async def test_get_candles(self): - """Test getting candles from real API.""" from lnmarkets_sdk.models.futures_data import GetCandlesParams async with LNMClient(create_public_config()) as client: @@ -139,7 +123,6 @@ async def test_get_candles(self): ) @pytest.mark.asyncio async def test_futures_isolated(self): - """Test complete futures isolated workflow on real API.""" async with LNMClient(create_auth_config()) as client: # Create a new trade params = FuturesOrder( @@ -180,7 +163,6 @@ class TestFuturesCrossIntegration: ) @pytest.mark.asyncio async def test_get_position(self): - """Test getting cross margin position from real API.""" async with LNMClient(create_auth_config()) as client: position = await client.futures.cross.get_position() assert position.margin >= 0 @@ -192,7 +174,6 @@ async def test_get_position(self): ) @pytest.mark.asyncio async def test_cross_orders(self): - """Test getting cross margin orders from real API.""" async with LNMClient(create_auth_config()) as client: # Get open orders open_orders = await client.futures.cross.get_open_orders() @@ -211,7 +192,6 @@ class TestOracleIntegration: @pytest.mark.asyncio async def test_get_last_price(self): - """Test getting last price from real API.""" async with LNMClient(create_public_config()) as client: result = await client.oracle.get_last_price() assert result[0].last_price > 0 @@ -219,7 +199,6 @@ async def test_get_last_price(self): @pytest.mark.asyncio async def test_get_index(self): - """Test getting index history from real API.""" from lnmarkets_sdk.models.oracle import GetIndexParams async with LNMClient(create_public_config()) as client: @@ -235,7 +214,6 @@ class TestSyntheticUSDIntegration: @pytest.mark.asyncio async def test_get_best_price(self): - """Test getting best price from real API.""" async with LNMClient(create_public_config()) as client: result = await client.synthetic_usd.get_best_price() assert result.ask_price @@ -246,7 +224,6 @@ async def test_get_best_price(self): ) @pytest.mark.asyncio async def test_get_swaps(self): - """Test getting swaps from real API.""" from lnmarkets_sdk.models.synthetic_usd import GetSwapsParams async with LNMClient(create_auth_config()) as client: diff --git a/uv.lock b/uv.lock index 25e248d..b041757 100644 --- a/uv.lock +++ b/uv.lock @@ -270,7 +270,7 @@ wheels = [ [[package]] name = "lnmarkets-sdk" -version = "0.0.5" +version = "0.0.6" source = { editable = "." } dependencies = [ { name = "httpx" }, From eab6ec1ae061d9c2bc0bef532d054630cd372f01 Mon Sep 17 00:00:00 2001 From: CaoKha Date: Thu, 6 Nov 2025 12:12:05 +0100 Subject: [PATCH 10/11] chore: add tests for 3 versions of python --- .github/workflows/check.yml | 9 +- pyproject.toml | 21 ++++- pytest.ini | 15 --- .../lnmarkets_sdk/tests}/test_integration.py | 29 +++--- uv.lock | 94 +------------------ 5 files changed, 37 insertions(+), 131 deletions(-) delete mode 100644 pytest.ini rename {tests => src/lnmarkets_sdk/tests}/test_integration.py (94%) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index a6dfdc1..a5307b6 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -38,11 +38,12 @@ jobs: run: uv run pyright - name: Run tests - run: uv run pytest + run: | + uv run --isolated --python=3.12 pytest + uv run --isolated --python=3.13 pytest + uv run --isolated --python=3.14 pytest + env: - V2_API_KEY: ${{ secrets.V2_API_KEY }} - V2_API_KEY_SECRET: ${{ secrets.V2_API_KEY_SECRET }} - V2_API_KEY_PASSPHRASE: ${{ secrets.V2_API_KEY_PASSPHRASE }} V3_API_KEY: ${{ secrets.V3_API_KEY }} V3_API_KEY_SECRET: ${{ secrets.V3_API_KEY_SECRET }} V3_API_KEY_PASSPHRASE: ${{ secrets.V3_API_KEY_PASSPHRASE }} diff --git a/pyproject.toml b/pyproject.toml index e7cb9c1..0a8aea1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" urls = { "Homepage" = "https://github.com/ln-markets/sdk-python", "Repository" = "https://github.com/ln-markets/sdk-python", "Bug Tracker" = "https://github.com/ln-markets/sdk-python/issues" } name = "lnmarkets-sdk" -version = "0.0.6" +version = "0.0.7" description = "LN Markets API Python SDK" readme = "README.md" license = { text = "MIT" } @@ -44,7 +44,7 @@ dev = [ "playwright>=1.40.0", ] lint = ["ruff>=0.12.0", "pyright>=1.1.390"] -test = ["pytest", "pytest-asyncio", "pytest-httpx>=0.35.0", "pytest-cov>=6.0.0"] +test = ["pytest", "pytest-asyncio", "pytest-httpx>=0.35.0"] [tool.hatch.build.metadata] include = ["src/**", "README.md", "LICENSE"] @@ -78,3 +78,20 @@ ignore = [ [tool.ruff.format] quote-style = "double" indent-style = "space" + +[tool.pytest.ini_options] +# testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" +addopts = [ + "--import-mode=importlib", +] +markers = [ + "asyncio: mark test as an async test", +] +filterwarnings = [ + "ignore::DeprecationWarning", +] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index 5266fbc..0000000 --- a/pytest.ini +++ /dev/null @@ -1,15 +0,0 @@ -[pytest] -testpaths = tests -python_files = test_*.py -python_classes = Test* -python_functions = test_* -asyncio_mode = auto -asyncio_default_fixture_loop_scope = function - -markers = - asyncio: mark test as an async test - integration: mark test as integration test (requires real API) - -filterwarnings = - ignore::DeprecationWarning - diff --git a/tests/test_integration.py b/src/lnmarkets_sdk/tests/test_integration.py similarity index 94% rename from tests/test_integration.py rename to src/lnmarkets_sdk/tests/test_integration.py index 195b222..16f7d3f 100644 --- a/tests/test_integration.py +++ b/src/lnmarkets_sdk/tests/test_integration.py @@ -14,10 +14,9 @@ # Add delay between tests to avoid rate limiting -@pytest.fixture(autouse=True) -async def rate_limit_delay(): +@pytest.fixture +async def public_rate_limit_delay(): """Add delay between tests to avoid rate limiting.""" - yield await asyncio.sleep(1) # 1s delay between tests @@ -38,16 +37,16 @@ def create_auth_config() -> APIClientConfig: ) +@pytest.mark.asyncio +@pytest.mark.usefixtures("public_rate_limit_delay") class TestBasicsIntegration: """Integration tests for basic API endpoints.""" - @pytest.mark.asyncio async def test_ping(self): async with LNMClient(create_public_config()) as client: result = await client.ping() assert "pong" in result - @pytest.mark.asyncio async def test_time(self): async with LNMClient(create_public_config()) as client: result = await client.request("GET", "/time") @@ -55,6 +54,7 @@ async def test_time(self): assert isinstance(result["time"], str) +@pytest.mark.asyncio class TestAccountIntegration: """Integration tests for account endpoints (require authentication).""" @@ -62,7 +62,6 @@ class TestAccountIntegration: not os.environ.get("V3_API_KEY"), reason="V3_API_KEY not set in environment", ) - @pytest.mark.asyncio async def test_get_account(self): async with LNMClient(create_auth_config()) as client: account = await client.account.get_account() @@ -76,7 +75,6 @@ async def test_get_account(self): not os.environ.get("V3_API_KEY"), reason="V3_API_KEY not set in environment", ) - @pytest.mark.asyncio async def test_deposit_lightning(self): async with LNMClient(create_auth_config()) as client: params = DepositLightningParams(amount=100_000) @@ -85,23 +83,24 @@ async def test_deposit_lightning(self): assert result.payment_request.startswith("ln") +@pytest.mark.asyncio class TestFuturesIntegration: """Integration tests for futures endpoints.""" - @pytest.mark.asyncio + @pytest.mark.usefixtures("public_rate_limit_delay") async def test_get_ticker(self): async with LNMClient(create_public_config()) as client: ticker = await client.futures.get_ticker() assert ticker.index > 0 assert ticker.last_price > 0 - @pytest.mark.asyncio + @pytest.mark.usefixtures("public_rate_limit_delay") async def test_get_leaderboard(self): async with LNMClient(create_public_config()) as client: leaderboard = await client.futures.get_leaderboard() assert isinstance(leaderboard.daily, list) - @pytest.mark.asyncio + @pytest.mark.usefixtures("public_rate_limit_delay") async def test_get_candles(self): from lnmarkets_sdk.models.futures_data import GetCandlesParams @@ -121,7 +120,6 @@ async def test_get_candles(self): not os.environ.get("V3_API_KEY"), reason="V3_API_KEY not set in environment", ) - @pytest.mark.asyncio async def test_futures_isolated(self): async with LNMClient(create_auth_config()) as client: # Create a new trade @@ -154,6 +152,7 @@ async def test_futures_isolated(self): assert canceled.canceled is True +@pytest.mark.asyncio class TestFuturesCrossIntegration: """Integration tests for cross margin futures.""" @@ -161,7 +160,6 @@ class TestFuturesCrossIntegration: not os.environ.get("V3_API_KEY"), reason="V3_API_KEY not set in environment", ) - @pytest.mark.asyncio async def test_get_position(self): async with LNMClient(create_auth_config()) as client: position = await client.futures.cross.get_position() @@ -172,7 +170,6 @@ async def test_get_position(self): not os.environ.get("V3_API_KEY"), reason="V3_API_KEY not set in environment", ) - @pytest.mark.asyncio async def test_cross_orders(self): async with LNMClient(create_auth_config()) as client: # Get open orders @@ -187,17 +184,16 @@ async def test_cross_orders(self): assert isinstance(filled_orders, list) +@pytest.mark.asyncio class TestOracleIntegration: """Integration tests for oracle endpoints.""" - @pytest.mark.asyncio async def test_get_last_price(self): async with LNMClient(create_public_config()) as client: result = await client.oracle.get_last_price() assert result[0].last_price > 0 assert result[0].time is not None - @pytest.mark.asyncio async def test_get_index(self): from lnmarkets_sdk.models.oracle import GetIndexParams @@ -209,10 +205,10 @@ async def test_get_index(self): assert result[0].index > 0 +@pytest.mark.asyncio class TestSyntheticUSDIntegration: """Integration tests for synthetic USD endpoints.""" - @pytest.mark.asyncio async def test_get_best_price(self): async with LNMClient(create_public_config()) as client: result = await client.synthetic_usd.get_best_price() @@ -222,7 +218,6 @@ async def test_get_best_price(self): not os.environ.get("V3_API_KEY"), reason="V3_API_KEY not set in environment", ) - @pytest.mark.asyncio async def test_get_swaps(self): from lnmarkets_sdk.models.synthetic_usd import GetSwapsParams diff --git a/uv.lock b/uv.lock index b041757..d79998a 100644 --- a/uv.lock +++ b/uv.lock @@ -100,80 +100,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "coverage" -version = "7.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/38/ee22495420457259d2f3390309505ea98f98a5eed40901cf62196abad006/coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", size = 811905, upload-time = "2025-10-15T15:15:08.542Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/db/86f6906a7c7edc1a52b2c6682d6dd9be775d73c0dfe2b84f8923dfea5784/coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", size = 216098, upload-time = "2025-10-15T15:13:02.916Z" }, - { url = "https://files.pythonhosted.org/packages/21/54/e7b26157048c7ba555596aad8569ff903d6cd67867d41b75287323678ede/coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", size = 216331, upload-time = "2025-10-15T15:13:04.403Z" }, - { url = "https://files.pythonhosted.org/packages/b9/19/1ce6bf444f858b83a733171306134a0544eaddf1ca8851ede6540a55b2ad/coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", size = 247825, upload-time = "2025-10-15T15:13:05.92Z" }, - { url = "https://files.pythonhosted.org/packages/71/0b/d3bcbbc259fcced5fb67c5d78f6e7ee965f49760c14afd931e9e663a83b2/coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", size = 250573, upload-time = "2025-10-15T15:13:07.471Z" }, - { url = "https://files.pythonhosted.org/packages/58/8d/b0ff3641a320abb047258d36ed1c21d16be33beed4152628331a1baf3365/coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", size = 251706, upload-time = "2025-10-15T15:13:09.4Z" }, - { url = "https://files.pythonhosted.org/packages/59/c8/5a586fe8c7b0458053d9c687f5cff515a74b66c85931f7fe17a1c958b4ac/coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", size = 248221, upload-time = "2025-10-15T15:13:10.964Z" }, - { url = "https://files.pythonhosted.org/packages/d0/ff/3a25e3132804ba44cfa9a778cdf2b73dbbe63ef4b0945e39602fc896ba52/coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", size = 249624, upload-time = "2025-10-15T15:13:12.5Z" }, - { url = "https://files.pythonhosted.org/packages/c5/12/ff10c8ce3895e1b17a73485ea79ebc1896a9e466a9d0f4aef63e0d17b718/coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", size = 247744, upload-time = "2025-10-15T15:13:14.554Z" }, - { url = "https://files.pythonhosted.org/packages/16/02/d500b91f5471b2975947e0629b8980e5e90786fe316b6d7299852c1d793d/coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", size = 247325, upload-time = "2025-10-15T15:13:16.438Z" }, - { url = "https://files.pythonhosted.org/packages/77/11/dee0284fbbd9cd64cfce806b827452c6df3f100d9e66188e82dfe771d4af/coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", size = 249180, upload-time = "2025-10-15T15:13:17.959Z" }, - { url = "https://files.pythonhosted.org/packages/59/1b/cdf1def928f0a150a057cab03286774e73e29c2395f0d30ce3d9e9f8e697/coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", size = 218479, upload-time = "2025-10-15T15:13:19.608Z" }, - { url = "https://files.pythonhosted.org/packages/ff/55/e5884d55e031da9c15b94b90a23beccc9d6beee65e9835cd6da0a79e4f3a/coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", size = 219290, upload-time = "2025-10-15T15:13:21.593Z" }, - { url = "https://files.pythonhosted.org/packages/23/a8/faa930cfc71c1d16bc78f9a19bb73700464f9c331d9e547bfbc1dbd3a108/coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", size = 217924, upload-time = "2025-10-15T15:13:23.39Z" }, - { url = "https://files.pythonhosted.org/packages/60/7f/85e4dfe65e400645464b25c036a26ac226cf3a69d4a50c3934c532491cdd/coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1", size = 216129, upload-time = "2025-10-15T15:13:25.371Z" }, - { url = "https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be", size = 216380, upload-time = "2025-10-15T15:13:26.976Z" }, - { url = "https://files.pythonhosted.org/packages/b2/f5/3da9cc9596708273385189289c0e4d8197d37a386bdf17619013554b3447/coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d", size = 247375, upload-time = "2025-10-15T15:13:28.923Z" }, - { url = "https://files.pythonhosted.org/packages/65/6c/f7f59c342359a235559d2bc76b0c73cfc4bac7d61bb0df210965cb1ecffd/coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82", size = 249978, upload-time = "2025-10-15T15:13:30.525Z" }, - { url = "https://files.pythonhosted.org/packages/e7/8c/042dede2e23525e863bf1ccd2b92689692a148d8b5fd37c37899ba882645/coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52", size = 251253, upload-time = "2025-10-15T15:13:32.174Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a9/3c58df67bfa809a7bddd786356d9c5283e45d693edb5f3f55d0986dd905a/coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b", size = 247591, upload-time = "2025-10-15T15:13:34.147Z" }, - { url = "https://files.pythonhosted.org/packages/26/5b/c7f32efd862ee0477a18c41e4761305de6ddd2d49cdeda0c1116227570fd/coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4", size = 249411, upload-time = "2025-10-15T15:13:38.425Z" }, - { url = "https://files.pythonhosted.org/packages/76/b5/78cb4f1e86c1611431c990423ec0768122905b03837e1b4c6a6f388a858b/coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd", size = 247303, upload-time = "2025-10-15T15:13:40.464Z" }, - { url = "https://files.pythonhosted.org/packages/87/c9/23c753a8641a330f45f221286e707c427e46d0ffd1719b080cedc984ec40/coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc", size = 247157, upload-time = "2025-10-15T15:13:42.087Z" }, - { url = "https://files.pythonhosted.org/packages/c5/42/6e0cc71dc8a464486e944a4fa0d85bdec031cc2969e98ed41532a98336b9/coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48", size = 248921, upload-time = "2025-10-15T15:13:43.715Z" }, - { url = "https://files.pythonhosted.org/packages/e8/1c/743c2ef665e6858cccb0f84377dfe3a4c25add51e8c7ef19249be92465b6/coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040", size = 218526, upload-time = "2025-10-15T15:13:45.336Z" }, - { url = "https://files.pythonhosted.org/packages/ff/d5/226daadfd1bf8ddbccefbd3aa3547d7b960fb48e1bdac124e2dd13a2b71a/coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05", size = 219317, upload-time = "2025-10-15T15:13:47.401Z" }, - { url = "https://files.pythonhosted.org/packages/97/54/47db81dcbe571a48a298f206183ba8a7ba79200a37cd0d9f4788fcd2af4a/coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a", size = 217948, upload-time = "2025-10-15T15:13:49.096Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8b/cb68425420154e7e2a82fd779a8cc01549b6fa83c2ad3679cd6c088ebd07/coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b", size = 216837, upload-time = "2025-10-15T15:13:51.09Z" }, - { url = "https://files.pythonhosted.org/packages/33/55/9d61b5765a025685e14659c8d07037247de6383c0385757544ffe4606475/coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37", size = 217061, upload-time = "2025-10-15T15:13:52.747Z" }, - { url = "https://files.pythonhosted.org/packages/52/85/292459c9186d70dcec6538f06ea251bc968046922497377bf4a1dc9a71de/coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de", size = 258398, upload-time = "2025-10-15T15:13:54.45Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e2/46edd73fb8bf51446c41148d81944c54ed224854812b6ca549be25113ee0/coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f", size = 260574, upload-time = "2025-10-15T15:13:56.145Z" }, - { url = "https://files.pythonhosted.org/packages/07/5e/1df469a19007ff82e2ca8fe509822820a31e251f80ee7344c34f6cd2ec43/coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c", size = 262797, upload-time = "2025-10-15T15:13:58.635Z" }, - { url = "https://files.pythonhosted.org/packages/f9/50/de216b31a1434b94d9b34a964c09943c6be45069ec704bfc379d8d89a649/coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa", size = 257361, upload-time = "2025-10-15T15:14:00.409Z" }, - { url = "https://files.pythonhosted.org/packages/82/1e/3f9f8344a48111e152e0fd495b6fff13cc743e771a6050abf1627a7ba918/coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740", size = 260349, upload-time = "2025-10-15T15:14:02.188Z" }, - { url = "https://files.pythonhosted.org/packages/65/9b/3f52741f9e7d82124272f3070bbe316006a7de1bad1093f88d59bfc6c548/coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef", size = 258114, upload-time = "2025-10-15T15:14:03.907Z" }, - { url = "https://files.pythonhosted.org/packages/0b/8b/918f0e15f0365d50d3986bbd3338ca01178717ac5678301f3f547b6619e6/coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0", size = 256723, upload-time = "2025-10-15T15:14:06.324Z" }, - { url = "https://files.pythonhosted.org/packages/44/9e/7776829f82d3cf630878a7965a7d70cc6ca94f22c7d20ec4944f7148cb46/coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca", size = 259238, upload-time = "2025-10-15T15:14:08.002Z" }, - { url = "https://files.pythonhosted.org/packages/9a/b8/49cf253e1e7a3bedb85199b201862dd7ca4859f75b6cf25ffa7298aa0760/coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", size = 219180, upload-time = "2025-10-15T15:14:09.786Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e1/1a541703826be7ae2125a0fb7f821af5729d56bb71e946e7b933cc7a89a4/coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", size = 220241, upload-time = "2025-10-15T15:14:11.471Z" }, - { url = "https://files.pythonhosted.org/packages/d5/d1/5ee0e0a08621140fd418ec4020f595b4d52d7eb429ae6a0c6542b4ba6f14/coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", size = 218510, upload-time = "2025-10-15T15:14:13.46Z" }, - { url = "https://files.pythonhosted.org/packages/f4/06/e923830c1985ce808e40a3fa3eb46c13350b3224b7da59757d37b6ce12b8/coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", size = 216110, upload-time = "2025-10-15T15:14:15.157Z" }, - { url = "https://files.pythonhosted.org/packages/42/82/cdeed03bfead45203fb651ed756dfb5266028f5f939e7f06efac4041dad5/coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", size = 216395, upload-time = "2025-10-15T15:14:16.863Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ba/e1c80caffc3199aa699813f73ff097bc2df7b31642bdbc7493600a8f1de5/coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", size = 247433, upload-time = "2025-10-15T15:14:18.589Z" }, - { url = "https://files.pythonhosted.org/packages/80/c0/5b259b029694ce0a5bbc1548834c7ba3db41d3efd3474489d7efce4ceb18/coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", size = 249970, upload-time = "2025-10-15T15:14:20.307Z" }, - { url = "https://files.pythonhosted.org/packages/8c/86/171b2b5e1aac7e2fd9b43f7158b987dbeb95f06d1fbecad54ad8163ae3e8/coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", size = 251324, upload-time = "2025-10-15T15:14:22.419Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/7e10414d343385b92024af3932a27a1caf75c6e27ee88ba211221ff1a145/coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", size = 247445, upload-time = "2025-10-15T15:14:24.205Z" }, - { url = "https://files.pythonhosted.org/packages/c4/3b/e4f966b21f5be8c4bf86ad75ae94efa0de4c99c7bbb8114476323102e345/coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", size = 249324, upload-time = "2025-10-15T15:14:26.234Z" }, - { url = "https://files.pythonhosted.org/packages/00/a2/8479325576dfcd909244d0df215f077f47437ab852ab778cfa2f8bf4d954/coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", size = 247261, upload-time = "2025-10-15T15:14:28.42Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d8/3a9e2db19d94d65771d0f2e21a9ea587d11b831332a73622f901157cc24b/coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", size = 247092, upload-time = "2025-10-15T15:14:30.784Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b1/bbca3c472544f9e2ad2d5116b2379732957048be4b93a9c543fcd0207e5f/coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", size = 248755, upload-time = "2025-10-15T15:14:32.585Z" }, - { url = "https://files.pythonhosted.org/packages/89/49/638d5a45a6a0f00af53d6b637c87007eb2297042186334e9923a61aa8854/coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", size = 218793, upload-time = "2025-10-15T15:14:34.972Z" }, - { url = "https://files.pythonhosted.org/packages/30/cc/b675a51f2d068adb3cdf3799212c662239b0ca27f4691d1fff81b92ea850/coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", size = 219587, upload-time = "2025-10-15T15:14:37.047Z" }, - { url = "https://files.pythonhosted.org/packages/93/98/5ac886876026de04f00820e5094fe22166b98dcb8b426bf6827aaf67048c/coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", size = 218168, upload-time = "2025-10-15T15:14:38.861Z" }, - { url = "https://files.pythonhosted.org/packages/14/d1/b4145d35b3e3ecf4d917e97fc8895bcf027d854879ba401d9ff0f533f997/coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", size = 216850, upload-time = "2025-10-15T15:14:40.651Z" }, - { url = "https://files.pythonhosted.org/packages/ca/d1/7f645fc2eccd318369a8a9948acc447bb7c1ade2911e31d3c5620544c22b/coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", size = 217071, upload-time = "2025-10-15T15:14:42.755Z" }, - { url = "https://files.pythonhosted.org/packages/54/7d/64d124649db2737ceced1dfcbdcb79898d5868d311730f622f8ecae84250/coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", size = 258570, upload-time = "2025-10-15T15:14:44.542Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3f/6f5922f80dc6f2d8b2c6f974835c43f53eb4257a7797727e6ca5b7b2ec1f/coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", size = 260738, upload-time = "2025-10-15T15:14:46.436Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5f/9e883523c4647c860b3812b417a2017e361eca5b635ee658387dc11b13c1/coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", size = 262994, upload-time = "2025-10-15T15:14:48.3Z" }, - { url = "https://files.pythonhosted.org/packages/07/bb/43b5a8e94c09c8bf51743ffc65c4c841a4ca5d3ed191d0a6919c379a1b83/coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", size = 257282, upload-time = "2025-10-15T15:14:50.236Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e5/0ead8af411411330b928733e1d201384b39251a5f043c1612970310e8283/coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", size = 260430, upload-time = "2025-10-15T15:14:52.413Z" }, - { url = "https://files.pythonhosted.org/packages/ae/66/03dd8bb0ba5b971620dcaac145461950f6d8204953e535d2b20c6b65d729/coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", size = 258190, upload-time = "2025-10-15T15:14:54.268Z" }, - { url = "https://files.pythonhosted.org/packages/45/ae/28a9cce40bf3174426cb2f7e71ee172d98e7f6446dff936a7ccecee34b14/coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", size = 256658, upload-time = "2025-10-15T15:14:56.436Z" }, - { url = "https://files.pythonhosted.org/packages/5c/7c/3a44234a8599513684bfc8684878fd7b126c2760f79712bb78c56f19efc4/coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", size = 259342, upload-time = "2025-10-15T15:14:58.538Z" }, - { url = "https://files.pythonhosted.org/packages/e1/e6/0108519cba871af0351725ebdb8660fd7a0fe2ba3850d56d32490c7d9b4b/coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", size = 219568, upload-time = "2025-10-15T15:15:00.382Z" }, - { url = "https://files.pythonhosted.org/packages/c9/76/44ba876e0942b4e62fdde23ccb029ddb16d19ba1bef081edd00857ba0b16/coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", size = 220687, upload-time = "2025-10-15T15:15:02.322Z" }, - { url = "https://files.pythonhosted.org/packages/b9/0c/0df55ecb20d0d0ed5c322e10a441775e1a3a5d78c60f0c4e1abfe6fcf949/coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", size = 218711, upload-time = "2025-10-15T15:15:04.575Z" }, - { url = "https://files.pythonhosted.org/packages/5f/04/642c1d8a448ae5ea1369eac8495740a79eb4e581a9fb0cbdce56bbf56da1/coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", size = 207761, upload-time = "2025-10-15T15:15:06.439Z" }, -] - [[package]] name = "greenlet" version = "3.2.4" @@ -270,7 +196,7 @@ wheels = [ [[package]] name = "lnmarkets-sdk" -version = "0.0.6" +version = "0.0.7" source = { editable = "." } dependencies = [ { name = "httpx" }, @@ -284,7 +210,6 @@ dev = [ { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, - { name = "pytest-cov" }, { name = "pytest-httpx" }, { name = "python-dotenv" }, { name = "ruff" }, @@ -296,7 +221,6 @@ lint = [ test = [ { name = "pytest" }, { name = "pytest-asyncio" }, - { name = "pytest-cov" }, { name = "pytest-httpx" }, ] @@ -313,7 +237,6 @@ dev = [ { name = "pyright", specifier = ">=1.1.390" }, { name = "pytest" }, { name = "pytest-asyncio" }, - { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "pytest-httpx", specifier = ">=0.35.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, { name = "ruff", specifier = ">=0.12.0" }, @@ -325,7 +248,6 @@ lint = [ test = [ { name = "pytest" }, { name = "pytest-asyncio" }, - { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "pytest-httpx", specifier = ">=0.35.0" }, ] @@ -520,20 +442,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, ] -[[package]] -name = "pytest-cov" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage" }, - { name = "pluggy" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, -] - [[package]] name = "pytest-httpx" version = "0.35.0" From f3b00b7cccc0626051aee6969f9b5d76b25b567f Mon Sep 17 00:00:00 2001 From: CaoKha Date: Thu, 6 Nov 2025 15:06:09 +0100 Subject: [PATCH 11/11] refactor: adjust schema output to respect naming convention --- src/lnmarkets_sdk/http/client/account.py | 32 +-- .../http/client/futures/__init__.py | 4 +- src/lnmarkets_sdk/models/account.py | 16 +- src/lnmarkets_sdk/models/funding_fees.py | 2 +- src/lnmarkets_sdk/tests/test_integration.py | 186 +++++++++++++++++- 5 files changed, 206 insertions(+), 34 deletions(-) diff --git a/src/lnmarkets_sdk/http/client/account.py b/src/lnmarkets_sdk/http/client/account.py index 4b82416..e1b5d98 100644 --- a/src/lnmarkets_sdk/http/client/account.py +++ b/src/lnmarkets_sdk/http/client/account.py @@ -6,22 +6,22 @@ from lnmarkets_sdk.models.account import ( Account, AddBitcoinAddressParams, - BitcoinAddressResponse, - BitcoinAddressWithTimestampResponse, - BitcoinDepositCondensed, + AddBitcoinAddressResponse, DepositLightningParams, DepositLightningResponse, + GetBitcoinAddressResponse, GetInternalDepositsParams, + GetInternalDepositsResponse, GetInternalWithdrawalsParams, + GetInternalWithdrawalsResponse, GetLightningDepositsParams, + GetLightningDepositsResponse, GetLightningWithdrawalsParams, + GetLightningWithdrawalsResponse, GetOnChainDepositsParams, + GetOnChainDepositsResponse, GetOnChainWithdrawalsParams, - InternalDepositCondensed, - InternalWithdrawalCondensed, - LightningDepositCondensed, - LightningWithdrawalCondensed, - OnChainWithdrawalCondensed, + GetOnChainWithdrawalsResponse, WithdrawInternalParams, WithdrawInternalResponse, WithdrawLightningParams, @@ -52,7 +52,7 @@ async def get_bitcoin_address(self): "GET", "/account/address/bitcoin", credentials=True, - response_model=BitcoinAddressResponse, + response_model=GetBitcoinAddressResponse, ) async def add_bitcoin_address(self, params: AddBitcoinAddressParams | None = None): @@ -62,7 +62,7 @@ async def add_bitcoin_address(self, params: AddBitcoinAddressParams | None = Non "/account/address/bitcoin", params=params, credentials=True, - response_model=BitcoinAddressWithTimestampResponse, + response_model=AddBitcoinAddressResponse, ) async def deposit_lightning(self, params: DepositLightningParams): @@ -114,7 +114,7 @@ async def get_lightning_deposits( "/account/deposits/lightning", params=params, credentials=True, - response_model=list[LightningDepositCondensed], + response_model=list[GetLightningDepositsResponse], ) async def get_lightning_withdrawals( @@ -126,7 +126,7 @@ async def get_lightning_withdrawals( "/account/withdrawals/lightning", params=params, credentials=True, - response_model=list[LightningWithdrawalCondensed], + response_model=list[GetLightningWithdrawalsResponse], ) async def get_internal_deposits( @@ -138,7 +138,7 @@ async def get_internal_deposits( "/account/deposits/internal", params=params, credentials=True, - response_model=list[InternalDepositCondensed], + response_model=list[GetInternalDepositsResponse], ) async def get_internal_withdrawals( @@ -150,7 +150,7 @@ async def get_internal_withdrawals( "/account/withdrawals/internal", params=params, credentials=True, - response_model=list[InternalWithdrawalCondensed], + response_model=list[GetInternalWithdrawalsResponse], ) async def get_on_chain_deposits( @@ -162,7 +162,7 @@ async def get_on_chain_deposits( "/account/deposits/bitcoin", params=params, credentials=True, - response_model=list[BitcoinDepositCondensed], + response_model=list[GetOnChainDepositsResponse], ) async def get_on_chain_withdrawals( @@ -174,5 +174,5 @@ async def get_on_chain_withdrawals( "/account/withdrawals/bitcoin", params=params, credentials=True, - response_model=list[OnChainWithdrawalCondensed], + response_model=list[GetOnChainWithdrawalsResponse], ) diff --git a/src/lnmarkets_sdk/http/client/futures/__init__.py b/src/lnmarkets_sdk/http/client/futures/__init__.py index 053528e..105737d 100644 --- a/src/lnmarkets_sdk/http/client/futures/__init__.py +++ b/src/lnmarkets_sdk/http/client/futures/__init__.py @@ -3,7 +3,7 @@ if TYPE_CHECKING: from lnmarkets_sdk.http.client import LNMClient -from lnmarkets_sdk.models.funding_fees import FundingSettlementResponse +from lnmarkets_sdk.models.funding_fees import GetFundingSettlementsResponse from lnmarkets_sdk.models.futures_data import ( Candle, GetCandlesParams, @@ -61,5 +61,5 @@ async def get_funding_settlements( "/futures/funding-settlements", params=params, credentials=False, - response_model=FundingSettlementResponse, + response_model=GetFundingSettlementsResponse, ) diff --git a/src/lnmarkets_sdk/models/account.py b/src/lnmarkets_sdk/models/account.py index 55f6b60..bafe49a 100644 --- a/src/lnmarkets_sdk/models/account.py +++ b/src/lnmarkets_sdk/models/account.py @@ -19,7 +19,7 @@ class Account(BaseModel, BaseConfig): ) -class BitcoinDepositCondensed(BaseModel, BaseConfig): +class GetOnChainDepositsResponse(BaseModel, BaseConfig): amount: float = Field(..., description="The amount of the deposit") block_height: int | None = Field( default=None, description="The block height of the deposit" @@ -35,21 +35,21 @@ class BitcoinDepositCondensed(BaseModel, BaseConfig): tx_id: str = Field(..., description="The transaction ID of the deposit") -class InternalDepositCondensed(BaseModel, BaseConfig): +class GetInternalDepositsResponse(BaseModel, BaseConfig): amount: float = Field(..., description="Amount of the deposit (in satoshis)") created_at: str = Field(..., description="Timestamp when the deposit was created") from_username: str = Field(..., description="Username of the sender") id: UUID = Field(..., description="Unique identifier for this deposit") -class InternalWithdrawalCondensed(BaseModel, BaseConfig): +class GetInternalWithdrawalsResponse(BaseModel, BaseConfig): amount: float = Field(..., description="Amount of the transfer (in satoshis)") created_at: str = Field(..., description="Timestamp when the transfer was created") id: UUID = Field(..., description="Unique identifier for this transfer") to_username: str = Field(..., description="Username of the recipient") -class LightningDepositCondensed(BaseModel, BaseConfig): +class GetLightningDepositsResponse(BaseModel, BaseConfig): amount: float | None = Field( None, description="Amount of the deposit (in satoshis)" ) @@ -64,7 +64,7 @@ class LightningDepositCondensed(BaseModel, BaseConfig): ) -class LightningWithdrawalCondensed(BaseModel, BaseConfig): +class GetLightningWithdrawalsResponse(BaseModel, BaseConfig): amount: float = Field(..., description="Amount of the withdrawal (in satoshis)") created_at: str = Field( ..., description="Timestamp when the withdrawal was created" @@ -77,7 +77,7 @@ class LightningWithdrawalCondensed(BaseModel, BaseConfig): ) -class OnChainWithdrawalCondensed(BaseModel, BaseConfig): +class GetOnChainWithdrawalsResponse(BaseModel, BaseConfig): address: str = Field(..., description="Address to withdraw to") amount: float = Field(..., description="Amount to withdraw") created_at: str = Field( @@ -145,11 +145,11 @@ class WithdrawOnChainResponse(BaseModel, BaseConfig): tx_id: None = None -class BitcoinAddressResponse(BaseModel, BaseConfig): +class GetBitcoinAddressResponse(BaseModel, BaseConfig): address: str = Field(..., description="Bitcoin address") -class BitcoinAddressWithTimestampResponse(BaseModel, BaseConfig): +class AddBitcoinAddressResponse(BaseModel, BaseConfig): address: str = Field(..., description="The generated Bitcoin address") created_at: str = Field(..., description="The creation time of the address") diff --git a/src/lnmarkets_sdk/models/funding_fees.py b/src/lnmarkets_sdk/models/funding_fees.py index bece3a4..fd740bc 100644 --- a/src/lnmarkets_sdk/models/funding_fees.py +++ b/src/lnmarkets_sdk/models/funding_fees.py @@ -21,7 +21,7 @@ class FundingSettlement(BaseModel, BaseConfig): time: str = Field(..., description="Funding settlement time") -class FundingSettlementResponse(BaseModel, BaseConfig): +class GetFundingSettlementsResponse(BaseModel, BaseConfig): """Funding settlement response.""" data: list[FundingSettlement] = Field( diff --git a/src/lnmarkets_sdk/tests/test_integration.py b/src/lnmarkets_sdk/tests/test_integration.py index 16f7d3f..d7477af 100644 --- a/src/lnmarkets_sdk/tests/test_integration.py +++ b/src/lnmarkets_sdk/tests/test_integration.py @@ -7,7 +7,18 @@ from dotenv import load_dotenv from lnmarkets_sdk.http.client import APIAuthContext, APIClientConfig, LNMClient -from lnmarkets_sdk.models.account import DepositLightningParams +from lnmarkets_sdk.models.account import ( + AddBitcoinAddressParams, + DepositLightningParams, + GetInternalDepositsParams, + GetInternalWithdrawalsParams, + GetLightningDepositsParams, + GetLightningWithdrawalsParams, + GetOnChainDepositsParams, + WithdrawInternalParams, + WithdrawLightningParams, + WithdrawOnChainParams, +) from lnmarkets_sdk.models.futures_isolated import FuturesOrder load_dotenv() @@ -16,8 +27,14 @@ # Add delay between tests to avoid rate limiting @pytest.fixture async def public_rate_limit_delay(): - """Add delay between tests to avoid rate limiting.""" - await asyncio.sleep(1) # 1s delay between tests + """Add delay between tests for public endpoints to avoid rate limiting.""" + await asyncio.sleep(0.5) # 0.5s delay between tests (2 requests per second) + + +@pytest.fixture +async def auth_rate_limit_delay(): + """Add delay between tests for authentication endpoints to avoid rate limiting.""" + await asyncio.sleep(0.1) # 0.1s delay between tests (10 requests per second) def create_public_config() -> APIClientConfig: @@ -55,6 +72,7 @@ async def test_time(self): @pytest.mark.asyncio +@pytest.mark.usefixtures("auth_rate_limit_delay") class TestAccountIntegration: """Integration tests for account endpoints (require authentication).""" @@ -71,6 +89,32 @@ async def test_get_account(self): assert account.fee_tier >= 0 assert account.id is not None + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_get_bitcoin_address(self): + async with LNMClient(create_auth_config()) as client: + result = await client.account.get_bitcoin_address() + assert result.address is not None + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_add_bitcoin_address(self): + async with LNMClient(create_auth_config()) as client: + params = AddBitcoinAddressParams(format="p2wpkh") + try: + result = await client.account.add_bitcoin_address(params) + assert result.address is not None + assert result.created_at is not None + except Exception as e: + assert ( + "You have too many unused addresses. Please use one of them." + in str(e) + ) + @pytest.mark.skipif( not os.environ.get("V3_API_KEY"), reason="V3_API_KEY not set in environment", @@ -82,25 +126,149 @@ async def test_deposit_lightning(self): assert result.deposit_id is not None assert result.payment_request.startswith("ln") + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_withdraw_lightning(self): + async with LNMClient(create_auth_config()) as client: + params = WithdrawLightningParams(invoice="test_invoice") + try: + result = await client.account.withdraw_lightning(params) + assert result.id is not None + assert result.amount is not None + assert result.max_fees is not None + except Exception as e: + assert "Send a correct BOLT 11 invoice" in str(e) + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_withdraw_internal(self): + async with LNMClient(create_auth_config()) as client: + params = WithdrawInternalParams(amount=100_000, to_username="test_username") + try: + result = await client.account.withdraw_internal(params) + assert result.id is not None + assert result.amount is not None + assert result.created_at is not None + assert result.from_uid is not None + assert result.to_uid is not None + except Exception as e: + assert "User not found" in str(e) + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_withdraw_on_chain(self): + async with LNMClient(create_auth_config()) as client: + params = WithdrawOnChainParams(amount=100_000, address="test_address") + try: + result = await client.account.withdraw_on_chain(params) + assert result.id is not None + assert result.amount is not None + assert result.created_at is not None + except Exception as e: + assert "Invalid address" in str(e) + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_get_lightning_deposits(self): + async with LNMClient(create_auth_config()) as client: + params = GetLightningDepositsParams(limit=2) + result = await client.account.get_lightning_deposits(params) + assert len(result) <= params.limit + if len(result) > 0: + assert result[0].id is not None + assert result[0].created_at is not None + assert result[0].amount is not None + assert result[0].comment is None + assert result[0].settled_at is None + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_get_lightning_withdrawals(self): + async with LNMClient(create_auth_config()) as client: + params = GetLightningWithdrawalsParams(limit=2) + result = await client.account.get_lightning_withdrawals(params) + assert len(result) <= params.limit + if len(result) > 0: + assert result[0].id is not None + assert result[0].created_at is not None + assert result[0].amount is not None + assert result[0].fee is not None + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_get_internal_deposits(self): + async with LNMClient(create_auth_config()) as client: + params = GetInternalDepositsParams(limit=2) + result = await client.account.get_internal_deposits(params) + assert len(result) <= params.limit + if len(result) > 0: + assert result[0].id is not None + assert result[0].created_at is not None + assert result[0].amount is not None + assert result[0].from_username is not None + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_get_internal_withdrawals(self): + async with LNMClient(create_auth_config()) as client: + params = GetInternalWithdrawalsParams(limit=2) + result = await client.account.get_internal_withdrawals(params) + assert len(result) <= params.limit + if len(result) > 0: + assert result[0].id is not None + assert result[0].created_at is not None + assert result[0].amount is not None + assert result[0].to_username is not None + + @pytest.mark.skipif( + not os.environ.get("V3_API_KEY"), + reason="V3_API_KEY not set in environment", + ) + async def test_get_on_chain_deposits(self): + async with LNMClient(create_auth_config()) as client: + params = GetOnChainDepositsParams(limit=2) + try: + result = await client.account.get_on_chain_deposits(params) + assert len(result) <= params.limit + if len(result) > 0: + assert result[0].id is not None + assert result[0].created_at is not None + assert result[0].amount is not None + assert result[0].block_height is not None + except Exception as e: + assert "HTTP 404: Not found" in str(e) + @pytest.mark.asyncio +@pytest.mark.usefixtures("public_rate_limit_delay") class TestFuturesIntegration: - """Integration tests for futures endpoints.""" + """Integration tests for futures data endpoints.""" - @pytest.mark.usefixtures("public_rate_limit_delay") async def test_get_ticker(self): async with LNMClient(create_public_config()) as client: ticker = await client.futures.get_ticker() assert ticker.index > 0 assert ticker.last_price > 0 - @pytest.mark.usefixtures("public_rate_limit_delay") async def test_get_leaderboard(self): async with LNMClient(create_public_config()) as client: leaderboard = await client.futures.get_leaderboard() assert isinstance(leaderboard.daily, list) - @pytest.mark.usefixtures("public_rate_limit_delay") async def test_get_candles(self): from lnmarkets_sdk.models.futures_data import GetCandlesParams @@ -116,6 +284,10 @@ async def test_get_candles(self): assert candles[0].low > 0 assert candles[0].close > 0 + +@pytest.mark.asyncio +@pytest.mark.usefixtures("auth_rate_limit_delay") +class TestFuturesIsolatedIntegration: @pytest.mark.skipif( not os.environ.get("V3_API_KEY"), reason="V3_API_KEY not set in environment",