defistream 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {defistream-1.2.0 → defistream-1.3.0}/.claude/settings.local.json +13 -2
- {defistream-1.2.0 → defistream-1.3.0}/PKG-INFO +78 -2
- {defistream-1.2.0 → defistream-1.3.0}/README.md +77 -1
- {defistream-1.2.0 → defistream-1.3.0}/pyproject.toml +1 -1
- {defistream-1.2.0 → defistream-1.3.0}/src/defistream/__init__.py +1 -1
- {defistream-1.2.0 → defistream-1.3.0}/src/defistream/protocols.py +26 -7
- {defistream-1.2.0 → defistream-1.3.0}/src/defistream/query.py +6 -6
- {defistream-1.2.0 → defistream-1.3.0}/tests/test_client.py +90 -0
- {defistream-1.2.0 → defistream-1.3.0}/.gitignore +0 -0
- {defistream-1.2.0 → defistream-1.3.0}/LICENSE +0 -0
- {defistream-1.2.0 → defistream-1.3.0}/src/defistream/client.py +0 -0
- {defistream-1.2.0 → defistream-1.3.0}/src/defistream/exceptions.py +0 -0
- {defistream-1.2.0 → defistream-1.3.0}/src/defistream/models.py +0 -0
- {defistream-1.2.0 → defistream-1.3.0}/src/defistream/py.typed +0 -0
- {defistream-1.2.0 → defistream-1.3.0}/tests/__init__.py +0 -0
- {defistream-1.2.0 → defistream-1.3.0}/tests/conftest.py +0 -0
- {defistream-1.2.0 → defistream-1.3.0}/tests/test_integration.py +0 -0
|
@@ -17,7 +17,18 @@
|
|
|
17
17
|
"Bash(git push:*)",
|
|
18
18
|
"Bash(git subtree push:*)",
|
|
19
19
|
"Bash(.venv/bin/python -m pytest:*)",
|
|
20
|
-
"Bash(.venv/bin/python:*)"
|
|
20
|
+
"Bash(.venv/bin/python:*)",
|
|
21
|
+
"Bash(git status:*)",
|
|
22
|
+
"Bash(grep:*)",
|
|
23
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nAccept case-insensitive network names in events server\n\nNormalize the network query parameter to uppercase at the entry point\nin both _fetch_events_dataframe and _handle_parquet_request so that\neth, Eth, and ETH are all accepted. Add tests for core and API gateway.\nUpdate API docs to note case-insensitivity.\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
24
|
+
"Bash(git commit:*)",
|
|
25
|
+
"Bash(curl:*)",
|
|
26
|
+
"Bash(tree:*)",
|
|
27
|
+
"Bash(python -m build:*)"
|
|
21
28
|
]
|
|
22
|
-
}
|
|
29
|
+
},
|
|
30
|
+
"enableAllProjectMcpServers": true,
|
|
31
|
+
"enabledMcpjsonServers": [
|
|
32
|
+
"defistream"
|
|
33
|
+
]
|
|
23
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: defistream
|
|
3
|
-
Version: 1.
|
|
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(
|
|
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 |
|
|
@@ -44,6 +44,7 @@ print(df.head())
|
|
|
44
44
|
## Features
|
|
45
45
|
|
|
46
46
|
- **Builder pattern**: Fluent query API with chainable methods
|
|
47
|
+
- **Aggregate queries**: Bucket events into time or block intervals with summary statistics
|
|
47
48
|
- **Type-safe**: Full type hints and Pydantic models
|
|
48
49
|
- **Multiple formats**: DataFrame (pandas/polars), CSV, Parquet, JSON
|
|
49
50
|
- **Async support**: Native async/await with `AsyncDeFiStream`
|
|
@@ -103,6 +104,23 @@ df = (
|
|
|
103
104
|
.as_df()
|
|
104
105
|
)
|
|
105
106
|
|
|
107
|
+
# Query multiple tokens at once (known symbols only, not contract addresses)
|
|
108
|
+
df = (
|
|
109
|
+
client.erc20.transfers("USDT", "USDC", "DAI")
|
|
110
|
+
.network("ETH")
|
|
111
|
+
.block_range(21000000, 21010000)
|
|
112
|
+
.as_df()
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Or set multiple tokens via chain method
|
|
116
|
+
df = (
|
|
117
|
+
client.erc20.transfers()
|
|
118
|
+
.token("USDT", "USDC")
|
|
119
|
+
.network("ETH")
|
|
120
|
+
.block_range(21000000, 21010000)
|
|
121
|
+
.as_df()
|
|
122
|
+
)
|
|
123
|
+
|
|
106
124
|
# Filter by sender
|
|
107
125
|
df = (
|
|
108
126
|
client.erc20.transfers("USDT")
|
|
@@ -211,6 +229,57 @@ df = (
|
|
|
211
229
|
)
|
|
212
230
|
```
|
|
213
231
|
|
|
232
|
+
### Aggregate Queries
|
|
233
|
+
|
|
234
|
+
Use `.aggregate()` to bucket raw events into time or block intervals with summary statistics. All existing filters work before `.aggregate()` is called.
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
# Aggregate USDT transfers into 2-hour buckets
|
|
238
|
+
df = (
|
|
239
|
+
client.erc20.transfers("USDT")
|
|
240
|
+
.network("ETH")
|
|
241
|
+
.block_range(21000000, 21100000)
|
|
242
|
+
.aggregate(group_by="time", period="2h")
|
|
243
|
+
.as_df()
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Aggregate by block intervals
|
|
247
|
+
df = (
|
|
248
|
+
client.erc20.transfers("USDT")
|
|
249
|
+
.network("ETH")
|
|
250
|
+
.block_range(21000000, 21100000)
|
|
251
|
+
.aggregate(group_by="block", period="100b")
|
|
252
|
+
.as_df()
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Combine with filters — large transfers from exchanges, bucketed hourly
|
|
256
|
+
df = (
|
|
257
|
+
client.erc20.transfers("USDT")
|
|
258
|
+
.network("ETH")
|
|
259
|
+
.block_range(21000000, 21100000)
|
|
260
|
+
.sender_category("exchange")
|
|
261
|
+
.min_amount(10000)
|
|
262
|
+
.aggregate(group_by="time", period="1h")
|
|
263
|
+
.as_df()
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Aggregate Uniswap swaps
|
|
267
|
+
df = (
|
|
268
|
+
client.uniswap.swaps("WETH", "USDC", 500)
|
|
269
|
+
.network("ETH")
|
|
270
|
+
.block_range(21000000, 21100000)
|
|
271
|
+
.aggregate(group_by="time", period="1h")
|
|
272
|
+
.as_df()
|
|
273
|
+
)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
You can also discover what aggregate fields are available for a protocol:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
schema = client.aggregate_schema("erc20")
|
|
280
|
+
print(schema)
|
|
281
|
+
```
|
|
282
|
+
|
|
214
283
|
### Verbose Mode
|
|
215
284
|
|
|
216
285
|
By default, responses omit metadata fields to reduce payload size. Use `.verbose()` to include all fields:
|
|
@@ -443,7 +512,7 @@ print(f"Request cost: {client.last_response.request_cost}")
|
|
|
443
512
|
|
|
444
513
|
| Method | Protocols | Description |
|
|
445
514
|
|--------|-----------|-------------|
|
|
446
|
-
| `.token(
|
|
515
|
+
| `.token(*symbols)` | ERC20 | Token symbol(s) (USDT, USDC) or contract address. Accepts multiple known symbols for multi-token queries (multi-value). |
|
|
447
516
|
| `.sender(*addrs)` | ERC20, Native | Filter by sender address (multi-value) |
|
|
448
517
|
| `.receiver(*addrs)` | ERC20, Native | Filter by receiver address (multi-value) |
|
|
449
518
|
| `.involving(*addrs)` | All | Filter by any involved address (multi-value) |
|
|
@@ -473,6 +542,13 @@ Filter events by entity names or categories using the labels database. Available
|
|
|
473
542
|
|
|
474
543
|
**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.
|
|
475
544
|
|
|
545
|
+
### Aggregate Methods
|
|
546
|
+
|
|
547
|
+
| Method | Description |
|
|
548
|
+
|--------|-------------|
|
|
549
|
+
| `.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. |
|
|
550
|
+
| `client.aggregate_schema(protocol)` | Get available aggregate fields for a protocol (e.g. `"erc20"`, `"aave"`). |
|
|
551
|
+
|
|
476
552
|
### Terminal Methods
|
|
477
553
|
|
|
478
554
|
| Method | Description |
|
|
@@ -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,
|
|
19
|
+
def transfers(self, *tokens: str) -> QueryBuilder:
|
|
20
20
|
"""
|
|
21
21
|
Start a query for ERC20 transfer events.
|
|
22
22
|
|
|
23
23
|
Args:
|
|
24
|
-
|
|
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":
|
|
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,
|
|
265
|
-
"""Start a query for ERC20 transfer events.
|
|
266
|
-
|
|
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
|
|
|
@@ -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,
|
|
209
|
-
"""Set token symbol or address (ERC20)."""
|
|
210
|
-
return self._copy_with(token=
|
|
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,
|
|
549
|
-
"""Set token symbol or address (ERC20)."""
|
|
550
|
-
return self._copy_with(token=
|
|
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":
|
|
@@ -253,6 +253,96 @@ class TestBuilderPattern:
|
|
|
253
253
|
assert query._params["since"] == "2024-01-01"
|
|
254
254
|
assert query._params["until"] == "2024-01-31"
|
|
255
255
|
|
|
256
|
+
class TestMultiTokenSupport:
|
|
257
|
+
"""Test multi-token support for ERC20 transfers."""
|
|
258
|
+
|
|
259
|
+
def test_transfers_single_token(self):
|
|
260
|
+
"""transfers('USDT') should set token param."""
|
|
261
|
+
client = DeFiStream(api_key="dsk_test")
|
|
262
|
+
query = client.erc20.transfers("USDT")
|
|
263
|
+
assert query._params["token"] == "USDT"
|
|
264
|
+
|
|
265
|
+
def test_transfers_multiple_tokens(self):
|
|
266
|
+
"""transfers('USDT', 'USDC', 'DAI') should join with comma."""
|
|
267
|
+
client = DeFiStream(api_key="dsk_test")
|
|
268
|
+
query = client.erc20.transfers("USDT", "USDC", "DAI")
|
|
269
|
+
assert query._params["token"] == "USDT,USDC,DAI"
|
|
270
|
+
|
|
271
|
+
def test_transfers_two_tokens(self):
|
|
272
|
+
"""transfers('USDT', 'USDC') should join with comma."""
|
|
273
|
+
client = DeFiStream(api_key="dsk_test")
|
|
274
|
+
query = client.erc20.transfers("USDT", "USDC")
|
|
275
|
+
assert query._params["token"] == "USDT,USDC"
|
|
276
|
+
|
|
277
|
+
def test_transfers_no_token(self):
|
|
278
|
+
"""transfers() with no args should have no token param."""
|
|
279
|
+
client = DeFiStream(api_key="dsk_test")
|
|
280
|
+
query = client.erc20.transfers()
|
|
281
|
+
assert "token" not in query._params
|
|
282
|
+
|
|
283
|
+
def test_transfers_pre_joined_string(self):
|
|
284
|
+
"""transfers('USDT,USDC') with pre-joined string should pass through."""
|
|
285
|
+
client = DeFiStream(api_key="dsk_test")
|
|
286
|
+
query = client.erc20.transfers("USDT,USDC")
|
|
287
|
+
assert query._params["token"] == "USDT,USDC"
|
|
288
|
+
|
|
289
|
+
def test_token_chain_multiple(self):
|
|
290
|
+
""".token('USDT', 'USDC') should join with comma."""
|
|
291
|
+
client = DeFiStream(api_key="dsk_test")
|
|
292
|
+
query = client.erc20.transfers().token("USDT", "USDC")
|
|
293
|
+
assert query._params["token"] == "USDT,USDC"
|
|
294
|
+
|
|
295
|
+
def test_token_chain_single(self):
|
|
296
|
+
""".token('USDT') should set single token."""
|
|
297
|
+
client = DeFiStream(api_key="dsk_test")
|
|
298
|
+
query = client.erc20.transfers().token("USDT")
|
|
299
|
+
assert query._params["token"] == "USDT"
|
|
300
|
+
|
|
301
|
+
def test_token_chain_no_args_raises(self):
|
|
302
|
+
""".token() with no args should raise ValueError."""
|
|
303
|
+
client = DeFiStream(api_key="dsk_test")
|
|
304
|
+
with pytest.raises(ValueError, match="At least one value is required"):
|
|
305
|
+
client.erc20.transfers().token()
|
|
306
|
+
|
|
307
|
+
def test_multi_token_with_chaining(self):
|
|
308
|
+
"""Multi-token should work with other builder methods."""
|
|
309
|
+
client = DeFiStream(api_key="dsk_test")
|
|
310
|
+
query = (
|
|
311
|
+
client.erc20.transfers("USDT", "USDC", "DAI")
|
|
312
|
+
.network("ETH")
|
|
313
|
+
.block_range(21000000, 21010000)
|
|
314
|
+
)
|
|
315
|
+
assert query._params["token"] == "USDT,USDC,DAI"
|
|
316
|
+
assert query._params["network"] == "ETH"
|
|
317
|
+
assert query._params["block_start"] == 21000000
|
|
318
|
+
|
|
319
|
+
def test_async_transfers_multiple_tokens(self):
|
|
320
|
+
"""Async transfers should support multi-token."""
|
|
321
|
+
client = AsyncDeFiStream(api_key="dsk_test")
|
|
322
|
+
query = client.erc20.transfers("USDT", "USDC")
|
|
323
|
+
assert isinstance(query, AsyncQueryBuilder)
|
|
324
|
+
assert query._params["token"] == "USDT,USDC"
|
|
325
|
+
|
|
326
|
+
def test_async_token_chain_multiple(self):
|
|
327
|
+
"""Async .token() should support multi-token."""
|
|
328
|
+
client = AsyncDeFiStream(api_key="dsk_test")
|
|
329
|
+
query = client.erc20.transfers().token("USDT", "USDC", "DAI")
|
|
330
|
+
assert query._params["token"] == "USDT,USDC,DAI"
|
|
331
|
+
|
|
332
|
+
def test_contract_address_still_works(self):
|
|
333
|
+
"""Single contract address should still work."""
|
|
334
|
+
client = DeFiStream(api_key="dsk_test")
|
|
335
|
+
query = client.erc20.transfers("0xdAC17F958D2ee523a2206206994597C13D831ec7")
|
|
336
|
+
assert query._params["token"] == "0xdAC17F958D2ee523a2206206994597C13D831ec7"
|
|
337
|
+
|
|
338
|
+
def test_multi_token_aggregate(self):
|
|
339
|
+
"""Multi-token should carry through to aggregate queries."""
|
|
340
|
+
client = DeFiStream(api_key="dsk_test")
|
|
341
|
+
query = client.erc20.transfers("USDT", "USDC").network("ETH").aggregate()
|
|
342
|
+
assert query._params["token"] == "USDT,USDC"
|
|
343
|
+
assert isinstance(query, AggregateQueryBuilder)
|
|
344
|
+
|
|
345
|
+
|
|
256
346
|
class TestAsyncBuilderPattern:
|
|
257
347
|
"""Test async builder pattern."""
|
|
258
348
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|