chain-sniper 0.1.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.
Files changed (68) hide show
  1. chain_sniper-0.1.0/.gitignore +113 -0
  2. chain_sniper-0.1.0/PKG-INFO +141 -0
  3. chain_sniper-0.1.0/README.md +117 -0
  4. chain_sniper-0.1.0/__init__.py +4 -0
  5. chain_sniper-0.1.0/chain_sniper/__init__.py +14 -0
  6. chain_sniper-0.1.0/chain_sniper/abstracts/base_filter.py +32 -0
  7. chain_sniper-0.1.0/chain_sniper/abstracts/base_strategy.py +11 -0
  8. chain_sniper-0.1.0/chain_sniper/chains/base.py +0 -0
  9. chain_sniper-0.1.0/chain_sniper/chains/bsc.py +0 -0
  10. chain_sniper-0.1.0/chain_sniper/chains/ethereum.py +0 -0
  11. chain_sniper-0.1.0/chain_sniper/cli/main.py +0 -0
  12. chain_sniper-0.1.0/chain_sniper/core/config.py +3 -0
  13. chain_sniper-0.1.0/chain_sniper/core/exceptions.py +0 -0
  14. chain_sniper-0.1.0/chain_sniper/core/logger.py +0 -0
  15. chain_sniper-0.1.0/chain_sniper/engine/pipeline.py +41 -0
  16. chain_sniper-0.1.0/chain_sniper/engine/registry.py +0 -0
  17. chain_sniper-0.1.0/chain_sniper/engine/worker.py +7 -0
  18. chain_sniper-0.1.0/chain_sniper/execution/executor.py +0 -0
  19. chain_sniper-0.1.0/chain_sniper/execution/http_client.py +0 -0
  20. chain_sniper-0.1.0/chain_sniper/execution/webhook.py +0 -0
  21. chain_sniper-0.1.0/chain_sniper/filters/__init__.py +14 -0
  22. chain_sniper-0.1.0/chain_sniper/filters/_log_filter.py +364 -0
  23. chain_sniper-0.1.0/chain_sniper/filters/_transaction_filter.py +121 -0
  24. chain_sniper-0.1.0/chain_sniper/listener/__init__.py +9 -0
  25. chain_sniper-0.1.0/chain_sniper/listener/common.py +50 -0
  26. chain_sniper-0.1.0/chain_sniper/listener/redis_rule_listener.py +203 -0
  27. chain_sniper-0.1.0/chain_sniper/listener/reorg.py +91 -0
  28. chain_sniper-0.1.0/chain_sniper/listener/websocket_listener.py +379 -0
  29. chain_sniper-0.1.0/chain_sniper/parser/block_fetcher.py +107 -0
  30. chain_sniper-0.1.0/chain_sniper/parser/block_parser.py +77 -0
  31. chain_sniper-0.1.0/chain_sniper/parser/block_processor.py +85 -0
  32. chain_sniper-0.1.0/chain_sniper/parser/event_dispatcher.py +33 -0
  33. chain_sniper-0.1.0/chain_sniper/parser/log_decoder.py +72 -0
  34. chain_sniper-0.1.0/chain_sniper/parser/rule_parser.py +175 -0
  35. chain_sniper-0.1.0/chain_sniper/parser/topics.py +3 -0
  36. chain_sniper-0.1.0/chain_sniper/rpc_pool/__init__.py +3 -0
  37. chain_sniper-0.1.0/chain_sniper/rpc_pool/rpc_node.py +37 -0
  38. chain_sniper-0.1.0/chain_sniper/rpc_pool/rpc_pool.py +290 -0
  39. chain_sniper-0.1.0/chain_sniper/sniper.py +420 -0
  40. chain_sniper-0.1.0/chain_sniper/storage/redis.py +0 -0
  41. chain_sniper-0.1.0/chain_sniper/storage/state.py +0 -0
  42. chain_sniper-0.1.0/chain_sniper/strategy/user_strategy.py +10 -0
  43. chain_sniper-0.1.0/chain_sniper/types.py +40 -0
  44. chain_sniper-0.1.0/chain_sniper/utils/__init__.py +28 -0
  45. chain_sniper-0.1.0/chain_sniper/utils/abi_filter.py +175 -0
  46. chain_sniper-0.1.0/chain_sniper/utils/abis.py +96 -0
  47. chain_sniper-0.1.0/chain_sniper/utils/config.py +31 -0
  48. chain_sniper-0.1.0/chain_sniper/utils/handlers.py +128 -0
  49. chain_sniper-0.1.0/chain_sniper/utils/logging.py +53 -0
  50. chain_sniper-0.1.0/chain_sniper/utils/runner.py +60 -0
  51. chain_sniper-0.1.0/chain_sniper/workers/block_worker.py +0 -0
  52. chain_sniper-0.1.0/chain_sniper/workers/execution_worker.py +0 -0
  53. chain_sniper-0.1.0/examples/abis/erc20.json +222 -0
  54. chain_sniper-0.1.0/examples/block_explorer.py +356 -0
  55. chain_sniper-0.1.0/examples/push_redis_rule.py +248 -0
  56. chain_sniper-0.1.0/examples/watch_blocks.py +34 -0
  57. chain_sniper-0.1.0/examples/watch_erc20_transfers.py +51 -0
  58. chain_sniper-0.1.0/examples/watch_transactions_with_filter.py +77 -0
  59. chain_sniper-0.1.0/examples/watch_with_rpc_pool.py +72 -0
  60. chain_sniper-0.1.0/examples/with_decorators_only.py +55 -0
  61. chain_sniper-0.1.0/examples/with_pipeline_filter_strategy.py +64 -0
  62. chain_sniper-0.1.0/examples/with_redis_listener_dynamic_filter.py +87 -0
  63. chain_sniper-0.1.0/main.py +66 -0
  64. chain_sniper-0.1.0/pyproject.toml +33 -0
  65. chain_sniper-0.1.0/test_tx.py +32 -0
  66. chain_sniper-0.1.0/tests/decode_log.py +39 -0
  67. chain_sniper-0.1.0/tests/ws_provider.py +55 -0
  68. chain_sniper-0.1.0/uv.lock +1781 -0
@@ -0,0 +1,113 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .nox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ pytest_cache/
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ *.py,cover
49
+ .hypothesis/
50
+
51
+ # Type checkers
52
+ .mypy_cache/
53
+ .pyre/
54
+ .pytype/
55
+
56
+ # Virtual environments
57
+ .env
58
+ .venv
59
+ env/
60
+ venv/
61
+ ENV/
62
+ env.bak/
63
+ venv.bak/
64
+
65
+ # Jupyter Notebook
66
+ .ipynb_checkpoints
67
+
68
+ # pyenv
69
+ .python-version
70
+
71
+ # dotenv
72
+ .env.*
73
+ !.env.example
74
+
75
+ # IDEs and editors
76
+ .vscode/
77
+ .idea/
78
+ *.sublime-project
79
+ *.sublime-workspace
80
+
81
+ # OS files
82
+ .DS_Store
83
+ Thumbs.db
84
+ desktop.ini
85
+
86
+ # Logs
87
+ *.log
88
+
89
+ # SQLite database files
90
+ *.sqlite3
91
+ *.db
92
+
93
+ # FastAPI / Flask instance folder
94
+ instance/
95
+
96
+ # MkDocs documentation
97
+ /site
98
+
99
+ # Ruff
100
+ .ruff_cache/
101
+
102
+ # Poetry
103
+ poetry.lock
104
+
105
+ # PDM
106
+ .pdm.toml
107
+ .pdm-python
108
+ .pdm-build/
109
+
110
+ # pipenv
111
+ Pipfile.lock
112
+ dump.rdb
113
+ .todo
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: chain-sniper
3
+ Version: 0.1.0
4
+ Summary: A high-performance EVM blockchain listener and transaction monitor
5
+ Project-URL: Homepage, https://github.com/rakibhossain72/chain-sniper
6
+ Project-URL: Repository, https://github.com/rakibhossain72/chain-sniper
7
+ License: MIT
8
+ Keywords: blockchain,ethereum,evm,listener,monitor,web3
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: aiofiles>=25.1.0
19
+ Requires-Dist: python-dotenv>=1.0.0
20
+ Requires-Dist: redis>=7.2.1
21
+ Requires-Dist: web3>=7.14.1
22
+ Requires-Dist: websockets>=15.0.1
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Chain Sniper
26
+
27
+ A high-performance EVM blockchain listener and transaction monitor.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ git clone https://github.com/rakibhossain72/chain-sniper.git
33
+ cd chain-sniper
34
+ uv sync
35
+ ```
36
+
37
+ Or with pip:
38
+
39
+ ```bash
40
+ pip install chain-sniper
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ ```python
46
+ from chain_sniper import ChainSniper
47
+
48
+ ERC20_ABI = [...] # Transfer event ABI
49
+ USDT = "0x55d398326f99059fF775485246999027B3197955"
50
+
51
+ sniper = ChainSniper("wss://bsc-ws-node.nariox.org:443")
52
+
53
+ @sniper.event(contract=USDT, abi=ERC20_ABI, name="Transfer")
54
+ async def handle_transfer(event):
55
+ print(f"Transfer: {event['args']['value'] / 10**18} USDT")
56
+
57
+ await sniper.start()
58
+ ```
59
+
60
+ Use a WebSocket URL (`wss://` or `ws://`) — that's the only supported transport.
61
+
62
+ ## RPC Pool (fault-tolerant)
63
+
64
+ ```python
65
+ from chain_sniper.rpc_pool import RPCPool
66
+
67
+ pool = await RPCPool.create(
68
+ rpcs=[
69
+ "https://bsc-dataseed1.binance.org",
70
+ "wss://bsc-ws-node.nariox.org:443",
71
+ ],
72
+ expected_chain_id=56,
73
+ )
74
+ sniper = ChainSniper(pool)
75
+ ```
76
+
77
+ ## Transaction Monitoring
78
+
79
+ ```python
80
+ sniper.block_detail("full_block")
81
+
82
+ @sniper.on_transaction
83
+ async def handle_tx(tx):
84
+ print(f"{tx['hash']} | {tx['from']} -> {tx['to']}")
85
+ ```
86
+
87
+ ## Filtering
88
+
89
+ MongoDB-style rules with operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$regex`, `$exists`.
90
+
91
+ ```python
92
+ from chain_sniper import Filter
93
+
94
+ f = Filter()
95
+ f.add_tx_rule({"value": {"_op": "$gte", "_value": 10**18}})
96
+ f.add_log_rule({"args.value": {"_op": "$gte", "_value": 1000 * 10**18}})
97
+
98
+ sniper.filter(f)
99
+ ```
100
+
101
+ ## Dynamic Rules via Redis
102
+
103
+ Inject or remove filter rules at runtime without restarting:
104
+
105
+ ```python
106
+ from chain_sniper.listener import RedisRuleListener
107
+
108
+ redis_listener = RedisRuleListener(
109
+ dynamic_filter=f,
110
+ redis_url="redis://localhost",
111
+ channel="sniper_rules",
112
+ )
113
+ await redis_listener.start()
114
+ ```
115
+
116
+ Push rules from anywhere:
117
+
118
+ ```python
119
+ import redis, json
120
+
121
+ r = redis.Redis(host="localhost", port=6379, decode_responses=True)
122
+ r.publish("sniper_rules", json.dumps({"action": "add_tx", "rule": {"to": "0x..."}}))
123
+ ```
124
+
125
+ ## Key API
126
+
127
+ | Method | Description |
128
+ |--------|-------------|
129
+ | `@sniper.event(contract, abi, name)` | Register an event handler |
130
+ | `@sniper.on_transaction` | Handle individual transactions |
131
+ | `@sniper.on_block` | Handle new blocks |
132
+ | `@sniper.on_reorg` | Handle chain reorgs |
133
+ | `sniper.filter(f)` | Attach a Filter |
134
+ | `sniper.block_detail("header" \| "full_block")` | Set block detail level |
135
+ | `sniper.start() / .stop()` | Start or stop monitoring |
136
+
137
+ See `examples/` for complete usage patterns.
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,117 @@
1
+ # Chain Sniper
2
+
3
+ A high-performance EVM blockchain listener and transaction monitor.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ git clone https://github.com/rakibhossain72/chain-sniper.git
9
+ cd chain-sniper
10
+ uv sync
11
+ ```
12
+
13
+ Or with pip:
14
+
15
+ ```bash
16
+ pip install chain-sniper
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```python
22
+ from chain_sniper import ChainSniper
23
+
24
+ ERC20_ABI = [...] # Transfer event ABI
25
+ USDT = "0x55d398326f99059fF775485246999027B3197955"
26
+
27
+ sniper = ChainSniper("wss://bsc-ws-node.nariox.org:443")
28
+
29
+ @sniper.event(contract=USDT, abi=ERC20_ABI, name="Transfer")
30
+ async def handle_transfer(event):
31
+ print(f"Transfer: {event['args']['value'] / 10**18} USDT")
32
+
33
+ await sniper.start()
34
+ ```
35
+
36
+ Use a WebSocket URL (`wss://` or `ws://`) — that's the only supported transport.
37
+
38
+ ## RPC Pool (fault-tolerant)
39
+
40
+ ```python
41
+ from chain_sniper.rpc_pool import RPCPool
42
+
43
+ pool = await RPCPool.create(
44
+ rpcs=[
45
+ "https://bsc-dataseed1.binance.org",
46
+ "wss://bsc-ws-node.nariox.org:443",
47
+ ],
48
+ expected_chain_id=56,
49
+ )
50
+ sniper = ChainSniper(pool)
51
+ ```
52
+
53
+ ## Transaction Monitoring
54
+
55
+ ```python
56
+ sniper.block_detail("full_block")
57
+
58
+ @sniper.on_transaction
59
+ async def handle_tx(tx):
60
+ print(f"{tx['hash']} | {tx['from']} -> {tx['to']}")
61
+ ```
62
+
63
+ ## Filtering
64
+
65
+ MongoDB-style rules with operators: `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$regex`, `$exists`.
66
+
67
+ ```python
68
+ from chain_sniper import Filter
69
+
70
+ f = Filter()
71
+ f.add_tx_rule({"value": {"_op": "$gte", "_value": 10**18}})
72
+ f.add_log_rule({"args.value": {"_op": "$gte", "_value": 1000 * 10**18}})
73
+
74
+ sniper.filter(f)
75
+ ```
76
+
77
+ ## Dynamic Rules via Redis
78
+
79
+ Inject or remove filter rules at runtime without restarting:
80
+
81
+ ```python
82
+ from chain_sniper.listener import RedisRuleListener
83
+
84
+ redis_listener = RedisRuleListener(
85
+ dynamic_filter=f,
86
+ redis_url="redis://localhost",
87
+ channel="sniper_rules",
88
+ )
89
+ await redis_listener.start()
90
+ ```
91
+
92
+ Push rules from anywhere:
93
+
94
+ ```python
95
+ import redis, json
96
+
97
+ r = redis.Redis(host="localhost", port=6379, decode_responses=True)
98
+ r.publish("sniper_rules", json.dumps({"action": "add_tx", "rule": {"to": "0x..."}}))
99
+ ```
100
+
101
+ ## Key API
102
+
103
+ | Method | Description |
104
+ |--------|-------------|
105
+ | `@sniper.event(contract, abi, name)` | Register an event handler |
106
+ | `@sniper.on_transaction` | Handle individual transactions |
107
+ | `@sniper.on_block` | Handle new blocks |
108
+ | `@sniper.on_reorg` | Handle chain reorgs |
109
+ | `sniper.filter(f)` | Attach a Filter |
110
+ | `sniper.block_detail("header" \| "full_block")` | Set block detail level |
111
+ | `sniper.start() / .stop()` | Start or stop monitoring |
112
+
113
+ See `examples/` for complete usage patterns.
114
+
115
+ ## License
116
+
117
+ MIT
@@ -0,0 +1,4 @@
1
+ from .rpc_node import RpcNode
2
+ from .rpc_pool import RPCPool, HttpRPCPool, WssRPCPool
3
+
4
+ __all__ = ["RPCPool", "HttpRPCPool", "WssRPCPool", "RpcNode"]
@@ -0,0 +1,14 @@
1
+ """
2
+ Chain Sniper - Simple blockchain event monitoring.
3
+ """
4
+
5
+ from .sniper import ChainSniper
6
+ from .listener.common import BlockDetail
7
+ from .filters import TransactionFilter, LogFilter
8
+
9
+ __all__ = [
10
+ "ChainSniper",
11
+ "BlockDetail",
12
+ "TransactionFilter",
13
+ "LogFilter",
14
+ ]
@@ -0,0 +1,32 @@
1
+ """
2
+ Abstract base classes for filters.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any
7
+
8
+
9
+ class BaseTransactionFilter(ABC):
10
+ """Abstract base for transaction / block filters."""
11
+
12
+ @abstractmethod
13
+ def match(self, tx: Any) -> bool: ...
14
+
15
+ @abstractmethod
16
+ def add_rule(self, rule: dict) -> str: ...
17
+
18
+ @abstractmethod
19
+ def remove_rule(self, rule_id: str) -> bool: ...
20
+
21
+
22
+ class BaseLogFilter(ABC):
23
+ """Abstract base for log subscription + post-filter managers."""
24
+
25
+ @abstractmethod
26
+ def subscribe(self, **kwargs) -> str: ...
27
+
28
+ @abstractmethod
29
+ def unsubscribe(self, sub_id: str) -> bool: ...
30
+
31
+ @abstractmethod
32
+ def match(self, log: Any) -> bool: ...
@@ -0,0 +1,11 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ class BaseStrategy(ABC):
5
+
6
+ @abstractmethod
7
+ async def execute(self, data: Any) -> None:
8
+ ...
9
+
10
+ async def execute_log(self, log: Any) -> None:
11
+ ...
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,3 @@
1
+ RPC_URL = "wss://bsc.drpc.org"
2
+
3
+ POLL_INTERVAL = 2
File without changes
File without changes
@@ -0,0 +1,41 @@
1
+ """
2
+ Processing pipeline that pairs a TransactionFilter + LogFilter with a Strategy.
3
+ """
4
+
5
+ from typing import Optional
6
+ from chain_sniper.parser.block_parser import parse_block
7
+ from chain_sniper.parser.log_decoder import parse_log
8
+ from chain_sniper.filters import TransactionFilter, LogFilter
9
+ from chain_sniper.abstracts.base_strategy import BaseStrategy
10
+
11
+
12
+ class Pipeline:
13
+
14
+ def __init__(
15
+ self,
16
+ *,
17
+ tx_filter: Optional[TransactionFilter] = None,
18
+ log_filter: Optional[LogFilter] = None,
19
+ strategy: Optional[BaseStrategy] = None,
20
+ ) -> None:
21
+ self.tx_filter = tx_filter
22
+ self.log_filter = log_filter
23
+ self.strategy = strategy
24
+
25
+ async def process_block(self, block) -> None:
26
+ txs = parse_block(block)
27
+
28
+ for tx in txs:
29
+ if self.tx_filter and self.tx_filter.match(tx):
30
+ await self.strategy.execute(tx)
31
+
32
+ async def process_log(self, log) -> None:
33
+ log = parse_log(log)
34
+ log_args = log.get("args", {})
35
+ print(f"Decoded log args: {log_args}")
36
+
37
+ # LogFilter post-filter rules (optional — if none, all pass)
38
+ if self.log_filter and not self.log_filter.match(log_args):
39
+ return
40
+
41
+ await self.strategy.execute_log(log_args)
File without changes
@@ -0,0 +1,7 @@
1
+ class Worker:
2
+ def __init__(self):
3
+ pass
4
+
5
+ async def run(self):
6
+ # Implementation for parallel or background worker
7
+ pass
File without changes
File without changes
@@ -0,0 +1,14 @@
1
+ """
2
+ Filter implementations for Chain Sniper.
3
+
4
+ - ``TransactionFilter`` — local block / transaction rule matching.
5
+ - ``LogFilter`` — node-level log subscriptions + optional post-filter.
6
+ """
7
+
8
+ from ._transaction_filter import TransactionFilter
9
+ from ._log_filter import LogFilter
10
+
11
+ __all__ = [
12
+ "TransactionFilter",
13
+ "LogFilter",
14
+ ]