defistream 1.2.1__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.
@@ -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.2.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
@@ -138,6 +138,23 @@ df = (
138
138
  .as_df()
139
139
  )
140
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
+
141
158
  # Filter by sender
142
159
  df = (
143
160
  client.erc20.transfers("USDT")
@@ -529,7 +546,7 @@ print(f"Request cost: {client.last_response.request_cost}")
529
546
 
530
547
  | Method | Protocols | Description |
531
548
  |--------|-----------|-------------|
532
- | `.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). |
533
550
  | `.sender(*addrs)` | ERC20, Native | Filter by sender address (multi-value) |
534
551
  | `.receiver(*addrs)` | ERC20, Native | Filter by receiver address (multi-value) |
535
552
  | `.involving(*addrs)` | All | Filter by any involved address (multi-value) |
@@ -104,6 +104,23 @@ df = (
104
104
  .as_df()
105
105
  )
106
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
+
107
124
  # Filter by sender
108
125
  df = (
109
126
  client.erc20.transfers("USDT")
@@ -495,7 +512,7 @@ print(f"Request cost: {client.last_response.request_cost}")
495
512
 
496
513
  | Method | Protocols | Description |
497
514
  |--------|-----------|-------------|
498
- | `.token(symbol)` | ERC20 | Token symbol (USDT, USDC) or contract address (required) |
515
+ | `.token(*symbols)` | ERC20 | Token symbol(s) (USDT, USDC) or contract address. Accepts multiple known symbols for multi-token queries (multi-value). |
499
516
  | `.sender(*addrs)` | ERC20, Native | Filter by sender address (multi-value) |
500
517
  | `.receiver(*addrs)` | ERC20, Native | Filter by receiver address (multi-value) |
501
518
  | `.involving(*addrs)` | All | Filter by any involved address (multi-value) |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "defistream"
7
- version = "1.2.1"
7
+ version = "1.3.0"
8
8
  description = "Python client for the DeFiStream API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -36,7 +36,7 @@ from .query import (
36
36
  QueryBuilder,
37
37
  )
38
38
 
39
- __version__ = "1.2.1"
39
+ __version__ = "1.3.0"
40
40
 
41
41
  __all__ = [
42
42
  # Clients
@@ -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
 
@@ -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":
@@ -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