<h1>
<p align="center">
<br>DexTrades π¦
</p >
</h1>
<p align="center">
A Python library for streaming DEX trades from RPC nodes.
</p>
<p align="center">
<a href="https://pypi.org/project/dextrades">
<img src="https://img.shields.io/pypi/v/dextrades.svg?label=pypi&logo=PyPI&logoColor=white" alt="PyPI">
</a>
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT">
</a>
</p>
## β¨ Features
* **Direct On-Chain Data**: Pulls event logs directly from a given RPC URL, requiring no indexers or third-party APIs. It currently decodes swaps from Uniswap V2 and V3.
* **Data Enrichment Pipeline**: The library can enrich raw log data with:
* Token metadata (symbols, decimals) for readable amounts.
* Block timestamps for each swap.
* USD values calculated using Chainlink ETH/USD price feeds at the swap's block height.
* **Rust Core**: Built with a Rust backend (`PyO3`, `alloy`) for processing. It implements RPC provider racing, a circuit breaker, and automatic retries for connection resilience.
* **Friendly Python API**: Provides an `async` generator to stream trades. Enrichments are controlled via boolean flags. Supports streaming individual swaps or Apache Arrow batches.
## π¦ Installation
Using `uv` (recommended):
```bash
uv add dextrades
````
Or with `pip`:
```bash
pip install dextrades
```
## π» Usage
The `Client` manages connections to one or more RPC endpoints. The `stream_swaps` method returns an async iterator of swap events.
```python
import asyncio
import dextrades
urls = [
"https://eth-pokt.nodies.app",
"https://ethereum.publicnode.com",
]
with dextrades.Client(urls) as client:
# Stream a small block range; normalized token amounts included
async for swap in client.stream_swaps(
["uniswap_v2", "uniswap_v3"],
17000003, 17000003,
batch_size=1,
enrich_timestamps=True,
enrich_usd=True,
):
print(
swap.get("dex_protocol"),
swap.get("token_sold_symbol"), swap.get("token_sold_amount"),
"β",
swap.get("token_bought_symbol"), swap.get("token_bought_amount"),
"USD:", swap.get("value_usd"),
)
```
### Example Output
```
time dex bought sold value_usd trader hash
-----------------------------------------------------------------------------------------------------------------------------
2023-04-08 01:58:47 Uniswap V2 0.0529 WETH 98.9990 USDC $99.00 0x5eA7 0x37f7
2023-04-08 01:58:47 Uniswap V2 0.0398 XMON 0.0529 WETH $98.63 0x5eA7 0x37f7
2023-04-08 01:58:47 Uniswap V2 0.0452 WETH 0.7000 QNT $84.38 0x4a30 0x5428
2023-04-08 01:58:47 Uniswap V2 3.2402 WETH 2.9994 PAXG $6,045.62 0xdBC2 0x8f46
```
## π Available Fields
| Enricher | Fields Added | Description |
|----------|--------------|-------------|
| **Core** | `block_number`, `tx_hash`, `log_index`, `dex_protocol`, `pool_address` | Always present |
| **transaction** | `tx_from`, `tx_to`, `gas_used` | Transaction context |
| **timestamp** | `block_timestamp` | Block mining time |
| **token_metadata** | `token0_address`, `token1_address`, `token0_symbol`, `token1_symbol`, `token0_decimals`, `token1_decimals` | Token information |
| **swap** | `token_bought_address`, `token_sold_address`, `token_bought_symbol`, `token_sold_symbol`, `token_bought_amount`, `token_sold_amount`, `token_bought_amount_raw`, `token_sold_amount_raw` | Trade direction & amounts |
| **price_usd** | `value_usd`, `value_usd_method`, `chainlink_updated_at` | USD valuation (stablecoins + Chainlink ETH/USD) |
### Network overrides (generic per-chain pricing)
Provide network-specific overrides at client initialization to make USD pricing and warmup work on non-mainnet chains.
```python
overrides = {
# Wrapped native token address (e.g., WETH, wCAMP)
"native_wrapped": "0x4200000000000000000000000000000000000006",
# Chainlink-style native/USD aggregator (optional)
# "native_usd_aggregator": "0x...",
# Stablecoin addresses to treat as USD stables on this network
"stable_addresses": [
# "0x...", "0x..."
],
# Optional warmup tokens that exist on the current network (avoid noisy warmup logs)
"warmup_tokens": []
}
with dextrades.Client(["<rpc-url>"], network_overrides=overrides) as client:
async for swap in client.stream_swaps(["uniswap_v2", "uniswap_v3"], 100, 100, enrich_usd=True):
...
```
If no per-network aggregator is provided, USD pricing falls back to:
- Stablecoin passthrough (by address or common stable symbols)
- Native/USD via Chainlink only on Ethereum mainnet (default mainnet aggregator)
### Router filter (optional)
Filter to SmartRouter transactions only:
```python
async for swap in client.stream_swaps(["uniswap_v2","uniswap_v3"], 100, 100, routers=["0xRouter..."]):
...
# Also available for individual streaming:
client.stream_individual_swaps(["uniswap_v2","uniswap_v3"], 100, 100, routers=["0xRouter..."])
```
## πΊοΈ Roadmap
- [x] Uniswap V2
- [x] Uniswap V3
- [x] and deduplication
- [x] Enrichments:
- [x] token metadata
- [x] trade direction
- [x] timestamps
- [x] USD values via Chainlink
- [x] USD values via stablecoin passthrough
- [x] RPC provider
- [x] racing
- [x] retries
- [x] circuit breakers
- [x] sharded `getLogs`
- [x] Python API
- [x] CLI
- [x] example and demo
- [x] benchmarks
- [ ] additional enrichments:
- [ ] trader balance
- [ ] Uniswap V3 Quoter fallback for non-WETH/stable tokens
- [ ] Chainlink Feed Registry (USD feeds) and multi-chain aggregator addresses
- [ ] CLI UX polish (enrichment flags, simple table mode)
- [ ] Light metrics: stage counters and provider health snapshot
- [ ] Additional DEX protocols
- [ ] Optional persistent caches and Parquet/Polars export helpers
Raw data
{
"_id": null,
"home_page": null,
"name": "dextrades",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.12",
"maintainer_email": null,
"keywords": "ethereum, dex, uniswap, streaming, polars, arrow, pyarrow, rust, pyo3",
"author": "Dextrades contributors",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/5c/7c/0802faad671db7de4796463ab11478e0b764efaee7d48c8863e08d6c426b/dextrades-0.1.12.tar.gz",
"platform": null,
"description": "<h1>\n<p align=\"center\">\n <br>DexTrades \ud83e\udd84 \n</p >\n</h1>\n\n<p align=\"center\">\nA Python library for streaming DEX trades from RPC nodes.\n</p>\n\n<p align=\"center\">\n <a href=\"https://pypi.org/project/dextrades\">\n <img src=\"https://img.shields.io/pypi/v/dextrades.svg?label=pypi&logo=PyPI&logoColor=white\" alt=\"PyPI\">\n </a>\n <a href=\"https://opensource.org/licenses/MIT\">\n <img src=\"https://img.shields.io/badge/License-MIT-yellow.svg\" alt=\"License: MIT\">\n </a>\n</p>\n\n## \u2728 Features\n\n* **Direct On-Chain Data**: Pulls event logs directly from a given RPC URL, requiring no indexers or third-party APIs. It currently decodes swaps from Uniswap V2 and V3.\n\n* **Data Enrichment Pipeline**: The library can enrich raw log data with:\n * Token metadata (symbols, decimals) for readable amounts.\n * Block timestamps for each swap.\n * USD values calculated using Chainlink ETH/USD price feeds at the swap's block height.\n\n* **Rust Core**: Built with a Rust backend (`PyO3`, `alloy`) for processing. It implements RPC provider racing, a circuit breaker, and automatic retries for connection resilience.\n\n* **Friendly Python API**: Provides an `async` generator to stream trades. Enrichments are controlled via boolean flags. Supports streaming individual swaps or Apache Arrow batches.\n\n## \ud83d\udce6 Installation\n\n\nUsing `uv` (recommended):\n```bash\nuv add dextrades\n````\n\nOr with `pip`:\n\n```bash\npip install dextrades\n```\n\n## \ud83d\udcbb Usage\n\nThe `Client` manages connections to one or more RPC endpoints. The `stream_swaps` method returns an async iterator of swap events.\n\n```python\nimport asyncio\nimport dextrades\n\nurls = [\n \"https://eth-pokt.nodies.app\",\n \"https://ethereum.publicnode.com\",\n]\nwith dextrades.Client(urls) as client:\n # Stream a small block range; normalized token amounts included\n async for swap in client.stream_swaps(\n [\"uniswap_v2\", \"uniswap_v3\"],\n 17000003, 17000003,\n batch_size=1,\n enrich_timestamps=True,\n enrich_usd=True,\n ):\n print(\n swap.get(\"dex_protocol\"),\n swap.get(\"token_sold_symbol\"), swap.get(\"token_sold_amount\"),\n \"\u2192\",\n swap.get(\"token_bought_symbol\"), swap.get(\"token_bought_amount\"),\n \"USD:\", swap.get(\"value_usd\"),\n )\n```\n### Example Output\n\n```\ntime dex bought sold value_usd trader hash \n-----------------------------------------------------------------------------------------------------------------------------\n2023-04-08 01:58:47 Uniswap V2 0.0529 WETH 98.9990 USDC $99.00 0x5eA7 0x37f7\n2023-04-08 01:58:47 Uniswap V2 0.0398 XMON 0.0529 WETH $98.63 0x5eA7 0x37f7\n2023-04-08 01:58:47 Uniswap V2 0.0452 WETH 0.7000 QNT $84.38 0x4a30 0x5428\n2023-04-08 01:58:47 Uniswap V2 3.2402 WETH 2.9994 PAXG $6,045.62 0xdBC2 0x8f46\n```\n\n\n\n## \ud83d\udcca Available Fields\n\n| Enricher | Fields Added | Description |\n|----------|--------------|-------------|\n| **Core** | `block_number`, `tx_hash`, `log_index`, `dex_protocol`, `pool_address` | Always present |\n| **transaction** | `tx_from`, `tx_to`, `gas_used` | Transaction context |\n| **timestamp** | `block_timestamp` | Block mining time |\n| **token_metadata** | `token0_address`, `token1_address`, `token0_symbol`, `token1_symbol`, `token0_decimals`, `token1_decimals` | Token information |\n| **swap** | `token_bought_address`, `token_sold_address`, `token_bought_symbol`, `token_sold_symbol`, `token_bought_amount`, `token_sold_amount`, `token_bought_amount_raw`, `token_sold_amount_raw` | Trade direction & amounts |\n| **price_usd** | `value_usd`, `value_usd_method`, `chainlink_updated_at` | USD valuation (stablecoins + Chainlink ETH/USD) |\n\n\n### Network overrides (generic per-chain pricing)\n\nProvide network-specific overrides at client initialization to make USD pricing and warmup work on non-mainnet chains.\n\n```python\noverrides = {\n # Wrapped native token address (e.g., WETH, wCAMP)\n \"native_wrapped\": \"0x4200000000000000000000000000000000000006\",\n # Chainlink-style native/USD aggregator (optional)\n # \"native_usd_aggregator\": \"0x...\",\n # Stablecoin addresses to treat as USD stables on this network\n \"stable_addresses\": [\n # \"0x...\", \"0x...\"\n ],\n # Optional warmup tokens that exist on the current network (avoid noisy warmup logs)\n \"warmup_tokens\": []\n}\n\nwith dextrades.Client([\"<rpc-url>\"], network_overrides=overrides) as client:\n async for swap in client.stream_swaps([\"uniswap_v2\", \"uniswap_v3\"], 100, 100, enrich_usd=True):\n ...\n```\n\nIf no per-network aggregator is provided, USD pricing falls back to:\n- Stablecoin passthrough (by address or common stable symbols)\n- Native/USD via Chainlink only on Ethereum mainnet (default mainnet aggregator)\n\n### Router filter (optional)\n\nFilter to SmartRouter transactions only:\n\n```python\nasync for swap in client.stream_swaps([\"uniswap_v2\",\"uniswap_v3\"], 100, 100, routers=[\"0xRouter...\"]):\n ...\n\n# Also available for individual streaming:\nclient.stream_individual_swaps([\"uniswap_v2\",\"uniswap_v3\"], 100, 100, routers=[\"0xRouter...\"])\n```\n\n\n\n## \ud83d\uddfa\ufe0f Roadmap\n\n- [x] Uniswap V2\n- [x] Uniswap V3\n- [x] and deduplication\n- [x] Enrichments: \n - [x] token metadata\n - [x] trade direction\n - [x] timestamps\n - [x] USD values via Chainlink\n - [x] USD values via stablecoin passthrough \n- [x] RPC provider\n - [x] racing\n - [x] retries\n - [x] circuit breakers\n - [x] sharded `getLogs`\n- [x] Python API\n- [x] CLI\n- [x] example and demo\n- [x] benchmarks\n- [ ] additional enrichments:\n - [ ] trader balance\n - [ ] Uniswap V3 Quoter fallback for non-WETH/stable tokens\n- [ ] Chainlink Feed Registry (USD feeds) and multi-chain aggregator addresses\n- [ ] CLI UX polish (enrichment flags, simple table mode)\n- [ ] Light metrics: stage counters and provider health snapshot\n- [ ] Additional DEX protocols\n- [ ] Optional persistent caches and Parquet/Polars export helpers\n\n",
"bugtrack_url": null,
"license": null,
"summary": "High-performance DEX swap streaming and analysis (Rust/PyO3)",
"version": "0.1.12",
"project_urls": {
"Documentation": "https://github.com/elyase/dextrades#readme",
"Homepage": "https://github.com/elyase/dextrades",
"Repository": "https://github.com/elyase/dextrades"
},
"split_keywords": [
"ethereum",
" dex",
" uniswap",
" streaming",
" polars",
" arrow",
" pyarrow",
" rust",
" pyo3"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "650d09880076289b2c9f1bb790843dd60f8e19e5bc38212f4e8a488234c4cbc6",
"md5": "7ac0313eb9fe0441d39732f7bb9a272d",
"sha256": "cf83e181a9f76aaedf89c2e12d0bc40a3b87fa5bd1f045aedfd55ae2d1a54b05"
},
"downloads": -1,
"filename": "dextrades-0.1.12-cp312-cp312-macosx_10_12_x86_64.whl",
"has_sig": false,
"md5_digest": "7ac0313eb9fe0441d39732f7bb9a272d",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.12",
"size": 3551729,
"upload_time": "2025-10-08T19:10:17",
"upload_time_iso_8601": "2025-10-08T19:10:17.631598Z",
"url": "https://files.pythonhosted.org/packages/65/0d/09880076289b2c9f1bb790843dd60f8e19e5bc38212f4e8a488234c4cbc6/dextrades-0.1.12-cp312-cp312-macosx_10_12_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "a6ea344d34b9ffa073a1feadb87b1b7666d664bed53c92fc6254ec3543153232",
"md5": "efa3972d4d536f78805bccafe58b95ea",
"sha256": "3a14f3c86bafe30fb032ce79e7b46472faa70b69dac6de28f6f0e31a9aec30e1"
},
"downloads": -1,
"filename": "dextrades-0.1.12-cp312-cp312-macosx_11_0_arm64.whl",
"has_sig": false,
"md5_digest": "efa3972d4d536f78805bccafe58b95ea",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.12",
"size": 3413949,
"upload_time": "2025-10-08T19:10:19",
"upload_time_iso_8601": "2025-10-08T19:10:19.174885Z",
"url": "https://files.pythonhosted.org/packages/a6/ea/344d34b9ffa073a1feadb87b1b7666d664bed53c92fc6254ec3543153232/dextrades-0.1.12-cp312-cp312-macosx_11_0_arm64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "fd2f5355e56324cfce41f90f04517556bdd1ee24fc0dd037efb3524e0b4f847e",
"md5": "0cceda807e6fbded05c6beb513153928",
"sha256": "c739cb01b83f1fb674746416a5942bff4d9ba98386bc805a10c55efcf16c3da7"
},
"downloads": -1,
"filename": "dextrades-0.1.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"has_sig": false,
"md5_digest": "0cceda807e6fbded05c6beb513153928",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.12",
"size": 3822548,
"upload_time": "2025-10-08T19:10:24",
"upload_time_iso_8601": "2025-10-08T19:10:24.772472Z",
"url": "https://files.pythonhosted.org/packages/fd/2f/5355e56324cfce41f90f04517556bdd1ee24fc0dd037efb3524e0b4f847e/dextrades-0.1.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "23154edc5d389cc60a9ba0e5b4a6fde7ca6cc47c14c6d157577a935bd76b72c8",
"md5": "0cebd521dcb447db4afed219073dc3dc",
"sha256": "0b07b4d6f56964a40ecb00701473052cb37a30bd43460d44856cd2191c41ab8a"
},
"downloads": -1,
"filename": "dextrades-0.1.12-cp312-cp312-manylinux_2_34_x86_64.whl",
"has_sig": false,
"md5_digest": "0cebd521dcb447db4afed219073dc3dc",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.12",
"size": 6051849,
"upload_time": "2025-10-08T19:10:26",
"upload_time_iso_8601": "2025-10-08T19:10:26.198695Z",
"url": "https://files.pythonhosted.org/packages/23/15/4edc5d389cc60a9ba0e5b4a6fde7ca6cc47c14c6d157577a935bd76b72c8/dextrades-0.1.12-cp312-cp312-manylinux_2_34_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "587d7bc164642f0b96a6b29d1ef52b5e0d69af74cb1aa5a13a84daf914e9bda1",
"md5": "41a4ef17f12e8bc44c13f495807a93dd",
"sha256": "ac8b5712e895fdbe2fe1838647a4ace505b05795b065511557a7712fa42defb4"
},
"downloads": -1,
"filename": "dextrades-0.1.12-cp312-cp312-win_amd64.whl",
"has_sig": false,
"md5_digest": "41a4ef17f12e8bc44c13f495807a93dd",
"packagetype": "bdist_wheel",
"python_version": "cp312",
"requires_python": ">=3.12",
"size": 3190830,
"upload_time": "2025-10-08T19:10:27",
"upload_time_iso_8601": "2025-10-08T19:10:27.778664Z",
"url": "https://files.pythonhosted.org/packages/58/7d/7bc164642f0b96a6b29d1ef52b5e0d69af74cb1aa5a13a84daf914e9bda1/dextrades-0.1.12-cp312-cp312-win_amd64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "3e712fa891a2df23d449693bd0d3b812da78e1407aece54b79a5584699cf5bd1",
"md5": "24dc12d200f371601af38c9bca91b3ec",
"sha256": "af6e69a86f865e96437de7a2e89498ac752d7d8e82103d533110de5517723b15"
},
"downloads": -1,
"filename": "dextrades-0.1.12-cp313-cp313-macosx_10_12_x86_64.whl",
"has_sig": false,
"md5_digest": "24dc12d200f371601af38c9bca91b3ec",
"packagetype": "bdist_wheel",
"python_version": "cp313",
"requires_python": ">=3.12",
"size": 3552341,
"upload_time": "2025-10-08T19:10:29",
"upload_time_iso_8601": "2025-10-08T19:10:29.584272Z",
"url": "https://files.pythonhosted.org/packages/3e/71/2fa891a2df23d449693bd0d3b812da78e1407aece54b79a5584699cf5bd1/dextrades-0.1.12-cp313-cp313-macosx_10_12_x86_64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "7e24c9ad740e44e1d75e7c9031bd86e7e5397b3a18eee8c916261068c6d403b9",
"md5": "004843c558f11f27beb9f01bf358f5f7",
"sha256": "d0097c84952cd5917a3f4caa2accc13e39074c5d3ac8d73debd5ddcb402373dd"
},
"downloads": -1,
"filename": "dextrades-0.1.12-cp313-cp313-macosx_11_0_arm64.whl",
"has_sig": false,
"md5_digest": "004843c558f11f27beb9f01bf358f5f7",
"packagetype": "bdist_wheel",
"python_version": "cp313",
"requires_python": ">=3.12",
"size": 3414411,
"upload_time": "2025-10-08T19:10:31",
"upload_time_iso_8601": "2025-10-08T19:10:31.344876Z",
"url": "https://files.pythonhosted.org/packages/7e/24/c9ad740e44e1d75e7c9031bd86e7e5397b3a18eee8c916261068c6d403b9/dextrades-0.1.12-cp313-cp313-macosx_11_0_arm64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "c7e5d7fa6b360f7df83f1e25e07a50eb35e83062bfaa255c295b2520ddcb9c7e",
"md5": "0e004f572fd13a1a6b0c0c8caf6f6982",
"sha256": "e279cf3a7c26a130298a498d5ef8e5c50fcb4ed136b5b8f7aa27cb504a6da4a8"
},
"downloads": -1,
"filename": "dextrades-0.1.12-cp313-cp313-win_amd64.whl",
"has_sig": false,
"md5_digest": "0e004f572fd13a1a6b0c0c8caf6f6982",
"packagetype": "bdist_wheel",
"python_version": "cp313",
"requires_python": ">=3.12",
"size": 3190222,
"upload_time": "2025-10-08T19:10:32",
"upload_time_iso_8601": "2025-10-08T19:10:32.676947Z",
"url": "https://files.pythonhosted.org/packages/c7/e5/d7fa6b360f7df83f1e25e07a50eb35e83062bfaa255c295b2520ddcb9c7e/dextrades-0.1.12-cp313-cp313-win_amd64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "5c7c0802faad671db7de4796463ab11478e0b764efaee7d48c8863e08d6c426b",
"md5": "772c0bbb57010c227c5bd2e2b1d9bd5c",
"sha256": "e62396e6491734f71859318c1444b4945177396183adbe9c36add8a378c448f1"
},
"downloads": -1,
"filename": "dextrades-0.1.12.tar.gz",
"has_sig": false,
"md5_digest": "772c0bbb57010c227c5bd2e2b1d9bd5c",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.12",
"size": 113717,
"upload_time": "2025-10-08T19:10:34",
"upload_time_iso_8601": "2025-10-08T19:10:34.263125Z",
"url": "https://files.pythonhosted.org/packages/5c/7c/0802faad671db7de4796463ab11478e0b764efaee7d48c8863e08d6c426b/dextrades-0.1.12.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-08 19:10:34",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "elyase",
"github_project": "dextrades#readme",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "dextrades"
}