Skip to content

Commit a7c131f

Browse files
committed
feat: Enhance safety guard and external API manager with oracle feed support
- Added a bypass for balance checks in SafetyGuard for testing purposes. - Refactored ExternalAPIManager to support multiple oracle feeds by chain. - Implemented loading of configured oracle feeds from settings. - Enhanced price fetching logic to fall back on oracle prices when no providers are available. - Improved transaction processing in TxPoolScanner with comprehensive MEV analysis and logging. - Added normalization for transaction hashes and support for Uniswap V3 multi-hop paths. - Updated error recovery mechanisms in ErrorRecoveryManager for better handling of Web3 reconnections and transaction retries. - Enhanced test coverage for new features, including oracle feed normalization and transaction manager profit analysis.
1 parent 8a2be6e commit a7c131f

19 files changed

+891
-116
lines changed

.env.example

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
# Application Settings (required)
2-
TESTNET=0
1+
# Application / Logging
32
RUN_LIVE_API_TESTS=0
43
LOG_LEVEL=INFO
5-
DEBUG=true
6-
SIMULATE_ONLY=0
4+
DEBUG=false
75

86
# Wallet (required)
97
WALLET_KEY=
@@ -13,45 +11,60 @@ PROFIT_RECEIVER_ADDRESS=
1311
# WALLET_KEY_1=
1412
# WALLET_ADDRESS_1=
1513

16-
# Chain and node endpoints (required) Publicnode for testing only.
14+
# Chain and node endpoints (required)
1715
CHAINS="1"
18-
POA_CHAINS="56,5"
16+
POA_CHAINS=""
1917
RPC_URL_1="https://ethereum-rpc.publicnode.com"
2018
WEBSOCKET_URL_1="wss://ethereum-rpc.publicnode.com"
21-
19+
# PublicNode RPCs (EVM networks)
20+
# RPC_URL_10="https://optimism-rpc.publicnode.com"
21+
# WEBSOCKET_URL_10="wss://optimism-rpc.publicnode.com"
22+
# RPC_URL_56="https://bsc-rpc.publicnode.com"
23+
# WEBSOCKET_URL_56="wss://bsc-rpc.publicnode.com"
24+
# RPC_URL_137="https://polygon-bor-rpc.publicnode.com"
25+
# WEBSOCKET_URL_137="wss://polygon-bor-rpc.publicnode.com"
26+
# RPC_URL_42161="https://arbitrum-one-rpc.publicnode.com"
27+
# WEBSOCKET_URL_42161="wss://arbitrum-one-rpc.publicnode.com"
28+
# RPC_URL_43114="https://avalanche-c-chain-rpc.publicnode.com"
29+
# WEBSOCKET_URL_43114="wss://avalanche-c-chain-rpc.publicnode.com"
30+
# RPC_URL_8453="https://base-rpc.publicnode.com"
31+
# WEBSOCKET_URL_8453="wss://base-rpc.publicnode.com"
2232

2333
# Transaction & Gas (required)
24-
TRANSACTION_RETRY_COUNT=0
25-
TRANSACTION_RETRY_DELAY=0
34+
TRANSACTION_RETRY_COUNT=3
35+
TRANSACTION_RETRY_DELAY=2.0
2636
MAX_GAS_PRICE_GWEI=200
27-
GAS_PRICE_MULTIPLIER=1.25
37+
GAS_PRICE_MULTIPLIER=1.1
2838
DEFAULT_GAS_LIMIT=500000
2939
FALLBACK_GAS_PRICE_GWEI=50
3040
DYNAMIC_GAS_PRICING=1
3141
GAS_PRICE_PERCENTILE=75
3242
MAX_GAS_FEE_PERCENTAGE=10.0
33-
MIN_WALLET_BALANCE=0.01
43+
MIN_WALLET_BALANCE=0.05
3444

3545
# Simulation & Submission (optional)
3646
ALLOW_UNSIMULATED_TRADES=0
3747
SIMULATION_BACKEND=eth_call
3848
SIMULATION_CONCURRENCY=5
3949
SUBMISSION_MODE=public
4050
PRIVATE_RPC_URL=
51+
TENDERLY_BASE_URL="https://api.tenderly.co/api/v1"
52+
TENDERLY_ACCOUNT_SLUG=
53+
TENDERLY_PROJECT_SLUG=
54+
TENDERLY_ACCESS_TOKEN=
4155
BUNDLE_RELAY_URL=
4256
BUNDLE_RELAY_AUTH_TOKEN=
4357
BUNDLE_SIGNER_KEY=
44-
BUNDLE_SIGNER_KEY_PATH=
58+
BUNDLE_SIGNER_KEY_PATH=~/.on1builder/bundle_signer.key
4559
BUNDLE_TARGET_BLOCK_OFFSET=1
4660
BUNDLE_TIMEOUT_SECONDS=30
47-
TENDERLY_BASE_URL="https://api.tenderly.co/api/v1"
48-
TENDERLY_ACCOUNT_SLUG=
49-
TENDERLY_PROJECT_SLUG=
50-
TENDERLY_ACCESS_TOKEN=
61+
ALLOW_INSUFFICIENT_FUNDS_TESTS=0
62+
STARTUP_TEST_TRANSACTION=0
5163

5264
# Strategy & Profit (required)
53-
MIN_PROFIT_ETH=0.01
65+
MIN_PROFIT_ETH=0.005
5466
MIN_PROFIT_PERCENTAGE=0.1
67+
PROFIT_ANALYSIS_ENABLED=0
5568
DYNAMIC_PROFIT_SCALING=1
5669
BALANCE_RISK_RATIO=0.3
5770
SLIPPAGE_TOLERANCE=0.5
@@ -73,69 +86,63 @@ FRONT_RUNNING_ENABLED=1
7386
BACK_RUNNING_ENABLED=1
7487
SANDWICH_ATTACKS_ENABLED=0
7588

76-
# Cross-chain (optional)
89+
# ML Strategy (optional)
90+
ML_ENABLED=1
91+
ML_LEARNING_RATE=0.01
92+
ML_EXPLORATION_RATE=0.1
93+
ML_DECAY_RATE=0.995
94+
ML_UPDATE_FREQUENCY=100
95+
96+
# Market Sentiment (optional)
97+
USE_MARKET_SENTIMENT=1
98+
SENTIMENT_WEIGHT=0.3
99+
100+
# Cross-chain & Arbitrage (optional)
77101
CROSS_CHAIN_ENABLED=1
78102
BRIDGE_MONITORING_ENABLED=1
79103
ARBITRAGE_SCAN_INTERVAL=15
80104

81105
# Contracts (mainnet)
82106
UNISWAP_V2_ROUTER_ADDRESSES='{"1": "0x1234567890abcdef1234567890abcdef1234567"}'
83-
UNISWAP_V2_FACTORY_ADDRESSES=
84-
UNISWAP_V3_ROUTER_ADDRESSES=
85-
UNISWAP_V3_FACTORY_ADDRESSES=
86-
UNISWAP_V3_POSITION_MANAGER_ADDRESSES=
87-
UNISWAP_V3_QUOTER_ADDRESSES=
88-
SUSHISWAP_ROUTER_ADDRESSES=
89-
AAVE_V3_POOL_ADDRESSES=
107+
UNISWAP_V3_ROUTER_ADDRESSES='{}'
108+
SUSHISWAP_ROUTER_ADDRESSES='{}'
109+
AAVE_V3_POOL_ADDRESSES='{}'
90110

91111
# Your deployed flashloan contract addresses per chain (if any)
92-
SIMPLE_FLASHLOAN_CONTRACT_ADDRESSES=
112+
SIMPLE_FLASHLOAN_CONTRACT_ADDRESSES='{}'
93113

94114
# External APIs (optional)
95-
ETHERSCAN_API_KEY=123456apikey654321
115+
ETHERSCAN_API_KEY=
96116
COINGECKO_API_KEY=
97117
COINMARKETCAP_API_KEY=
98118
CRYPTOCOMPARE_API_KEY=
99-
BINANCE_API_KEY=
100119
INFURA_PROJECT_ID=
101120

121+
# Oracle feeds (optional, chain_id -> symbol -> feed address)
122+
# ORACLE_FEEDS='{"1": {"ETH": "0x..."} }'
123+
ORACLE_FEEDS='{"10": {"ETH": "0x13e3Ee699D1909E989722E753853AE30b17e08c5", "OP": "0x0D276FC14719f9292D5C1eA2198673d1f4269246"}, "137": {"ETH": "0xF9680D99D6C9589e2a93a78A04A279e509205945", "MATIC": "0xAB594600376Ec9fD91F8e885dADF0CE036862dE0", "USDC": "0xfE4A8cc5b5B2366C1B58Bea3858e81843581b2F7"}, "42161": {"ARB": "0xb2A824043730FE05F3DA2efaFa1CBbe83fa548D6", "BTC": "0x6ce185860a4963106506C203335A2910413708e9", "ETH": "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612"}, "56": {"BNB": "0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE", "BTC": "0x264990fbd0A4796A3E3d8E37C4d5F87a3aCa5Ebf", "ETH": "0x9ef1B8c0E4F7dc8bF5719Ea496883DC6401d5b2e"}, "8453": {"ETH": "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70"}}'
124+
ORACLE_STALE_SECONDS=3600
125+
102126
# System & Performance (required)
103127
MEMORY_CHECK_INTERVAL=300
104-
HEARTBEAT_INTERVAL=90
128+
HEARTBEAT_INTERVAL=30
105129
CONNECTION_RETRY_COUNT=5
106130
CONNECTION_RETRY_DELAY=5.0
131+
PERFORMANCE_REPORT_INTERVAL=3600
107132

108133
# Database (required)
109134
DATABASE_URL="sqlite+aiosqlite:///on1builder_data.db"
110135

111136
# Notifications (optional)
112-
NOTIFICATION_CHANNELS=discord,slack,email
113-
MIN_NOTIFICATION_LEVEL=
137+
# NOTIFICATION_CHANNELS=discord,slack,email
138+
NOTIFICATION_CHANNELS=
139+
MIN_NOTIFICATION_LEVEL=INFO
114140
SLACK_WEBHOOK_URL=
115141
TELEGRAM_BOT_TOKEN=
116142
TELEGRAM_CHAT_ID=
117143
DISCORD_WEBHOOK_URL=
118144
SMTP_SERVER=
119-
SMTP_PORT=
145+
SMTP_PORT=587
120146
SMTP_USERNAME=
121147
SMTP_PASSWORD=
122148
ALERT_EMAIL=
123-
124-
#######################################
125-
# Testnet related settings
126-
STARTUP_TEST_SWAP=0
127-
STARTUP_TEST_SWAP_AMOUNT_ETH=0
128-
STARTUP_TEST_SWAP_PATH=
129-
STARTUP_TEST_SWAP_DEX=
130-
STARTUP_TEST_SWAP_FEE=
131-
STARTUP_TEST_SWAP_DELAY=
132-
TESTNET_ARB_ENABLED=
133-
TESTNET_ARB_INTERVAL=
134-
TESTNET_ARB_AMOUNT_ETH=
135-
TESTNET_ARB_PATH=
136-
TESTNET_ARB_V3_FEE=
137-
TESTNET_ARB_MIN_PROFIT_ETH=
138-
TESTNET_ARB_GAS_LIMIT=
139-
TESTNET_ARB_INITIAL_DELAY=
140-
RPC_URL_11155111=
141-
WEBSOCKET_URL_11155111=

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,16 @@ python -m on1builder run start
9696
| `SIMULATION_BACKEND` | `eth_call`, `anvil`, or `tenderly` |
9797
| `SIMULATION_CONCURRENCY` | Max concurrent simulations |
9898
| `NOTIFICATION_CHANNELS` | `slack,telegram,discord,email` (blank = off) |
99+
| `ORACLE_FEEDS` | JSON map of chain_id → symbol → Chainlink feed address |
100+
| `ORACLE_STALE_SECONDS` | Max age (seconds) before oracle price is treated as stale |
101+
| `STARTUP_TEST_TRANSACTION` | Run a diagnostic self-tx on startup (requires `ALLOW_INSUFFICIENT_FUNDS_TESTS`) |
102+
| `ALLOW_INSUFFICIENT_FUNDS_TESTS` | Bypass local balance checks for test sends (debug only) |
99103

100104
Full list lives in `.env.example`.
101105

106+
PublicNode endpoints can be used for EVM chains listed in `.env.example`. Non-EVM endpoints
107+
(Sui, Aptos, Osmosis, Avalanche P/X, Polygon Heimdall) are not supported by ON1Builder.
108+
102109
## Running & Monitoring
103110

104111
```bash
@@ -127,6 +134,17 @@ black src tests && flake8 src tests && mypy src
127134

128135
Pre-commit hooks are configured in `.pre-commit-config.yaml`.
129136

137+
## Optional Utilities
138+
139+
ON1Builder ships a few utilities that are not required for core execution but can be
140+
integrated in advanced deployments:
141+
142+
- `src/on1builder/utils/error_recovery.py`: Retry/circuit-breaker helpers and a recovery
143+
manager. The TransactionManager now reports failures to this manager for tracking and
144+
optional recovery strategies, but most strategies are placeholders by default.
145+
- `src/on1builder/utils/container.py`: A lightweight DI container for advanced lifecycle
146+
management. The core runtime does not use it yet; opt in if you want centralized wiring.
147+
130148
## Flashloan setup
131149
You need to deploy your own flashloan provider contract or use an existing one. Make sure to configure the flashloan provider address in your strategy settings.
132150

src/on1builder/config/loaders.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from __future__ import annotations
66

7+
import json
78
import os
89
from pathlib import Path
910
from typing import Dict, Any, Optional
@@ -91,11 +92,18 @@ class _EnvSettings(BaseSettings):
9192
)
9293
bundle_target_block_offset: int = 1
9394
bundle_timeout_seconds: int = 30
95+
allow_insufficient_funds_tests: bool = Field(
96+
False, alias="ALLOW_INSUFFICIENT_FUNDS_TESTS"
97+
)
98+
startup_test_transaction: bool = Field(
99+
False, alias="STARTUP_TEST_TRANSACTION"
100+
)
94101

95102
# Balance and profit settings
96103
min_wallet_balance: float = 0.05
97104
min_profit_eth: float = 0.005
98105
min_profit_percentage: float = 0.1
106+
profit_analysis_enabled: bool = False
99107
dynamic_profit_scaling: bool = True
100108
balance_risk_ratio: float = 0.3
101109
slippage_tolerance: float = 0.5
@@ -187,6 +195,25 @@ class _EnvSettings(BaseSettings):
187195
"sqlite+aiosqlite:///on1builder_data.db", alias="DATABASE_URL"
188196
)
189197

198+
oracle_feeds: str = Field("{}", alias="ORACLE_FEEDS")
199+
oracle_stale_seconds: int = Field(3600, alias="ORACLE_STALE_SECONDS")
200+
201+
202+
def _parse_json_env(value: Any, default: Any) -> Any:
203+
if isinstance(value, dict):
204+
return value
205+
if isinstance(value, str):
206+
stripped = value.strip()
207+
if not stripped:
208+
return default
209+
if stripped.startswith("{"):
210+
try:
211+
return json.loads(stripped)
212+
except json.JSONDecodeError:
213+
logger.warning("Invalid JSON for ORACLE_FEEDS; using defaults.")
214+
return default
215+
return default
216+
190217

191218
def _gather_dynamic_env_vars() -> Dict[str, Any]:
192219
"""
@@ -282,6 +309,9 @@ def load_settings(env_path: Optional[Path] = None) -> GlobalSettings:
282309
# Gather all static and dynamic data
283310
final_config_data = env_settings.model_dump()
284311
final_config_data.update(_gather_dynamic_env_vars())
312+
final_config_data["oracle_feeds"] = _parse_json_env(
313+
env_settings.oracle_feeds, {}
314+
)
285315

286316
# Populate nested models
287317
final_config_data["api"] = api_settings

src/on1builder/config/settings.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def normalize_level(cls, v):
7575
@field_validator("channels", mode="before")
7676
def split_str(cls, v):
7777
if isinstance(v, str):
78-
return [item.strip() for item in v.split(",")]
78+
return [item.strip() for item in v.split(",") if item.strip()]
7979
return v
8080

8181

@@ -290,12 +290,24 @@ def validate_complete_settings(self):
290290
)
291291
fallback_gas_price_gwei: int = Field(default=50, gt=0)
292292
min_wallet_balance: float = Field(default=0.05, ge=0)
293+
allow_insufficient_funds_tests: bool = Field(
294+
default=False,
295+
description="Bypass local balance checks for startup test transactions.",
296+
)
297+
startup_test_transaction: bool = Field(
298+
default=False,
299+
description="Attempt a startup test transaction for diagnostics.",
300+
)
293301

294302
# Strategy & Profit - ON1Builder with dynamic thresholds
295303
min_profit_eth: float = Field(default=0.005, ge=0)
296304
min_profit_percentage: float = Field(
297305
default=0.1, ge=0
298306
) # Minimum profit as % of investment
307+
profit_analysis_enabled: bool = Field(
308+
default=False,
309+
description="Enable detailed post-trade profit analysis (extra RPC/API calls).",
310+
)
299311
dynamic_profit_scaling: bool = Field(
300312
default=True
301313
) # Scale profit requirements based on balance
@@ -385,6 +397,14 @@ def validate_complete_settings(self):
385397
cross_chain_enabled: bool = Field(default=True)
386398
bridge_monitoring_enabled: bool = Field(default=True)
387399

400+
# Oracle feeds (chain_id -> symbol -> feed address)
401+
oracle_feeds: Dict[str, Dict[str, str]] = Field(default_factory=dict)
402+
oracle_stale_seconds: int = Field(
403+
default=3600,
404+
gt=0,
405+
description="Max age in seconds for oracle prices before treating as stale.",
406+
)
407+
388408
# Nested Settings Models
389409
api: APISettings = Field(default_factory=APISettings)
390410
contracts: ContractAddressSettings = Field(default_factory=ContractAddressSettings)

src/on1builder/config/validation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ConfigValidator:
2727
10: "Optimism",
2828
56: "BSC",
2929
43114: "Avalanche",
30+
8453: "Base",
3031
250: "Fantom",
3132
# Add test networks
3233
5: "Goerli",

0 commit comments

Comments
 (0)