typed-grvt 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- typed_grvt-0.1.0/LICENSE +21 -0
- typed_grvt-0.1.0/PKG-INFO +111 -0
- typed_grvt-0.1.0/README.md +88 -0
- typed_grvt-0.1.0/pyproject.toml +32 -0
- typed_grvt-0.1.0/setup.cfg +4 -0
- typed_grvt-0.1.0/src/typed_grvt/__init__.py +15 -0
- typed_grvt-0.1.0/src/typed_grvt/__init__.pyi +23 -0
- typed_grvt-0.1.0/src/typed_grvt/core/__init__.py +5 -0
- typed_grvt-0.1.0/src/typed_grvt/core/__init__.pyi +73 -0
- typed_grvt-0.1.0/src/typed_grvt/core/auth.py +283 -0
- typed_grvt-0.1.0/src/typed_grvt/core/base.py +164 -0
- typed_grvt-0.1.0/src/typed_grvt/core/endpoint/__init__.py +1 -0
- typed_grvt-0.1.0/src/typed_grvt/core/endpoint/edge.py +93 -0
- typed_grvt-0.1.0/src/typed_grvt/core/endpoint/gateway.py +188 -0
- typed_grvt-0.1.0/src/typed_grvt/core/env.py +57 -0
- typed_grvt-0.1.0/src/typed_grvt/core/envelope.py +245 -0
- typed_grvt-0.1.0/src/typed_grvt/core/exc.py +23 -0
- typed_grvt-0.1.0/src/typed_grvt/core/locks.py +37 -0
- typed_grvt-0.1.0/src/typed_grvt/core/lookup.py +146 -0
- typed_grvt-0.1.0/src/typed_grvt/core/owners.py +121 -0
- typed_grvt-0.1.0/src/typed_grvt/core/signing.py +494 -0
- typed_grvt-0.1.0/src/typed_grvt/core/transport/__init__.py +1 -0
- typed_grvt-0.1.0/src/typed_grvt/core/transport/edge.py +119 -0
- typed_grvt-0.1.0/src/typed_grvt/core/transport/gateway.py +536 -0
- typed_grvt-0.1.0/src/typed_grvt/core/transport/ws.py +375 -0
- typed_grvt-0.1.0/src/typed_grvt/core/types.py +62 -0
- typed_grvt-0.1.0/src/typed_grvt/core/wire.py +247 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/__init__.py +32 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/bridge_withdrawal_info.py +58 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/bridge_withdrawal_quote.py +82 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/bridge_withdrawal_status.py +61 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/deposit_addresses.py +40 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/generate_deposit_address.py +40 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/indirect_referral_data.py +146 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/investment_strategies.py +101 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/referral_data.py +150 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/referral_epochs.py +48 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/referral_points.py +57 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/rhino_chain_id.py +50 -0
- typed_grvt-0.1.0/src/typed_grvt/edge/schemas.py +55 -0
- typed_grvt-0.1.0/src/typed_grvt/main.py +61 -0
- typed_grvt-0.1.0/src/typed_grvt/market/__init__.py +40 -0
- typed_grvt-0.1.0/src/typed_grvt/market/all_instruments.py +55 -0
- typed_grvt-0.1.0/src/typed_grvt/market/candlesticks.py +183 -0
- typed_grvt-0.1.0/src/typed_grvt/market/collateral_asset_info.py +63 -0
- typed_grvt-0.1.0/src/typed_grvt/market/currency.py +53 -0
- typed_grvt-0.1.0/src/typed_grvt/market/funding_rate.py +148 -0
- typed_grvt-0.1.0/src/typed_grvt/market/instrument.py +43 -0
- typed_grvt-0.1.0/src/typed_grvt/market/instruments.py +73 -0
- typed_grvt-0.1.0/src/typed_grvt/market/margin_rules.py +73 -0
- typed_grvt-0.1.0/src/typed_grvt/market/mini_ticker.py +43 -0
- typed_grvt-0.1.0/src/typed_grvt/market/orderbook.py +47 -0
- typed_grvt-0.1.0/src/typed_grvt/market/supported_assets.py +58 -0
- typed_grvt-0.1.0/src/typed_grvt/market/ticker.py +43 -0
- typed_grvt-0.1.0/src/typed_grvt/market/time.py +37 -0
- typed_grvt-0.1.0/src/typed_grvt/market/trade_history.py +121 -0
- typed_grvt-0.1.0/src/typed_grvt/market/trades.py +50 -0
- typed_grvt-0.1.0/src/typed_grvt/market_streams/__init__.py +26 -0
- typed_grvt-0.1.0/src/typed_grvt/market_streams/candlesticks.py +41 -0
- typed_grvt-0.1.0/src/typed_grvt/market_streams/mini_ticker.py +34 -0
- typed_grvt-0.1.0/src/typed_grvt/market_streams/mini_ticker_delta.py +34 -0
- typed_grvt-0.1.0/src/typed_grvt/market_streams/orderbook.py +36 -0
- typed_grvt-0.1.0/src/typed_grvt/market_streams/orderbook_delta.py +34 -0
- typed_grvt-0.1.0/src/typed_grvt/market_streams/ticker.py +34 -0
- typed_grvt-0.1.0/src/typed_grvt/market_streams/ticker_delta.py +34 -0
- typed_grvt-0.1.0/src/typed_grvt/market_streams/trades.py +48 -0
- typed_grvt-0.1.0/src/typed_grvt/py.typed +0 -0
- typed_grvt-0.1.0/src/typed_grvt/schemas.py +1024 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/__init__.py +88 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/account_summary.py +94 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/aggregated_account_summary.py +62 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/all_initial_leverage.py +69 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/authorized_builders.py +56 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/builder_fill_history.py +141 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/bulk_orders.py +107 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/cancel_all_orders.py +64 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/cancel_on_disconnect.py +52 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/cancel_order.py +63 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/cancel_rfq.py +50 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/collateral_preference.py +63 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/create_order.py +43 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/create_rfq.py +50 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/deposit_history.py +153 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/ecn_from_broker.py +74 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/fill_history.py +150 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/funding_account_summary.py +47 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/funding_payment_history.py +177 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/interest_payment_history.py +149 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/open_orders.py +67 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/order.py +59 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/order_history.py +150 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/position_history.py +240 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/position_margin_limits.py +61 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/positions.py +67 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/set_derisk_mm_ratio.py +64 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/set_indicative_prices.py +59 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/set_initial_leverage.py +61 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/spot_account_summary.py +60 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/sub_accounts.py +44 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/transfer.py +107 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/transfer_history.py +150 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/vault_investor_summary.py +72 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/vault_manager_investor_history.py +87 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/vault_redeem_cancel.py +43 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/vault_redemption_queue.py +61 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/withdrawal.py +68 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/withdrawal_fee.py +52 -0
- typed_grvt-0.1.0/src/typed_grvt/trading/withdrawal_history.py +156 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/__init__.py +32 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/cancels.py +61 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/deposits.py +46 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/ecn_to_broker.py +80 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/fills.py +50 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/order_states.py +61 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/orders.py +50 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/positions.py +50 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/rfq_quotes.py +66 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/rfqs.py +70 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/transfers.py +48 -0
- typed_grvt-0.1.0/src/typed_grvt/trading_streams/withdrawals.py +48 -0
- typed_grvt-0.1.0/src/typed_grvt.egg-info/PKG-INFO +111 -0
- typed_grvt-0.1.0/src/typed_grvt.egg-info/SOURCES.txt +123 -0
- typed_grvt-0.1.0/src/typed_grvt.egg-info/dependency_links.txt +1 -0
- typed_grvt-0.1.0/src/typed_grvt.egg-info/requires.txt +5 -0
- typed_grvt-0.1.0/src/typed_grvt.egg-info/top_level.txt +1 -0
typed_grvt-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tribulnation Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: typed-grvt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A fully typed, validated async client for the GRVT API.
|
|
5
|
+
Author-email: Marcel Claramunt <marcel@tribulnation.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/tribulnation/typed
|
|
8
|
+
Project-URL: Documentation, https://tribulnation.com/typed/grvt
|
|
9
|
+
Project-URL: Website, https://tribulnation.com/typed
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: typed-core>=0.13
|
|
18
|
+
Requires-Dist: lazy-loader
|
|
19
|
+
Requires-Dist: eth-account>=0.13
|
|
20
|
+
Requires-Dist: httpx>=0.26
|
|
21
|
+
Requires-Dist: pydantic>=2.7
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# Typed GRVT
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<em>A fully typed, validated async client for the GRVT API.</em>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<a href="https://pypi.org/project/typed-grvt/">
|
|
32
|
+
<img src="https://img.shields.io/pypi/v/typed-grvt.svg" alt="PyPI version">
|
|
33
|
+
</a>
|
|
34
|
+
<a href="https://pypi.org/project/typed-grvt/">
|
|
35
|
+
<img src="https://img.shields.io/pypi/pyversions/typed-grvt.svg" alt="Python versions">
|
|
36
|
+
</a>
|
|
37
|
+
<a href="https://tribulnation.com/typed/grvt">
|
|
38
|
+
<img src="https://img.shields.io/badge/docs-live-black" alt="Docs">
|
|
39
|
+
</a>
|
|
40
|
+
<a href="LICENSE">
|
|
41
|
+
<img src="https://img.shields.io/pypi/l/typed-grvt.svg" alt="License">
|
|
42
|
+
</a>
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
- **Documentation**: [https://tribulnation.com/typed/grvt](https://tribulnation.com/typed/grvt)
|
|
48
|
+
- **Source Code**: [https://github.com/tribulnation/typed/tree/main/packages/grvt](https://github.com/tribulnation/typed/tree/main/packages/grvt)
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from typed_grvt import Grvt
|
|
54
|
+
|
|
55
|
+
async with Grvt.new(public=True) as client:
|
|
56
|
+
ticker = await client.market.ticker('BTC_USDT_Perp')
|
|
57
|
+
print(ticker.get('mark_price'))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`Grvt.new()` connects to GRVT production. Pass `env='testnet'` (or `env='staging'`) to use a
|
|
61
|
+
test deployment instead.
|
|
62
|
+
|
|
63
|
+
## Why Typed GRVT?
|
|
64
|
+
|
|
65
|
+
- **🎯 Precise Types**: Typed orders, fills, positions and market data across perpetuals, futures, options, spot swaps and stable perpetuals, not `dict`/`Any`.
|
|
66
|
+
- **✅ Runtime Validation**: Every HTTP reply and WebSocket feed message is validated by default, with a per-call `validate` override.
|
|
67
|
+
- **⚡ Async First**: Async HTTP and JSON-RPC over WebSocket, with a per-call `transport` choice and live streams, built for concurrent workflows.
|
|
68
|
+
- **📚 Full Surface**: Market data, trading, account history, transfers, withdrawals, deposits, RFQs and vault summaries, with session login and EIP-712 signing handled for you.
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install typed-grvt
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Surfaces
|
|
77
|
+
|
|
78
|
+
| Attribute | What it covers | Credentials |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `client.market` | Instruments, currencies, tickers, order books, trades, candles, funding rates | none |
|
|
81
|
+
| `client.market_streams` | Live tickers, order books, trades and candles | none |
|
|
82
|
+
| `client.trading` | Orders, fills, positions, account summaries, transfers, withdrawals, leverage, RFQs, vault summaries, redemption queues and redemption cancels | API key (+ signer key for `create_order`, `bulk_orders`, `transfer`, `withdrawal` and `set_derisk_mm_ratio`) |
|
|
83
|
+
| `client.trading_streams` | Your orders, fills, positions, transfers, deposits and withdrawals, live | API key |
|
|
84
|
+
| `client.edge` | Deposit addresses, bridge withdrawal quotes and status, referrals, strategies | API key (none for `investment_strategies` and `rhino_chain_id`) |
|
|
85
|
+
|
|
86
|
+
## How To
|
|
87
|
+
|
|
88
|
+
- [Fetch Market Data](https://tribulnation.com/typed/grvt/how-to/fetch-market-data)
|
|
89
|
+
- [Fetch Account Data](https://tribulnation.com/typed/grvt/how-to/fetch-account-data)
|
|
90
|
+
- [Place & Manage Orders](https://tribulnation.com/typed/grvt/how-to/place-and-manage-orders)
|
|
91
|
+
- [Paginate Through Results](https://tribulnation.com/typed/grvt/how-to/paginate-through-results)
|
|
92
|
+
- [Listen To Live Streams](https://tribulnation.com/typed/grvt/how-to/listen-to-live-streams)
|
|
93
|
+
- [Manage Transfers & Withdrawals](https://tribulnation.com/typed/grvt/how-to/manage-transfers-and-withdrawals)
|
|
94
|
+
|
|
95
|
+
## Reference
|
|
96
|
+
|
|
97
|
+
- [API Keys Setup](https://tribulnation.com/typed/grvt/api-keys)
|
|
98
|
+
- [Async Usage](https://tribulnation.com/typed/grvt/reference/async-usage)
|
|
99
|
+
- [Error Handling](https://tribulnation.com/typed/grvt/reference/error-handling)
|
|
100
|
+
- [Environment Variables](https://tribulnation.com/typed/grvt/reference/env-vars)
|
|
101
|
+
- [Timestamps](https://tribulnation.com/typed/grvt/reference/timestamps)
|
|
102
|
+
|
|
103
|
+
## Design Philosophy
|
|
104
|
+
|
|
105
|
+
Typed GRVT follows the principles outlined in [this blog post](https://tribulnation.com/blog/clients).
|
|
106
|
+
|
|
107
|
+
*Details matter. Developer experience matters.*
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Typed GRVT
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<em>A fully typed, validated async client for the GRVT API.</em>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://pypi.org/project/typed-grvt/">
|
|
9
|
+
<img src="https://img.shields.io/pypi/v/typed-grvt.svg" alt="PyPI version">
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://pypi.org/project/typed-grvt/">
|
|
12
|
+
<img src="https://img.shields.io/pypi/pyversions/typed-grvt.svg" alt="Python versions">
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://tribulnation.com/typed/grvt">
|
|
15
|
+
<img src="https://img.shields.io/badge/docs-live-black" alt="Docs">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="LICENSE">
|
|
18
|
+
<img src="https://img.shields.io/pypi/l/typed-grvt.svg" alt="License">
|
|
19
|
+
</a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
- **Documentation**: [https://tribulnation.com/typed/grvt](https://tribulnation.com/typed/grvt)
|
|
25
|
+
- **Source Code**: [https://github.com/tribulnation/typed/tree/main/packages/grvt](https://github.com/tribulnation/typed/tree/main/packages/grvt)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from typed_grvt import Grvt
|
|
31
|
+
|
|
32
|
+
async with Grvt.new(public=True) as client:
|
|
33
|
+
ticker = await client.market.ticker('BTC_USDT_Perp')
|
|
34
|
+
print(ticker.get('mark_price'))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`Grvt.new()` connects to GRVT production. Pass `env='testnet'` (or `env='staging'`) to use a
|
|
38
|
+
test deployment instead.
|
|
39
|
+
|
|
40
|
+
## Why Typed GRVT?
|
|
41
|
+
|
|
42
|
+
- **🎯 Precise Types**: Typed orders, fills, positions and market data across perpetuals, futures, options, spot swaps and stable perpetuals, not `dict`/`Any`.
|
|
43
|
+
- **✅ Runtime Validation**: Every HTTP reply and WebSocket feed message is validated by default, with a per-call `validate` override.
|
|
44
|
+
- **⚡ Async First**: Async HTTP and JSON-RPC over WebSocket, with a per-call `transport` choice and live streams, built for concurrent workflows.
|
|
45
|
+
- **📚 Full Surface**: Market data, trading, account history, transfers, withdrawals, deposits, RFQs and vault summaries, with session login and EIP-712 signing handled for you.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install typed-grvt
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Surfaces
|
|
54
|
+
|
|
55
|
+
| Attribute | What it covers | Credentials |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `client.market` | Instruments, currencies, tickers, order books, trades, candles, funding rates | none |
|
|
58
|
+
| `client.market_streams` | Live tickers, order books, trades and candles | none |
|
|
59
|
+
| `client.trading` | Orders, fills, positions, account summaries, transfers, withdrawals, leverage, RFQs, vault summaries, redemption queues and redemption cancels | API key (+ signer key for `create_order`, `bulk_orders`, `transfer`, `withdrawal` and `set_derisk_mm_ratio`) |
|
|
60
|
+
| `client.trading_streams` | Your orders, fills, positions, transfers, deposits and withdrawals, live | API key |
|
|
61
|
+
| `client.edge` | Deposit addresses, bridge withdrawal quotes and status, referrals, strategies | API key (none for `investment_strategies` and `rhino_chain_id`) |
|
|
62
|
+
|
|
63
|
+
## How To
|
|
64
|
+
|
|
65
|
+
- [Fetch Market Data](https://tribulnation.com/typed/grvt/how-to/fetch-market-data)
|
|
66
|
+
- [Fetch Account Data](https://tribulnation.com/typed/grvt/how-to/fetch-account-data)
|
|
67
|
+
- [Place & Manage Orders](https://tribulnation.com/typed/grvt/how-to/place-and-manage-orders)
|
|
68
|
+
- [Paginate Through Results](https://tribulnation.com/typed/grvt/how-to/paginate-through-results)
|
|
69
|
+
- [Listen To Live Streams](https://tribulnation.com/typed/grvt/how-to/listen-to-live-streams)
|
|
70
|
+
- [Manage Transfers & Withdrawals](https://tribulnation.com/typed/grvt/how-to/manage-transfers-and-withdrawals)
|
|
71
|
+
|
|
72
|
+
## Reference
|
|
73
|
+
|
|
74
|
+
- [API Keys Setup](https://tribulnation.com/typed/grvt/api-keys)
|
|
75
|
+
- [Async Usage](https://tribulnation.com/typed/grvt/reference/async-usage)
|
|
76
|
+
- [Error Handling](https://tribulnation.com/typed/grvt/reference/error-handling)
|
|
77
|
+
- [Environment Variables](https://tribulnation.com/typed/grvt/reference/env-vars)
|
|
78
|
+
- [Timestamps](https://tribulnation.com/typed/grvt/reference/timestamps)
|
|
79
|
+
|
|
80
|
+
## Design Philosophy
|
|
81
|
+
|
|
82
|
+
Typed GRVT follows the principles outlined in [this blog post](https://tribulnation.com/blog/clients).
|
|
83
|
+
|
|
84
|
+
*Details matter. Developer experience matters.*
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "typed-grvt"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{name="Marcel Claramunt", email="marcel@tribulnation.com"}
|
|
10
|
+
]
|
|
11
|
+
description = "A fully typed, validated async client for the GRVT API."
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"typed-core>=0.13", "lazy-loader", "eth-account>=0.13", "httpx>=0.26", "pydantic>=2.7",
|
|
22
|
+
]
|
|
23
|
+
requires-python = ">=3.10"
|
|
24
|
+
readme = {file="README.md", content-type="text/markdown"}
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.package-data]
|
|
27
|
+
"typed_grvt" = ["py.typed", "*.pyi", "**/*.pyi"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Repository = "https://github.com/tribulnation/typed"
|
|
31
|
+
Documentation = "https://tribulnation.com/typed/grvt"
|
|
32
|
+
Website = "https://tribulnation.com/typed"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""A fully typed, validated async client for the GRVT API.
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
```python
|
|
5
|
+
from typed_grvt import Grvt
|
|
6
|
+
|
|
7
|
+
async with Grvt.new(public=True) as client:
|
|
8
|
+
instrument = await client.market.instrument('BTC_USDT_Perp')
|
|
9
|
+
print(instrument['tick_size'])
|
|
10
|
+
```
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import lazy_loader as lazy
|
|
14
|
+
|
|
15
|
+
__getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .core import (
|
|
2
|
+
ApiError,
|
|
3
|
+
AuthError,
|
|
4
|
+
BadRequest,
|
|
5
|
+
Error,
|
|
6
|
+
LogicError,
|
|
7
|
+
NetworkError,
|
|
8
|
+
RateLimited,
|
|
9
|
+
ValidationError,
|
|
10
|
+
)
|
|
11
|
+
from .main import Grvt
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'Grvt',
|
|
15
|
+
'Error',
|
|
16
|
+
'NetworkError',
|
|
17
|
+
'ValidationError',
|
|
18
|
+
'ApiError',
|
|
19
|
+
'BadRequest',
|
|
20
|
+
'AuthError',
|
|
21
|
+
'RateLimited',
|
|
22
|
+
'LogicError',
|
|
23
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from .auth import Credentials, Session, SessionState, resolve_credentials
|
|
2
|
+
from .base import ClientBase
|
|
3
|
+
from .endpoint.edge import EdgeClient, EdgeEndpoint, EdgeMeta
|
|
4
|
+
from .endpoint.gateway import (
|
|
5
|
+
GatewayClient,
|
|
6
|
+
GatewayEndpoint,
|
|
7
|
+
Meta,
|
|
8
|
+
Sign,
|
|
9
|
+
SubAccount,
|
|
10
|
+
Transport,
|
|
11
|
+
)
|
|
12
|
+
from .env import Environment, Hosts
|
|
13
|
+
from .envelope import Unwrap
|
|
14
|
+
from .exc import (
|
|
15
|
+
ApiError,
|
|
16
|
+
AuthError,
|
|
17
|
+
BadRequest,
|
|
18
|
+
Error,
|
|
19
|
+
LogicError,
|
|
20
|
+
NetworkError,
|
|
21
|
+
RateLimited,
|
|
22
|
+
ValidationError,
|
|
23
|
+
)
|
|
24
|
+
from .signing import Signer
|
|
25
|
+
from .transport.edge import EdgeTransport
|
|
26
|
+
from .transport.gateway import GatewayTransport
|
|
27
|
+
from .transport.ws import GatewaySocket
|
|
28
|
+
from .types import (
|
|
29
|
+
TimestampIso,
|
|
30
|
+
TimestampMillis,
|
|
31
|
+
TimestampNanos,
|
|
32
|
+
timestamp_iso,
|
|
33
|
+
timestamp_millis,
|
|
34
|
+
timestamp_nanos,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
'Credentials',
|
|
39
|
+
'Session',
|
|
40
|
+
'SessionState',
|
|
41
|
+
'resolve_credentials',
|
|
42
|
+
'ClientBase',
|
|
43
|
+
'EdgeClient',
|
|
44
|
+
'EdgeEndpoint',
|
|
45
|
+
'EdgeMeta',
|
|
46
|
+
'GatewayClient',
|
|
47
|
+
'GatewayEndpoint',
|
|
48
|
+
'Meta',
|
|
49
|
+
'Sign',
|
|
50
|
+
'SubAccount',
|
|
51
|
+
'Transport',
|
|
52
|
+
'Environment',
|
|
53
|
+
'Hosts',
|
|
54
|
+
'Unwrap',
|
|
55
|
+
'Error',
|
|
56
|
+
'NetworkError',
|
|
57
|
+
'ValidationError',
|
|
58
|
+
'ApiError',
|
|
59
|
+
'BadRequest',
|
|
60
|
+
'AuthError',
|
|
61
|
+
'RateLimited',
|
|
62
|
+
'LogicError',
|
|
63
|
+
'Signer',
|
|
64
|
+
'EdgeTransport',
|
|
65
|
+
'GatewayTransport',
|
|
66
|
+
'GatewaySocket',
|
|
67
|
+
'TimestampNanos',
|
|
68
|
+
'timestamp_nanos',
|
|
69
|
+
'TimestampMillis',
|
|
70
|
+
'timestamp_millis',
|
|
71
|
+
'TimestampIso',
|
|
72
|
+
'timestamp_iso',
|
|
73
|
+
]
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""Credential resolution and the GRVT session: cookie login, shared by every authenticated transport.
|
|
2
|
+
|
|
3
|
+
GRVT authenticates calls with a session, not per-request signatures:
|
|
4
|
+
|
|
5
|
+
1. `POST {edge}/auth/api_key/login` with `{"api_key"}` (or `POST {edge}/auth/wallet/login`
|
|
6
|
+
with an EIP-712 `WalletLogin` signature, for a funding wallet without an API key).
|
|
7
|
+
2. The reply sets a `gravity` cookie (24 h `Max-Age` on testnet) and an `X-Grvt-Account-Id`
|
|
8
|
+
header; the body names the funding account and, for a trading-account key, its
|
|
9
|
+
sub-account.
|
|
10
|
+
3. Every authenticated call sends `Cookie: gravity=...` and `X-Grvt-Account-Id: ...`.
|
|
11
|
+
|
|
12
|
+
Payload signatures (orders, transfers, withdrawals) are separate: see `signing.py`.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from datetime import timedelta
|
|
17
|
+
from http.cookies import CookieError, SimpleCookie
|
|
18
|
+
from email.utils import parsedate_to_datetime
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
import httpx
|
|
24
|
+
from typing_extensions import Any, Literal
|
|
25
|
+
|
|
26
|
+
from typed_core.exceptions import AuthError, ValidationError
|
|
27
|
+
from typed_core.http import HttpClient
|
|
28
|
+
|
|
29
|
+
from .env import Environment
|
|
30
|
+
from .envelope import check_edge
|
|
31
|
+
from .locks import LoopLock
|
|
32
|
+
from .owners import Connection, Owned
|
|
33
|
+
from .signing import Signer
|
|
34
|
+
|
|
35
|
+
ENV_PREFIXES: dict[Environment, str] = {
|
|
36
|
+
'prod': 'GRVT_',
|
|
37
|
+
'testnet': 'GRVT_TESTNET_',
|
|
38
|
+
'staging': 'GRVT_STAGING_',
|
|
39
|
+
}
|
|
40
|
+
"""Environment-variable prefix per environment, so a testnet credential can never be read
|
|
41
|
+
for production: `GRVT_TESTNET_API_KEY`, `GRVT_TESTNET_PRIVATE_KEY`,
|
|
42
|
+
`GRVT_TESTNET_TRADING_ACCOUNT_ID` (and `GRVT_API_KEY`, ... for `prod`, the default). The
|
|
43
|
+
last is the default trading sub-account: see `GatewayTransport.sub_account_id()`."""
|
|
44
|
+
|
|
45
|
+
REFRESH_BEFORE = timedelta(minutes=1)
|
|
46
|
+
"""Log in again this long before the session cookie expires."""
|
|
47
|
+
|
|
48
|
+
SESSION_COOKIE = 'gravity'
|
|
49
|
+
ACCOUNT_HEADER = 'X-Grvt-Account-Id'
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True, kw_only=True)
|
|
53
|
+
class Credentials:
|
|
54
|
+
"""GRVT credentials. Either secret alone can open a session; the private key also signs."""
|
|
55
|
+
|
|
56
|
+
api_key: str | None = field(default=None, repr=False)
|
|
57
|
+
"""API key, exchanged for a session cookie at login."""
|
|
58
|
+
private_key: str | None = field(default=None, repr=False)
|
|
59
|
+
"""EIP-712 signer key: the API key's signer, or the funding wallet itself."""
|
|
60
|
+
trading_account_id: str | None = None
|
|
61
|
+
"""Default trading sub-account id, filled into a trading call that leaves its
|
|
62
|
+
`sub_account_id` out (see `GatewayTransport.sub_account_id()`)."""
|
|
63
|
+
source: Literal['arguments', 'environment'] = 'arguments'
|
|
64
|
+
"""Where the secrets came from: `new()`'s arguments, or the environment variables."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def resolve_credentials(
|
|
68
|
+
env: Environment,
|
|
69
|
+
*,
|
|
70
|
+
api_key: str | None = None,
|
|
71
|
+
private_key: str | None = None,
|
|
72
|
+
trading_account_id: str | None = None,
|
|
73
|
+
public: bool = False,
|
|
74
|
+
) -> Credentials | None:
|
|
75
|
+
"""Resolve the one `Credentials` every authenticated transport of a client shares.
|
|
76
|
+
|
|
77
|
+
Called once, from the root's `.new()`. The secrets come from one source only, so an
|
|
78
|
+
explicit key is never paired with another account's key from the environment:
|
|
79
|
+
|
|
80
|
+
- If `api_key` or `private_key` is passed, the arguments are the whole credential set and
|
|
81
|
+
nothing is read from the environment (`trading_account_id` then defaults to the
|
|
82
|
+
sub-account the API key is scoped to, when not passed).
|
|
83
|
+
- Otherwise all three are read from the environment, under the environment-specific
|
|
84
|
+
prefix (`ENV_PREFIXES`), and an explicit `trading_account_id` overrides
|
|
85
|
+
`{prefix}TRADING_ACCOUNT_ID`.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
env: Target environment, which picks the variable prefix.
|
|
89
|
+
api_key: API key; read from `{prefix}API_KEY` when neither secret is passed.
|
|
90
|
+
private_key: Signer private key; read from `{prefix}PRIVATE_KEY` when neither secret is
|
|
91
|
+
passed.
|
|
92
|
+
trading_account_id: Default trading sub-account id; read from
|
|
93
|
+
`{prefix}TRADING_ACCOUNT_ID` when not passed and neither secret is passed.
|
|
94
|
+
public: Skip resolution and return `None`, for a market-data-only client.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
AuthError: `public` is false and neither an API key nor a private key was found.
|
|
98
|
+
"""
|
|
99
|
+
if public:
|
|
100
|
+
return None
|
|
101
|
+
prefix = ENV_PREFIXES[env]
|
|
102
|
+
source: Literal['arguments', 'environment'] = 'arguments'
|
|
103
|
+
if not api_key and not private_key:
|
|
104
|
+
source = 'environment'
|
|
105
|
+
api_key = os.environ.get(f'{prefix}API_KEY')
|
|
106
|
+
private_key = os.environ.get(f'{prefix}PRIVATE_KEY')
|
|
107
|
+
trading_account_id = trading_account_id or os.environ.get(
|
|
108
|
+
f'{prefix}TRADING_ACCOUNT_ID'
|
|
109
|
+
)
|
|
110
|
+
if not api_key and not private_key:
|
|
111
|
+
raise AuthError(
|
|
112
|
+
f'No GRVT credentials: set {prefix}API_KEY (and {prefix}PRIVATE_KEY to sign orders, '
|
|
113
|
+
'transfers and withdrawals), pass api_key/private_key, or build with `public=True` '
|
|
114
|
+
'for market data only.'
|
|
115
|
+
)
|
|
116
|
+
return Credentials(
|
|
117
|
+
api_key=api_key,
|
|
118
|
+
private_key=private_key,
|
|
119
|
+
trading_account_id=trading_account_id,
|
|
120
|
+
source=source,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass(frozen=True, kw_only=True)
|
|
125
|
+
class SessionState:
|
|
126
|
+
"""One logged-in session."""
|
|
127
|
+
|
|
128
|
+
token: str = field(repr=False)
|
|
129
|
+
"""`gravity` cookie value."""
|
|
130
|
+
account_id: str | None = field(repr=False)
|
|
131
|
+
"""`X-Grvt-Account-Id` header value (an off-chain `ACC:...` id)."""
|
|
132
|
+
expires_at: float
|
|
133
|
+
"""Unix seconds at which the cookie expires."""
|
|
134
|
+
funding_account_address: str | None
|
|
135
|
+
"""Funding (main) account address the session belongs to."""
|
|
136
|
+
sub_account_id: str | None
|
|
137
|
+
"""Trading sub-account id, for a trading-account-level API key."""
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def headers(self) -> dict[str, str]:
|
|
141
|
+
"""Headers authenticating one call with this session."""
|
|
142
|
+
headers = {'Cookie': f'{SESSION_COOKIE}={self.token}'}
|
|
143
|
+
if self.account_id:
|
|
144
|
+
headers[ACCOUNT_HEADER] = self.account_id
|
|
145
|
+
return headers
|
|
146
|
+
|
|
147
|
+
def fresh(self) -> bool:
|
|
148
|
+
"""Whether the cookie is valid for at least `REFRESH_BEFORE` more."""
|
|
149
|
+
return time.time() < self.expires_at - REFRESH_BEFORE.total_seconds()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
MAX_AGE = re.compile(r'-?[0-9]+')
|
|
153
|
+
"""A valid cookie `Max-Age`: an integer number of seconds (RFC 6265 §5.2.2)."""
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def cookie_expiry(response: httpx.Response) -> float:
|
|
157
|
+
"""Unix seconds at which the response's `gravity` cookie expires.
|
|
158
|
+
|
|
159
|
+
Reads `Max-Age` first, then `Expires`. An attribute that does not parse is ignored, as
|
|
160
|
+
RFC 6265 has a browser do: a malformed `Max-Age` falls back to `Expires`.
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
ValidationError: The response set no `gravity` cookie, or one with no valid lifetime.
|
|
164
|
+
"""
|
|
165
|
+
for header in response.headers.get_list('set-cookie'):
|
|
166
|
+
cookie = SimpleCookie()
|
|
167
|
+
try:
|
|
168
|
+
cookie.load(header)
|
|
169
|
+
except CookieError:
|
|
170
|
+
continue
|
|
171
|
+
if (morsel := cookie.get(SESSION_COOKIE)) is None:
|
|
172
|
+
continue
|
|
173
|
+
max_age = morsel['max-age'].strip()
|
|
174
|
+
if MAX_AGE.fullmatch(max_age):
|
|
175
|
+
return time.time() + int(max_age)
|
|
176
|
+
if morsel['expires']:
|
|
177
|
+
try:
|
|
178
|
+
return parsedate_to_datetime(morsel['expires']).timestamp()
|
|
179
|
+
except (TypeError, ValueError, OverflowError):
|
|
180
|
+
pass
|
|
181
|
+
raise ValidationError('Login response set no gravity cookie with a valid lifetime')
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def login_failure(payload: Any) -> AuthError:
|
|
185
|
+
"""The error for a login the edge host answered without success (an HTTP `200` whose body
|
|
186
|
+
says `"status": "failure"`, or one that is not an object).
|
|
187
|
+
|
|
188
|
+
Carries `(code, message, body)` when the body names a venue code, else one message.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
payload: The decoded login reply.
|
|
192
|
+
"""
|
|
193
|
+
if not isinstance(payload, dict):
|
|
194
|
+
return AuthError(f'Login failed: unexpected reply {payload!r:.500}')
|
|
195
|
+
message = str(
|
|
196
|
+
payload.get('message') or payload.get('error') or payload.get('status') or ''
|
|
197
|
+
)
|
|
198
|
+
if (code := payload.get('code')) is not None:
|
|
199
|
+
return AuthError(code, message, payload)
|
|
200
|
+
return AuthError(f'Login failed: {message or "no reason given"}')
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@dataclass(kw_only=True)
|
|
204
|
+
class Session(Owned):
|
|
205
|
+
"""Lazily logs in, caches the session and refreshes it ahead of expiry.
|
|
206
|
+
|
|
207
|
+
Shared by every authenticated transport of one client (trading HTTP, trading WebSocket,
|
|
208
|
+
edge), so a single login serves all of them. Concurrent callers wait on one login; the
|
|
209
|
+
lock is per event loop (`LoopLock`), so a client survives being reused across two
|
|
210
|
+
`asyncio.run` calls, provided each one enters it with `async with client`: leaving it
|
|
211
|
+
closes the HTTP clients and sockets, whose pooled connections otherwise stay bound to the
|
|
212
|
+
first loop and fail in the next one with a raw `RuntimeError`.
|
|
213
|
+
|
|
214
|
+
Each transport sharing the session takes one ownership of it while it is owned itself
|
|
215
|
+
(`Owned.shared`), and the login connection closes when the last owner leaves: `async with
|
|
216
|
+
client.trading` alone closes it on exit, while an edge block still open keeps it.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
edge_url: str
|
|
220
|
+
"""Edge host base URL, where the login endpoints live."""
|
|
221
|
+
api_key: str | None = field(default=None, repr=False)
|
|
222
|
+
"""API key to log in with; preferred over `signer`."""
|
|
223
|
+
signer: Signer | None = None
|
|
224
|
+
"""Funding-wallet signer to log in with when there is no API key."""
|
|
225
|
+
http: HttpClient = field(default_factory=HttpClient)
|
|
226
|
+
"""Dedicated client, so the login's `Set-Cookie` never lands in a shared cookie jar."""
|
|
227
|
+
current: SessionState | None = field(default=None, init=False, repr=False)
|
|
228
|
+
lock: LoopLock = field(default_factory=LoopLock, init=False, repr=False)
|
|
229
|
+
|
|
230
|
+
def connections(self) -> list[Connection]:
|
|
231
|
+
"""The login connection, closed when the last owner leaves."""
|
|
232
|
+
return [self.http]
|
|
233
|
+
|
|
234
|
+
async def state(self) -> SessionState:
|
|
235
|
+
"""The live session, logging in first when there is none or it is about to expire."""
|
|
236
|
+
async with self.lock:
|
|
237
|
+
if self.current is None or not self.current.fresh():
|
|
238
|
+
self.current = await self.login()
|
|
239
|
+
return self.current
|
|
240
|
+
|
|
241
|
+
async def headers(self) -> dict[str, str]:
|
|
242
|
+
"""Headers authenticating one call."""
|
|
243
|
+
return (await self.state()).headers
|
|
244
|
+
|
|
245
|
+
def invalidate(self, state: SessionState):
|
|
246
|
+
"""Forget `state` after the venue rejected it, unless a newer session already replaced it.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
state: The session the rejected call was sent with.
|
|
250
|
+
"""
|
|
251
|
+
if self.current is state:
|
|
252
|
+
self.current = None
|
|
253
|
+
|
|
254
|
+
async def login(self) -> SessionState:
|
|
255
|
+
"""Log in with the API key, or with a wallet signature when there is no API key.
|
|
256
|
+
|
|
257
|
+
Raises:
|
|
258
|
+
AuthError: The venue rejected the credentials, or there are none to log in with.
|
|
259
|
+
"""
|
|
260
|
+
if self.api_key:
|
|
261
|
+
path, body = '/auth/api_key/login', {'api_key': self.api_key}
|
|
262
|
+
elif self.signer is not None:
|
|
263
|
+
signature = self.signer.wallet_login()
|
|
264
|
+
path, body = (
|
|
265
|
+
'/auth/wallet/login',
|
|
266
|
+
{'address': self.signer.address, 'signature': signature},
|
|
267
|
+
)
|
|
268
|
+
else:
|
|
269
|
+
raise AuthError('No API key or wallet key to log in with')
|
|
270
|
+
response = await self.http.request('POST', self.edge_url + path, json=body)
|
|
271
|
+
payload = check_edge(response)
|
|
272
|
+
if not isinstance(payload, dict) or payload.get('status', 'success') != 'success':
|
|
273
|
+
raise login_failure(payload)
|
|
274
|
+
token = response.cookies.get(SESSION_COOKIE)
|
|
275
|
+
if not token:
|
|
276
|
+
raise ValidationError('Login response set no gravity cookie')
|
|
277
|
+
return SessionState(
|
|
278
|
+
token=token,
|
|
279
|
+
account_id=response.headers.get(ACCOUNT_HEADER),
|
|
280
|
+
expires_at=cookie_expiry(response),
|
|
281
|
+
funding_account_address=payload.get('funding_account_address'),
|
|
282
|
+
sub_account_id=payload.get('sub_account_id'),
|
|
283
|
+
)
|