# Strategy seams — the substrate map and how to add a strategy Companion to ADR-054 (frame evidence seams), ADR-055 (pending-transaction strategy seams), ADR-019 (strategy-vs-engine), ADR-025 (execution strategy), ADR-018 (engine-family trigger, pulled by decision at ADR-055 Phase C), and ADR-057 (the strategy host). ## The runbook The step-by-step companion to this map is [adding-a-strategy.md](adding-a-strategy.md): it carries the real signatures, the admission/lane/ledger/hub wiring, the driver partitions, the config-facet additions, and the test-surface pattern. ## The shared substrate | Layer | Owner | Notes | |---|---|---| | Pool state + identity values | `degenbot-pools` | `PoolEntry`, family states, `slot_layout` (the one location table) | | Solver math + envelopes | `degenbot-solvers` | value-only; no chain, no async; `SolveOutcome` carries the typed `Envelope` verdict to planning | | Path enumeration primitive | `degenbot-pathfinding` | leaf `PathGraph` DFS; both discovery styles build on it | | Simulation engine | `degenbot-simulation` | `BlockSimHandle`, `ScratchEvm`, replay seam, journal extraction (V2/V3/V4) | | Command grammar | `degenbot-executor` | `encode_cmd_stream`, `EncodeRequest` | | RPC spine | `degenbot-rpc` | provider, multicall3, head watch, pending-tx feeds, fee oracle | | State owner + admission | `degenbot-substrate` | live registry `BotState` (pump-fed) and the planning sandbox (`planning::Workspace` + `ExplicitPoolState`) — the substrate's one home since ADR-067; `degenbot-bot` composes it as a peer and re-exports it at the historical `bot_core::*` paths | | Session object identity | `degenbot-substrate::session_registry` | the ONE per-session answer to "is this the same object?" — pools/tokens as typed `DashMap` + `Arc` behind get-or-create, paths reached through a `PathObjectAdapter` handle, positions reached through a `PositionObserver` handle. Identity only: no live state, no I/O, no solver, no submission. A strategy borrows an object reference and derives its own plan; it never holds a private object map. ADR-064 | | Pool ingress (`Db → Chain` tick-map precedence, sealed seed, verify policy) | `degenbot-substrate::pool_ingress` | the one V3/V4 pool-state admission seam; Db/Chain `TickMapSeed` provenance is minted only here, and V4 full-map verification targets `PoolManager` + `PoolId` rather than `StateView` | | Strategy kit (boot-resolved composition) | `degenbot-strategy/src/strategy_kit.rs` | `StrategyKit::resolve` — the provision cell (ingress) + discovery handles a strategy composes | | Strategy plane + concrete compositions | `degenbot-strategy` | the backrun reaction arm (frame pipeline, anchored DFS, gap quarantine, pending-tx driver) and the settlement composition; capability implementations re-exported, never moved; hosted families drive the boot-resolved `StrategyKit` cells (`strategy_kit.rs`) | | Submission machinery | `degenbot-submission` | signer, fee/params/bundle, dispatcher, monitor, submission ledger, finality-liveness FSM | ## The two reaction kinds A strategy picks exactly ONE: - **Pending-transaction strategies** (`PendingTxReaction` trait, `degenbot-strategy/src/pending_tx.rs`) react to observed mempool transactions. The pending-transaction driver owns the substrate loop: simulate the pending tx (`ScratchEvm::replay`) → recover pool post-states (`extract_pool_post_states`) → admit → discover → evaluate → compose → bundle-sim gate → decide → submit. `BackrunStrategy` (`degenbot-strategy/src/backrun_strategy.rs`) is the reference implementation. - **Settled-block strategies** react to sealed blocks via the block pump / `StageMachine` seam; settlement arbitrage is the only one today. Their product types are deliberately settlement-shaped; generalization is Phase C work (ADR-055 D5). ## Adding a pending-transaction strategy — the checklist 1. Write one `PendingTxReaction` impl in `degenbot-strategy`: your `admit` selection (from recovered pool post-states), `discover` (anchored DFS over the connector index, or otherwise), `evaluate` pricing, `compose` payload policy, `decide` gate. **State provisioning is a plane capability, not a private one** (ADR-061; `adding-a-strategy.md` §0.1): the boot resolves a `StrategyKit` once per strategy (`StrategyKit::resolve`) and hands it in, so a strategy composes `kit.provision.ingress` / `kit.discovery` and never an ingress it built itself. Tick maps enter the sandbox only through the sealed `TickMapSeed` boundary — Db/Chain seeds are minted inside `degenbot-substrate::pool_ingress` alone, and replay admission is an ingress operation; strategies do not construct a seed or register directly into the workspace. Verification is a typed `VerifyLevel` policy (default `bootstrap`) on the provisioning cell; the Tracked intake reconciliation and the two-stamp clock are unconditional integrity, never gated by the knob. Replay, journal extraction, the pathfinding primitive, the sim executor, liveness, and the submission channel are provided. 2. V4 works out of the box: the journal extracts V4 post-states for pools whose identities your descriptors carry (`V4PoolSet`), and `PoolIngress::admit_v4_replay` owns staging, backfill, bitmap merge, verification, and registration. The full-map verifier targets the `PoolManager` and `PoolId`; `StateView` is optional scalar/bootstrap configuration, not a verification target. 3. Declare your strategy facet in the typed config (each `strategy.` facet's `active` key is its activation — one declaration site). 4. Choose your `SubmissionTarget` (`Bundle` / `Public`). 5. Register your launcher/console row so the operator can select the strategy explicitly. **Loud-abort rule** (ADR-055 D4): if your strategy asks the substrate for a pool family it cannot serve, that surfaces as a loud typed failure immediately — silent skips are reserved for transient I/O failure, never for capability gaps. ## The strategy host (Phase C, landed) The dynamic strategy host landed 2026-09-19 ([ADR-057](adr/ADR-057-strategy-host.md)): `StrategyHost` (`degenbot-bot/src/strategy_host.rs`) owns the hub, the boot-snapshot `RouteRegistry`, and the `NonceAuthority` (`degenbot-bot/src/nonce_authority.rs`), and drivers attach through the bound `HostHub` pair (`EngineDriver::from_stages_with_hub`, `degenbot-bot/src/arb_engine/driver.rs`). The per-strategy submission ledger, `NonceLane`, and the default `HeadPolicy` live in `degenbot-submission/src/submission_ledger.rs`; the backrun arm is a registrable driver (`degenbot-strategy/src/backrun_driver.rs`); and the operator drives admission through the engine adapter's `enable_strategy` / `disable_strategy` / `strategies` verbs. The driver FSM is `Registered → Enabled → Running → {Halted, Disabled}` with terminal tombstones, and nonces are leased at sign time (lowest-free, contiguous above the confirmed chain nonce). Forward-looking statements reserved to the deferred work: shadow-feedback between drivers (reconciliation observes the nonce-level shadow and acts on nothing) and more than one outstanding nonce lease per strategy. Both are post-v1 revisits, as are process-level submission arbitration and lane hot registration. ## Adding a settled-block strategy — on-demand engine generalization The pump/stage seam and its payload typing are settlement-specific today by design (a sample of one). When a second settled-block strategy exists, the ADR-018-named extraction runs: parameterized stage payloads, a generic `EngineDriver`, per-family fleet globals. The strategy host is landed and can run such a driver; the payload generalization is not pulled by a sample of one, so it stays on-demand. **Phase B landed (2026-09-19):** `degenbot-eventhub` owns per-process intake fan-out with declared overflow policies (`OverflowPolicy`); the backrun feed ring and head watch subscribe through it (B1/B2), both engine→driver channels are hub-registered `UnboundedFlagged` sources with observable depth via `Hub::named_pending` (B3), the gated serving seam was retired (B4, ADR-056), and the boot-snapshot `RouteRegistry` answers pool membership for strategies (B5). A second pending transaction strategy now implements `PendingTxReaction` and subscribes; no intake wiring. The Phase C runtime host now lands on top of it ([ADR-057](adr/ADR-057-strategy-host.md)). ## Where the seams are (exact owners) | Seam | Lives at | |---|---| | Strategy plane (`StrategyName`, `Strategy`, `SelectedStrategy`) | `degenbot-strategy/src/strategy_plane.rs` | | Concrete compositions (`MevblockerBackrun`, `TxpoolBackrun`, `Settlement`) | `degenbot-strategy/src/backrun.rs`, `degenbot-strategy/src/settlement.rs` | | `MarketContext` (frame-surviving caches) | `degenbot-strategy/src/market_context.rs` | | `PendingTxReaction` + artifacts | `degenbot-strategy/src/pending_tx.rs` | | `BackrunStrategy` (reference composition) | `degenbot-strategy/src/backrun_strategy.rs` | | Pending-tx driver (replay/extract/stages/gate) | `degenbot-strategy/src/frame_pipeline.rs` | | `SubmissionTarget` + `dispatch_and_submit` | `degenbot-submission/src/submit.rs` | | Journal extraction (V2/V3/V4) | `degenbot-simulation/src/sim/evm/journal_pools.rs` | | Sandbox + `ExplicitPoolState` | `degenbot-substrate/src/planning.rs` | | Strategy facets (activation + knobs) | `degenbot-config/src/schema.rs` (`strategy.*`) | | Strategy host (hub + registry + authority + FSM) | `degenbot-bot/src/strategy_host.rs` | | Nonce authority | `degenbot-bot/src/nonce_authority.rs` | | Hub hoist / driver attach | `degenbot-bot/src/arb_engine/driver.rs` | | Submission ledger + `NonceLane` + `HeadPolicy` | `degenbot-submission/src/submission_ledger.rs` | | Registrable backrun driver | `degenbot-strategy/src/backrun_driver.rs` | | Settled-block seam today | `degenbot-bot/src/bot_core/stage_handlers.rs` |