defistream 1.2.0__py3-none-any.whl → 1.3.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.
defistream/__init__.py CHANGED
@@ -36,7 +36,7 @@ from .query import (
36
36
  QueryBuilder,
37
37
  )
38
38
 
39
- __version__ = "1.2.0"
39
+ __version__ = "1.3.0"
40
40
 
41
41
  __all__ = [
42
42
  # Clients
defistream/protocols.py CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
- from .query import QueryBuilder, AsyncQueryBuilder
7
+ from .query import QueryBuilder, AsyncQueryBuilder, _normalize_multi
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from .client import BaseClient
@@ -16,25 +16,37 @@ class ERC20Protocol:
16
16
  def __init__(self, client: "BaseClient"):
17
17
  self._client = client
18
18
 
19
- def transfers(self, token: str | None = None) -> QueryBuilder:
19
+ def transfers(self, *tokens: str) -> QueryBuilder:
20
20
  """
21
21
  Start a query for ERC20 transfer events.
22
22
 
23
23
  Args:
24
- token: Token symbol (USDT, USDC, WETH, etc.) or contract address
24
+ *tokens: One or more token symbols (USDT, USDC, DAI, etc.) or a
25
+ single contract address. Multiple tokens are joined as
26
+ comma-separated symbols (multi-token queries only support
27
+ known symbol names, not contract addresses).
25
28
 
26
29
  Returns:
27
30
  QueryBuilder for chaining filters
28
31
 
29
32
  Example:
33
+ # Single token
30
34
  df = (
31
35
  client.erc20.transfers("USDT")
32
36
  .network("ETH")
33
37
  .block_range(21000000, 21010000)
34
38
  .as_df()
35
39
  )
40
+
41
+ # Multiple tokens
42
+ df = (
43
+ client.erc20.transfers("USDT", "USDC", "DAI")
44
+ .network("ETH")
45
+ .block_range(21000000, 21010000)
46
+ .as_df()
47
+ )
36
48
  """
37
- params = {"token": token} if token else {}
49
+ params = {"token": _normalize_multi(*tokens)} if tokens else {}
38
50
  return QueryBuilder(self._client, "/erc20/events/transfer", params)
39
51
 
40
52
 
@@ -261,9 +273,16 @@ class AsyncERC20Protocol:
261
273
  def __init__(self, client: "BaseClient"):
262
274
  self._client = client
263
275
 
264
- def transfers(self, token: str | None = None) -> AsyncQueryBuilder:
265
- """Start a query for ERC20 transfer events."""
266
- params = {"token": token} if token else {}
276
+ def transfers(self, *tokens: str) -> AsyncQueryBuilder:
277
+ """Start a query for ERC20 transfer events.
278
+
279
+ Args:
280
+ *tokens: One or more token symbols (USDT, USDC, DAI, etc.) or a
281
+ single contract address. Multiple tokens are joined as
282
+ comma-separated symbols (multi-token queries only support
283
+ known symbol names, not contract addresses).
284
+ """
285
+ params = {"token": _normalize_multi(*tokens)} if tokens else {}
267
286
  return AsyncQueryBuilder(self._client, "/erc20/events/transfer", params)
268
287
 
269
288
 
defistream/query.py CHANGED
@@ -205,9 +205,9 @@ class QueryBuilder:
205
205
  return self._copy_with(max_amount=amount)
206
206
 
207
207
  # ERC20 specific
208
- def token(self, symbol: str) -> "QueryBuilder":
209
- """Set token symbol or address (ERC20)."""
210
- return self._copy_with(token=symbol)
208
+ def token(self, *symbols: str) -> "QueryBuilder":
209
+ """Set token symbol(s) or address (ERC20). Accepts multiple known symbols for multi-token queries."""
210
+ return self._copy_with(token=_normalize_multi(*symbols))
211
211
 
212
212
  # AAVE specific
213
213
  def eth_market_type(self, market_type: str) -> "QueryBuilder":
@@ -545,9 +545,9 @@ class AsyncQueryBuilder:
545
545
  return self._copy_with(max_amount=amount)
546
546
 
547
547
  # ERC20 specific
548
- def token(self, symbol: str) -> "AsyncQueryBuilder":
549
- """Set token symbol or address (ERC20)."""
550
- return self._copy_with(token=symbol)
548
+ def token(self, *symbols: str) -> "AsyncQueryBuilder":
549
+ """Set token symbol(s) or address (ERC20). Accepts multiple known symbols for multi-token queries."""
550
+ return self._copy_with(token=_normalize_multi(*symbols))
551
551
 
552
552
  # AAVE specific
553
553
  def eth_market_type(self, market_type: str) -> "AsyncQueryBuilder":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: defistream
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Python client for the DeFiStream API
5
5
  Project-URL: Homepage, https://defistream.dev
6
6
  Project-URL: Documentation, https://docs.defistream.dev
@@ -78,6 +78,7 @@ print(df.head())
78
78
  ## Features
79
79
 
80
80
  - **Builder pattern**: Fluent query API with chainable methods
81
+ - **Aggregate queries**: Bucket events into time or block intervals with summary statistics
81
82
  - **Type-safe**: Full type hints and Pydantic models
82
83
  - **Multiple formats**: DataFrame (pandas/polars), CSV, Parquet, JSON
83
84
  - **Async support**: Native async/await with `AsyncDeFiStream`
@@ -137,6 +138,23 @@ df = (
137
138
  .as_df()
138
139
  )
139
140
 
141
+ # Query multiple tokens at once (known symbols only, not contract addresses)
142
+ df = (
143
+ client.erc20.transfers("USDT", "USDC", "DAI")
144
+ .network("ETH")
145
+ .block_range(21000000, 21010000)
146
+ .as_df()
147
+ )
148
+
149
+ # Or set multiple tokens via chain method
150
+ df = (
151
+ client.erc20.transfers()
152
+ .token("USDT", "USDC")
153
+ .network("ETH")
154
+ .block_range(21000000, 21010000)
155
+ .as_df()
156
+ )
157
+
140
158
  # Filter by sender
141
159
  df = (
142
160
  client.erc20.transfers("USDT")
@@ -245,6 +263,57 @@ df = (
245
263
  )
246
264
  ```
247
265
 
266
+ ### Aggregate Queries
267
+
268
+ Use `.aggregate()` to bucket raw events into time or block intervals with summary statistics. All existing filters work before `.aggregate()` is called.
269
+
270
+ ```python
271
+ # Aggregate USDT transfers into 2-hour buckets
272
+ df = (
273
+ client.erc20.transfers("USDT")
274
+ .network("ETH")
275
+ .block_range(21000000, 21100000)
276
+ .aggregate(group_by="time", period="2h")
277
+ .as_df()
278
+ )
279
+
280
+ # Aggregate by block intervals
281
+ df = (
282
+ client.erc20.transfers("USDT")
283
+ .network("ETH")
284
+ .block_range(21000000, 21100000)
285
+ .aggregate(group_by="block", period="100b")
286
+ .as_df()
287
+ )
288
+
289
+ # Combine with filters — large transfers from exchanges, bucketed hourly
290
+ df = (
291
+ client.erc20.transfers("USDT")
292
+ .network("ETH")
293
+ .block_range(21000000, 21100000)
294
+ .sender_category("exchange")
295
+ .min_amount(10000)
296
+ .aggregate(group_by="time", period="1h")
297
+ .as_df()
298
+ )
299
+
300
+ # Aggregate Uniswap swaps
301
+ df = (
302
+ client.uniswap.swaps("WETH", "USDC", 500)
303
+ .network("ETH")
304
+ .block_range(21000000, 21100000)
305
+ .aggregate(group_by="time", period="1h")
306
+ .as_df()
307
+ )
308
+ ```
309
+
310
+ You can also discover what aggregate fields are available for a protocol:
311
+
312
+ ```python
313
+ schema = client.aggregate_schema("erc20")
314
+ print(schema)
315
+ ```
316
+
248
317
  ### Verbose Mode
249
318
 
250
319
  By default, responses omit metadata fields to reduce payload size. Use `.verbose()` to include all fields:
@@ -477,7 +546,7 @@ print(f"Request cost: {client.last_response.request_cost}")
477
546
 
478
547
  | Method | Protocols | Description |
479
548
  |--------|-----------|-------------|
480
- | `.token(symbol)` | ERC20 | Token symbol (USDT, USDC) or contract address (required) |
549
+ | `.token(*symbols)` | ERC20 | Token symbol(s) (USDT, USDC) or contract address. Accepts multiple known symbols for multi-token queries (multi-value). |
481
550
  | `.sender(*addrs)` | ERC20, Native | Filter by sender address (multi-value) |
482
551
  | `.receiver(*addrs)` | ERC20, Native | Filter by receiver address (multi-value) |
483
552
  | `.involving(*addrs)` | All | Filter by any involved address (multi-value) |
@@ -507,6 +576,13 @@ Filter events by entity names or categories using the labels database. Available
507
576
 
508
577
  **Mutual exclusivity:** Within each slot (involving/sender/receiver), only one of address/label/category can be set. `involving*` filters cannot be combined with `sender*`/`receiver*` filters.
509
578
 
579
+ ### Aggregate Methods
580
+
581
+ | Method | Description |
582
+ |--------|-------------|
583
+ | `.aggregate(group_by, period)` | Transition to aggregate query. `group_by`: `"time"` or `"block"`. `period`: bucket size (e.g. `"1h"`, `"100b"`). Returns an `AggregateQueryBuilder` that supports all the same terminal and filter methods. |
584
+ | `client.aggregate_schema(protocol)` | Get available aggregate fields for a protocol (e.g. `"erc20"`, `"aave"`). |
585
+
510
586
  ### Terminal Methods
511
587
 
512
588
  | Method | Description |
@@ -0,0 +1,11 @@
1
+ defistream/__init__.py,sha256=444yPKGSGiJqmgD5CPcgbDyL6OZBAz4WBula-RRbxz4,1224
2
+ defistream/client.py,sha256=OZsaa7kgDxWCS37geoTGlp-IFaO3Kn8bm1ghNE9tqB4,13973
3
+ defistream/exceptions.py,sha256=_GxZQ18_YvXFtmNHeddWV8fHPIllHgFeP7fP0CmHF1k,1492
4
+ defistream/models.py,sha256=Zw3DHAISxB6pivKybNzyHXR5IcRwvTZl23DHbjfyKwM,622
5
+ defistream/protocols.py,sha256=lVWtFnZwjyyVjH_9glvFDxVs-bpHlFLXEyIEbEAKN1Q,16174
6
+ defistream/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ defistream/query.py,sha256=mS_ClbpMXRsBpwqL9kDRW50eaXvoarP3xvmItD9MDms,30595
8
+ defistream-1.3.0.dist-info/METADATA,sha256=0A8Q3JBoHYujiLAwyiBdYAlXuZxqwRn10enbmKPoH8w,15856
9
+ defistream-1.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
10
+ defistream-1.3.0.dist-info/licenses/LICENSE,sha256=72DWAof8dMePfFQmfaswClW5d-sE6k7p-7VpuSKLmU4,1067
11
+ defistream-1.3.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- defistream/__init__.py,sha256=j9vKDoqInjyvxau2lDb1eBfr0ZWKVouhBNBbgzXbgpg,1224
2
- defistream/client.py,sha256=OZsaa7kgDxWCS37geoTGlp-IFaO3Kn8bm1ghNE9tqB4,13973
3
- defistream/exceptions.py,sha256=_GxZQ18_YvXFtmNHeddWV8fHPIllHgFeP7fP0CmHF1k,1492
4
- defistream/models.py,sha256=Zw3DHAISxB6pivKybNzyHXR5IcRwvTZl23DHbjfyKwM,622
5
- defistream/protocols.py,sha256=5_bYd46lDy-mK6LZ8sTGW0Z2IVH4g0cQ5rBbbOXsw0U,15368
6
- defistream/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- defistream/query.py,sha256=Pr-zwUCBcjBUkqa1CFzVJmsnZ4DpV29c0_9p1VtfGak,30433
8
- defistream-1.2.0.dist-info/METADATA,sha256=7rNfboEYqF87-3RKkUlOzMRsKRKEns0H1lmeesEJwsM,13657
9
- defistream-1.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
10
- defistream-1.2.0.dist-info/licenses/LICENSE,sha256=72DWAof8dMePfFQmfaswClW5d-sE6k7p-7VpuSKLmU4,1067
11
- defistream-1.2.0.dist-info/RECORD,,