dexpaprika-sdk 0.2.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.
- dexpaprika_sdk/__init__.py +34 -0
- dexpaprika_sdk/api/__init__.py +15 -0
- dexpaprika_sdk/api/base.py +225 -0
- dexpaprika_sdk/api/dexes.py +35 -0
- dexpaprika_sdk/api/networks.py +45 -0
- dexpaprika_sdk/api/pools.py +251 -0
- dexpaprika_sdk/api/search.py +28 -0
- dexpaprika_sdk/api/tokens.py +90 -0
- dexpaprika_sdk/api/utils.py +16 -0
- dexpaprika_sdk/client.py +130 -0
- dexpaprika_sdk/models/__init__.py +30 -0
- dexpaprika_sdk/models/base.py +18 -0
- dexpaprika_sdk/models/networks.py +26 -0
- dexpaprika_sdk/models/pools.py +117 -0
- dexpaprika_sdk/models/search.py +27 -0
- dexpaprika_sdk/models/tokens.py +64 -0
- dexpaprika_sdk/models/utils.py +10 -0
- dexpaprika_sdk/utils/__init__.py +0 -0
- dexpaprika_sdk/utils/perf.py +58 -0
- dexpaprika_sdk-0.2.0.dist-info/METADATA +333 -0
- dexpaprika_sdk-0.2.0.dist-info/RECORD +23 -0
- dexpaprika_sdk-0.2.0.dist-info/WHEEL +5 -0
- dexpaprika_sdk-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DexPaprika SDK for Python
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
A Python client library for the DexPaprika API,
|
|
6
|
+
providing access to token, pool, and DEX data
|
|
7
|
+
across multiple blockchain networks.
|
|
8
|
+
|
|
9
|
+
:copyright: (c) 2024 CoinPaprika
|
|
10
|
+
:license: MIT, see LICENSE for more details.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .client import DexPaprikaClient
|
|
14
|
+
# Import models for easier access
|
|
15
|
+
from .models import (
|
|
16
|
+
Network, Dex, DexesResponse,
|
|
17
|
+
Token, Pool, PoolsResponse, TimeIntervalMetrics,
|
|
18
|
+
PoolDetails, OHLCVRecord, Transaction, TransactionsResponse,
|
|
19
|
+
TokenSummary, TokenDetails,
|
|
20
|
+
DexInfo, SearchResult,
|
|
21
|
+
Stats
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__version__ = "0.2.0"
|
|
25
|
+
__all__ = [
|
|
26
|
+
"DexPaprikaClient",
|
|
27
|
+
# Models
|
|
28
|
+
"Network", "Dex", "DexesResponse",
|
|
29
|
+
"Token", "Pool", "PoolsResponse", "TimeIntervalMetrics",
|
|
30
|
+
"PoolDetails", "OHLCVRecord", "Transaction", "TransactionsResponse",
|
|
31
|
+
"TokenSummary", "TokenDetails",
|
|
32
|
+
"DexInfo", "SearchResult",
|
|
33
|
+
"Stats"
|
|
34
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .base import BaseAPI
|
|
2
|
+
from .networks import NetworksAPI
|
|
3
|
+
from .pools import PoolsAPI
|
|
4
|
+
from .tokens import TokensAPI
|
|
5
|
+
from .search import SearchAPI
|
|
6
|
+
from .utils import UtilsAPI
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"BaseAPI",
|
|
10
|
+
"NetworksAPI",
|
|
11
|
+
"PoolsAPI",
|
|
12
|
+
"TokensAPI",
|
|
13
|
+
"SearchAPI",
|
|
14
|
+
"UtilsAPI",
|
|
15
|
+
]
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union, TYPE_CHECKING, Callable, TypeVar, Set
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..client import DexPaprikaClient
|
|
8
|
+
|
|
9
|
+
T = TypeVar('T')
|
|
10
|
+
|
|
11
|
+
class CacheEntry:
|
|
12
|
+
"""Class representing a cached response with an expiration time."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, data: Any, expires_at: Optional[datetime] = None):
|
|
15
|
+
"""
|
|
16
|
+
Initialize a new cache entry.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
data: The data to cache
|
|
20
|
+
expires_at: The time when the cache entry expires
|
|
21
|
+
"""
|
|
22
|
+
self.data = data
|
|
23
|
+
self.expires_at = expires_at
|
|
24
|
+
|
|
25
|
+
def is_expired(self) -> bool:
|
|
26
|
+
"""
|
|
27
|
+
Check if the cache entry has expired.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
True if the cache entry has expired, False otherwise
|
|
31
|
+
"""
|
|
32
|
+
return self.expires_at is not None and datetime.now() > self.expires_at
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BaseAPI:
|
|
36
|
+
"""Base class for all API service classes."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, client: "DexPaprikaClient"):
|
|
39
|
+
"""
|
|
40
|
+
Initialize a new API service.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
client: The DexPaprika client instance
|
|
44
|
+
"""
|
|
45
|
+
self.client = client
|
|
46
|
+
self._cache: Dict[str, CacheEntry] = {} # TTL-based cache
|
|
47
|
+
|
|
48
|
+
# Default TTLs for different types of data
|
|
49
|
+
self._cache_ttls = {
|
|
50
|
+
"networks": timedelta(hours=24), # Network list rarely changes
|
|
51
|
+
"pools": timedelta(minutes=5), # Pool data changes frequently
|
|
52
|
+
"tokens": timedelta(minutes=10), # Token data changes moderately
|
|
53
|
+
"stats": timedelta(minutes=15), # Stats change moderately
|
|
54
|
+
"default": timedelta(minutes=5) # Default TTL for other endpoints
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def _get_cache_key(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Generate a unique cache key for the request.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
endpoint: API endpoint
|
|
63
|
+
params: Query parameters
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
A unique cache key as a string
|
|
67
|
+
"""
|
|
68
|
+
key_parts = [endpoint]
|
|
69
|
+
if params:
|
|
70
|
+
# Sort params to ensure consistent keys
|
|
71
|
+
sorted_params = json.dumps(params, sort_keys=True)
|
|
72
|
+
key_parts.append(sorted_params)
|
|
73
|
+
|
|
74
|
+
key_string = ":".join(key_parts)
|
|
75
|
+
return hashlib.md5(key_string.encode()).hexdigest()
|
|
76
|
+
|
|
77
|
+
def _get_ttl(self, endpoint: str) -> timedelta:
|
|
78
|
+
"""
|
|
79
|
+
Get the TTL for a specific endpoint.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
endpoint: API endpoint
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The TTL as a timedelta
|
|
86
|
+
"""
|
|
87
|
+
for key, ttl in self._cache_ttls.items():
|
|
88
|
+
if key in endpoint:
|
|
89
|
+
return ttl
|
|
90
|
+
return self._cache_ttls["default"]
|
|
91
|
+
|
|
92
|
+
def _get(
|
|
93
|
+
self,
|
|
94
|
+
endpoint: str,
|
|
95
|
+
params: Optional[Dict[str, Any]] = None,
|
|
96
|
+
skip_cache: bool = False,
|
|
97
|
+
ttl: Optional[timedelta] = None
|
|
98
|
+
) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
|
|
99
|
+
"""
|
|
100
|
+
Make a GET request to the specified endpoint.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
endpoint: API endpoint (e.g., "/networks")
|
|
104
|
+
params: Query parameters
|
|
105
|
+
skip_cache: Whether to skip the cache and force a fresh request
|
|
106
|
+
ttl: Custom TTL for this request
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Response data as a dictionary or list
|
|
110
|
+
"""
|
|
111
|
+
if skip_cache:
|
|
112
|
+
return self.client.get(endpoint, params=params)
|
|
113
|
+
|
|
114
|
+
cache_key = self._get_cache_key(endpoint, params)
|
|
115
|
+
cache_entry = self._cache.get(cache_key)
|
|
116
|
+
|
|
117
|
+
# Return cached data if valid
|
|
118
|
+
if cache_entry and not cache_entry.is_expired():
|
|
119
|
+
return cache_entry.data
|
|
120
|
+
|
|
121
|
+
# Get fresh data
|
|
122
|
+
result = self.client.get(endpoint, params=params)
|
|
123
|
+
|
|
124
|
+
# Cache the result with appropriate TTL
|
|
125
|
+
if ttl is None:
|
|
126
|
+
ttl = self._get_ttl(endpoint)
|
|
127
|
+
|
|
128
|
+
expires_at = datetime.now() + ttl
|
|
129
|
+
self._cache[cache_key] = CacheEntry(result, expires_at)
|
|
130
|
+
|
|
131
|
+
return result
|
|
132
|
+
|
|
133
|
+
def _post(self, endpoint: str, data: Dict[str, Any], params: Optional[Dict[str, Any]] = None) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
|
|
134
|
+
"""
|
|
135
|
+
Make a POST request to the specified endpoint.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
endpoint: API endpoint
|
|
139
|
+
data: Request body
|
|
140
|
+
params: Query parameters
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Response data as a dictionary or list
|
|
144
|
+
"""
|
|
145
|
+
return self.client.post(endpoint, data=data, params=params)
|
|
146
|
+
|
|
147
|
+
def _clean_params(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
148
|
+
"""Clean None values from params."""
|
|
149
|
+
return {k: v for k, v in params.items() if v is not None}
|
|
150
|
+
|
|
151
|
+
def _validate_required(self, param_name: str, value: Any) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Validate that a parameter is not None or empty.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
param_name: Name of the parameter for error messages
|
|
157
|
+
value: Value to validate
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
ValueError: If the value is None or empty
|
|
161
|
+
"""
|
|
162
|
+
if value is None or (isinstance(value, str) and value.strip() == ""):
|
|
163
|
+
raise ValueError(f"{param_name} is required")
|
|
164
|
+
|
|
165
|
+
def _validate_enum(self, param_name: str, value: str, valid_values: Set[str]) -> None:
|
|
166
|
+
"""
|
|
167
|
+
Validate that a parameter is one of a set of valid values.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
param_name: Name of the parameter for error messages
|
|
171
|
+
value: Value to validate
|
|
172
|
+
valid_values: Set of valid values
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
ValueError: If the value is not one of the valid values
|
|
176
|
+
"""
|
|
177
|
+
if value is None:
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
if value not in valid_values:
|
|
181
|
+
raise ValueError(f"{param_name} must be one of: {', '.join(sorted(valid_values))}")
|
|
182
|
+
|
|
183
|
+
def _validate_range(self, param_name: str, value: Union[int, float], min_val: Optional[Union[int, float]] = None, max_val: Optional[Union[int, float]] = None) -> None:
|
|
184
|
+
"""
|
|
185
|
+
Validate that a numeric parameter is within a specified range.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
param_name: Name of the parameter for error messages
|
|
189
|
+
value: Value to validate
|
|
190
|
+
min_val: Minimum allowed value (inclusive)
|
|
191
|
+
max_val: Maximum allowed value (inclusive)
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ValueError: If the value is outside the specified range
|
|
195
|
+
"""
|
|
196
|
+
if value is None:
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
if min_val is not None and value < min_val:
|
|
200
|
+
raise ValueError(f"{param_name} must be at least {min_val}")
|
|
201
|
+
|
|
202
|
+
if max_val is not None and value > max_val:
|
|
203
|
+
raise ValueError(f"{param_name} must be at most {max_val}")
|
|
204
|
+
|
|
205
|
+
def clear_cache(self, endpoint_prefix: Optional[str] = None) -> None:
|
|
206
|
+
"""
|
|
207
|
+
Clear the cache, optionally only for endpoints with a specific prefix.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
endpoint_prefix: Optional prefix to filter which cache entries to clear
|
|
211
|
+
"""
|
|
212
|
+
if endpoint_prefix:
|
|
213
|
+
# Get cache keys from the original endpoints that contain the prefix
|
|
214
|
+
keys_to_remove = []
|
|
215
|
+
for key in list(self._cache.keys()):
|
|
216
|
+
cache_entry = self._cache[key]
|
|
217
|
+
if endpoint_prefix in key:
|
|
218
|
+
keys_to_remove.append(key)
|
|
219
|
+
|
|
220
|
+
# Remove the entries
|
|
221
|
+
for key in keys_to_remove:
|
|
222
|
+
del self._cache[key]
|
|
223
|
+
else:
|
|
224
|
+
# Clear the entire cache
|
|
225
|
+
self._cache.clear()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from .base import BaseAPI
|
|
4
|
+
from ..models.networks import DexesResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DexesAPI(BaseAPI):
|
|
8
|
+
"""API service for DEX-related endpoints."""
|
|
9
|
+
|
|
10
|
+
def list(self, network: str, page: int = 0, limit: int = 10) -> DexesResponse:
|
|
11
|
+
"""
|
|
12
|
+
Get a list of available decentralized exchanges on a specific network.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
network: Network ID (e.g., "ethereum", "solana")
|
|
16
|
+
page: Page number for pagination
|
|
17
|
+
limit: Number of items per page
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Response containing list of DEXes
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
ValueError: If any parameter is invalid
|
|
24
|
+
"""
|
|
25
|
+
# Validate parameters
|
|
26
|
+
self._validate_required("network", network)
|
|
27
|
+
self._validate_range("page", page, min_val=0)
|
|
28
|
+
self._validate_range("limit", limit, min_val=1, max_val=100)
|
|
29
|
+
|
|
30
|
+
params = {
|
|
31
|
+
"page": page,
|
|
32
|
+
"limit": limit
|
|
33
|
+
}
|
|
34
|
+
data = self._get(f"/networks/{network}/dexes", params=params)
|
|
35
|
+
return DexesResponse(**data)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import List, Optional, Set
|
|
2
|
+
|
|
3
|
+
from .base import BaseAPI
|
|
4
|
+
from ..models.networks import Network, DexesResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class NetworksAPI(BaseAPI):
|
|
8
|
+
"""API service for network-related endpoints."""
|
|
9
|
+
|
|
10
|
+
def list(self) -> List[Network]:
|
|
11
|
+
"""
|
|
12
|
+
Retrieve a list of all supported blockchain networks.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
List of Network objects
|
|
16
|
+
"""
|
|
17
|
+
data = self._get("/networks")
|
|
18
|
+
return [Network(**item) for item in data]
|
|
19
|
+
|
|
20
|
+
def list_dexes(self, network_id: str, page: int = 0, limit: int = 10) -> DexesResponse:
|
|
21
|
+
"""
|
|
22
|
+
Get a list of all available dexes on a specific network.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
network_id: Network ID (e.g., "ethereum", "solana")
|
|
26
|
+
page: Page number for pagination
|
|
27
|
+
limit: Number of items per page
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Response containing a list of DEXes
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: If any parameter is invalid
|
|
34
|
+
"""
|
|
35
|
+
# Validate parameters
|
|
36
|
+
self._validate_required("network_id", network_id)
|
|
37
|
+
self._validate_range("page", page, min_val=0)
|
|
38
|
+
self._validate_range("limit", limit, min_val=1, max_val=100)
|
|
39
|
+
|
|
40
|
+
params = {
|
|
41
|
+
"page": page,
|
|
42
|
+
"limit": limit,
|
|
43
|
+
}
|
|
44
|
+
data = self._get(f"/networks/{network_id}/dexes", params=params)
|
|
45
|
+
return DexesResponse(**data)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from typing import List, Optional, Dict, Any, Set
|
|
2
|
+
|
|
3
|
+
from .base import BaseAPI
|
|
4
|
+
from ..models.pools import (
|
|
5
|
+
PoolsResponse, PoolDetails, OHLCVRecord, TransactionsResponse
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PoolsAPI(BaseAPI):
|
|
10
|
+
"""API service for pool-related endpoints."""
|
|
11
|
+
|
|
12
|
+
# Valid values for common parameters
|
|
13
|
+
VALID_SORT_VALUES: Set[str] = {"asc", "desc"}
|
|
14
|
+
VALID_ORDER_BY_VALUES: Set[str] = {"volume_usd", "price_usd", "transactions", "last_price_change_usd_24h", "created_at"}
|
|
15
|
+
VALID_INTERVAL_VALUES: Set[str] = {"1m", "5m", "10m", "15m", "30m", "1h", "6h", "12h", "24h"}
|
|
16
|
+
|
|
17
|
+
def list(
|
|
18
|
+
self,
|
|
19
|
+
page: int = 0,
|
|
20
|
+
limit: int = 10,
|
|
21
|
+
sort: str = "desc",
|
|
22
|
+
order_by: str = "volume_usd"
|
|
23
|
+
) -> PoolsResponse:
|
|
24
|
+
"""
|
|
25
|
+
Get a list of top pools across all networks.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
page: Page number for pagination
|
|
29
|
+
limit: Number of items per page
|
|
30
|
+
sort: Sort order ("asc" or "desc")
|
|
31
|
+
order_by: Field to order by ("volume_usd", "price_usd", etc.)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Response containing a list of pools
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValueError: If any parameter is invalid
|
|
38
|
+
"""
|
|
39
|
+
# Validate parameters
|
|
40
|
+
self._validate_range("page", page, min_val=0)
|
|
41
|
+
self._validate_range("limit", limit, min_val=1, max_val=100)
|
|
42
|
+
self._validate_enum("sort", sort, self.VALID_SORT_VALUES)
|
|
43
|
+
self._validate_enum("order_by", order_by, self.VALID_ORDER_BY_VALUES)
|
|
44
|
+
|
|
45
|
+
# Get top pools
|
|
46
|
+
params = {"page": page, "limit": limit, "sort": sort, "order_by": order_by}
|
|
47
|
+
data = self._get("/pools", params=params)
|
|
48
|
+
|
|
49
|
+
# ensure pools exists
|
|
50
|
+
if 'pools' not in data: data['pools'] = []
|
|
51
|
+
|
|
52
|
+
return PoolsResponse(**data)
|
|
53
|
+
|
|
54
|
+
def list_by_network(
|
|
55
|
+
self,
|
|
56
|
+
network_id: str,
|
|
57
|
+
page: int = 0,
|
|
58
|
+
limit: int = 10,
|
|
59
|
+
sort: str = "desc",
|
|
60
|
+
order_by: str = "volume_usd"
|
|
61
|
+
) -> PoolsResponse:
|
|
62
|
+
"""
|
|
63
|
+
Get a list of pools on a specific network.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
network_id: Network ID (e.g., "ethereum", "solana")
|
|
67
|
+
page: Page number for pagination
|
|
68
|
+
limit: Number of items per page
|
|
69
|
+
sort: Sort order ("asc" or "desc")
|
|
70
|
+
order_by: Field to order by ("volume_usd", "price_usd", etc.)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Response containing a list of pools
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValueError: If any parameter is invalid
|
|
77
|
+
"""
|
|
78
|
+
# Validate parameters
|
|
79
|
+
self._validate_required("network_id", network_id)
|
|
80
|
+
self._validate_range("page", page, min_val=0)
|
|
81
|
+
self._validate_range("limit", limit, min_val=1, max_val=100)
|
|
82
|
+
self._validate_enum("sort", sort, self.VALID_SORT_VALUES)
|
|
83
|
+
self._validate_enum("order_by", order_by, self.VALID_ORDER_BY_VALUES)
|
|
84
|
+
|
|
85
|
+
# Get network pools
|
|
86
|
+
params = {"page": page, "limit": limit, "sort": sort, "order_by": order_by}
|
|
87
|
+
data = self._get(f"/networks/{network_id}/pools", params=params)
|
|
88
|
+
|
|
89
|
+
# ensure pools exists
|
|
90
|
+
if 'pools' not in data: data['pools'] = []
|
|
91
|
+
|
|
92
|
+
return PoolsResponse(**data)
|
|
93
|
+
|
|
94
|
+
def list_by_dex(
|
|
95
|
+
self,
|
|
96
|
+
network_id: str,
|
|
97
|
+
dex_id: str,
|
|
98
|
+
page: int = 0,
|
|
99
|
+
limit: int = 10,
|
|
100
|
+
sort: str = "desc",
|
|
101
|
+
order_by: str = "volume_usd"
|
|
102
|
+
) -> PoolsResponse:
|
|
103
|
+
"""
|
|
104
|
+
Get a list of pools for a specific DEX on a network.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
network_id: Network ID (e.g., "ethereum", "solana")
|
|
108
|
+
dex_id: DEX ID (e.g., "uniswap_v3")
|
|
109
|
+
page: Page number for pagination
|
|
110
|
+
limit: Number of items per page
|
|
111
|
+
sort: Sort order ("asc" or "desc")
|
|
112
|
+
order_by: Field to order by ("volume_usd", "price_usd", etc.)
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Response containing a list of pools
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
ValueError: If any parameter is invalid
|
|
119
|
+
"""
|
|
120
|
+
# Validate parameters
|
|
121
|
+
self._validate_required("network_id", network_id)
|
|
122
|
+
self._validate_required("dex_id", dex_id)
|
|
123
|
+
self._validate_range("page", page, min_val=0)
|
|
124
|
+
self._validate_range("limit", limit, min_val=1, max_val=100)
|
|
125
|
+
self._validate_enum("sort", sort, self.VALID_SORT_VALUES)
|
|
126
|
+
self._validate_enum("order_by", order_by, self.VALID_ORDER_BY_VALUES)
|
|
127
|
+
|
|
128
|
+
# Get dex pools
|
|
129
|
+
params = {"page": page, "limit": limit, "sort": sort, "order_by": order_by}
|
|
130
|
+
data = self._get(f"/networks/{network_id}/dexes/{dex_id}/pools", params=params)
|
|
131
|
+
|
|
132
|
+
# ensure pools exists
|
|
133
|
+
if 'pools' not in data: data['pools'] = []
|
|
134
|
+
|
|
135
|
+
return PoolsResponse(**data)
|
|
136
|
+
|
|
137
|
+
def get_details(
|
|
138
|
+
self,
|
|
139
|
+
network_id: str,
|
|
140
|
+
pool_address: str,
|
|
141
|
+
inversed: bool = False
|
|
142
|
+
) -> PoolDetails:
|
|
143
|
+
"""
|
|
144
|
+
Get detailed information about a specific pool.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
network_id: Network ID (e.g., "ethereum", "solana")
|
|
148
|
+
pool_address: Pool address or identifier
|
|
149
|
+
inversed: Whether to invert the price ratio
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Detailed pool information
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
ValueError: If any parameter is invalid
|
|
156
|
+
"""
|
|
157
|
+
# Validate parameters
|
|
158
|
+
self._validate_required("network_id", network_id)
|
|
159
|
+
self._validate_required("pool_address", pool_address)
|
|
160
|
+
|
|
161
|
+
# Get pool details
|
|
162
|
+
params = {"inversed": "true" if inversed else None}
|
|
163
|
+
params = self._clean_params(params)
|
|
164
|
+
|
|
165
|
+
data = self._get(f"/networks/{network_id}/pools/{pool_address}", params=params)
|
|
166
|
+
return PoolDetails(**data)
|
|
167
|
+
|
|
168
|
+
def get_ohlcv(
|
|
169
|
+
self,
|
|
170
|
+
network_id: str,
|
|
171
|
+
pool_address: str,
|
|
172
|
+
start: str,
|
|
173
|
+
end: Optional[str] = None,
|
|
174
|
+
limit: int = 1,
|
|
175
|
+
interval: str = "24h",
|
|
176
|
+
inversed: bool = False
|
|
177
|
+
) -> List[OHLCVRecord]:
|
|
178
|
+
"""
|
|
179
|
+
Get OHLCV (Open-High-Low-Close-Volume) data for a specific pool.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
network_id: Network ID (e.g., "ethereum", "solana")
|
|
183
|
+
pool_address: Pool address or identifier
|
|
184
|
+
start: Start time for historical data (ISO-8601, yyyy-mm-dd, or Unix timestamp)
|
|
185
|
+
end: End time for historical data (max 1 year from start)
|
|
186
|
+
limit: Number of data points to retrieve (max 366)
|
|
187
|
+
interval: Interval granularity for OHLCV data (1m, 5m, 10m, 15m, 30m, 1h, 6h, 12h, 24h)
|
|
188
|
+
inversed: Whether to invert the price ratio in OHLCV calculations
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
List of OHLCV records
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ValueError: If any parameter is invalid
|
|
195
|
+
"""
|
|
196
|
+
# Validate parameters
|
|
197
|
+
self._validate_required("network_id", network_id)
|
|
198
|
+
self._validate_required("pool_address", pool_address)
|
|
199
|
+
self._validate_required("start", start)
|
|
200
|
+
self._validate_range("limit", limit, min_val=1, max_val=366)
|
|
201
|
+
self._validate_enum("interval", interval, self.VALID_INTERVAL_VALUES)
|
|
202
|
+
|
|
203
|
+
# Get price history
|
|
204
|
+
params = {
|
|
205
|
+
"start": start,
|
|
206
|
+
"end": end,
|
|
207
|
+
"limit": limit,
|
|
208
|
+
"interval": interval,
|
|
209
|
+
"inversed": "true" if inversed else None,
|
|
210
|
+
}
|
|
211
|
+
params = self._clean_params(params)
|
|
212
|
+
|
|
213
|
+
data = self._get(f"/networks/{network_id}/pools/{pool_address}/ohlcv", params=params)
|
|
214
|
+
return [OHLCVRecord(**item) for item in data]
|
|
215
|
+
|
|
216
|
+
def get_transactions(
|
|
217
|
+
self,
|
|
218
|
+
network_id: str,
|
|
219
|
+
pool_address: str,
|
|
220
|
+
page: int = 0,
|
|
221
|
+
limit: int = 10,
|
|
222
|
+
cursor: Optional[str] = None
|
|
223
|
+
) -> TransactionsResponse:
|
|
224
|
+
"""
|
|
225
|
+
Get transactions of a pool on a network.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
network_id: Network ID (e.g., "ethereum", "solana")
|
|
229
|
+
pool_address: Pool address or identifier
|
|
230
|
+
page: Page number for pagination
|
|
231
|
+
limit: Number of items per page
|
|
232
|
+
cursor: Transaction ID used for cursor-based pagination
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Response containing a list of transactions
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
ValueError: If any parameter is invalid
|
|
239
|
+
"""
|
|
240
|
+
# Validate parameters
|
|
241
|
+
self._validate_required("network_id", network_id)
|
|
242
|
+
self._validate_required("pool_address", pool_address)
|
|
243
|
+
self._validate_range("page", page, min_val=0)
|
|
244
|
+
self._validate_range("limit", limit, min_val=1, max_val=100)
|
|
245
|
+
|
|
246
|
+
# Get txs
|
|
247
|
+
params = {"page": page, "limit": limit, "cursor": cursor}
|
|
248
|
+
params = self._clean_params(params)
|
|
249
|
+
|
|
250
|
+
data = self._get(f"/networks/{network_id}/pools/{pool_address}/transactions", params=params)
|
|
251
|
+
return TransactionsResponse(**data)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from urllib.parse import quote
|
|
2
|
+
|
|
3
|
+
from .base import BaseAPI
|
|
4
|
+
from ..models.search import SearchResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SearchAPI(BaseAPI):
|
|
8
|
+
"""API service for search-related endpoints."""
|
|
9
|
+
|
|
10
|
+
def search(self, query: str) -> SearchResult:
|
|
11
|
+
"""
|
|
12
|
+
Search for tokens, pools, and DEXes by name or identifier.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
query: Search term (e.g., "uniswap", "bitcoin", or a token address)
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Search results across tokens, pools, and DEXes
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
ValueError: If the query parameter is invalid
|
|
22
|
+
"""
|
|
23
|
+
# Validate parameters
|
|
24
|
+
self._validate_required("query", query)
|
|
25
|
+
|
|
26
|
+
params = {"query": query}
|
|
27
|
+
data = self._get("/search", params=params)
|
|
28
|
+
return SearchResult(**data)
|