brawny 0.1.13__py3-none-any.whl

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 (141) hide show
  1. brawny/__init__.py +106 -0
  2. brawny/_context.py +232 -0
  3. brawny/_rpc/__init__.py +38 -0
  4. brawny/_rpc/broadcast.py +172 -0
  5. brawny/_rpc/clients.py +98 -0
  6. brawny/_rpc/context.py +49 -0
  7. brawny/_rpc/errors.py +252 -0
  8. brawny/_rpc/gas.py +158 -0
  9. brawny/_rpc/manager.py +982 -0
  10. brawny/_rpc/selector.py +156 -0
  11. brawny/accounts.py +534 -0
  12. brawny/alerts/__init__.py +132 -0
  13. brawny/alerts/abi_resolver.py +530 -0
  14. brawny/alerts/base.py +152 -0
  15. brawny/alerts/context.py +271 -0
  16. brawny/alerts/contracts.py +635 -0
  17. brawny/alerts/encoded_call.py +201 -0
  18. brawny/alerts/errors.py +267 -0
  19. brawny/alerts/events.py +680 -0
  20. brawny/alerts/function_caller.py +364 -0
  21. brawny/alerts/health.py +185 -0
  22. brawny/alerts/routing.py +118 -0
  23. brawny/alerts/send.py +364 -0
  24. brawny/api.py +660 -0
  25. brawny/chain.py +93 -0
  26. brawny/cli/__init__.py +16 -0
  27. brawny/cli/app.py +17 -0
  28. brawny/cli/bootstrap.py +37 -0
  29. brawny/cli/commands/__init__.py +41 -0
  30. brawny/cli/commands/abi.py +93 -0
  31. brawny/cli/commands/accounts.py +632 -0
  32. brawny/cli/commands/console.py +495 -0
  33. brawny/cli/commands/contract.py +139 -0
  34. brawny/cli/commands/health.py +112 -0
  35. brawny/cli/commands/init_project.py +86 -0
  36. brawny/cli/commands/intents.py +130 -0
  37. brawny/cli/commands/job_dev.py +254 -0
  38. brawny/cli/commands/jobs.py +308 -0
  39. brawny/cli/commands/logs.py +87 -0
  40. brawny/cli/commands/maintenance.py +182 -0
  41. brawny/cli/commands/migrate.py +51 -0
  42. brawny/cli/commands/networks.py +253 -0
  43. brawny/cli/commands/run.py +249 -0
  44. brawny/cli/commands/script.py +209 -0
  45. brawny/cli/commands/signer.py +248 -0
  46. brawny/cli/helpers.py +265 -0
  47. brawny/cli_templates.py +1445 -0
  48. brawny/config/__init__.py +74 -0
  49. brawny/config/models.py +404 -0
  50. brawny/config/parser.py +633 -0
  51. brawny/config/routing.py +55 -0
  52. brawny/config/validation.py +246 -0
  53. brawny/daemon/__init__.py +14 -0
  54. brawny/daemon/context.py +69 -0
  55. brawny/daemon/core.py +702 -0
  56. brawny/daemon/loops.py +327 -0
  57. brawny/db/__init__.py +78 -0
  58. brawny/db/base.py +986 -0
  59. brawny/db/base_new.py +165 -0
  60. brawny/db/circuit_breaker.py +97 -0
  61. brawny/db/global_cache.py +298 -0
  62. brawny/db/mappers.py +182 -0
  63. brawny/db/migrate.py +349 -0
  64. brawny/db/migrations/001_init.sql +186 -0
  65. brawny/db/migrations/002_add_included_block.sql +7 -0
  66. brawny/db/migrations/003_add_broadcast_at.sql +10 -0
  67. brawny/db/migrations/004_broadcast_binding.sql +20 -0
  68. brawny/db/migrations/005_add_retry_after.sql +9 -0
  69. brawny/db/migrations/006_add_retry_count_column.sql +11 -0
  70. brawny/db/migrations/007_add_gap_tracking.sql +18 -0
  71. brawny/db/migrations/008_add_transactions.sql +72 -0
  72. brawny/db/migrations/009_add_intent_metadata.sql +5 -0
  73. brawny/db/migrations/010_add_nonce_gap_index.sql +9 -0
  74. brawny/db/migrations/011_add_job_logs.sql +24 -0
  75. brawny/db/migrations/012_add_claimed_by.sql +5 -0
  76. brawny/db/ops/__init__.py +29 -0
  77. brawny/db/ops/attempts.py +108 -0
  78. brawny/db/ops/blocks.py +83 -0
  79. brawny/db/ops/cache.py +93 -0
  80. brawny/db/ops/intents.py +296 -0
  81. brawny/db/ops/jobs.py +110 -0
  82. brawny/db/ops/logs.py +97 -0
  83. brawny/db/ops/nonces.py +322 -0
  84. brawny/db/postgres.py +2535 -0
  85. brawny/db/postgres_new.py +196 -0
  86. brawny/db/queries.py +584 -0
  87. brawny/db/sqlite.py +2733 -0
  88. brawny/db/sqlite_new.py +191 -0
  89. brawny/history.py +126 -0
  90. brawny/interfaces.py +136 -0
  91. brawny/invariants.py +155 -0
  92. brawny/jobs/__init__.py +26 -0
  93. brawny/jobs/base.py +287 -0
  94. brawny/jobs/discovery.py +233 -0
  95. brawny/jobs/job_validation.py +111 -0
  96. brawny/jobs/kv.py +125 -0
  97. brawny/jobs/registry.py +283 -0
  98. brawny/keystore.py +484 -0
  99. brawny/lifecycle.py +551 -0
  100. brawny/logging.py +290 -0
  101. brawny/metrics.py +594 -0
  102. brawny/model/__init__.py +53 -0
  103. brawny/model/contexts.py +319 -0
  104. brawny/model/enums.py +70 -0
  105. brawny/model/errors.py +194 -0
  106. brawny/model/events.py +93 -0
  107. brawny/model/startup.py +20 -0
  108. brawny/model/types.py +483 -0
  109. brawny/networks/__init__.py +96 -0
  110. brawny/networks/config.py +269 -0
  111. brawny/networks/manager.py +423 -0
  112. brawny/obs/__init__.py +67 -0
  113. brawny/obs/emit.py +158 -0
  114. brawny/obs/health.py +175 -0
  115. brawny/obs/heartbeat.py +133 -0
  116. brawny/reconciliation.py +108 -0
  117. brawny/scheduler/__init__.py +19 -0
  118. brawny/scheduler/poller.py +472 -0
  119. brawny/scheduler/reorg.py +632 -0
  120. brawny/scheduler/runner.py +708 -0
  121. brawny/scheduler/shutdown.py +371 -0
  122. brawny/script_tx.py +297 -0
  123. brawny/scripting.py +251 -0
  124. brawny/startup.py +76 -0
  125. brawny/telegram.py +393 -0
  126. brawny/testing.py +108 -0
  127. brawny/tx/__init__.py +41 -0
  128. brawny/tx/executor.py +1071 -0
  129. brawny/tx/fees.py +50 -0
  130. brawny/tx/intent.py +423 -0
  131. brawny/tx/monitor.py +628 -0
  132. brawny/tx/nonce.py +498 -0
  133. brawny/tx/replacement.py +456 -0
  134. brawny/tx/utils.py +26 -0
  135. brawny/utils.py +205 -0
  136. brawny/validation.py +69 -0
  137. brawny-0.1.13.dist-info/METADATA +156 -0
  138. brawny-0.1.13.dist-info/RECORD +141 -0
  139. brawny-0.1.13.dist-info/WHEEL +5 -0
  140. brawny-0.1.13.dist-info/entry_points.txt +2 -0
  141. brawny-0.1.13.dist-info/top_level.txt +1 -0
@@ -0,0 +1,74 @@
1
+ """Configuration management for brawny.
2
+
3
+ All configuration is validated at startup. Invalid config prevents startup with clear error messages.
4
+ Environment variables use the BRAWNY_ prefix.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+
11
+ from brawny.config.models import AdvancedConfig, Config, RPCGroupConfig
12
+ from brawny.config.validation import (
13
+ InvalidEndpointError,
14
+ canonicalize_endpoint,
15
+ dedupe_preserve_order,
16
+ validate_config,
17
+ )
18
+ from brawny.model.errors import ConfigError
19
+
20
+ __all__ = [
21
+ # Models
22
+ "Config",
23
+ "AdvancedConfig",
24
+ "RPCGroupConfig",
25
+ # Validation
26
+ "InvalidEndpointError",
27
+ "canonicalize_endpoint",
28
+ "dedupe_preserve_order",
29
+ "validate_config",
30
+ # Errors
31
+ "ConfigError",
32
+ # Global instance
33
+ "get_config",
34
+ "set_config",
35
+ "reset_config",
36
+ ]
37
+
38
+
39
+ # Global configuration instance (lazy loaded)
40
+ _config: Config | None = None
41
+
42
+
43
+ def get_config() -> Config:
44
+ """Get the global configuration instance.
45
+
46
+ Loads from environment on first access.
47
+ """
48
+ global _config
49
+ if _config is None:
50
+ default_path = os.path.join(os.getcwd(), "config.yaml")
51
+ if os.path.exists(default_path):
52
+ config = Config.from_yaml(default_path)
53
+ _config, _ = config.apply_env_overrides()
54
+ else:
55
+ _config = Config.from_env()
56
+ return _config
57
+
58
+
59
+ def set_config(config: Config) -> None:
60
+ """Set the global configuration instance.
61
+
62
+ Useful for testing or programmatic configuration.
63
+ """
64
+ global _config
65
+ _config = config
66
+
67
+
68
+ def reset_config() -> None:
69
+ """Reset the global configuration instance.
70
+
71
+ Forces reload from environment on next access.
72
+ """
73
+ global _config
74
+ _config = None
@@ -0,0 +1,404 @@
1
+ """Configuration models for brawny.
2
+
3
+ Defines dataclass models for all configuration sections.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import asdict, dataclass, field
9
+ from brawny.model.enums import KeystoreType
10
+
11
+ DEFAULT_BLOCK_HASH_HISTORY_SIZE = 256
12
+ DEFAULT_JOB_ERROR_BACKOFF_BLOCKS = 1
13
+ DEFAULT_INTENT_RETRY_BACKOFF_SECONDS = 5
14
+ DEFAULT_NONCE_RECONCILE_INTERVAL_SECONDS = 300
15
+ DEFAULT_STUCK_TX_BLOCKS = 50
16
+ DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 30
17
+ DEFAULT_RPC_RETRY_BACKOFF_BASE = 1.0
18
+ DEFAULT_RPC_CIRCUIT_BREAKER_SECONDS = 300
19
+ DEFAULT_DB_CIRCUIT_BREAKER_FAILURES = 5
20
+ DEFAULT_DB_CIRCUIT_BREAKER_SECONDS = 30
21
+ DEFAULT_GAS_REFRESH_SECONDS = 15
22
+ DEFAULT_FALLBACK_GAS_LIMIT = 500_000
23
+ DEFAULT_TELEGRAM_RATE_LIMIT_PER_MINUTE = 20
24
+ DEFAULT_ABI_CACHE_TTL_SECONDS = 86400 * 7
25
+ DEFAULT_DATABASE_POOL_TIMEOUT_SECONDS = 30.0
26
+ DEFAULT_NONCE_GAP_ALERT_SECONDS = 300
27
+ DEFAULT_MAX_EXECUTOR_RETRIES = 5
28
+ DEFAULT_FINALITY_CONFIRMATIONS = 12
29
+
30
+
31
+ @dataclass
32
+ class TelegramConfig:
33
+ """Telegram alert configuration.
34
+
35
+ Fields:
36
+ bot_token: Bot token for API calls (None = disabled)
37
+ chats: Named chat targets (e.g., {"ops": "-100...", "dev": "-100..."})
38
+ default: Default targets when job.alert_to not specified
39
+ parse_mode: Default parse mode for telegram messages (None = plain text)
40
+ health_chat: Chat name for daemon health alerts (None = logged only)
41
+ health_cooldown_seconds: Deduplication window for health alerts
42
+ """
43
+
44
+ bot_token: str | None = None
45
+ chats: dict[str, str] = field(default_factory=dict) # name -> chat_id
46
+ default: list[str] = field(default_factory=list) # Always a list internally
47
+ parse_mode: str | None = "Markdown"
48
+ health_chat: str | None = None # e.g. "ops" - where daemon health alerts go
49
+ health_cooldown_seconds: int = 1800 # 30 minutes between identical alerts
50
+
51
+
52
+ @dataclass
53
+ class RPCGroupConfig:
54
+ """A named collection of RPC endpoints."""
55
+
56
+ endpoints: list[str] = field(default_factory=list) # Canonicalized + deduped at parse time
57
+
58
+
59
+ @dataclass
60
+ class AdvancedConfig:
61
+ """
62
+ Advanced options.
63
+
64
+ RULE:
65
+ - If this exceeds ~25 fields, something is wrong.
66
+ - New options must justify why they are user-facing at all.
67
+ - AdvancedConfig may only contain tuning parameters, not semantic switches.
68
+ No feature flags or behavior-class booleans (e.g., enable_x/disable_y).
69
+ """
70
+
71
+ # Polling
72
+ poll_interval_seconds: float = 1.0
73
+ reorg_depth: int = 32
74
+ finality_confirmations: int = DEFAULT_FINALITY_CONFIRMATIONS
75
+
76
+ # Execution
77
+ default_deadline_seconds: int = 3600
78
+ stuck_tx_seconds: int = 300
79
+ max_replacement_attempts: int = 5
80
+
81
+ # Gas (gwei)
82
+ gas_limit_multiplier: float = 1.2
83
+ default_priority_fee_gwei: float = 1.5
84
+ max_fee_cap_gwei: float | None = 500.0
85
+ fee_bump_percent: int = 15
86
+
87
+ # RPC
88
+ rpc_timeout_seconds: float = 30.0
89
+ rpc_max_retries: int = 3
90
+
91
+ # Database pool (Postgres only)
92
+ database_pool_size: int = 5
93
+ database_pool_max_overflow: int = 10
94
+
95
+ # Job logs
96
+ log_retention_days: int = 7
97
+
98
+
99
+ @dataclass
100
+ class Config:
101
+ """Main configuration for brawny.
102
+
103
+ NOTE: Direct construction does NOT validate. Use Config.from_yaml() or
104
+ Config.from_env() for validated configuration, or call .validate() explicitly.
105
+ """
106
+
107
+ # Required fields (no defaults) must come first
108
+ database_url: str
109
+ rpc_endpoints: list[str] # Derived from rpc_groups; not user-facing
110
+
111
+ # RPC Groups (for per-job read/broadcast routing)
112
+ rpc_groups: dict[str, RPCGroupConfig]
113
+
114
+ # Chain (required)
115
+ chain_id: int
116
+
117
+ # RPC default group (optional)
118
+ rpc_default_group: str | None = None
119
+
120
+ # Execution
121
+ worker_count: int = 1
122
+
123
+ # Advanced (rarely changed)
124
+ advanced: AdvancedConfig | None = None
125
+
126
+ # Telegram (canonical form - parsed from telegram: or legacy fields)
127
+ telegram: TelegramConfig = field(default_factory=TelegramConfig)
128
+
129
+ # Metrics
130
+ metrics_port: int = 9091
131
+
132
+ # Keystore (required)
133
+ keystore_type: KeystoreType = KeystoreType.FILE
134
+ keystore_path: str = "~/.brawny/keys"
135
+
136
+ def validate(self) -> None:
137
+ """Validate configuration.
138
+
139
+ Raises:
140
+ ConfigError: If validation fails
141
+ """
142
+ from brawny.config.validation import validate_config, validate_advanced_config
143
+
144
+ validate_config(self)
145
+ validate_advanced_config(self._advanced_or_default())
146
+
147
+ @property
148
+ def is_sqlite(self) -> bool:
149
+ """Check if using SQLite database."""
150
+ return self.database_url.startswith("sqlite:///")
151
+
152
+ @property
153
+ def is_postgres(self) -> bool:
154
+ """Check if using PostgreSQL database."""
155
+ return self.database_url.startswith(("postgresql://", "postgres://"))
156
+
157
+ @classmethod
158
+ def from_env(cls) -> "Config":
159
+ """Load configuration from environment variables."""
160
+ from brawny.config.parser import from_env as _from_env
161
+ return _from_env()
162
+
163
+ @classmethod
164
+ def from_yaml(cls, path: str) -> "Config":
165
+ """Load configuration from a YAML file.
166
+
167
+ Supports environment variable interpolation using ${VAR}, ${{VAR}}, or ${VAR:-default} syntax.
168
+ """
169
+ from brawny.config.parser import from_yaml as _from_yaml
170
+ return _from_yaml(path)
171
+
172
+ def apply_env_overrides(self) -> tuple["Config", list[str]]:
173
+ """Apply environment overrides to the current config."""
174
+ from brawny.config.parser import apply_env_overrides as _apply_env_overrides
175
+ return _apply_env_overrides(self)
176
+
177
+ def to_dict(self) -> dict[str, object]:
178
+ return asdict(self)
179
+
180
+ def redacted_dict(self) -> dict[str, object]:
181
+ from urllib.parse import urlsplit, urlunsplit
182
+
183
+ def _redact_url(value: str) -> str:
184
+ try:
185
+ split = urlsplit(value)
186
+ except Exception:
187
+ return "***"
188
+ netloc = split.netloc
189
+ if "@" in netloc:
190
+ netloc = "***@" + netloc.split("@", 1)[1]
191
+ return urlunsplit((split.scheme, netloc, split.path, "", ""))
192
+
193
+ data = self.to_dict()
194
+ redacted: dict[str, object] = {}
195
+ for key, value in data.items():
196
+ if any(word in key.lower() for word in ("token", "secret", "key", "password")):
197
+ redacted[key] = "***"
198
+ elif key == "rpc_endpoints" and isinstance(value, list):
199
+ redacted[key] = [_redact_url(str(v)) for v in value]
200
+ elif key == "rpc_groups" and isinstance(value, dict):
201
+ redacted_groups: dict[str, object] = {}
202
+ for group_name, group_value in value.items():
203
+ if isinstance(group_value, dict):
204
+ endpoints = group_value.get("endpoints")
205
+ if isinstance(endpoints, list):
206
+ group_value = {
207
+ **group_value,
208
+ "endpoints": [_redact_url(str(v)) for v in endpoints],
209
+ }
210
+ redacted_groups[group_name] = group_value
211
+ redacted[key] = redacted_groups
212
+ elif key == "rpc_rate_limits" and isinstance(value, dict):
213
+ redacted[key] = {
214
+ _redact_url(str(endpoint)): limiter for endpoint, limiter in value.items()
215
+ }
216
+ elif key == "telegram" and isinstance(value, dict):
217
+ # Redact bot_token within telegram config
218
+ redacted[key] = {
219
+ **value,
220
+ "bot_token": "***" if value.get("bot_token") else None,
221
+ }
222
+ elif isinstance(value, str) and key.endswith("url"):
223
+ redacted[key] = _redact_url(value)
224
+ else:
225
+ redacted[key] = value
226
+ return redacted
227
+
228
+ def _advanced_or_default(self) -> AdvancedConfig:
229
+ return self.advanced or AdvancedConfig()
230
+
231
+ def _derive_claim_timeout_seconds(self) -> int:
232
+ deadline = self._advanced_or_default().default_deadline_seconds
233
+ return max(60, int(min(300, deadline / 12)))
234
+
235
+ @property
236
+ def poll_interval_seconds(self) -> float:
237
+ return self._advanced_or_default().poll_interval_seconds
238
+
239
+ @property
240
+ def reorg_depth(self) -> int:
241
+ return self._advanced_or_default().reorg_depth
242
+
243
+ @property
244
+ def finality_confirmations(self) -> int:
245
+ return self._advanced_or_default().finality_confirmations
246
+
247
+ @property
248
+ def default_deadline_seconds(self) -> int:
249
+ return self._advanced_or_default().default_deadline_seconds
250
+
251
+ @property
252
+ def stuck_tx_seconds(self) -> int:
253
+ return self._advanced_or_default().stuck_tx_seconds
254
+
255
+ @property
256
+ def max_replacement_attempts(self) -> int:
257
+ return self._advanced_or_default().max_replacement_attempts
258
+
259
+ @property
260
+ def gas_limit_multiplier(self) -> float:
261
+ return self._advanced_or_default().gas_limit_multiplier
262
+
263
+ @property
264
+ def default_priority_fee_gwei(self) -> float:
265
+ return self._advanced_or_default().default_priority_fee_gwei
266
+
267
+ @property
268
+ def max_fee_cap_gwei(self) -> float | None:
269
+ return self._advanced_or_default().max_fee_cap_gwei
270
+
271
+ @property
272
+ def fee_bump_percent(self) -> int:
273
+ return self._advanced_or_default().fee_bump_percent
274
+
275
+ @property
276
+ def rpc_timeout_seconds(self) -> float:
277
+ return self._advanced_or_default().rpc_timeout_seconds
278
+
279
+ @property
280
+ def rpc_max_retries(self) -> int:
281
+ return self._advanced_or_default().rpc_max_retries
282
+
283
+ @property
284
+ def database_pool_size(self) -> int:
285
+ return self._advanced_or_default().database_pool_size
286
+
287
+ @property
288
+ def database_pool_max_overflow(self) -> int:
289
+ return self._advanced_or_default().database_pool_max_overflow
290
+
291
+ @property
292
+ def priority_fee(self) -> int:
293
+ return int(self.default_priority_fee_gwei * 1_000_000_000)
294
+
295
+ @property
296
+ def max_fee(self) -> int | None:
297
+ cap = self.max_fee_cap_gwei
298
+ if cap is None:
299
+ return None
300
+ return int(cap * 1_000_000_000)
301
+
302
+ @property
303
+ def job_error_backoff_blocks(self) -> int:
304
+ return DEFAULT_JOB_ERROR_BACKOFF_BLOCKS
305
+
306
+ @property
307
+ def block_hash_history_size(self) -> int:
308
+ return DEFAULT_BLOCK_HASH_HISTORY_SIZE
309
+
310
+ @property
311
+ def deep_reorg_pause(self) -> bool:
312
+ return False
313
+
314
+ @property
315
+ def claim_timeout_seconds(self) -> int:
316
+ return self._derive_claim_timeout_seconds()
317
+
318
+ @property
319
+ def intent_retry_backoff_seconds(self) -> int:
320
+ return DEFAULT_INTENT_RETRY_BACKOFF_SECONDS
321
+
322
+ @property
323
+ def max_executor_retries(self) -> int:
324
+ return DEFAULT_MAX_EXECUTOR_RETRIES
325
+
326
+ @property
327
+ def nonce_reconcile_interval_seconds(self) -> int:
328
+ return DEFAULT_NONCE_RECONCILE_INTERVAL_SECONDS
329
+
330
+ @property
331
+ def stuck_tx_blocks(self) -> int:
332
+ return DEFAULT_STUCK_TX_BLOCKS
333
+
334
+ @property
335
+ def shutdown_timeout_seconds(self) -> int:
336
+ return DEFAULT_SHUTDOWN_TIMEOUT_SECONDS
337
+
338
+ @property
339
+ def shutdown_grace_seconds(self) -> int:
340
+ return DEFAULT_SHUTDOWN_TIMEOUT_SECONDS
341
+
342
+ @property
343
+ def rpc_retry_backoff_base(self) -> float:
344
+ return DEFAULT_RPC_RETRY_BACKOFF_BASE
345
+
346
+ @property
347
+ def rpc_circuit_breaker_seconds(self) -> int:
348
+ return DEFAULT_RPC_CIRCUIT_BREAKER_SECONDS
349
+
350
+ @property
351
+ def db_circuit_breaker_failures(self) -> int:
352
+ return DEFAULT_DB_CIRCUIT_BREAKER_FAILURES
353
+
354
+ @property
355
+ def db_circuit_breaker_seconds(self) -> int:
356
+ return DEFAULT_DB_CIRCUIT_BREAKER_SECONDS
357
+
358
+ @property
359
+ def rpc_rate_limit_per_second(self) -> float | None:
360
+ return None
361
+
362
+ @property
363
+ def rpc_rate_limit_burst(self) -> int | None:
364
+ return None
365
+
366
+ @property
367
+ def rpc_rate_limits(self) -> dict[str, dict[str, float | int]]:
368
+ return {}
369
+
370
+ @property
371
+ def gas_refresh_seconds(self) -> int:
372
+ return DEFAULT_GAS_REFRESH_SECONDS
373
+
374
+ @property
375
+ def fallback_gas_limit(self) -> int:
376
+ return DEFAULT_FALLBACK_GAS_LIMIT
377
+
378
+ @property
379
+ def telegram_rate_limit_per_minute(self) -> int:
380
+ return DEFAULT_TELEGRAM_RATE_LIMIT_PER_MINUTE
381
+
382
+ @property
383
+ def abi_cache_ttl_seconds(self) -> int:
384
+ return DEFAULT_ABI_CACHE_TTL_SECONDS
385
+
386
+ @property
387
+ def database_pool_timeout_seconds(self) -> float:
388
+ return DEFAULT_DATABASE_POOL_TIMEOUT_SECONDS
389
+
390
+ @property
391
+ def allow_unsafe_nonce_reset(self) -> bool:
392
+ return False
393
+
394
+ @property
395
+ def nonce_gap_alert_seconds(self) -> int:
396
+ return DEFAULT_NONCE_GAP_ALERT_SECONDS
397
+
398
+ @property
399
+ def brownie_password_fallback(self) -> bool:
400
+ return False
401
+
402
+ @property
403
+ def log_retention_days(self) -> int:
404
+ return self._advanced_or_default().log_retention_days