polyester-sdk 0.1.0a0__py3-none-any.whl
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.
- polyester/__init__.py +32 -0
- polyester/_wire.py +55 -0
- polyester/auth.py +116 -0
- polyester/auth_interceptor.py +43 -0
- polyester/catalogs/__init__.py +157 -0
- polyester/catalogs/zipper.py +71 -0
- polyester/catalogs/zipper_supply.py +84 -0
- polyester/client.py +486 -0
- polyester/codecs/__init__.py +30 -0
- polyester/codecs/address_book.py +73 -0
- polyester/codecs/decode/__init__.py +95 -0
- polyester/codecs/decode/address_book.py +458 -0
- polyester/codecs/decode/api_keys.py +57 -0
- polyester/codecs/decode/auth.py +56 -0
- polyester/codecs/decode/balances.py +119 -0
- polyester/codecs/decode/common.py +8 -0
- polyester/codecs/decode/deposit.py +28 -0
- polyester/codecs/decode/guard_signer.py +78 -0
- polyester/codecs/decode/heatmap.py +9 -0
- polyester/codecs/decode/internal_transfers.py +16 -0
- polyester/codecs/decode/ledger_write.py +15 -0
- polyester/codecs/decode/lifecycle.py +72 -0
- polyester/codecs/decode/market_data.py +88 -0
- polyester/codecs/decode/market_overview.py +23 -0
- polyester/codecs/decode/orderbook.py +39 -0
- polyester/codecs/decode/orders.py +172 -0
- polyester/codecs/decode/policies.py +146 -0
- polyester/codecs/decode/resolve.py +21 -0
- polyester/codecs/decode/sub_accounts.py +166 -0
- polyester/codecs/decode/transfers.py +31 -0
- polyester/codecs/decode/triggers.py +111 -0
- polyester/codecs/decode/withdraw.py +11 -0
- polyester/codecs/decode/zipper.py +116 -0
- polyester/codecs/enums.py +25 -0
- polyester/codecs/guard_signer.py +55 -0
- polyester/codecs/heatmap.py +34 -0
- polyester/codecs/ledger.py +34 -0
- polyester/codecs/ledger_amounts.py +17 -0
- polyester/codecs/ledger_write.py +104 -0
- polyester/codecs/orderbook.py +20 -0
- polyester/codecs/orders.py +361 -0
- polyester/codecs/proto_build.py +28 -0
- polyester/codecs/proto_helpers.py +80 -0
- polyester/codecs/realtime_decode.py +186 -0
- polyester/codecs/scalars.py +130 -0
- polyester/codecs/triggers.py +135 -0
- polyester/codecs/wire_decode.py +652 -0
- polyester/codecs/withdraw.py +66 -0
- polyester/connect_transport.py +85 -0
- polyester/errors.py +59 -0
- polyester/gen/__init__.py +0 -0
- polyester/gen/auth/__init__.py +0 -0
- polyester/gen/auth/v1/__init__.py +0 -0
- polyester/gen/auth/v1/address_book_connect.py +976 -0
- polyester/gen/auth/v1/address_book_pb2.py +412 -0
- polyester/gen/auth/v1/address_book_pb2.pyi +640 -0
- polyester/gen/auth/v1/api_keys_connect.py +391 -0
- polyester/gen/auth/v1/api_keys_pb2.py +114 -0
- polyester/gen/auth/v1/api_keys_pb2.pyi +142 -0
- polyester/gen/auth/v1/auth_connect.py +261 -0
- polyester/gen/auth/v1/auth_pb2.py +81 -0
- polyester/gen/auth/v1/auth_pb2.pyi +158 -0
- polyester/gen/auth/v1/mfa_connect.py +1041 -0
- polyester/gen/auth/v1/mfa_pb2.py +231 -0
- polyester/gen/auth/v1/mfa_pb2.pyi +299 -0
- polyester/gen/auth/v1/policies_connect.py +846 -0
- polyester/gen/auth/v1/policies_pb2.py +218 -0
- polyester/gen/auth/v1/policies_pb2.pyi +436 -0
- polyester/gen/auth/v1/profile_connect.py +261 -0
- polyester/gen/auth/v1/profile_pb2.py +93 -0
- polyester/gen/auth/v1/profile_pb2.pyi +113 -0
- polyester/gen/auth/v1/resolve_connect.py +131 -0
- polyester/gen/auth/v1/resolve_pb2.py +55 -0
- polyester/gen/auth/v1/resolve_pb2.pyi +53 -0
- polyester/gen/auth/v1/social_verification_connect.py +261 -0
- polyester/gen/auth/v1/social_verification_pb2.py +80 -0
- polyester/gen/auth/v1/social_verification_pb2.pyi +127 -0
- polyester/gen/auth/v1/subaccounts_connect.py +888 -0
- polyester/gen/auth/v1/subaccounts_pb2.py +221 -0
- polyester/gen/auth/v1/subaccounts_pb2.pyi +369 -0
- polyester/gen/buf/__init__.py +0 -0
- polyester/gen/buf/validate/__init__.py +0 -0
- polyester/gen/buf/validate/validate_pb2.py +450 -0
- polyester/gen/buf/validate/validate_pb2.pyi +627 -0
- polyester/gen/chain/__init__.py +0 -0
- polyester/gen/chain/analytics/__init__.py +0 -0
- polyester/gen/chain/analytics/v1/__init__.py +0 -0
- polyester/gen/chain/analytics/v1/analytics_read_connect.py +261 -0
- polyester/gen/chain/analytics/v1/analytics_read_pb2.py +98 -0
- polyester/gen/chain/analytics/v1/analytics_read_pb2.pyi +132 -0
- polyester/gen/chain/deposit/__init__.py +0 -0
- polyester/gen/chain/deposit/v1/__init__.py +0 -0
- polyester/gen/chain/deposit/v1/deposit_connect.py +196 -0
- polyester/gen/chain/deposit/v1/deposit_pb2.py +62 -0
- polyester/gen/chain/deposit/v1/deposit_pb2.pyi +46 -0
- polyester/gen/chain/guard/__init__.py +0 -0
- polyester/gen/chain/guard/v1/__init__.py +0 -0
- polyester/gen/chain/guard/v1/guard_signer_connect.py +456 -0
- polyester/gen/chain/guard/v1/guard_signer_pb2.py +124 -0
- polyester/gen/chain/guard/v1/guard_signer_pb2.pyi +170 -0
- polyester/gen/chain/lifecycle/__init__.py +0 -0
- polyester/gen/chain/lifecycle/v1/__init__.py +0 -0
- polyester/gen/chain/lifecycle/v1/lifecycle_read_connect.py +261 -0
- polyester/gen/chain/lifecycle/v1/lifecycle_read_pb2.py +132 -0
- polyester/gen/chain/lifecycle/v1/lifecycle_read_pb2.pyi +396 -0
- polyester/gen/chain/lifecycle/v1/types_pb2.py +52 -0
- polyester/gen/chain/lifecycle/v1/types_pb2.pyi +118 -0
- polyester/gen/chain/withdraw/__init__.py +0 -0
- polyester/gen/chain/withdraw/v1/__init__.py +0 -0
- polyester/gen/chain/withdraw/v1/withdraw_connect.py +196 -0
- polyester/gen/chain/withdraw/v1/withdraw_pb2.py +79 -0
- polyester/gen/chain/withdraw/v1/withdraw_pb2.pyi +72 -0
- polyester/gen/chain/zipper/__init__.py +0 -0
- polyester/gen/chain/zipper/v1/__init__.py +0 -0
- polyester/gen/chain/zipper/v1/zipper_connect.py +131 -0
- polyester/gen/chain/zipper/v1/zipper_pb2.py +58 -0
- polyester/gen/chain/zipper/v1/zipper_pb2.pyi +130 -0
- polyester/gen/collab/__init__.py +0 -0
- polyester/gen/collab/v1/__init__.py +0 -0
- polyester/gen/collab/v1/whiteboard_connect.py +521 -0
- polyester/gen/collab/v1/whiteboard_pb2.py +155 -0
- polyester/gen/collab/v1/whiteboard_pb2.pyi +259 -0
- polyester/gen/gnostic/__init__.py +0 -0
- polyester/gen/gnostic/openapi/__init__.py +0 -0
- polyester/gen/gnostic/openapi/v3/__init__.py +0 -0
- polyester/gen/gnostic/openapi/v3/annotations_pb2.py +37 -0
- polyester/gen/gnostic/openapi/v3/annotations_pb2.pyi +14 -0
- polyester/gen/gnostic/openapi/v3/openapiv3_pb2.py +192 -0
- polyester/gen/gnostic/openapi/v3/openapiv3_pb2.pyi +895 -0
- polyester/gen/google/__init__.py +0 -0
- polyester/gen/google/api/__init__.py +0 -0
- polyester/gen/google/api/annotations_pb2.py +37 -0
- polyester/gen/google/api/annotations_pb2.pyi +8 -0
- polyester/gen/google/api/field_behavior_pb2.py +40 -0
- polyester/gen/google/api/field_behavior_pb2.pyi +29 -0
- polyester/gen/google/api/http_pb2.py +41 -0
- polyester/gen/google/api/http_pb2.pyi +47 -0
- polyester/gen/layout/__init__.py +0 -0
- polyester/gen/layout/v1/__init__.py +0 -0
- polyester/gen/layout/v1/layout_connect.py +1041 -0
- polyester/gen/layout/v1/layout_pb2.py +194 -0
- polyester/gen/layout/v1/layout_pb2.pyi +384 -0
- polyester/gen/ledger/__init__.py +0 -0
- polyester/gen/ledger/read/__init__.py +0 -0
- polyester/gen/ledger/read/v1/__init__.py +0 -0
- polyester/gen/ledger/read/v1/ledger_read_connect.py +456 -0
- polyester/gen/ledger/read/v1/ledger_read_pb2.py +113 -0
- polyester/gen/ledger/read/v1/ledger_read_pb2.pyi +318 -0
- polyester/gen/ledger/v1/__init__.py +0 -0
- polyester/gen/ledger/v1/catalog_pb2.py +39 -0
- polyester/gen/ledger/v1/catalog_pb2.pyi +43 -0
- polyester/gen/ledger/write/v1/ledger_write_connect.py +326 -0
- polyester/gen/ledger/write/v1/ledger_write_pb2.py +55 -0
- polyester/gen/ledger/write/v1/ledger_write_pb2.pyi +98 -0
- polyester/gen/marketdata/__init__.py +0 -0
- polyester/gen/marketdata/v1/__init__.py +0 -0
- polyester/gen/marketdata/v1/heatmap_connect.py +131 -0
- polyester/gen/marketdata/v1/heatmap_pb2.py +92 -0
- polyester/gen/marketdata/v1/heatmap_pb2.pyi +194 -0
- polyester/gen/marketdata/v1/marketdata_connect.py +326 -0
- polyester/gen/marketdata/v1/marketdata_pb2.py +116 -0
- polyester/gen/marketdata/v1/marketdata_pb2.pyi +289 -0
- polyester/gen/marketoverview/__init__.py +0 -0
- polyester/gen/marketoverview/v1/__init__.py +0 -0
- polyester/gen/marketoverview/v1/marketoverview_connect.py +131 -0
- polyester/gen/marketoverview/v1/marketoverview_pb2.py +77 -0
- polyester/gen/marketoverview/v1/marketoverview_pb2.pyi +144 -0
- polyester/gen/orderbook/__init__.py +0 -0
- polyester/gen/orderbook/v1/__init__.py +0 -0
- polyester/gen/orderbook/v1/orderbook_connect.py +131 -0
- polyester/gen/orderbook/v1/orderbook_pb2.py +58 -0
- polyester/gen/orderbook/v1/orderbook_pb2.pyi +86 -0
- polyester/gen/orders/__init__.py +0 -0
- polyester/gen/orders/v1/__init__.py +0 -0
- polyester/gen/orders/v1/orders_connect.py +586 -0
- polyester/gen/orders/v1/orders_pb2.py +257 -0
- polyester/gen/orders/v1/orders_pb2.pyi +634 -0
- polyester/gen/orders/v1/orders_read_connect.py +326 -0
- polyester/gen/orders/v1/orders_read_pb2.py +145 -0
- polyester/gen/orders/v1/orders_read_pb2.pyi +347 -0
- polyester/gen/polychart/__init__.py +0 -0
- polyester/gen/polychart/events/__init__.py +0 -0
- polyester/gen/polychart/events/v1/__init__.py +0 -0
- polyester/gen/polychart/events/v1/polychart_events_pb2.py +50 -0
- polyester/gen/polychart/events/v1/polychart_events_pb2.pyi +71 -0
- polyester/gen/polychart/v1/__init__.py +0 -0
- polyester/gen/polychart/v1/polychart_connect.py +1041 -0
- polyester/gen/polychart/v1/polychart_pb2.py +171 -0
- polyester/gen/polychart/v1/polychart_pb2.pyi +355 -0
- polyester/gen/polyester/__init__.py +0 -0
- polyester/gen/polyester/api/__init__.py +0 -0
- polyester/gen/polyester/api/options_pb2.py +36 -0
- polyester/gen/polyester/api/options_pb2.pyi +9 -0
- polyester/gen/polyester/api/validation/__init__.py +0 -0
- polyester/gen/polyester/api/validation/v1/__init__.py +0 -0
- polyester/gen/polyester/api/validation/v1/predefined_string_rules_pb2.py +42 -0
- polyester/gen/polyester/api/validation/v1/predefined_string_rules_pb2.pyi +11 -0
- polyester/gen/polyester/type/__init__.py +0 -0
- polyester/gen/polyester/type/v1/__init__.py +0 -0
- polyester/gen/polyester/type/v1/u128_pb2.py +37 -0
- polyester/gen/polyester/type/v1/u128_pb2.pyi +13 -0
- polyester/gen/tradews/__init__.py +0 -0
- polyester/gen/tradews/v1/__init__.py +0 -0
- polyester/gen/tradews/v1/trade_ws_pb2.py +52 -0
- polyester/gen/tradews/v1/trade_ws_pb2.pyi +91 -0
- polyester/gen/transfer/__init__.py +0 -0
- polyester/gen/transfer/v1/__init__.py +0 -0
- polyester/gen/transfer/v1/internal_transfer_connect.py +131 -0
- polyester/gen/transfer/v1/internal_transfer_pb2.py +66 -0
- polyester/gen/transfer/v1/internal_transfer_pb2.pyi +71 -0
- polyester/gen/triggers/__init__.py +0 -0
- polyester/gen/triggers/v1/__init__.py +0 -0
- polyester/gen/triggers/v1/triggers_connect.py +586 -0
- polyester/gen/triggers/v1/triggers_pb2.py +171 -0
- polyester/gen/triggers/v1/triggers_pb2.pyi +430 -0
- polyester/market_overview/__init__.py +3 -0
- polyester/market_overview/subscription.py +49 -0
- polyester/models/__init__.py +255 -0
- polyester/models/address_book.py +212 -0
- polyester/models/auth.py +52 -0
- polyester/models/common.py +66 -0
- polyester/models/guard_signer.py +34 -0
- polyester/models/market.py +46 -0
- polyester/models/policies.py +71 -0
- polyester/models/realtime.py +17 -0
- polyester/models/sub_accounts.py +93 -0
- polyester/models/trading.py +306 -0
- polyester/models/zipper.py +114 -0
- polyester/orderbook/__init__.py +17 -0
- polyester/orderbook/local_book.py +166 -0
- polyester/orderbook/subscription.py +55 -0
- polyester/py.typed +1 -0
- polyester/realtime/__init__.py +16 -0
- polyester/realtime/auth.py +66 -0
- polyester/realtime/client.py +234 -0
- polyester/realtime/snapshot_then_stream.py +144 -0
- polyester/services/__init__.py +59 -0
- polyester/services/_base.py +47 -0
- polyester/services/_generated.py +84 -0
- polyester/services/_realtime_subscribe.py +42 -0
- polyester/services/_scope.py +92 -0
- polyester/services/_symbols.py +24 -0
- polyester/services/address_book.py +424 -0
- polyester/services/api_keys.py +173 -0
- polyester/services/auth.py +25 -0
- polyester/services/balances.py +171 -0
- polyester/services/chain_analytics.py +104 -0
- polyester/services/deposit.py +67 -0
- polyester/services/guard_signer.py +191 -0
- polyester/services/heatmap.py +143 -0
- polyester/services/internal_transfers.py +77 -0
- polyester/services/layout.py +200 -0
- polyester/services/ledger_write.py +128 -0
- polyester/services/lifecycle.py +166 -0
- polyester/services/market_data.py +251 -0
- polyester/services/market_overview.py +131 -0
- polyester/services/orderbook.py +233 -0
- polyester/services/orders.py +429 -0
- polyester/services/policies.py +450 -0
- polyester/services/polychart.py +208 -0
- polyester/services/profile.py +70 -0
- polyester/services/resolve.py +53 -0
- polyester/services/social_verification.py +74 -0
- polyester/services/sub_accounts.py +398 -0
- polyester/services/trades.py +78 -0
- polyester/services/transfers.py +66 -0
- polyester/services/triggers.py +299 -0
- polyester/services/whiteboard.py +139 -0
- polyester/services/withdraw.py +144 -0
- polyester/services/zipper.py +85 -0
- polyester/sync_subscribe.py +86 -0
- polyester/transport.py +94 -0
- polyester_sdk-0.1.0a0.dist-info/METADATA +236 -0
- polyester_sdk-0.1.0a0.dist-info/RECORD +276 -0
- polyester_sdk-0.1.0a0.dist-info/WHEEL +4 -0
- polyester_sdk-0.1.0a0.dist-info/licenses/LICENSE +7 -0
polyester/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from polyester.client import AsyncPolyester, Polyester
|
|
2
|
+
from polyester.codecs.ledger_amounts import LEDGER_SCALE, format_ledger_u128
|
|
3
|
+
from polyester.errors import (
|
|
4
|
+
PolyesterApiError,
|
|
5
|
+
PolyesterAuthError,
|
|
6
|
+
PolyesterError,
|
|
7
|
+
PolyesterRateLimitError,
|
|
8
|
+
PolyesterRealtimeError,
|
|
9
|
+
PolyesterRouteNotFoundError,
|
|
10
|
+
PolyesterServerError,
|
|
11
|
+
PolyesterTransportError,
|
|
12
|
+
PolyesterValidationError,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0a0"
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"AsyncPolyester",
|
|
19
|
+
"LEDGER_SCALE",
|
|
20
|
+
"Polyester",
|
|
21
|
+
"PolyesterApiError",
|
|
22
|
+
"PolyesterAuthError",
|
|
23
|
+
"PolyesterError",
|
|
24
|
+
"PolyesterRateLimitError",
|
|
25
|
+
"PolyesterRealtimeError",
|
|
26
|
+
"PolyesterRouteNotFoundError",
|
|
27
|
+
"PolyesterServerError",
|
|
28
|
+
"PolyesterTransportError",
|
|
29
|
+
"PolyesterValidationError",
|
|
30
|
+
"__version__",
|
|
31
|
+
"format_ledger_u128",
|
|
32
|
+
]
|
polyester/_wire.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from connectrpc.errors import ConnectError
|
|
6
|
+
from google.protobuf.json_format import MessageToDict
|
|
7
|
+
from google.protobuf.message import Message
|
|
8
|
+
|
|
9
|
+
from polyester.errors import (
|
|
10
|
+
PolyesterApiError,
|
|
11
|
+
PolyesterAuthError,
|
|
12
|
+
PolyesterRouteNotFoundError,
|
|
13
|
+
PolyesterServerError,
|
|
14
|
+
PolyesterTransportError,
|
|
15
|
+
)
|
|
16
|
+
from polyester.gen.orders.v1 import orders_pb2
|
|
17
|
+
|
|
18
|
+
_ROUTE_NOT_FOUND_MESSAGES = frozenset(
|
|
19
|
+
{"not found", "404 page not found", "404 not found"}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def protobuf_to_public_dict(message: Message) -> dict[str, Any]:
|
|
24
|
+
return MessageToDict(
|
|
25
|
+
message,
|
|
26
|
+
preserving_proto_field_name=False,
|
|
27
|
+
always_print_fields_with_no_presence=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def map_connect_error(exc: ConnectError):
|
|
32
|
+
if exc.details:
|
|
33
|
+
for detail in exc.details:
|
|
34
|
+
full_name = detail.DESCRIPTOR.full_name
|
|
35
|
+
if full_name.endswith("ErrorDetail") and hasattr(detail, "code"):
|
|
36
|
+
code_name = orders_pb2.ErrorCode.Name(detail.code)
|
|
37
|
+
if (
|
|
38
|
+
"POLICY" in code_name
|
|
39
|
+
or "UNAUTHENTICATED" in code_name
|
|
40
|
+
or "PERMISSION" in code_name
|
|
41
|
+
):
|
|
42
|
+
return PolyesterAuthError(exc.message)
|
|
43
|
+
if code_name.startswith("ERROR_CODE_"):
|
|
44
|
+
return PolyesterApiError(exc.message, code=code_name)
|
|
45
|
+
code = exc.code.value
|
|
46
|
+
if code in ("unauthenticated", "permission_denied"):
|
|
47
|
+
return PolyesterAuthError(exc.message)
|
|
48
|
+
if code in ("unavailable", "internal"):
|
|
49
|
+
return PolyesterServerError(exc.message)
|
|
50
|
+
if code == "deadline_exceeded":
|
|
51
|
+
return PolyesterTransportError(exc.message)
|
|
52
|
+
message = (exc.message or "").strip().lower()
|
|
53
|
+
if code == "unimplemented" and message in _ROUTE_NOT_FOUND_MESSAGES:
|
|
54
|
+
return PolyesterRouteNotFoundError()
|
|
55
|
+
return PolyesterApiError(exc.message, code=code)
|
polyester/auth.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from urllib.parse import parse_qsl, quote, urlsplit
|
|
8
|
+
|
|
9
|
+
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
10
|
+
|
|
11
|
+
from polyester.errors import PolyesterAuthError
|
|
12
|
+
|
|
13
|
+
API_KEY_ID_ENV = "POLYESTER_API_KEY_ID"
|
|
14
|
+
API_PRIVATE_KEY_ENV = "POLYESTER_API_PRIVATE_KEY"
|
|
15
|
+
ACCOUNT_ID_ENV = "POLYESTER_ACCOUNT_ID"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class ApiKeyCredentials:
|
|
20
|
+
key_id: str
|
|
21
|
+
private_key: bytes
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_api_key_credentials(
|
|
25
|
+
*,
|
|
26
|
+
api_key_id: str | None = None,
|
|
27
|
+
api_private_key: str | bytes | None = None,
|
|
28
|
+
from_env: bool = True,
|
|
29
|
+
) -> ApiKeyCredentials | None:
|
|
30
|
+
key_id = api_key_id
|
|
31
|
+
private = api_private_key
|
|
32
|
+
if from_env:
|
|
33
|
+
key_id = key_id or os.getenv(API_KEY_ID_ENV)
|
|
34
|
+
private = private if private is not None else os.getenv(API_PRIVATE_KEY_ENV)
|
|
35
|
+
if not key_id and not private:
|
|
36
|
+
return None
|
|
37
|
+
if not key_id or private is None:
|
|
38
|
+
if from_env:
|
|
39
|
+
message = "Both POLYESTER_API_KEY_ID and POLYESTER_API_PRIVATE_KEY are required"
|
|
40
|
+
else:
|
|
41
|
+
message = "Both api_key_id and api_private_key are required"
|
|
42
|
+
raise PolyesterAuthError(
|
|
43
|
+
message
|
|
44
|
+
)
|
|
45
|
+
return ApiKeyCredentials(key_id=key_id, private_key=normalize_private_key(private))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def normalize_private_key(value: str | bytes) -> bytes:
|
|
49
|
+
if isinstance(value, bytes):
|
|
50
|
+
private = value
|
|
51
|
+
else:
|
|
52
|
+
try:
|
|
53
|
+
private = bytes.fromhex(value.strip())
|
|
54
|
+
except ValueError as exc:
|
|
55
|
+
raise PolyesterAuthError(
|
|
56
|
+
"API private key must be a valid hex string or raw bytes"
|
|
57
|
+
) from exc
|
|
58
|
+
if len(private) != 32:
|
|
59
|
+
raise PolyesterAuthError("Ed25519 API private key must be exactly 32 bytes")
|
|
60
|
+
return private
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def generate_ed25519_keypair() -> tuple[bytes, bytes]:
|
|
64
|
+
"""Generate a new Ed25519 keypair (public, secret), each 32 bytes."""
|
|
65
|
+
private_key = Ed25519PrivateKey.generate()
|
|
66
|
+
public_key = private_key.public_key()
|
|
67
|
+
return public_key.public_bytes_raw(), private_key.private_bytes_raw()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def sha256_hex(body: bytes) -> str:
|
|
71
|
+
return hashlib.sha256(body).hexdigest()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def canonical_query(url: str) -> str:
|
|
75
|
+
pairs = []
|
|
76
|
+
for key, value in parse_qsl(urlsplit(url).query, keep_blank_values=True):
|
|
77
|
+
pairs.append(f"{quote(key, safe='')}={quote(value, safe='')}")
|
|
78
|
+
pairs.sort()
|
|
79
|
+
return "&".join(pairs)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def canonical_signing_string(
|
|
83
|
+
*,
|
|
84
|
+
timestamp_ms: str,
|
|
85
|
+
method: str,
|
|
86
|
+
url: str,
|
|
87
|
+
body: bytes,
|
|
88
|
+
) -> str:
|
|
89
|
+
pathname = urlsplit(url).path or "/"
|
|
90
|
+
return "\n".join(
|
|
91
|
+
[timestamp_ms, method.upper(), pathname, canonical_query(url), sha256_hex(body)]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def sign_request(
|
|
96
|
+
credentials: ApiKeyCredentials,
|
|
97
|
+
*,
|
|
98
|
+
method: str,
|
|
99
|
+
url: str,
|
|
100
|
+
body: bytes = b"",
|
|
101
|
+
timestamp_ms: str | None = None,
|
|
102
|
+
) -> dict[str, str]:
|
|
103
|
+
timestamp = timestamp_ms or str(int(time.time() * 1000))
|
|
104
|
+
canonical = canonical_signing_string(
|
|
105
|
+
timestamp_ms=timestamp,
|
|
106
|
+
method=method,
|
|
107
|
+
url=url,
|
|
108
|
+
body=body,
|
|
109
|
+
)
|
|
110
|
+
key = Ed25519PrivateKey.from_private_bytes(credentials.private_key)
|
|
111
|
+
signature = key.sign(canonical.encode("utf-8")).hex()
|
|
112
|
+
return {
|
|
113
|
+
"X-API-KEY-ID": credentials.key_id,
|
|
114
|
+
"X-API-TIMESTAMP": timestamp,
|
|
115
|
+
"X-API-SIGNATURE": signature,
|
|
116
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
|
|
5
|
+
from connectrpc.codec import Codec
|
|
6
|
+
from connectrpc.interceptor import UnaryInterceptor
|
|
7
|
+
from connectrpc.request import RequestContext
|
|
8
|
+
from google.protobuf.message import Message
|
|
9
|
+
|
|
10
|
+
from polyester.auth import ApiKeyCredentials, sign_request
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ApiKeyAuthUnaryInterceptor(UnaryInterceptor):
|
|
14
|
+
"""Attach Polyester API-key headers to generated Connect unary calls."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
credentials: ApiKeyCredentials,
|
|
19
|
+
*,
|
|
20
|
+
base_url: str,
|
|
21
|
+
codec: Codec,
|
|
22
|
+
) -> None:
|
|
23
|
+
self._credentials = credentials
|
|
24
|
+
self._base_url = base_url.rstrip("/")
|
|
25
|
+
self._codec = codec
|
|
26
|
+
|
|
27
|
+
async def intercept_unary(
|
|
28
|
+
self,
|
|
29
|
+
call_next: Callable[[Message, RequestContext], Awaitable[Message]],
|
|
30
|
+
request: Message,
|
|
31
|
+
ctx: RequestContext,
|
|
32
|
+
) -> Message:
|
|
33
|
+
info = ctx.method()
|
|
34
|
+
url = f"{self._base_url}/{info.service_name}/{info.name}"
|
|
35
|
+
body = self._codec.encode(request)
|
|
36
|
+
headers = sign_request(
|
|
37
|
+
self._credentials,
|
|
38
|
+
method=ctx.http_method(),
|
|
39
|
+
url=url,
|
|
40
|
+
body=body,
|
|
41
|
+
)
|
|
42
|
+
ctx.request_headers().update(headers)
|
|
43
|
+
return await call_next(request, ctx)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from polyester.catalogs.zipper import build_zipper_catalog_data
|
|
7
|
+
from polyester.catalogs.zipper_supply import patch_zipper_catalog_supply
|
|
8
|
+
from polyester.models.zipper import (
|
|
9
|
+
DepositWithdrawConfig,
|
|
10
|
+
ZippedAssetSupplyUpdate,
|
|
11
|
+
ZipperCatalogData,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(slots=True)
|
|
16
|
+
class CatalogManager:
|
|
17
|
+
spot_config: dict[str, Any] = field(default_factory=dict)
|
|
18
|
+
zipper: ZipperCatalogData | None = None
|
|
19
|
+
deposit_withdraw_config: DepositWithdrawConfig | None = None
|
|
20
|
+
_legacy_zipper_raw: dict[str, Any] = field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def zipper_config(self) -> dict[str, Any]:
|
|
24
|
+
"""Backward-compatible raw dict view of the typed zipper catalog."""
|
|
25
|
+
if self.deposit_withdraw_config is None:
|
|
26
|
+
return {}
|
|
27
|
+
config = self.deposit_withdraw_config
|
|
28
|
+
return {
|
|
29
|
+
"chains": [msgspec_to_dict(chain) for chain in config.chains],
|
|
30
|
+
"assets": [
|
|
31
|
+
{
|
|
32
|
+
**msgspec_to_dict(asset),
|
|
33
|
+
"variants": [msgspec_to_dict(variant) for variant in asset.variants],
|
|
34
|
+
}
|
|
35
|
+
for asset in config.assets
|
|
36
|
+
],
|
|
37
|
+
"contracts": [msgspec_to_dict(item) for item in config.contracts],
|
|
38
|
+
"polyesterChainId": config.polyester_chain_id,
|
|
39
|
+
"tsSec": config.ts_ms // 1000 if config.ts_ms else 0,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def hydrate_spot_config(self, config: dict[str, Any]) -> None:
|
|
43
|
+
self.spot_config = config
|
|
44
|
+
|
|
45
|
+
def hydrate_zipper_config(
|
|
46
|
+
self,
|
|
47
|
+
config: DepositWithdrawConfig | dict[str, Any],
|
|
48
|
+
) -> None:
|
|
49
|
+
if isinstance(config, DepositWithdrawConfig):
|
|
50
|
+
self.deposit_withdraw_config = config
|
|
51
|
+
self.zipper = build_zipper_catalog_data(config)
|
|
52
|
+
return
|
|
53
|
+
self.deposit_withdraw_config = None
|
|
54
|
+
self.zipper = None
|
|
55
|
+
self._legacy_zipper_raw = config
|
|
56
|
+
|
|
57
|
+
def hydrate_deposit_withdraw_config(self, config: DepositWithdrawConfig) -> None:
|
|
58
|
+
self.hydrate_zipper_config(config)
|
|
59
|
+
|
|
60
|
+
def symbol_id_for_symbol(self, symbol: str) -> int | None:
|
|
61
|
+
for pair in self._pairs():
|
|
62
|
+
if pair.get("symbol") == symbol:
|
|
63
|
+
value = pair.get("symbol_id") or pair.get("symbolId")
|
|
64
|
+
return int(value) if value is not None else None
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def base_quantity_scale_for_symbol(self, symbol: str) -> int:
|
|
68
|
+
for pair in self._pairs():
|
|
69
|
+
if pair.get("symbol") == symbol:
|
|
70
|
+
value = (
|
|
71
|
+
pair.get("base_quantity_scale")
|
|
72
|
+
or pair.get("baseQuantityScale")
|
|
73
|
+
or pair.get("qtyScale")
|
|
74
|
+
)
|
|
75
|
+
return int(value) if value is not None else 8
|
|
76
|
+
return 8
|
|
77
|
+
|
|
78
|
+
def orderbook_price_buckets_for_symbol(self, symbol: str) -> list[str]:
|
|
79
|
+
pair = self._pair_for_symbol(symbol)
|
|
80
|
+
if pair is None:
|
|
81
|
+
return []
|
|
82
|
+
marketdata = pair.get("marketdata") or pair.get("marketData") or {}
|
|
83
|
+
buckets = marketdata.get("orderbook_price_buckets") or marketdata.get(
|
|
84
|
+
"orderbookPriceBuckets"
|
|
85
|
+
)
|
|
86
|
+
if not isinstance(buckets, list):
|
|
87
|
+
return []
|
|
88
|
+
result: list[str] = []
|
|
89
|
+
for value in buckets:
|
|
90
|
+
if isinstance(value, (int, float)):
|
|
91
|
+
text = format(value, "f").rstrip("0").rstrip(".") or "0"
|
|
92
|
+
result.append(text)
|
|
93
|
+
elif value is not None:
|
|
94
|
+
result.append(str(value))
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
def ledger_id_for_asset(self, asset_symbol: str) -> int | None:
|
|
98
|
+
catalog = self.zipper
|
|
99
|
+
if catalog is not None:
|
|
100
|
+
for asset in catalog.assets:
|
|
101
|
+
if asset.asset == asset_symbol:
|
|
102
|
+
return asset.ledger_id or None
|
|
103
|
+
raw = self._legacy_zipper_raw or self.zipper_config
|
|
104
|
+
for row in raw.get("assets") or []:
|
|
105
|
+
symbol = row.get("asset") or row.get("code")
|
|
106
|
+
if symbol == asset_symbol:
|
|
107
|
+
value = row.get("ledgerId") or row.get("ledger_id")
|
|
108
|
+
return int(value) if value is not None else None
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
def quantity_scale_for_asset(self, asset_symbol: str) -> int | None:
|
|
112
|
+
catalog = self.zipper
|
|
113
|
+
if catalog is not None:
|
|
114
|
+
for asset in catalog.assets:
|
|
115
|
+
if asset.asset == asset_symbol:
|
|
116
|
+
return asset.quantity_scale
|
|
117
|
+
raw = self._legacy_zipper_raw or self.zipper_config
|
|
118
|
+
for row in raw.get("assets") or []:
|
|
119
|
+
symbol = row.get("asset") or row.get("code")
|
|
120
|
+
if symbol == asset_symbol:
|
|
121
|
+
value = row.get("quantityScale") or row.get("quantity_scale")
|
|
122
|
+
return int(value) if value is not None else None
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
def quantity_scale_for_zipped_asset_id(self, zipped_asset_id: int) -> int:
|
|
126
|
+
catalog = self.zipper
|
|
127
|
+
if catalog is not None:
|
|
128
|
+
for asset in catalog.assets:
|
|
129
|
+
for chain in asset.chains:
|
|
130
|
+
if chain.zipped_asset_id == zipped_asset_id:
|
|
131
|
+
return asset.quantity_scale
|
|
132
|
+
return 18
|
|
133
|
+
|
|
134
|
+
def patch_zipper_supply(self, updates: list[ZippedAssetSupplyUpdate]) -> bool:
|
|
135
|
+
if self.zipper is None or not updates:
|
|
136
|
+
return False
|
|
137
|
+
patched = patch_zipper_catalog_supply(self.zipper, updates)
|
|
138
|
+
if patched is self.zipper:
|
|
139
|
+
return False
|
|
140
|
+
self.zipper = patched
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
def _pair_for_symbol(self, symbol: str) -> dict[str, Any] | None:
|
|
144
|
+
for pair in self._pairs():
|
|
145
|
+
if pair.get("symbol") == symbol:
|
|
146
|
+
return pair
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
def _pairs(self) -> list[dict[str, Any]]:
|
|
150
|
+
pairs = self.spot_config.get("pairs") or self.spot_config.get("symbols") or []
|
|
151
|
+
return [pair for pair in pairs if isinstance(pair, dict)]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def msgspec_to_dict(value: object) -> dict[str, Any]:
|
|
155
|
+
import msgspec
|
|
156
|
+
|
|
157
|
+
return msgspec.to_builtins(value)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from polyester.models.zipper import (
|
|
4
|
+
DepositWithdrawConfig,
|
|
5
|
+
ZipperAssetConfig,
|
|
6
|
+
ZipperCatalogData,
|
|
7
|
+
ZipperChainConfig,
|
|
8
|
+
ZipperEnrichedAssetChain,
|
|
9
|
+
ZipperEnrichedAssetConfig,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_zipper_catalog_data(config: DepositWithdrawConfig) -> ZipperCatalogData:
|
|
14
|
+
chains_by_id = {chain.chain_id: chain for chain in config.chains}
|
|
15
|
+
enriched_assets: list[ZipperEnrichedAssetConfig] = []
|
|
16
|
+
for asset in config.assets:
|
|
17
|
+
enriched_assets.append(
|
|
18
|
+
ZipperEnrichedAssetConfig(
|
|
19
|
+
asset=asset.asset,
|
|
20
|
+
ledger_id=asset.ledger_id,
|
|
21
|
+
name=asset.name,
|
|
22
|
+
icon=asset.icon,
|
|
23
|
+
quantity_scale=asset.quantity_scale,
|
|
24
|
+
quantity_display_decimals=asset.quantity_display_decimals,
|
|
25
|
+
u_asset_id=asset.u_asset_id,
|
|
26
|
+
chains=_enrich_asset_chains(asset, chains_by_id),
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
return ZipperCatalogData(
|
|
30
|
+
chains=list(config.chains),
|
|
31
|
+
assets=enriched_assets,
|
|
32
|
+
contracts=list(config.contracts),
|
|
33
|
+
ts_ms=config.ts_ms,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _enrich_asset_chains(
|
|
38
|
+
asset: ZipperAssetConfig,
|
|
39
|
+
chains_by_id: dict[int, ZipperChainConfig],
|
|
40
|
+
) -> list[ZipperEnrichedAssetChain]:
|
|
41
|
+
enriched: list[ZipperEnrichedAssetChain] = []
|
|
42
|
+
for variant in asset.variants:
|
|
43
|
+
chain = chains_by_id.get(variant.chain_id)
|
|
44
|
+
if chain is None:
|
|
45
|
+
continue
|
|
46
|
+
enriched.append(
|
|
47
|
+
ZipperEnrichedAssetChain(
|
|
48
|
+
chain_id=chain.chain_id,
|
|
49
|
+
code=chain.code,
|
|
50
|
+
name=chain.name,
|
|
51
|
+
native_chain_id=chain.native_chain_id,
|
|
52
|
+
native_currency_symbol=chain.native_currency_symbol,
|
|
53
|
+
explorer_url=chain.explorer_url,
|
|
54
|
+
icon=chain.icon,
|
|
55
|
+
required_confirmations=chain.required_confirmations,
|
|
56
|
+
confirmation_time_seconds=chain.confirmation_time_seconds,
|
|
57
|
+
is_case_sensitive=chain.is_case_sensitive,
|
|
58
|
+
min_address_length=chain.min_address_length,
|
|
59
|
+
max_address_length=chain.max_address_length,
|
|
60
|
+
zipped_asset_id=variant.zipped_asset_id,
|
|
61
|
+
is_native_asset=variant.is_native_asset,
|
|
62
|
+
network_fee=variant.network_fee,
|
|
63
|
+
network_fee_ts_sec=variant.network_fee_ts_sec,
|
|
64
|
+
deposit_min_amount=variant.deposit_min_amount,
|
|
65
|
+
withdraw_min_amount=variant.withdraw_min_amount,
|
|
66
|
+
supply=variant.supply,
|
|
67
|
+
source_token=variant.source_token,
|
|
68
|
+
z_token=variant.z_token,
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
return enriched
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from polyester.models.zipper import (
|
|
6
|
+
ZippedAssetSupplyUpdate,
|
|
7
|
+
ZipperCatalogData,
|
|
8
|
+
ZipperEnrichedAssetChain,
|
|
9
|
+
ZipperEnrichedAssetConfig,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def patch_zipper_catalog_supply(
|
|
14
|
+
catalog: ZipperCatalogData,
|
|
15
|
+
updates: list[ZippedAssetSupplyUpdate],
|
|
16
|
+
) -> ZipperCatalogData:
|
|
17
|
+
if not updates:
|
|
18
|
+
return catalog
|
|
19
|
+
|
|
20
|
+
supply_by_id = {item.zipped_asset_id: item.supply for item in updates}
|
|
21
|
+
changed = False
|
|
22
|
+
next_assets: list[ZipperEnrichedAssetConfig] = []
|
|
23
|
+
|
|
24
|
+
for asset in catalog.assets:
|
|
25
|
+
asset_changed = False
|
|
26
|
+
next_chains: list[ZipperEnrichedAssetChain] = []
|
|
27
|
+
for chain in asset.chains:
|
|
28
|
+
supply = supply_by_id.get(chain.zipped_asset_id)
|
|
29
|
+
if supply is None or supply == chain.supply:
|
|
30
|
+
next_chains.append(chain)
|
|
31
|
+
continue
|
|
32
|
+
asset_changed = True
|
|
33
|
+
changed = True
|
|
34
|
+
next_chains.append(
|
|
35
|
+
ZipperEnrichedAssetChain(
|
|
36
|
+
chain_id=chain.chain_id,
|
|
37
|
+
code=chain.code,
|
|
38
|
+
name=chain.name,
|
|
39
|
+
native_chain_id=chain.native_chain_id,
|
|
40
|
+
native_currency_symbol=chain.native_currency_symbol,
|
|
41
|
+
explorer_url=chain.explorer_url,
|
|
42
|
+
icon=chain.icon,
|
|
43
|
+
required_confirmations=chain.required_confirmations,
|
|
44
|
+
confirmation_time_seconds=chain.confirmation_time_seconds,
|
|
45
|
+
is_case_sensitive=chain.is_case_sensitive,
|
|
46
|
+
min_address_length=chain.min_address_length,
|
|
47
|
+
max_address_length=chain.max_address_length,
|
|
48
|
+
zipped_asset_id=chain.zipped_asset_id,
|
|
49
|
+
is_native_asset=chain.is_native_asset,
|
|
50
|
+
network_fee=chain.network_fee,
|
|
51
|
+
network_fee_ts_sec=chain.network_fee_ts_sec,
|
|
52
|
+
deposit_min_amount=chain.deposit_min_amount,
|
|
53
|
+
withdraw_min_amount=chain.withdraw_min_amount,
|
|
54
|
+
supply=supply,
|
|
55
|
+
source_token=chain.source_token,
|
|
56
|
+
z_token=chain.z_token,
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
if asset_changed:
|
|
60
|
+
next_assets.append(
|
|
61
|
+
ZipperEnrichedAssetConfig(
|
|
62
|
+
asset=asset.asset,
|
|
63
|
+
ledger_id=asset.ledger_id,
|
|
64
|
+
name=asset.name,
|
|
65
|
+
icon=asset.icon,
|
|
66
|
+
quantity_scale=asset.quantity_scale,
|
|
67
|
+
quantity_display_decimals=asset.quantity_display_decimals,
|
|
68
|
+
u_asset_id=asset.u_asset_id,
|
|
69
|
+
chains=next_chains,
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
next_assets.append(asset)
|
|
74
|
+
|
|
75
|
+
if not changed:
|
|
76
|
+
return catalog
|
|
77
|
+
|
|
78
|
+
ts_ms = int(time.time() * 1000)
|
|
79
|
+
return ZipperCatalogData(
|
|
80
|
+
chains=list(catalog.chains),
|
|
81
|
+
assets=next_assets,
|
|
82
|
+
contracts=list(catalog.contracts),
|
|
83
|
+
ts_ms=ts_ms,
|
|
84
|
+
)
|