chainstream-sdk 0.1.0__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.
- chainstream/__init__.py +46 -0
- chainstream/client.py +104 -0
- chainstream/openapi_client/__init__.py +156 -0
- chainstream/openapi_client/api/__init__.py +20 -0
- chainstream/openapi_client/api/blockchain_api.py +549 -0
- chainstream/openapi_client/api/defi_sol_moonshot_api.py +590 -0
- chainstream/openapi_client/api/defi_sol_pumpfun_api.py +314 -0
- chainstream/openapi_client/api/dex_api.py +1576 -0
- chainstream/openapi_client/api/dex_pool_api.py +645 -0
- chainstream/openapi_client/api/endpoint_api.py +1934 -0
- chainstream/openapi_client/api/ipfs_api.py +283 -0
- chainstream/openapi_client/api/jobs_api.py +562 -0
- chainstream/openapi_client/api/kyt_api.py +3743 -0
- chainstream/openapi_client/api/ranking_api.py +2067 -0
- chainstream/openapi_client/api/red_packet_api.py +2444 -0
- chainstream/openapi_client/api/token_api.py +9211 -0
- chainstream/openapi_client/api/trade_api.py +1352 -0
- chainstream/openapi_client/api/transaction_api.py +882 -0
- chainstream/openapi_client/api/wallet_api.py +1608 -0
- chainstream/openapi_client/api/watchlist_api.py +316 -0
- chainstream/openapi_client/api_client.py +801 -0
- chainstream/openapi_client/api_response.py +21 -0
- chainstream/openapi_client/configuration.py +572 -0
- chainstream/openapi_client/exceptions.py +217 -0
- chainstream/openapi_client/models/__init__.py +122 -0
- chainstream/openapi_client/models/address_exposure.py +94 -0
- chainstream/openapi_client/models/address_risk_response_dto.py +112 -0
- chainstream/openapi_client/models/alert_detail.py +100 -0
- chainstream/openapi_client/models/alterya_identification.py +106 -0
- chainstream/openapi_client/models/balance_change_type.py +38 -0
- chainstream/openapi_client/models/balance_token_type.py +38 -0
- chainstream/openapi_client/models/balance_update_dto.py +130 -0
- chainstream/openapi_client/models/balance_update_page.py +104 -0
- chainstream/openapi_client/models/blockchain_dto.py +94 -0
- chainstream/openapi_client/models/blockchain_latest_block_dto.py +90 -0
- chainstream/openapi_client/models/boolean_result_dto.py +88 -0
- chainstream/openapi_client/models/calculate_pnl_input.py +88 -0
- chainstream/openapi_client/models/candle.py +101 -0
- chainstream/openapi_client/models/chain.py +39 -0
- chainstream/openapi_client/models/chain_symbol.py +39 -0
- chainstream/openapi_client/models/chainalysis_address_identification.py +92 -0
- chainstream/openapi_client/models/claim_red_packet_input.py +103 -0
- chainstream/openapi_client/models/create_endpoint_input.py +102 -0
- chainstream/openapi_client/models/create_red_packet_input.py +112 -0
- chainstream/openapi_client/models/create_red_packet_reply.py +90 -0
- chainstream/openapi_client/models/create_token_input.py +110 -0
- chainstream/openapi_client/models/create_token_reply.py +90 -0
- chainstream/openapi_client/models/dev_token_dto.py +106 -0
- chainstream/openapi_client/models/dex_dto.py +94 -0
- chainstream/openapi_client/models/dex_page.py +106 -0
- chainstream/openapi_client/models/dex_pool_dto.py +161 -0
- chainstream/openapi_client/models/dex_pool_snapshot_dto.py +111 -0
- chainstream/openapi_client/models/dex_pool_snapshot_page.py +104 -0
- chainstream/openapi_client/models/dex_pool_token_liquidity.py +100 -0
- chainstream/openapi_client/models/dex_pool_token_snapshot_dto.py +100 -0
- chainstream/openapi_client/models/dex_quote_response.py +98 -0
- chainstream/openapi_client/models/direct_exposure_detail.py +90 -0
- chainstream/openapi_client/models/endpoint_list_response.py +102 -0
- chainstream/openapi_client/models/endpoint_operation_response.py +88 -0
- chainstream/openapi_client/models/endpoint_response.py +108 -0
- chainstream/openapi_client/models/endpoint_secret_response.py +88 -0
- chainstream/openapi_client/models/estimate_gas_limit_input.py +94 -0
- chainstream/openapi_client/models/estimate_gas_limit_response.py +90 -0
- chainstream/openapi_client/models/filter_condition.py +102 -0
- chainstream/openapi_client/models/gas_price_response.py +90 -0
- chainstream/openapi_client/models/job_dto.py +90 -0
- chainstream/openapi_client/models/job_streaming_dto.py +90 -0
- chainstream/openapi_client/models/kyt_register_transfer_request.py +108 -0
- chainstream/openapi_client/models/kyt_register_withdrawal_request.py +111 -0
- chainstream/openapi_client/models/link.py +97 -0
- chainstream/openapi_client/models/moonshot_create_token_input.py +145 -0
- chainstream/openapi_client/models/moonshot_create_token_reply.py +90 -0
- chainstream/openapi_client/models/moonshot_submit_create_token200_response.py +90 -0
- chainstream/openapi_client/models/moonshot_submit_create_token_input.py +90 -0
- chainstream/openapi_client/models/network_identification_org.py +88 -0
- chainstream/openapi_client/models/on_chain_activity.py +88 -0
- chainstream/openapi_client/models/pump_create_token_input.py +155 -0
- chainstream/openapi_client/models/pump_create_token_reply.py +90 -0
- chainstream/openapi_client/models/red_packet_claim_dto.py +103 -0
- chainstream/openapi_client/models/red_packet_claims_page.py +104 -0
- chainstream/openapi_client/models/red_packet_dto.py +121 -0
- chainstream/openapi_client/models/red_packet_reply.py +88 -0
- chainstream/openapi_client/models/red_packet_send_tx_input.py +88 -0
- chainstream/openapi_client/models/red_packet_send_tx_response.py +88 -0
- chainstream/openapi_client/models/red_packets_page.py +104 -0
- chainstream/openapi_client/models/register_address_request.py +88 -0
- chainstream/openapi_client/models/register_address_response_dto.py +88 -0
- chainstream/openapi_client/models/resolution.py +46 -0
- chainstream/openapi_client/models/send_tx_input.py +102 -0
- chainstream/openapi_client/models/send_tx_response.py +92 -0
- chainstream/openapi_client/models/swap_input.py +119 -0
- chainstream/openapi_client/models/swap_reply.py +90 -0
- chainstream/openapi_client/models/swap_route_input.py +127 -0
- chainstream/openapi_client/models/swap_route_response.py +98 -0
- chainstream/openapi_client/models/token.py +158 -0
- chainstream/openapi_client/models/token_creation_dto.py +107 -0
- chainstream/openapi_client/models/token_creation_page.py +106 -0
- chainstream/openapi_client/models/token_creators_dto.py +92 -0
- chainstream/openapi_client/models/token_extra_dto.py +128 -0
- chainstream/openapi_client/models/token_holder.py +94 -0
- chainstream/openapi_client/models/token_holder_page.py +106 -0
- chainstream/openapi_client/models/token_liquidity_snapshot_dto.py +106 -0
- chainstream/openapi_client/models/token_liquidity_snapshot_page.py +104 -0
- chainstream/openapi_client/models/token_list_page.py +106 -0
- chainstream/openapi_client/models/token_market_data.py +174 -0
- chainstream/openapi_client/models/token_metadata.py +132 -0
- chainstream/openapi_client/models/token_page.py +108 -0
- chainstream/openapi_client/models/token_price_dto.py +94 -0
- chainstream/openapi_client/models/token_price_page.py +106 -0
- chainstream/openapi_client/models/token_social_medias_dto.py +112 -0
- chainstream/openapi_client/models/token_stat.py +340 -0
- chainstream/openapi_client/models/token_trader.py +113 -0
- chainstream/openapi_client/models/token_trader_tag.py +45 -0
- chainstream/openapi_client/models/top_traders_dto.py +110 -0
- chainstream/openapi_client/models/top_traders_page.py +106 -0
- chainstream/openapi_client/models/trade_detail_dto.py +136 -0
- chainstream/openapi_client/models/trade_event.py +113 -0
- chainstream/openapi_client/models/trade_page.py +106 -0
- chainstream/openapi_client/models/trade_type.py +38 -0
- chainstream/openapi_client/models/transfer_alerts_response_dto.py +96 -0
- chainstream/openapi_client/models/transfer_base_response_dto.py +112 -0
- chainstream/openapi_client/models/transfer_direct_exposure_response_dto.py +92 -0
- chainstream/openapi_client/models/transfer_network_identifications_response_dto.py +98 -0
- chainstream/openapi_client/models/update_endpoint_input.py +104 -0
- chainstream/openapi_client/models/wallet_balance_detail_dto.py +136 -0
- chainstream/openapi_client/models/wallet_balances_dto.py +100 -0
- chainstream/openapi_client/models/wallet_pnl_dto.py +132 -0
- chainstream/openapi_client/models/withdrawal_address_identifications_response_dto.py +98 -0
- chainstream/openapi_client/models/withdrawal_base_response_dto.py +106 -0
- chainstream/openapi_client/models/withdrawal_fraud_assessment_response_dto.py +96 -0
- chainstream/openapi_client/rest.py +213 -0
- chainstream/openapi_client/test/__init__.py +0 -0
- chainstream/openapi_client/test/test_address_exposure.py +59 -0
- chainstream/openapi_client/test/test_address_risk_response_dto.py +81 -0
- chainstream/openapi_client/test/test_alert_detail.py +65 -0
- chainstream/openapi_client/test/test_alterya_identification.py +69 -0
- chainstream/openapi_client/test/test_balance_change_type.py +34 -0
- chainstream/openapi_client/test/test_balance_token_type.py +34 -0
- chainstream/openapi_client/test/test_balance_update_dto.py +93 -0
- chainstream/openapi_client/test/test_balance_update_page.py +105 -0
- chainstream/openapi_client/test/test_blockchain_api.py +46 -0
- chainstream/openapi_client/test/test_blockchain_dto.py +59 -0
- chainstream/openapi_client/test/test_blockchain_latest_block_dto.py +55 -0
- chainstream/openapi_client/test/test_boolean_result_dto.py +53 -0
- chainstream/openapi_client/test/test_calculate_pnl_input.py +52 -0
- chainstream/openapi_client/test/test_candle.py +65 -0
- chainstream/openapi_client/test/test_chain.py +34 -0
- chainstream/openapi_client/test/test_chain_symbol.py +34 -0
- chainstream/openapi_client/test/test_chainalysis_address_identification.py +57 -0
- chainstream/openapi_client/test/test_claim_red_packet_input.py +58 -0
- chainstream/openapi_client/test/test_create_endpoint_input.py +63 -0
- chainstream/openapi_client/test/test_create_red_packet_input.py +64 -0
- chainstream/openapi_client/test/test_create_red_packet_reply.py +55 -0
- chainstream/openapi_client/test/test_create_token_input.py +63 -0
- chainstream/openapi_client/test/test_create_token_reply.py +55 -0
- chainstream/openapi_client/test/test_defi_sol_moonshot_api.py +46 -0
- chainstream/openapi_client/test/test_defi_sol_pumpfun_api.py +39 -0
- chainstream/openapi_client/test/test_dev_token_dto.py +441 -0
- chainstream/openapi_client/test/test_dex_api.py +67 -0
- chainstream/openapi_client/test/test_dex_dto.py +56 -0
- chainstream/openapi_client/test/test_dex_page.py +70 -0
- chainstream/openapi_client/test/test_dex_pool_api.py +46 -0
- chainstream/openapi_client/test/test_dex_pool_dto.py +88 -0
- chainstream/openapi_client/test/test_dex_pool_snapshot_dto.py +96 -0
- chainstream/openapi_client/test/test_dex_pool_snapshot_page.py +81 -0
- chainstream/openapi_client/test/test_dex_pool_token_liquidity.py +65 -0
- chainstream/openapi_client/test/test_dex_pool_token_snapshot_dto.py +65 -0
- chainstream/openapi_client/test/test_dex_quote_response.py +57 -0
- chainstream/openapi_client/test/test_direct_exposure_detail.py +55 -0
- chainstream/openapi_client/test/test_endpoint_api.py +81 -0
- chainstream/openapi_client/test/test_endpoint_list_response.py +72 -0
- chainstream/openapi_client/test/test_endpoint_operation_response.py +52 -0
- chainstream/openapi_client/test/test_endpoint_response.py +66 -0
- chainstream/openapi_client/test/test_endpoint_secret_response.py +52 -0
- chainstream/openapi_client/test/test_estimate_gas_limit_input.py +58 -0
- chainstream/openapi_client/test/test_estimate_gas_limit_response.py +55 -0
- chainstream/openapi_client/test/test_filter_condition.py +54 -0
- chainstream/openapi_client/test/test_gas_price_response.py +55 -0
- chainstream/openapi_client/test/test_ipfs_api.py +39 -0
- chainstream/openapi_client/test/test_job_dto.py +55 -0
- chainstream/openapi_client/test/test_job_streaming_dto.py +55 -0
- chainstream/openapi_client/test/test_jobs_api.py +46 -0
- chainstream/openapi_client/test/test_kyt_api.py +130 -0
- chainstream/openapi_client/test/test_kyt_register_transfer_request.py +59 -0
- chainstream/openapi_client/test/test_kyt_register_withdrawal_request.py +65 -0
- chainstream/openapi_client/test/test_link.py +55 -0
- chainstream/openapi_client/test/test_moonshot_create_token_input.py +79 -0
- chainstream/openapi_client/test/test_moonshot_create_token_reply.py +55 -0
- chainstream/openapi_client/test/test_moonshot_submit_create_token200_response.py +53 -0
- chainstream/openapi_client/test/test_moonshot_submit_create_token_input.py +54 -0
- chainstream/openapi_client/test/test_network_identification_org.py +53 -0
- chainstream/openapi_client/test/test_on_chain_activity.py +53 -0
- chainstream/openapi_client/test/test_pump_create_token_input.py +71 -0
- chainstream/openapi_client/test/test_pump_create_token_reply.py +54 -0
- chainstream/openapi_client/test/test_ranking_api.py +67 -0
- chainstream/openapi_client/test/test_red_packet_api.py +88 -0
- chainstream/openapi_client/test/test_red_packet_claim_dto.py +67 -0
- chainstream/openapi_client/test/test_red_packet_claims_page.py +81 -0
- chainstream/openapi_client/test/test_red_packet_dto.py +85 -0
- chainstream/openapi_client/test/test_red_packet_reply.py +53 -0
- chainstream/openapi_client/test/test_red_packet_send_tx_input.py +53 -0
- chainstream/openapi_client/test/test_red_packet_send_tx_response.py +53 -0
- chainstream/openapi_client/test/test_red_packets_page.py +99 -0
- chainstream/openapi_client/test/test_register_address_request.py +53 -0
- chainstream/openapi_client/test/test_register_address_response_dto.py +53 -0
- chainstream/openapi_client/test/test_resolution.py +34 -0
- chainstream/openapi_client/test/test_send_tx_input.py +55 -0
- chainstream/openapi_client/test/test_send_tx_response.py +57 -0
- chainstream/openapi_client/test/test_swap_input.py +65 -0
- chainstream/openapi_client/test/test_swap_reply.py +55 -0
- chainstream/openapi_client/test/test_swap_route_input.py +69 -0
- chainstream/openapi_client/test/test_swap_route_response.py +85 -0
- chainstream/openapi_client/test/test_token.py +352 -0
- chainstream/openapi_client/test/test_token_api.py +193 -0
- chainstream/openapi_client/test/test_token_creation_dto.py +64 -0
- chainstream/openapi_client/test/test_token_creation_page.py +76 -0
- chainstream/openapi_client/test/test_token_creators_dto.py +54 -0
- chainstream/openapi_client/test/test_token_extra_dto.py +72 -0
- chainstream/openapi_client/test/test_token_holder.py +59 -0
- chainstream/openapi_client/test/test_token_holder_page.py +70 -0
- chainstream/openapi_client/test/test_token_liquidity_snapshot_dto.py +71 -0
- chainstream/openapi_client/test/test_token_liquidity_snapshot_page.py +83 -0
- chainstream/openapi_client/test/test_token_list_page.py +154 -0
- chainstream/openapi_client/test/test_token_market_data.py +101 -0
- chainstream/openapi_client/test/test_token_metadata.py +110 -0
- chainstream/openapi_client/test/test_token_page.py +155 -0
- chainstream/openapi_client/test/test_token_price_dto.py +59 -0
- chainstream/openapi_client/test/test_token_price_page.py +70 -0
- chainstream/openapi_client/test/test_token_social_medias_dto.py +64 -0
- chainstream/openapi_client/test/test_token_stat.py +257 -0
- chainstream/openapi_client/test/test_token_trader.py +65 -0
- chainstream/openapi_client/test/test_token_trader_tag.py +34 -0
- chainstream/openapi_client/test/test_top_traders_dto.py +75 -0
- chainstream/openapi_client/test/test_top_traders_page.py +86 -0
- chainstream/openapi_client/test/test_trade_api.py +53 -0
- chainstream/openapi_client/test/test_trade_detail_dto.py +101 -0
- chainstream/openapi_client/test/test_trade_event.py +77 -0
- chainstream/openapi_client/test/test_trade_page.py +112 -0
- chainstream/openapi_client/test/test_trade_type.py +34 -0
- chainstream/openapi_client/test/test_transaction_api.py +53 -0
- chainstream/openapi_client/test/test_transfer_alerts_response_dto.py +71 -0
- chainstream/openapi_client/test/test_transfer_base_response_dto.py +77 -0
- chainstream/openapi_client/test/test_transfer_direct_exposure_response_dto.py +57 -0
- chainstream/openapi_client/test/test_transfer_network_identifications_response_dto.py +61 -0
- chainstream/openapi_client/test/test_update_endpoint_input.py +64 -0
- chainstream/openapi_client/test/test_wallet_api.py +67 -0
- chainstream/openapi_client/test/test_wallet_balance_detail_dto.py +101 -0
- chainstream/openapi_client/test/test_wallet_balances_dto.py +111 -0
- chainstream/openapi_client/test/test_wallet_pnl_dto.py +97 -0
- chainstream/openapi_client/test/test_watchlist_api.py +39 -0
- chainstream/openapi_client/test/test_withdrawal_address_identifications_response_dto.py +65 -0
- chainstream/openapi_client/test/test_withdrawal_base_response_dto.py +71 -0
- chainstream/openapi_client/test/test_withdrawal_fraud_assessment_response_dto.py +73 -0
- chainstream/stream/__init__.py +74 -0
- chainstream/stream/client.py +761 -0
- chainstream/stream/fields.py +284 -0
- chainstream/stream/models.py +415 -0
- chainstream_sdk-0.1.0.dist-info/METADATA +80 -0
- chainstream_sdk-0.1.0.dist-info/RECORD +261 -0
- chainstream_sdk-0.1.0.dist-info/WHEEL +4 -0
- chainstream_sdk-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
"""Centrifuge WebSocket client implementation for ChainStream Stream API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
|
10
|
+
|
|
11
|
+
import aiohttp
|
|
12
|
+
|
|
13
|
+
from chainstream.stream.fields import replace_filter_fields
|
|
14
|
+
from chainstream.stream.models import (
|
|
15
|
+
DexPoolBalance,
|
|
16
|
+
MetricType,
|
|
17
|
+
NewToken,
|
|
18
|
+
RankingTokenList,
|
|
19
|
+
RankingType,
|
|
20
|
+
Resolution,
|
|
21
|
+
TokenCandle,
|
|
22
|
+
TokenHolder,
|
|
23
|
+
TokenLiquidity,
|
|
24
|
+
TokenMaxLiquidity,
|
|
25
|
+
TokenMetadata,
|
|
26
|
+
TokenStat,
|
|
27
|
+
TokenSupply,
|
|
28
|
+
TokenTotalLiquidity,
|
|
29
|
+
TradeActivity,
|
|
30
|
+
WalletBalance,
|
|
31
|
+
WalletTokenPnl,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
T = TypeVar("T")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Unsubscribe:
|
|
41
|
+
"""Unsubscribe handle for stream subscriptions."""
|
|
42
|
+
|
|
43
|
+
channel: str
|
|
44
|
+
callback_id: int
|
|
45
|
+
_api: "StreamApi"
|
|
46
|
+
|
|
47
|
+
def unsubscribe(self) -> None:
|
|
48
|
+
"""Unsubscribe from the channel."""
|
|
49
|
+
self._api._remove_listener(self.channel, self.callback_id)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class StreamApi:
|
|
53
|
+
"""Stream API client for real-time data subscriptions using Centrifuge protocol."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, url: str, access_token: str) -> None:
|
|
56
|
+
"""Initialize the StreamApi.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
url: The WebSocket URL for the stream server.
|
|
60
|
+
access_token: The access token for authentication.
|
|
61
|
+
"""
|
|
62
|
+
# Build URL with token
|
|
63
|
+
if "?" in url:
|
|
64
|
+
self._url = f"{url}&token={access_token}"
|
|
65
|
+
else:
|
|
66
|
+
self._url = f"{url}?token={access_token}"
|
|
67
|
+
|
|
68
|
+
self._access_token = access_token
|
|
69
|
+
self._connected = False
|
|
70
|
+
self._command_id = 0
|
|
71
|
+
self._callback_id = 0
|
|
72
|
+
self._listeners: Dict[str, List[Dict[str, Any]]] = {}
|
|
73
|
+
self._subscriptions: Dict[str, int] = {}
|
|
74
|
+
self._ws: Optional[aiohttp.ClientWebSocketResponse] = None
|
|
75
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
76
|
+
self._read_task: Optional[asyncio.Task] = None
|
|
77
|
+
self._lock = asyncio.Lock()
|
|
78
|
+
|
|
79
|
+
def _next_command_id(self) -> int:
|
|
80
|
+
"""Get the next command ID."""
|
|
81
|
+
self._command_id += 1
|
|
82
|
+
return self._command_id
|
|
83
|
+
|
|
84
|
+
def _next_callback_id(self) -> int:
|
|
85
|
+
"""Get the next callback ID."""
|
|
86
|
+
self._callback_id += 1
|
|
87
|
+
return self._callback_id
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def is_connected(self) -> bool:
|
|
91
|
+
"""Check if the client is connected."""
|
|
92
|
+
return self._connected
|
|
93
|
+
|
|
94
|
+
async def connect(self) -> None:
|
|
95
|
+
"""Connect to the WebSocket server."""
|
|
96
|
+
async with self._lock:
|
|
97
|
+
if self._connected:
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
self._session = aiohttp.ClientSession()
|
|
101
|
+
self._ws = await self._session.ws_connect(self._url)
|
|
102
|
+
|
|
103
|
+
# Send connect command with token
|
|
104
|
+
connect_cmd = {
|
|
105
|
+
"id": self._next_command_id(),
|
|
106
|
+
"connect": {"token": self._access_token},
|
|
107
|
+
}
|
|
108
|
+
await self._ws.send_json(connect_cmd)
|
|
109
|
+
|
|
110
|
+
self._connected = True
|
|
111
|
+
|
|
112
|
+
# Start read task
|
|
113
|
+
self._read_task = asyncio.create_task(self._read_loop())
|
|
114
|
+
|
|
115
|
+
# Give the read task a chance to start
|
|
116
|
+
await asyncio.sleep(0.1)
|
|
117
|
+
|
|
118
|
+
async def _read_loop(self) -> None:
|
|
119
|
+
"""Read messages from the WebSocket."""
|
|
120
|
+
if not self._ws:
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
async for msg in self._ws:
|
|
125
|
+
if msg.type == aiohttp.WSMsgType.TEXT:
|
|
126
|
+
try:
|
|
127
|
+
response = json.loads(msg.data)
|
|
128
|
+
await self._handle_response(response)
|
|
129
|
+
except json.JSONDecodeError:
|
|
130
|
+
pass # Ignore malformed messages
|
|
131
|
+
elif msg.type == aiohttp.WSMsgType.CLOSED:
|
|
132
|
+
logger.info("[streaming] connection closed")
|
|
133
|
+
self._connected = False
|
|
134
|
+
break
|
|
135
|
+
elif msg.type == aiohttp.WSMsgType.ERROR:
|
|
136
|
+
logger.error("[streaming] connection error")
|
|
137
|
+
self._connected = False
|
|
138
|
+
break
|
|
139
|
+
except asyncio.CancelledError:
|
|
140
|
+
pass
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.error(f"[streaming] read error: {e}")
|
|
143
|
+
self._connected = False
|
|
144
|
+
|
|
145
|
+
async def _handle_response(self, response: Dict[str, Any]) -> None:
|
|
146
|
+
"""Handle a response from the server."""
|
|
147
|
+
# Handle push messages (publications)
|
|
148
|
+
if "push" in response:
|
|
149
|
+
push = response["push"]
|
|
150
|
+
channel = push.get("channel", "")
|
|
151
|
+
pub = push.get("pub", {})
|
|
152
|
+
if pub and "data" in pub:
|
|
153
|
+
self._dispatch_message(channel, pub["data"])
|
|
154
|
+
|
|
155
|
+
# Handle errors
|
|
156
|
+
if "error" in response:
|
|
157
|
+
error = response["error"]
|
|
158
|
+
logger.error(
|
|
159
|
+
f"[streaming] error: code={error.get('code')}, message={error.get('message')}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Connect and subscribe responses are handled silently
|
|
163
|
+
|
|
164
|
+
def _dispatch_message(self, channel: str, data: Any) -> None:
|
|
165
|
+
"""Dispatch a message to listeners."""
|
|
166
|
+
listeners = self._listeners.get(channel, [])
|
|
167
|
+
for listener in listeners:
|
|
168
|
+
try:
|
|
169
|
+
listener["callback"](data)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.error(f"[streaming] callback error: {e}")
|
|
172
|
+
|
|
173
|
+
async def _send_subscribe(self, channel: str, filter_expr: Optional[str] = None) -> None:
|
|
174
|
+
"""Send a subscribe command."""
|
|
175
|
+
if not self._ws:
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
cmd: Dict[str, Any] = {
|
|
179
|
+
"id": self._next_command_id(),
|
|
180
|
+
"subscribe": {
|
|
181
|
+
"channel": channel,
|
|
182
|
+
"delta": "fossil",
|
|
183
|
+
},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if filter_expr:
|
|
187
|
+
cmd["subscribe"]["filter"] = filter_expr
|
|
188
|
+
|
|
189
|
+
await self._ws.send_json(cmd)
|
|
190
|
+
|
|
191
|
+
async def _send_unsubscribe(self, channel: str) -> None:
|
|
192
|
+
"""Send an unsubscribe command."""
|
|
193
|
+
if not self._ws:
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
cmd = {
|
|
197
|
+
"id": self._next_command_id(),
|
|
198
|
+
"unsubscribe": {"channel": channel},
|
|
199
|
+
}
|
|
200
|
+
await self._ws.send_json(cmd)
|
|
201
|
+
|
|
202
|
+
def _add_listener(self, channel: str, callback: Callable[[Any], None]) -> int:
|
|
203
|
+
"""Add a listener for a channel."""
|
|
204
|
+
callback_id = self._next_callback_id()
|
|
205
|
+
|
|
206
|
+
if channel not in self._listeners:
|
|
207
|
+
self._listeners[channel] = []
|
|
208
|
+
|
|
209
|
+
self._listeners[channel].append({"id": callback_id, "callback": callback})
|
|
210
|
+
return callback_id
|
|
211
|
+
|
|
212
|
+
def _remove_listener(self, channel: str, callback_id: int) -> None:
|
|
213
|
+
"""Remove a listener from a channel."""
|
|
214
|
+
if channel not in self._listeners:
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
self._listeners[channel] = [
|
|
218
|
+
l for l in self._listeners[channel] if l["id"] != callback_id
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
# If no more listeners, unsubscribe
|
|
222
|
+
if not self._listeners[channel]:
|
|
223
|
+
del self._listeners[channel]
|
|
224
|
+
asyncio.create_task(self._send_unsubscribe(channel))
|
|
225
|
+
if channel in self._subscriptions:
|
|
226
|
+
del self._subscriptions[channel]
|
|
227
|
+
logger.info(f"[streaming] unsubscribed from channel: {channel}")
|
|
228
|
+
|
|
229
|
+
async def disconnect(self) -> None:
|
|
230
|
+
"""Disconnect from the WebSocket server."""
|
|
231
|
+
async with self._lock:
|
|
232
|
+
if self._read_task:
|
|
233
|
+
self._read_task.cancel()
|
|
234
|
+
try:
|
|
235
|
+
await self._read_task
|
|
236
|
+
except asyncio.CancelledError:
|
|
237
|
+
pass
|
|
238
|
+
self._read_task = None
|
|
239
|
+
|
|
240
|
+
if self._ws:
|
|
241
|
+
await self._ws.close()
|
|
242
|
+
self._ws = None
|
|
243
|
+
|
|
244
|
+
if self._session:
|
|
245
|
+
await self._session.close()
|
|
246
|
+
self._session = None
|
|
247
|
+
|
|
248
|
+
self._connected = False
|
|
249
|
+
logger.info("[streaming] disconnected")
|
|
250
|
+
|
|
251
|
+
async def subscribe(
|
|
252
|
+
self,
|
|
253
|
+
channel: str,
|
|
254
|
+
callback: Callable[[Any], None],
|
|
255
|
+
filter_expr: Optional[str] = None,
|
|
256
|
+
method_name: Optional[str] = None,
|
|
257
|
+
) -> Unsubscribe:
|
|
258
|
+
"""Subscribe to a channel with a raw callback.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
channel: The channel to subscribe to.
|
|
262
|
+
callback: The callback function to call when data is received.
|
|
263
|
+
filter_expr: Optional filter expression.
|
|
264
|
+
method_name: Optional method name for field mapping.
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
An Unsubscribe handle to cancel the subscription.
|
|
268
|
+
"""
|
|
269
|
+
# Ensure connected
|
|
270
|
+
if not self._connected:
|
|
271
|
+
await self.connect()
|
|
272
|
+
|
|
273
|
+
# Process filter if method name is provided
|
|
274
|
+
processed_filter = None
|
|
275
|
+
if filter_expr and method_name:
|
|
276
|
+
processed_filter = replace_filter_fields(filter_expr, method_name)
|
|
277
|
+
elif filter_expr:
|
|
278
|
+
processed_filter = filter_expr
|
|
279
|
+
|
|
280
|
+
# Check if already subscribed to this channel
|
|
281
|
+
needs_subscribe = channel not in self._subscriptions
|
|
282
|
+
|
|
283
|
+
# Add callback
|
|
284
|
+
callback_id = self._add_listener(channel, callback)
|
|
285
|
+
|
|
286
|
+
# Subscribe to channel if not already subscribed
|
|
287
|
+
if needs_subscribe:
|
|
288
|
+
await self._send_subscribe(channel, processed_filter)
|
|
289
|
+
self._subscriptions[channel] = self._next_command_id()
|
|
290
|
+
logger.info(f"[streaming] subscribed to channel: {channel}")
|
|
291
|
+
|
|
292
|
+
return Unsubscribe(channel=channel, callback_id=callback_id, _api=self)
|
|
293
|
+
|
|
294
|
+
# ==================== Subscription Methods ====================
|
|
295
|
+
|
|
296
|
+
async def subscribe_token_candles(
|
|
297
|
+
self,
|
|
298
|
+
chain: str,
|
|
299
|
+
token_address: str,
|
|
300
|
+
resolution: Resolution,
|
|
301
|
+
callback: Callable[[TokenCandle], None],
|
|
302
|
+
filter_expr: Optional[str] = None,
|
|
303
|
+
) -> Unsubscribe:
|
|
304
|
+
"""Subscribe to token candle data.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
chain: The blockchain (e.g., "sol").
|
|
308
|
+
token_address: The token address.
|
|
309
|
+
resolution: The candle resolution.
|
|
310
|
+
callback: The callback function to call when data is received.
|
|
311
|
+
filter_expr: Optional filter expression.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
An Unsubscribe handle to cancel the subscription.
|
|
315
|
+
"""
|
|
316
|
+
channel = f"dex-candle:{chain}_{token_address}_{resolution.value}"
|
|
317
|
+
|
|
318
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
319
|
+
candle = TokenCandle(
|
|
320
|
+
open=str(data.get("o", "")),
|
|
321
|
+
close=str(data.get("c", "")),
|
|
322
|
+
high=str(data.get("h", "")),
|
|
323
|
+
low=str(data.get("l", "")),
|
|
324
|
+
volume=str(data.get("v", "")),
|
|
325
|
+
resolution=str(data.get("r", "")),
|
|
326
|
+
time=int(data.get("t", 0)),
|
|
327
|
+
number=int(data.get("n", 0)),
|
|
328
|
+
)
|
|
329
|
+
callback(candle)
|
|
330
|
+
|
|
331
|
+
return await self.subscribe(channel, parse_callback, filter_expr, "subscribe_token_candles")
|
|
332
|
+
|
|
333
|
+
async def subscribe_token_stats(
|
|
334
|
+
self,
|
|
335
|
+
chain: str,
|
|
336
|
+
token_address: str,
|
|
337
|
+
callback: Callable[[TokenStat], None],
|
|
338
|
+
filter_expr: Optional[str] = None,
|
|
339
|
+
) -> Unsubscribe:
|
|
340
|
+
"""Subscribe to token statistics.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
chain: The blockchain.
|
|
344
|
+
token_address: The token address.
|
|
345
|
+
callback: The callback function.
|
|
346
|
+
filter_expr: Optional filter expression.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
An Unsubscribe handle.
|
|
350
|
+
"""
|
|
351
|
+
channel = f"dex-token-stats:{chain}_{token_address}"
|
|
352
|
+
|
|
353
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
354
|
+
stat = TokenStat(
|
|
355
|
+
address=str(data.get("a", "")),
|
|
356
|
+
timestamp=int(data.get("t", 0)),
|
|
357
|
+
buys_1m=data.get("b1m"),
|
|
358
|
+
sells_1m=data.get("s1m"),
|
|
359
|
+
buyers_1m=data.get("be1m"),
|
|
360
|
+
sellers_1m=data.get("se1m"),
|
|
361
|
+
buy_volume_in_usd_1m=data.get("bviu1m"),
|
|
362
|
+
sell_volume_in_usd_1m=data.get("sviu1m"),
|
|
363
|
+
price_1m=data.get("p1m"),
|
|
364
|
+
open_in_usd_1m=data.get("oiu1m"),
|
|
365
|
+
close_in_usd_1m=data.get("ciu1m"),
|
|
366
|
+
price=data.get("p"),
|
|
367
|
+
)
|
|
368
|
+
callback(stat)
|
|
369
|
+
|
|
370
|
+
return await self.subscribe(channel, parse_callback, filter_expr, "subscribe_token_stats")
|
|
371
|
+
|
|
372
|
+
async def subscribe_new_token(
|
|
373
|
+
self,
|
|
374
|
+
chain: str,
|
|
375
|
+
callback: Callable[[NewToken], None],
|
|
376
|
+
filter_expr: Optional[str] = None,
|
|
377
|
+
) -> Unsubscribe:
|
|
378
|
+
"""Subscribe to new tokens.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
chain: The blockchain.
|
|
382
|
+
callback: The callback function.
|
|
383
|
+
filter_expr: Optional filter expression.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
An Unsubscribe handle.
|
|
387
|
+
"""
|
|
388
|
+
channel = f"dex-new-token:{chain}"
|
|
389
|
+
|
|
390
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
391
|
+
token = NewToken(
|
|
392
|
+
token_address=str(data.get("a", "")),
|
|
393
|
+
name=str(data.get("n", "")),
|
|
394
|
+
symbol=str(data.get("s", "")),
|
|
395
|
+
decimals=data.get("d"),
|
|
396
|
+
created_at_ms=int(data.get("cts", 0)),
|
|
397
|
+
)
|
|
398
|
+
callback(token)
|
|
399
|
+
|
|
400
|
+
return await self.subscribe(channel, parse_callback, filter_expr, "subscribe_new_token")
|
|
401
|
+
|
|
402
|
+
async def subscribe_token_trade(
|
|
403
|
+
self,
|
|
404
|
+
chain: str,
|
|
405
|
+
token_address: str,
|
|
406
|
+
callback: Callable[[TradeActivity], None],
|
|
407
|
+
filter_expr: Optional[str] = None,
|
|
408
|
+
) -> Unsubscribe:
|
|
409
|
+
"""Subscribe to token trades.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
chain: The blockchain.
|
|
413
|
+
token_address: The token address.
|
|
414
|
+
callback: The callback function.
|
|
415
|
+
filter_expr: Optional filter expression.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
An Unsubscribe handle.
|
|
419
|
+
"""
|
|
420
|
+
channel = f"dex-trade:{chain}_{token_address}"
|
|
421
|
+
|
|
422
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
423
|
+
trade = TradeActivity(
|
|
424
|
+
token_address=str(data.get("a", "")),
|
|
425
|
+
timestamp=int(data.get("t", 0)),
|
|
426
|
+
kind=str(data.get("k", "")),
|
|
427
|
+
buy_amount=str(data.get("ba", "")),
|
|
428
|
+
buy_amount_in_usd=str(data.get("baiu", "")),
|
|
429
|
+
buy_token_address=str(data.get("btma", "")),
|
|
430
|
+
buy_token_name=str(data.get("btn", "")),
|
|
431
|
+
buy_token_symbol=str(data.get("bts", "")),
|
|
432
|
+
buy_wallet_address=str(data.get("bwa", "")),
|
|
433
|
+
sell_amount=str(data.get("sa", "")),
|
|
434
|
+
sell_amount_in_usd=str(data.get("saiu", "")),
|
|
435
|
+
sell_token_address=str(data.get("stma", "")),
|
|
436
|
+
sell_token_name=str(data.get("stn", "")),
|
|
437
|
+
sell_token_symbol=str(data.get("sts", "")),
|
|
438
|
+
sell_wallet_address=str(data.get("swa", "")),
|
|
439
|
+
tx_hash=str(data.get("h", "")),
|
|
440
|
+
)
|
|
441
|
+
callback(trade)
|
|
442
|
+
|
|
443
|
+
return await self.subscribe(channel, parse_callback, filter_expr, "subscribe_token_trades")
|
|
444
|
+
|
|
445
|
+
async def subscribe_wallet_balance(
|
|
446
|
+
self,
|
|
447
|
+
chain: str,
|
|
448
|
+
wallet_address: str,
|
|
449
|
+
callback: Callable[[WalletBalance], None],
|
|
450
|
+
filter_expr: Optional[str] = None,
|
|
451
|
+
) -> Unsubscribe:
|
|
452
|
+
"""Subscribe to wallet balance.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
chain: The blockchain.
|
|
456
|
+
wallet_address: The wallet address.
|
|
457
|
+
callback: The callback function.
|
|
458
|
+
filter_expr: Optional filter expression.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
An Unsubscribe handle.
|
|
462
|
+
"""
|
|
463
|
+
channel = f"dex-wallet-balance:{chain}_{wallet_address}"
|
|
464
|
+
|
|
465
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
466
|
+
balance = WalletBalance(
|
|
467
|
+
wallet_address=str(data.get("a", "")),
|
|
468
|
+
token_address=str(data.get("ta", "")),
|
|
469
|
+
token_price_in_usd=str(data.get("tpiu", "")),
|
|
470
|
+
balance=str(data.get("b", "")),
|
|
471
|
+
timestamp=int(data.get("t", 0)),
|
|
472
|
+
)
|
|
473
|
+
callback(balance)
|
|
474
|
+
|
|
475
|
+
return await self.subscribe(
|
|
476
|
+
channel, parse_callback, filter_expr, "subscribe_wallet_balance"
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
async def subscribe_token_holders(
|
|
480
|
+
self,
|
|
481
|
+
chain: str,
|
|
482
|
+
token_address: str,
|
|
483
|
+
callback: Callable[[TokenHolder], None],
|
|
484
|
+
filter_expr: Optional[str] = None,
|
|
485
|
+
) -> Unsubscribe:
|
|
486
|
+
"""Subscribe to token holders.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
chain: The blockchain.
|
|
490
|
+
token_address: The token address.
|
|
491
|
+
callback: The callback function.
|
|
492
|
+
filter_expr: Optional filter expression.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
An Unsubscribe handle.
|
|
496
|
+
"""
|
|
497
|
+
channel = f"dex-token-holder:{chain}_{token_address}"
|
|
498
|
+
|
|
499
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
500
|
+
holder = TokenHolder(
|
|
501
|
+
token_address=str(data.get("a", "")),
|
|
502
|
+
timestamp=int(data.get("ts", 0)),
|
|
503
|
+
holders=data.get("h"),
|
|
504
|
+
top100_amount=data.get("t100a"),
|
|
505
|
+
top10_amount=data.get("t10a"),
|
|
506
|
+
top100_holders=data.get("t100h"),
|
|
507
|
+
top10_holders=data.get("t10h"),
|
|
508
|
+
top100_ratio=data.get("t100r"),
|
|
509
|
+
top10_ratio=data.get("t10r"),
|
|
510
|
+
)
|
|
511
|
+
callback(holder)
|
|
512
|
+
|
|
513
|
+
return await self.subscribe(
|
|
514
|
+
channel, parse_callback, filter_expr, "subscribe_token_holders"
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
async def subscribe_token_supply(
|
|
518
|
+
self,
|
|
519
|
+
chain: str,
|
|
520
|
+
token_address: str,
|
|
521
|
+
callback: Callable[[TokenSupply], None],
|
|
522
|
+
filter_expr: Optional[str] = None,
|
|
523
|
+
) -> Unsubscribe:
|
|
524
|
+
"""Subscribe to token supply.
|
|
525
|
+
|
|
526
|
+
Args:
|
|
527
|
+
chain: The blockchain.
|
|
528
|
+
token_address: The token address.
|
|
529
|
+
callback: The callback function.
|
|
530
|
+
filter_expr: Optional filter expression.
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
An Unsubscribe handle.
|
|
534
|
+
"""
|
|
535
|
+
channel = f"dex-token-supply:{chain}_{token_address}"
|
|
536
|
+
|
|
537
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
538
|
+
supply = TokenSupply(
|
|
539
|
+
token_address=str(data.get("a", "")),
|
|
540
|
+
timestamp=int(data.get("ts", 0)),
|
|
541
|
+
supply=data.get("s"),
|
|
542
|
+
market_cap_in_usd=data.get("mciu"),
|
|
543
|
+
)
|
|
544
|
+
callback(supply)
|
|
545
|
+
|
|
546
|
+
return await self.subscribe(channel, parse_callback, filter_expr, "subscribe_token_supply")
|
|
547
|
+
|
|
548
|
+
async def subscribe_dex_pool_balance(
|
|
549
|
+
self,
|
|
550
|
+
chain: str,
|
|
551
|
+
pool_address: str,
|
|
552
|
+
callback: Callable[[DexPoolBalance], None],
|
|
553
|
+
filter_expr: Optional[str] = None,
|
|
554
|
+
) -> Unsubscribe:
|
|
555
|
+
"""Subscribe to DEX pool balance.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
chain: The blockchain.
|
|
559
|
+
pool_address: The pool address.
|
|
560
|
+
callback: The callback function.
|
|
561
|
+
filter_expr: Optional filter expression.
|
|
562
|
+
|
|
563
|
+
Returns:
|
|
564
|
+
An Unsubscribe handle.
|
|
565
|
+
"""
|
|
566
|
+
channel = f"dex-pool-balance:{chain}_{pool_address}"
|
|
567
|
+
|
|
568
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
569
|
+
balance = DexPoolBalance(
|
|
570
|
+
pool_address=str(data.get("a", "")),
|
|
571
|
+
token_a_address=str(data.get("taa", "")),
|
|
572
|
+
token_a_liquidity_in_usd=str(data.get("taliu", "")),
|
|
573
|
+
token_b_address=str(data.get("tba", "")),
|
|
574
|
+
token_b_liquidity_in_usd=str(data.get("tbliu", "")),
|
|
575
|
+
)
|
|
576
|
+
callback(balance)
|
|
577
|
+
|
|
578
|
+
return await self.subscribe(
|
|
579
|
+
channel, parse_callback, filter_expr, "subscribe_dex_pool_balance"
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
async def subscribe_token_max_liquidity(
|
|
583
|
+
self,
|
|
584
|
+
chain: str,
|
|
585
|
+
token_address: str,
|
|
586
|
+
callback: Callable[[TokenMaxLiquidity], None],
|
|
587
|
+
filter_expr: Optional[str] = None,
|
|
588
|
+
) -> Unsubscribe:
|
|
589
|
+
"""Subscribe to token max liquidity.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
chain: The blockchain.
|
|
593
|
+
token_address: The token address.
|
|
594
|
+
callback: The callback function.
|
|
595
|
+
filter_expr: Optional filter expression.
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
An Unsubscribe handle.
|
|
599
|
+
"""
|
|
600
|
+
channel = f"dex-token-liquidity:{chain}_{token_address}"
|
|
601
|
+
|
|
602
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
603
|
+
liquidity = TokenMaxLiquidity(
|
|
604
|
+
token_address=str(data.get("a", "")),
|
|
605
|
+
pool_address=str(data.get("p", "")),
|
|
606
|
+
liquidity_in_usd=str(data.get("liu", "")),
|
|
607
|
+
liquidity_in_native=str(data.get("lin", "")),
|
|
608
|
+
timestamp=int(data.get("ts", 0)),
|
|
609
|
+
)
|
|
610
|
+
callback(liquidity)
|
|
611
|
+
|
|
612
|
+
return await self.subscribe(
|
|
613
|
+
channel, parse_callback, filter_expr, "subscribe_token_max_liquidity"
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
async def subscribe_token_total_liquidity(
|
|
617
|
+
self,
|
|
618
|
+
chain: str,
|
|
619
|
+
token_address: str,
|
|
620
|
+
callback: Callable[[TokenTotalLiquidity], None],
|
|
621
|
+
filter_expr: Optional[str] = None,
|
|
622
|
+
) -> Unsubscribe:
|
|
623
|
+
"""Subscribe to token total liquidity.
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
chain: The blockchain.
|
|
627
|
+
token_address: The token address.
|
|
628
|
+
callback: The callback function.
|
|
629
|
+
filter_expr: Optional filter expression.
|
|
630
|
+
|
|
631
|
+
Returns:
|
|
632
|
+
An Unsubscribe handle.
|
|
633
|
+
"""
|
|
634
|
+
channel = f"dex-token-total-liquidity:{chain}_{token_address}"
|
|
635
|
+
|
|
636
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
637
|
+
liquidity = TokenTotalLiquidity(
|
|
638
|
+
token_address=str(data.get("a", "")),
|
|
639
|
+
liquidity_in_usd=str(data.get("liu", "")),
|
|
640
|
+
liquidity_in_native=str(data.get("lin", "")),
|
|
641
|
+
pool_count=int(data.get("pc", 0)),
|
|
642
|
+
timestamp=int(data.get("ts", 0)),
|
|
643
|
+
)
|
|
644
|
+
callback(liquidity)
|
|
645
|
+
|
|
646
|
+
return await self.subscribe(
|
|
647
|
+
channel, parse_callback, filter_expr, "subscribe_token_total_liquidity"
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
async def subscribe_wallet_pnl(
|
|
651
|
+
self,
|
|
652
|
+
chain: str,
|
|
653
|
+
wallet_address: str,
|
|
654
|
+
callback: Callable[[WalletTokenPnl], None],
|
|
655
|
+
filter_expr: Optional[str] = None,
|
|
656
|
+
) -> Unsubscribe:
|
|
657
|
+
"""Subscribe to wallet PnL.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
chain: The blockchain.
|
|
661
|
+
wallet_address: The wallet address.
|
|
662
|
+
callback: The callback function.
|
|
663
|
+
filter_expr: Optional filter expression.
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
An Unsubscribe handle.
|
|
667
|
+
"""
|
|
668
|
+
channel = f"dex-wallet-pnl:{chain}_{wallet_address}"
|
|
669
|
+
|
|
670
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
671
|
+
pnl = WalletTokenPnl(
|
|
672
|
+
wallet_address=str(data.get("a", "")),
|
|
673
|
+
token_address=str(data.get("ta", "")),
|
|
674
|
+
token_price_in_usd=str(data.get("tpiu", "")),
|
|
675
|
+
timestamp=int(data.get("t", 0)),
|
|
676
|
+
open_time=int(data.get("ot", 0)),
|
|
677
|
+
last_time=int(data.get("lt", 0)),
|
|
678
|
+
close_time=int(data.get("ct", 0)),
|
|
679
|
+
buy_amount=str(data.get("ba", "")),
|
|
680
|
+
buy_amount_in_usd=str(data.get("baiu", "")),
|
|
681
|
+
buy_count=int(data.get("bs", 0)),
|
|
682
|
+
buy_count_30d=int(data.get("bs30d", 0)),
|
|
683
|
+
buy_count_7d=int(data.get("bs7d", 0)),
|
|
684
|
+
sell_amount=str(data.get("sa", "")),
|
|
685
|
+
sell_amount_in_usd=str(data.get("saiu", "")),
|
|
686
|
+
sell_count=int(data.get("ss", 0)),
|
|
687
|
+
sell_count_30d=int(data.get("ss30d", 0)),
|
|
688
|
+
sell_count_7d=int(data.get("ss7d", 0)),
|
|
689
|
+
held_duration_timestamp=int(data.get("hdts", 0)),
|
|
690
|
+
average_buy_price_in_usd=str(data.get("abpiu", "")),
|
|
691
|
+
average_sell_price_in_usd=str(data.get("aspiu", "")),
|
|
692
|
+
unrealized_profit_in_usd=str(data.get("upiu", "")),
|
|
693
|
+
unrealized_profit_ratio=str(data.get("upr", "")),
|
|
694
|
+
realized_profit_in_usd=str(data.get("rpiu", "")),
|
|
695
|
+
realized_profit_ratio=str(data.get("rpr", "")),
|
|
696
|
+
total_realized_profit_in_usd=str(data.get("trpiu", "")),
|
|
697
|
+
total_realized_profit_ratio=str(data.get("trr", "")),
|
|
698
|
+
)
|
|
699
|
+
callback(pnl)
|
|
700
|
+
|
|
701
|
+
return await self.subscribe(channel, parse_callback, filter_expr, "subscribe_wallet_pnl")
|
|
702
|
+
|
|
703
|
+
async def subscribe_new_tokens_metadata(
|
|
704
|
+
self,
|
|
705
|
+
chain: str,
|
|
706
|
+
callback: Callable[[TokenMetadata], None],
|
|
707
|
+
filter_expr: Optional[str] = None,
|
|
708
|
+
) -> Unsubscribe:
|
|
709
|
+
"""Subscribe to new tokens metadata.
|
|
710
|
+
|
|
711
|
+
Args:
|
|
712
|
+
chain: The blockchain.
|
|
713
|
+
callback: The callback function.
|
|
714
|
+
filter_expr: Optional filter expression.
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
An Unsubscribe handle.
|
|
718
|
+
"""
|
|
719
|
+
channel = f"dex-new-token-metadata:{chain}"
|
|
720
|
+
|
|
721
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
722
|
+
metadata = TokenMetadata(
|
|
723
|
+
token_address=str(data.get("a", "")),
|
|
724
|
+
name=data.get("n"),
|
|
725
|
+
symbol=data.get("s"),
|
|
726
|
+
image_url=data.get("iu"),
|
|
727
|
+
description=data.get("de"),
|
|
728
|
+
created_at_ms=data.get("cts"),
|
|
729
|
+
)
|
|
730
|
+
callback(metadata)
|
|
731
|
+
|
|
732
|
+
return await self.subscribe(
|
|
733
|
+
channel, parse_callback, filter_expr, "subscribe_new_tokens_metadata"
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
async def subscribe_ranking_tokens_list(
|
|
737
|
+
self,
|
|
738
|
+
chain: str,
|
|
739
|
+
ranking_type: RankingType,
|
|
740
|
+
callback: Callable[[RankingTokenList], None],
|
|
741
|
+
filter_expr: Optional[str] = None,
|
|
742
|
+
) -> Unsubscribe:
|
|
743
|
+
"""Subscribe to ranking tokens list.
|
|
744
|
+
|
|
745
|
+
Args:
|
|
746
|
+
chain: The blockchain.
|
|
747
|
+
ranking_type: The ranking type.
|
|
748
|
+
callback: The callback function.
|
|
749
|
+
filter_expr: Optional filter expression.
|
|
750
|
+
|
|
751
|
+
Returns:
|
|
752
|
+
An Unsubscribe handle.
|
|
753
|
+
"""
|
|
754
|
+
channel = f"dex-ranking-token-list:{chain}_{ranking_type.value}"
|
|
755
|
+
|
|
756
|
+
def parse_callback(data: Dict[str, Any]) -> None:
|
|
757
|
+
ranking = RankingTokenList()
|
|
758
|
+
# TODO: Parse nested objects if needed
|
|
759
|
+
callback(ranking)
|
|
760
|
+
|
|
761
|
+
return await self.subscribe(channel, parse_callback, filter_expr, None)
|