@intentsolutionsio/mempool-analyzer 1.0.0
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.
- package/.claude-plugin/plugin.json +22 -0
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/agents/mempool-agent.md +158 -0
- package/package.json +43 -0
- package/skills/analyzing-mempool/ARD.md +146 -0
- package/skills/analyzing-mempool/PRD.md +71 -0
- package/skills/analyzing-mempool/SKILL.md +110 -0
- package/skills/analyzing-mempool/config/settings.yaml +43 -0
- package/skills/analyzing-mempool/references/errors.md +122 -0
- package/skills/analyzing-mempool/references/examples.md +189 -0
- package/skills/analyzing-mempool/references/implementation.md +67 -0
- package/skills/analyzing-mempool/scripts/formatters.py +244 -0
- package/skills/analyzing-mempool/scripts/gas_analyzer.py +299 -0
- package/skills/analyzing-mempool/scripts/mempool_analyzer.py +320 -0
- package/skills/analyzing-mempool/scripts/mev_detector.py +387 -0
- package/skills/analyzing-mempool/scripts/rpc_client.py +311 -0
- package/skills/analyzing-mempool/scripts/tx_decoder.py +273 -0
- package/skills/skill-adapter/assets/README.md +6 -0
- package/skills/skill-adapter/assets/config-template.json +32 -0
- package/skills/skill-adapter/assets/skill-schema.json +28 -0
- package/skills/skill-adapter/assets/test-data.json +27 -0
- package/skills/skill-adapter/references/README.md +4 -0
- package/skills/skill-adapter/references/best-practices.md +69 -0
- package/skills/skill-adapter/references/examples.md +73 -0
- package/skills/skill-adapter/scripts/README.md +8 -0
- package/skills/skill-adapter/scripts/helper-template.sh +42 -0
- package/skills/skill-adapter/scripts/validation.sh +32 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Mempool Analyzer Configuration
|
|
2
|
+
# Copy to ~/.mempool_analyzer.yaml and customize
|
|
3
|
+
|
|
4
|
+
# RPC Configuration
|
|
5
|
+
rpc:
|
|
6
|
+
ethereum: "https://eth.llamarpc.com"
|
|
7
|
+
polygon: "https://polygon-rpc.com"
|
|
8
|
+
arbitrum: "https://arb1.arbitrum.io/rpc"
|
|
9
|
+
optimism: "https://mainnet.optimism.io"
|
|
10
|
+
base: "https://mainnet.base.org"
|
|
11
|
+
|
|
12
|
+
# Default Settings
|
|
13
|
+
defaults:
|
|
14
|
+
chain: "ethereum"
|
|
15
|
+
limit: 100
|
|
16
|
+
eth_price: 3000.0
|
|
17
|
+
|
|
18
|
+
# Gas Analysis
|
|
19
|
+
gas:
|
|
20
|
+
cache_ttl: 10 # seconds
|
|
21
|
+
percentiles: [10, 25, 50, 75, 90]
|
|
22
|
+
|
|
23
|
+
# MEV Detection
|
|
24
|
+
mev:
|
|
25
|
+
min_swap_value_usd: 10000 # $10k minimum
|
|
26
|
+
min_profit_usd: 100 # $100 minimum profit
|
|
27
|
+
detection_types:
|
|
28
|
+
- sandwich
|
|
29
|
+
- arbitrage
|
|
30
|
+
- liquidation
|
|
31
|
+
|
|
32
|
+
# Known DEX Routers
|
|
33
|
+
routers:
|
|
34
|
+
uniswap_v2: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
|
|
35
|
+
uniswap_v3: "0xE592427A0AEce92De3Edee1F18E0157C05861564"
|
|
36
|
+
sushiswap: "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F"
|
|
37
|
+
oneinch: "0x1111111254fb6c44bac0bed2854e76f90643097d"
|
|
38
|
+
|
|
39
|
+
# Output Settings
|
|
40
|
+
output:
|
|
41
|
+
format: "table"
|
|
42
|
+
max_display: 50
|
|
43
|
+
show_raw_data: false
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Error Handling Guide
|
|
2
|
+
|
|
3
|
+
## RPC Connection Errors
|
|
4
|
+
|
|
5
|
+
### Connection Refused
|
|
6
|
+
```
|
|
7
|
+
Error: Connection refused to RPC endpoint
|
|
8
|
+
```
|
|
9
|
+
**Cause**: RPC endpoint is down or unreachable
|
|
10
|
+
**Solution**:
|
|
11
|
+
- Check if RPC URL is correct
|
|
12
|
+
- Try alternative endpoint (Alchemy, Chainstack, Infura, or public RPC)
|
|
13
|
+
- Verify network connectivity
|
|
14
|
+
|
|
15
|
+
### Timeout
|
|
16
|
+
```
|
|
17
|
+
Error: Request timed out
|
|
18
|
+
```
|
|
19
|
+
**Cause**: RPC node is slow or overloaded
|
|
20
|
+
**Solution**:
|
|
21
|
+
- Increase timeout setting
|
|
22
|
+
- Switch to faster RPC provider
|
|
23
|
+
- Reduce request frequency
|
|
24
|
+
|
|
25
|
+
### Rate Limited
|
|
26
|
+
```
|
|
27
|
+
Error: Too many requests (429)
|
|
28
|
+
```
|
|
29
|
+
**Cause**: Exceeded RPC provider rate limits
|
|
30
|
+
**Solution**:
|
|
31
|
+
- Reduce polling frequency
|
|
32
|
+
- Upgrade RPC tier (paid Alchemy, Chainstack, or Infura plans)
|
|
33
|
+
- Use multiple RPC endpoints
|
|
34
|
+
|
|
35
|
+
## Mempool Access Errors
|
|
36
|
+
|
|
37
|
+
### txpool_content Not Available
|
|
38
|
+
```
|
|
39
|
+
Error: txpool_content not supported
|
|
40
|
+
```
|
|
41
|
+
**Cause**: Not all nodes support txpool methods
|
|
42
|
+
**Solution**:
|
|
43
|
+
- Use Geth node with txpool enabled
|
|
44
|
+
- Try eth_pendingTransactions instead
|
|
45
|
+
- Use mock data for demo purposes
|
|
46
|
+
|
|
47
|
+
### Empty Mempool
|
|
48
|
+
```
|
|
49
|
+
No pending transactions found
|
|
50
|
+
```
|
|
51
|
+
**Cause**: Mempool is empty or access limited
|
|
52
|
+
**Solution**:
|
|
53
|
+
- Normal during low activity periods
|
|
54
|
+
- Verify RPC supports mempool access
|
|
55
|
+
- Check if node is fully synced
|
|
56
|
+
|
|
57
|
+
## Transaction Decoding Errors
|
|
58
|
+
|
|
59
|
+
### Unknown Method Signature
|
|
60
|
+
```
|
|
61
|
+
Warning: Unknown method 0x12345678
|
|
62
|
+
```
|
|
63
|
+
**Cause**: Method signature not in known ABI list
|
|
64
|
+
**Solution**:
|
|
65
|
+
- Transaction will show as "Unknown" type
|
|
66
|
+
- Add ABI to decoder if needed
|
|
67
|
+
- Use Etherscan to look up contract ABI
|
|
68
|
+
|
|
69
|
+
### Invalid Input Data
|
|
70
|
+
```
|
|
71
|
+
Error: Cannot decode input data
|
|
72
|
+
```
|
|
73
|
+
**Cause**: Malformed or non-standard input
|
|
74
|
+
**Solution**:
|
|
75
|
+
- Skip transaction, continue analysis
|
|
76
|
+
- Raw data still available
|
|
77
|
+
|
|
78
|
+
## Gas Analysis Errors
|
|
79
|
+
|
|
80
|
+
### No Sample Data
|
|
81
|
+
```
|
|
82
|
+
Warning: Insufficient data for gas analysis
|
|
83
|
+
```
|
|
84
|
+
**Cause**: Too few pending transactions
|
|
85
|
+
**Solution**:
|
|
86
|
+
- Use default recommendations
|
|
87
|
+
- Wait for more mempool activity
|
|
88
|
+
- Reduce sample requirements
|
|
89
|
+
|
|
90
|
+
## MEV Detection Errors
|
|
91
|
+
|
|
92
|
+
### Pool Data Unavailable
|
|
93
|
+
```
|
|
94
|
+
Warning: Cannot fetch pool reserves
|
|
95
|
+
```
|
|
96
|
+
**Cause**: DEX subgraph or pool query failed
|
|
97
|
+
**Solution**:
|
|
98
|
+
- MEV detection will have lower confidence
|
|
99
|
+
- Use estimated values
|
|
100
|
+
- Check subgraph health
|
|
101
|
+
|
|
102
|
+
## Debugging
|
|
103
|
+
|
|
104
|
+
### Enable Verbose Mode
|
|
105
|
+
```bash
|
|
106
|
+
python mempool_analyzer.py -v pending
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Check Connection
|
|
110
|
+
```bash
|
|
111
|
+
python mempool_analyzer.py status
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Test RPC Endpoint
|
|
115
|
+
```bash
|
|
116
|
+
curl -X POST -H "Content-Type: application/json" \
|
|
117
|
+
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
|
|
118
|
+
YOUR_RPC_URL
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Usage Examples
|
|
2
|
+
|
|
3
|
+
## View Pending Transactions
|
|
4
|
+
|
|
5
|
+
### Basic View
|
|
6
|
+
```bash
|
|
7
|
+
python mempool_analyzer.py pending
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### Limit Results
|
|
11
|
+
```bash
|
|
12
|
+
python mempool_analyzer.py pending --limit 20
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### JSON Output
|
|
16
|
+
```bash
|
|
17
|
+
python mempool_analyzer.py pending --format json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Gas Price Analysis
|
|
21
|
+
|
|
22
|
+
### Current Gas Prices
|
|
23
|
+
```bash
|
|
24
|
+
python mempool_analyzer.py gas
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Gas Recommendations
|
|
28
|
+
```bash
|
|
29
|
+
python mempool_analyzer.py gas
|
|
30
|
+
# Output shows:
|
|
31
|
+
# - Current base fee
|
|
32
|
+
# - Gas price distribution (10th, 25th, 50th, 75th, 90th percentile)
|
|
33
|
+
# - Recommendations for slow, standard, fast, instant
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### JSON for Programmatic Use
|
|
37
|
+
```bash
|
|
38
|
+
python mempool_analyzer.py gas --format json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Pending DEX Swaps
|
|
42
|
+
|
|
43
|
+
### View All Pending Swaps
|
|
44
|
+
```bash
|
|
45
|
+
python mempool_analyzer.py swaps
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Analyze More Transactions
|
|
49
|
+
```bash
|
|
50
|
+
python mempool_analyzer.py swaps --limit 200
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## MEV Opportunity Scanning
|
|
54
|
+
|
|
55
|
+
### Scan for Opportunities
|
|
56
|
+
```bash
|
|
57
|
+
python mempool_analyzer.py mev
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Detailed Analysis
|
|
61
|
+
```bash
|
|
62
|
+
python mempool_analyzer.py mev --limit 300 -v
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### JSON Output
|
|
66
|
+
```bash
|
|
67
|
+
python mempool_analyzer.py mev --format json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Mempool Summary
|
|
71
|
+
|
|
72
|
+
### Quick Overview
|
|
73
|
+
```bash
|
|
74
|
+
python mempool_analyzer.py summary
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Watch Specific Contract
|
|
78
|
+
|
|
79
|
+
### Monitor Uniswap Router
|
|
80
|
+
```bash
|
|
81
|
+
python mempool_analyzer.py watch 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Monitor Any Contract
|
|
85
|
+
```bash
|
|
86
|
+
python mempool_analyzer.py watch 0xYOUR_CONTRACT_ADDRESS --limit 200
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Connection Status
|
|
90
|
+
|
|
91
|
+
### Check RPC Connection
|
|
92
|
+
```bash
|
|
93
|
+
python mempool_analyzer.py status
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Different Chains
|
|
97
|
+
|
|
98
|
+
### Polygon
|
|
99
|
+
```bash
|
|
100
|
+
python mempool_analyzer.py --chain polygon pending
|
|
101
|
+
python mempool_analyzer.py --chain polygon gas
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Arbitrum
|
|
105
|
+
```bash
|
|
106
|
+
python mempool_analyzer.py --chain arbitrum summary
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Custom RPC URL
|
|
110
|
+
```bash
|
|
111
|
+
python mempool_analyzer.py --rpc-url https://your-rpc.example.com pending
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Common Workflows
|
|
115
|
+
|
|
116
|
+
### Pre-Transaction Gas Check
|
|
117
|
+
```bash
|
|
118
|
+
# Before sending a transaction, check optimal gas
|
|
119
|
+
python mempool_analyzer.py gas
|
|
120
|
+
|
|
121
|
+
# Use "Fast" recommendation for quick confirmation
|
|
122
|
+
# Use "Standard" for normal priority
|
|
123
|
+
# Use "Slow" if not time-sensitive
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Monitor for Front-Running Risk
|
|
127
|
+
```bash
|
|
128
|
+
# Check if there are pending swaps that might affect your trade
|
|
129
|
+
python mempool_analyzer.py swaps
|
|
130
|
+
|
|
131
|
+
# High-value pending swaps = potential slippage
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### MEV Opportunity Research
|
|
135
|
+
```bash
|
|
136
|
+
# Educational: See what MEV opportunities exist
|
|
137
|
+
python mempool_analyzer.py mev -v
|
|
138
|
+
|
|
139
|
+
# Note: This is for research/education only
|
|
140
|
+
# Real MEV extraction requires specialized infrastructure
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Programmatic Usage
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from rpc_client import RPCClient
|
|
147
|
+
from gas_analyzer import GasAnalyzer
|
|
148
|
+
from mev_detector import MEVDetector
|
|
149
|
+
|
|
150
|
+
# Initialize
|
|
151
|
+
client = RPCClient()
|
|
152
|
+
gas_analyzer = GasAnalyzer()
|
|
153
|
+
mev_detector = MEVDetector()
|
|
154
|
+
|
|
155
|
+
# Get pending transactions
|
|
156
|
+
pending = client.get_pending_transactions(limit=100)
|
|
157
|
+
|
|
158
|
+
# Analyze gas
|
|
159
|
+
gas_info = client.get_gas_price()
|
|
160
|
+
distribution = gas_analyzer.analyze_pending_gas(pending)
|
|
161
|
+
|
|
162
|
+
print(f"Median gas: {distribution.median_gwei:.1f} gwei")
|
|
163
|
+
|
|
164
|
+
# Detect swaps
|
|
165
|
+
swaps = mev_detector.detect_pending_swaps(pending)
|
|
166
|
+
print(f"Pending swaps: {len(swaps)}")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Integration with jq
|
|
170
|
+
|
|
171
|
+
### Filter High Gas Transactions
|
|
172
|
+
```bash
|
|
173
|
+
python mempool_analyzer.py pending --format json | \
|
|
174
|
+
jq '[.[] | select(.gas_price > 50000000000)]'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Count by Transaction Type
|
|
178
|
+
```bash
|
|
179
|
+
python mempool_analyzer.py swaps --format json | \
|
|
180
|
+
jq 'group_by(.dex) | map({dex: .[0].dex, count: length})'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Export Gas Data
|
|
184
|
+
```bash
|
|
185
|
+
python mempool_analyzer.py gas --format json > gas_snapshot.json
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Implementation Details
|
|
2
|
+
|
|
3
|
+
## Gas Price Distribution Analysis
|
|
4
|
+
|
|
5
|
+
The gas analysis command calculates percentile-based recommendations from pending transactions:
|
|
6
|
+
|
|
7
|
+
**Gas Recommendations:**
|
|
8
|
+
- Slow (10th percentile): May take 10+ blocks
|
|
9
|
+
- Standard (50th percentile): 2-5 blocks
|
|
10
|
+
- Fast (75th percentile): 1-2 blocks
|
|
11
|
+
- Instant (90th percentile): Next block likely
|
|
12
|
+
|
|
13
|
+
## MEV Detection Details
|
|
14
|
+
|
|
15
|
+
MEV detection scans for common patterns:
|
|
16
|
+
- **Sandwich attacks**: Detects front-run + back-run pairs targeting the same swap
|
|
17
|
+
- **Arbitrage**: Identifies circular token paths (A→B→A) across DEXs
|
|
18
|
+
- **Liquidation**: Finds health-factor monitoring and repay+seize patterns
|
|
19
|
+
|
|
20
|
+
**Important:** MEV detection is for educational purposes only. Real MEV extraction requires:
|
|
21
|
+
- Specialized block-builder infrastructure (Flashbots, MEV-Boost)
|
|
22
|
+
- Sub-millisecond execution timing
|
|
23
|
+
- Significant capital for gas auctions
|
|
24
|
+
|
|
25
|
+
## DEX Swap Detection
|
|
26
|
+
|
|
27
|
+
The `swaps` command identifies pending DEX transactions by matching method selectors:
|
|
28
|
+
- Uniswap V2/V3: `swapExactTokensForTokens`, `exactInputSingle`
|
|
29
|
+
- SushiSwap: Same interface as Uniswap V2
|
|
30
|
+
- 1inch: `swap`, `unoswap` aggregation methods
|
|
31
|
+
|
|
32
|
+
## Multi-Chain Support
|
|
33
|
+
|
|
34
|
+
Supported chains and their mempool characteristics:
|
|
35
|
+
| Chain | Mempool Access | Block Time | Notes |
|
|
36
|
+
|-------|---------------|------------|-------|
|
|
37
|
+
| Ethereum | Full | ~12s | Richest MEV environment |
|
|
38
|
+
| Polygon | Full | ~2s | Lower gas, faster blocks |
|
|
39
|
+
| Arbitrum | Limited | ~0.25s | Sequencer-based, less MEV |
|
|
40
|
+
| Optimism | Limited | ~2s | Sequencer-based |
|
|
41
|
+
| Base | Limited | ~2s | Coinbase sequencer |
|
|
42
|
+
|
|
43
|
+
## Contract Watching
|
|
44
|
+
|
|
45
|
+
The `watch` command monitors pending transactions to a specific contract address:
|
|
46
|
+
1. Filters mempool by `to` address
|
|
47
|
+
2. Decodes known method selectors
|
|
48
|
+
3. Estimates gas cost in USD
|
|
49
|
+
4. Identifies transaction type (swap, approve, transfer, etc.)
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
Edit `${CLAUDE_SKILL_DIR}/config/settings.yaml`:
|
|
54
|
+
```yaml
|
|
55
|
+
rpc_endpoints:
|
|
56
|
+
ethereum: "https://rpc.ankr.com/eth"
|
|
57
|
+
polygon: "https://rpc.ankr.com/polygon"
|
|
58
|
+
arbitrum: "https://rpc.ankr.com/arbitrum"
|
|
59
|
+
|
|
60
|
+
defaults:
|
|
61
|
+
chain: ethereum
|
|
62
|
+
limit: 50
|
|
63
|
+
output_format: table
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
*[Tons of Skills](https://tonsofskills.com) by [Intent Solutions](https://intentsolutions.io) | [jeremylongshore.com](https://jeremylongshore.com)*
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Mempool Analyzer Formatters
|
|
4
|
+
|
|
5
|
+
Format mempool data for various outputs.
|
|
6
|
+
|
|
7
|
+
Author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
8
|
+
Version: 1.0.0
|
|
9
|
+
License: MIT
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Any, List, Optional, TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from tx_decoder import TransactionDecoder
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def format_gwei(wei: int) -> str:
|
|
21
|
+
"""Format wei as gwei."""
|
|
22
|
+
gwei = wei / 10**9
|
|
23
|
+
return f"{gwei:.1f} gwei"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def format_eth(wei: int) -> str:
|
|
27
|
+
"""Format wei as ETH."""
|
|
28
|
+
eth = wei / 10**18
|
|
29
|
+
if eth >= 1:
|
|
30
|
+
return f"{eth:.4f} ETH"
|
|
31
|
+
else:
|
|
32
|
+
return f"{eth:.6f} ETH"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def format_usd(value: float) -> str:
|
|
36
|
+
"""Format USD value."""
|
|
37
|
+
if value >= 1e6:
|
|
38
|
+
return f"${value / 1e6:.2f}M"
|
|
39
|
+
elif value >= 1e3:
|
|
40
|
+
return f"${value / 1e3:.1f}K"
|
|
41
|
+
else:
|
|
42
|
+
return f"${value:,.2f}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def format_address(address: str, length: int = 12) -> str:
|
|
46
|
+
"""Truncate address for display."""
|
|
47
|
+
if not address:
|
|
48
|
+
return "N/A"
|
|
49
|
+
if len(address) <= length:
|
|
50
|
+
return address
|
|
51
|
+
half = (length - 3) // 2
|
|
52
|
+
return f"{address[:half]}...{address[-half:]}"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def format_pending_tx_table(
|
|
56
|
+
transactions: List[Any],
|
|
57
|
+
eth_price: float = 3000.0,
|
|
58
|
+
decoder: Optional["TransactionDecoder"] = None,
|
|
59
|
+
) -> str:
|
|
60
|
+
"""Format pending transactions as table.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
transactions: List of PendingTransaction objects
|
|
64
|
+
eth_price: Current ETH price for USD conversion
|
|
65
|
+
decoder: Optional TransactionDecoder instance (created if not provided)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Formatted table string
|
|
69
|
+
"""
|
|
70
|
+
if not transactions:
|
|
71
|
+
return "No pending transactions."
|
|
72
|
+
|
|
73
|
+
lines = [
|
|
74
|
+
"",
|
|
75
|
+
"PENDING TRANSACTIONS",
|
|
76
|
+
"=" * 100,
|
|
77
|
+
f"{'Hash':<18} {'From':<14} {'To':<14} {'Value':<12} {'Gas Price':<12} {'Gas':<10} {'Type':<12}",
|
|
78
|
+
"-" * 100,
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
# Create decoder only if not provided
|
|
82
|
+
if decoder is None:
|
|
83
|
+
from tx_decoder import TransactionDecoder
|
|
84
|
+
decoder = TransactionDecoder()
|
|
85
|
+
|
|
86
|
+
for tx in transactions[:50]:
|
|
87
|
+
tx_hash = format_address(tx.hash, 16)
|
|
88
|
+
from_addr = format_address(tx.from_address, 12)
|
|
89
|
+
to_addr = format_address(tx.to_address or "Contract", 12)
|
|
90
|
+
|
|
91
|
+
# Value
|
|
92
|
+
if tx.value > 0:
|
|
93
|
+
value_str = format_eth(tx.value)
|
|
94
|
+
else:
|
|
95
|
+
value_str = "0"
|
|
96
|
+
|
|
97
|
+
gas_price_str = format_gwei(tx.gas_price)
|
|
98
|
+
gas_str = f"{tx.gas:,}"
|
|
99
|
+
|
|
100
|
+
# Decode type
|
|
101
|
+
decoded = decoder.decode_input(tx.input_data, tx.to_address)
|
|
102
|
+
tx_type = decoded.method_type[:10]
|
|
103
|
+
|
|
104
|
+
lines.append(f"{tx_hash:<18} {from_addr:<14} {to_addr:<14} {value_str:<12} {gas_price_str:<12} {gas_str:<10} {tx_type:<12}")
|
|
105
|
+
|
|
106
|
+
lines.append("-" * 100)
|
|
107
|
+
lines.append(f"Showing {min(len(transactions), 50)} of {len(transactions)} pending transactions")
|
|
108
|
+
|
|
109
|
+
return "\n".join(lines)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def format_pending_swaps_table(swaps: List[Any]) -> str:
|
|
113
|
+
"""Format pending swaps as table.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
swaps: List of PendingSwap objects
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Formatted table string
|
|
120
|
+
"""
|
|
121
|
+
if not swaps:
|
|
122
|
+
return "No pending swaps detected."
|
|
123
|
+
|
|
124
|
+
lines = [
|
|
125
|
+
"",
|
|
126
|
+
"PENDING DEX SWAPS",
|
|
127
|
+
"=" * 90,
|
|
128
|
+
f"{'Hash':<18} {'DEX':<20} {'Amount In':<16} {'Gas Price':<12} {'From':<14}",
|
|
129
|
+
"-" * 90,
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
for swap in swaps[:30]:
|
|
133
|
+
tx_hash = format_address(swap.tx_hash, 16)
|
|
134
|
+
dex = swap.dex[:18] if swap.dex else "Unknown"
|
|
135
|
+
|
|
136
|
+
if swap.amount_in:
|
|
137
|
+
amount = format_eth(swap.amount_in)
|
|
138
|
+
else:
|
|
139
|
+
amount = "Unknown"
|
|
140
|
+
|
|
141
|
+
gas_price = format_gwei(swap.gas_price)
|
|
142
|
+
from_addr = format_address(swap.from_address, 12)
|
|
143
|
+
|
|
144
|
+
lines.append(f"{tx_hash:<18} {dex:<20} {amount:<16} {gas_price:<12} {from_addr:<14}")
|
|
145
|
+
|
|
146
|
+
lines.append("-" * 90)
|
|
147
|
+
lines.append(f"Total: {len(swaps)} pending swaps")
|
|
148
|
+
|
|
149
|
+
return "\n".join(lines)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def format_mempool_summary(
|
|
153
|
+
pending_count: int,
|
|
154
|
+
gas_info: Any,
|
|
155
|
+
swap_count: int,
|
|
156
|
+
opportunities: int
|
|
157
|
+
) -> str:
|
|
158
|
+
"""Format mempool summary.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
pending_count: Number of pending transactions
|
|
162
|
+
gas_info: GasInfo object
|
|
163
|
+
swap_count: Number of pending swaps
|
|
164
|
+
opportunities: Number of MEV opportunities
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Formatted summary string
|
|
168
|
+
"""
|
|
169
|
+
lines = [
|
|
170
|
+
"",
|
|
171
|
+
"MEMPOOL SUMMARY",
|
|
172
|
+
"=" * 50,
|
|
173
|
+
f"Pending Transactions: {pending_count:,}",
|
|
174
|
+
f"Pending Swaps: {swap_count}",
|
|
175
|
+
f"MEV Opportunities: {opportunities}",
|
|
176
|
+
"",
|
|
177
|
+
"Gas Prices:",
|
|
178
|
+
f" Base Fee: {format_gwei(gas_info.base_fee)}",
|
|
179
|
+
f" Priority Fee: {format_gwei(gas_info.priority_fee)}",
|
|
180
|
+
f" Gas Price: {format_gwei(gas_info.gas_price)}",
|
|
181
|
+
"=" * 50,
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
return "\n".join(lines)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def format_json(data: Any) -> str:
|
|
188
|
+
"""Format data as JSON."""
|
|
189
|
+
if hasattr(data, "__iter__") and not isinstance(data, (str, dict)):
|
|
190
|
+
serialized = []
|
|
191
|
+
for item in data:
|
|
192
|
+
if hasattr(item, "__dict__"):
|
|
193
|
+
serialized.append(vars(item))
|
|
194
|
+
else:
|
|
195
|
+
serialized.append(item)
|
|
196
|
+
return json.dumps(serialized, indent=2, default=str)
|
|
197
|
+
elif hasattr(data, "__dict__"):
|
|
198
|
+
return json.dumps(vars(data), indent=2, default=str)
|
|
199
|
+
else:
|
|
200
|
+
return json.dumps(data, indent=2, default=str)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def format_stream_alert(
|
|
204
|
+
tx: Any,
|
|
205
|
+
decoder: Optional["TransactionDecoder"] = None,
|
|
206
|
+
) -> str:
|
|
207
|
+
"""Format single transaction for stream output.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
tx: Transaction object
|
|
211
|
+
decoder: Optional TransactionDecoder instance (created if not provided)
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Single-line alert string
|
|
215
|
+
"""
|
|
216
|
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
217
|
+
tx_hash = format_address(tx.hash, 12)
|
|
218
|
+
gas_price = tx.gas_price / 10**9
|
|
219
|
+
|
|
220
|
+
# Create decoder only if not provided
|
|
221
|
+
if decoder is None:
|
|
222
|
+
from tx_decoder import TransactionDecoder
|
|
223
|
+
decoder = TransactionDecoder()
|
|
224
|
+
decoded = decoder.decode_input(tx.input_data, tx.to_address)
|
|
225
|
+
|
|
226
|
+
if tx.value > 0:
|
|
227
|
+
value = format_eth(tx.value)
|
|
228
|
+
return f"[{timestamp}] {tx_hash} | {gas_price:.1f} gwei | {value} | {decoded.method_type}"
|
|
229
|
+
else:
|
|
230
|
+
return f"[{timestamp}] {tx_hash} | {gas_price:.1f} gwei | {decoded.method_name}"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def main():
|
|
234
|
+
"""CLI entry point for testing."""
|
|
235
|
+
# Test formatting functions
|
|
236
|
+
print("=== Format Tests ===")
|
|
237
|
+
print(f"Gwei: {format_gwei(30 * 10**9)}")
|
|
238
|
+
print(f"ETH: {format_eth(1.5 * 10**18)}")
|
|
239
|
+
print(f"USD: {format_usd(1234567)}")
|
|
240
|
+
print(f"Address: {format_address('0x1234567890abcdef1234567890abcdef12345678')}")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
main()
|