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.
- chain_sniper-0.1.0/.gitignore +113 -0
- chain_sniper-0.1.0/PKG-INFO +141 -0
- chain_sniper-0.1.0/README.md +117 -0
- chain_sniper-0.1.0/__init__.py +4 -0
- chain_sniper-0.1.0/chain_sniper/__init__.py +14 -0
- chain_sniper-0.1.0/chain_sniper/abstracts/base_filter.py +32 -0
- chain_sniper-0.1.0/chain_sniper/abstracts/base_strategy.py +11 -0
- chain_sniper-0.1.0/chain_sniper/chains/base.py +0 -0
- chain_sniper-0.1.0/chain_sniper/chains/bsc.py +0 -0
- chain_sniper-0.1.0/chain_sniper/chains/ethereum.py +0 -0
- chain_sniper-0.1.0/chain_sniper/cli/main.py +0 -0
- chain_sniper-0.1.0/chain_sniper/core/config.py +3 -0
- chain_sniper-0.1.0/chain_sniper/core/exceptions.py +0 -0
- chain_sniper-0.1.0/chain_sniper/core/logger.py +0 -0
- chain_sniper-0.1.0/chain_sniper/engine/pipeline.py +41 -0
- chain_sniper-0.1.0/chain_sniper/engine/registry.py +0 -0
- chain_sniper-0.1.0/chain_sniper/engine/worker.py +7 -0
- chain_sniper-0.1.0/chain_sniper/execution/executor.py +0 -0
- chain_sniper-0.1.0/chain_sniper/execution/http_client.py +0 -0
- chain_sniper-0.1.0/chain_sniper/execution/webhook.py +0 -0
- chain_sniper-0.1.0/chain_sniper/filters/__init__.py +14 -0
- chain_sniper-0.1.0/chain_sniper/filters/_log_filter.py +364 -0
- chain_sniper-0.1.0/chain_sniper/filters/_transaction_filter.py +121 -0
- chain_sniper-0.1.0/chain_sniper/listener/__init__.py +9 -0
- chain_sniper-0.1.0/chain_sniper/listener/common.py +50 -0
- chain_sniper-0.1.0/chain_sniper/listener/redis_rule_listener.py +203 -0
- chain_sniper-0.1.0/chain_sniper/listener/reorg.py +91 -0
- chain_sniper-0.1.0/chain_sniper/listener/websocket_listener.py +379 -0
- chain_sniper-0.1.0/chain_sniper/parser/block_fetcher.py +107 -0
- chain_sniper-0.1.0/chain_sniper/parser/block_parser.py +77 -0
- chain_sniper-0.1.0/chain_sniper/parser/block_processor.py +85 -0
- chain_sniper-0.1.0/chain_sniper/parser/event_dispatcher.py +33 -0
- chain_sniper-0.1.0/chain_sniper/parser/log_decoder.py +72 -0
- chain_sniper-0.1.0/chain_sniper/parser/rule_parser.py +175 -0
- chain_sniper-0.1.0/chain_sniper/parser/topics.py +3 -0
- chain_sniper-0.1.0/chain_sniper/rpc_pool/__init__.py +3 -0
- chain_sniper-0.1.0/chain_sniper/rpc_pool/rpc_node.py +37 -0
- chain_sniper-0.1.0/chain_sniper/rpc_pool/rpc_pool.py +290 -0
- chain_sniper-0.1.0/chain_sniper/sniper.py +420 -0
- chain_sniper-0.1.0/chain_sniper/storage/redis.py +0 -0
- chain_sniper-0.1.0/chain_sniper/storage/state.py +0 -0
- chain_sniper-0.1.0/chain_sniper/strategy/user_strategy.py +10 -0
- chain_sniper-0.1.0/chain_sniper/types.py +40 -0
- chain_sniper-0.1.0/chain_sniper/utils/__init__.py +28 -0
- chain_sniper-0.1.0/chain_sniper/utils/abi_filter.py +175 -0
- chain_sniper-0.1.0/chain_sniper/utils/abis.py +96 -0
- chain_sniper-0.1.0/chain_sniper/utils/config.py +31 -0
- chain_sniper-0.1.0/chain_sniper/utils/handlers.py +128 -0
- chain_sniper-0.1.0/chain_sniper/utils/logging.py +53 -0
- chain_sniper-0.1.0/chain_sniper/utils/runner.py +60 -0
- chain_sniper-0.1.0/chain_sniper/workers/block_worker.py +0 -0
- chain_sniper-0.1.0/chain_sniper/workers/execution_worker.py +0 -0
- chain_sniper-0.1.0/examples/abis/erc20.json +222 -0
- chain_sniper-0.1.0/examples/block_explorer.py +356 -0
- chain_sniper-0.1.0/examples/push_redis_rule.py +248 -0
- chain_sniper-0.1.0/examples/watch_blocks.py +34 -0
- chain_sniper-0.1.0/examples/watch_erc20_transfers.py +51 -0
- chain_sniper-0.1.0/examples/watch_transactions_with_filter.py +77 -0
- chain_sniper-0.1.0/examples/watch_with_rpc_pool.py +72 -0
- chain_sniper-0.1.0/examples/with_decorators_only.py +55 -0
- chain_sniper-0.1.0/examples/with_pipeline_filter_strategy.py +64 -0
- chain_sniper-0.1.0/examples/with_redis_listener_dynamic_filter.py +87 -0
- chain_sniper-0.1.0/main.py +66 -0
- chain_sniper-0.1.0/pyproject.toml +33 -0
- chain_sniper-0.1.0/test_tx.py +32 -0
- chain_sniper-0.1.0/tests/decode_log.py +39 -0
- chain_sniper-0.1.0/tests/ws_provider.py +55 -0
- 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,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: ...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|
|
File without changes
|
|
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
|
+
]
|