|
33 | 33 | # Constants |
34 | 34 | # --------------------------------------------------------------------------- |
35 | 35 |
|
36 | | -STATE_FILE: Path = Path("/home/user/workspace/hydra-bootstrap/state.json") |
| 36 | +_STATE_DIR: Path = Path( |
| 37 | + os.getenv("HYDRA_STATE_DIR", os.getenv("HYDRA_BOOTSTRAP_DIR", "/tmp/hydra-data")) |
| 38 | +) |
| 39 | +STATE_FILE: Path = _STATE_DIR / "state.json" |
37 | 40 | USDC_DECIMALS: int = 6 |
38 | 41 | HEARTBEAT_INTERVAL: int = 60 # seconds |
39 | 42 |
|
@@ -143,6 +146,7 @@ def __init__( |
143 | 146 | self._last_heartbeat: Optional[datetime] = None |
144 | 147 | self._cached_balance: Decimal = Decimal("0") |
145 | 148 | self._automaton_state: AutomatonState = AutomatonState.BOOT |
| 149 | + self._running: bool = True |
146 | 150 |
|
147 | 151 | # Lifecycle manager (loads phase from state.json) |
148 | 152 | self.lifecycle: LifecycleManager = LifecycleManager() |
@@ -353,7 +357,7 @@ async def _remittance_check(self, balance: Decimal) -> None: |
353 | 357 | from src.runtime.remittance import RemittanceManager |
354 | 358 | rm = RemittanceManager( |
355 | 359 | private_key=self._private_key, |
356 | | - wallet_address=self._wallet_address, |
| 360 | + wallet_address=self.wallet_address, |
357 | 361 | ) |
358 | 362 |
|
359 | 363 | if not rm.receiving_wallet: |
@@ -414,7 +418,7 @@ async def run(self) -> None: |
414 | 418 | asyncio.create_task(automaton.run()) |
415 | 419 | """ |
416 | 420 | logger.info("HydraAutomaton run loop started.") |
417 | | - while True: |
| 421 | + while self._running: |
418 | 422 | try: |
419 | 423 | await self.heartbeat() |
420 | 424 | except Exception as exc: # noqa: BLE001 |
@@ -456,6 +460,44 @@ def get_status(self) -> Dict[str, Any]: |
456 | 460 | # Helpers |
457 | 461 | # ------------------------------------------------------------------ |
458 | 462 |
|
| 463 | + async def stop(self) -> None: |
| 464 | + """Signal the automaton to stop on next heartbeat iteration.""" |
| 465 | + self._running = False |
| 466 | + logger.info("HydraAutomaton stop requested.") |
| 467 | + |
459 | 468 | def _uptime_seconds(self) -> float: |
460 | 469 | """Return seconds since the automaton was initialised.""" |
461 | 470 | return (datetime.now(timezone.utc) - self._start_time).total_seconds() |
| 471 | + |
| 472 | + |
| 473 | +# --------------------------------------------------------------------------- |
| 474 | +# Module-level singleton accessor |
| 475 | +# --------------------------------------------------------------------------- |
| 476 | + |
| 477 | +_automaton_instance: Optional[HydraAutomaton] = None |
| 478 | + |
| 479 | + |
| 480 | +def get_automaton() -> HydraAutomaton: |
| 481 | + """ |
| 482 | + Return the module-level HydraAutomaton singleton. |
| 483 | +
|
| 484 | + The instance is set by ``set_automaton()`` during app startup (lifespan). |
| 485 | + If not set, creates a read-only placeholder using env vars. |
| 486 | + """ |
| 487 | + global _automaton_instance |
| 488 | + if _automaton_instance is not None: |
| 489 | + return _automaton_instance |
| 490 | + |
| 491 | + # Create a fallback instance from environment |
| 492 | + _automaton_instance = HydraAutomaton( |
| 493 | + wallet_address=os.getenv("WALLET_ADDRESS", "0x2F12A73e1e08F3BCE12212005cCaBE2ACEf87141"), |
| 494 | + private_key=os.getenv("WALLET_PRIVATE_KEY", "0x" + "00" * 32), |
| 495 | + base_rpc_url=os.getenv("BASE_RPC_URL", "https://mainnet.base.org"), |
| 496 | + ) |
| 497 | + return _automaton_instance |
| 498 | + |
| 499 | + |
| 500 | +def set_automaton(instance: HydraAutomaton) -> None: |
| 501 | + """Set the module-level HydraAutomaton singleton (called during startup).""" |
| 502 | + global _automaton_instance |
| 503 | + _automaton_instance = instance |
0 commit comments