Skip to content

docs: multi-chain deployment patterns — Mainnet/Gnosis/Arbitrum/Base#310

Closed
lgahdl wants to merge 9 commits into
dev/m1from
feat/124-multi-chain-deployment
Closed

docs: multi-chain deployment patterns — Mainnet/Gnosis/Arbitrum/Base#310
lgahdl wants to merge 9 commits into
dev/m1from
feat/124-multi-chain-deployment

Conversation

@lgahdl

@lgahdl lgahdl commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

What

Adds docs/deployment/multi-chain.md, a guide for running Shepherd against multiple EVM chains at once. It covers the chain support matrix (Mainnet, Gnosis, Base, Arbitrum One, Sepolia) with chain IDs, orderbook slugs and barn availability, the [chains.<id>] wiring pattern in engine.toml including ${VAR} substitution and the require_ws = false opt-out for poll-only chains, the CoW API orderbook URL resolution and override pattern, the CREATE2-stable contract addresses versus the EthFlow per-network caveat (with a pointer to cowprotocol/ethflowcontract networks.prod.json), the [[subscription]] duplication pattern with annotated twap-monitor and ethflow-watcher examples spanning two chains each, an event topic-0 reference table, and per-chain resource sizing guidance.

Why

Shepherd's engine already dispatches modules per chain, but there was no reference for how to configure and reason about a multi-chain deployment. This is the M5 grant deliverable that documents the pattern so operators can wire additional chains without reverse-engineering the module manifests.

Testing

Documentation only, no code changes. All contract addresses, orderbook slugs and barn eligibility are derived directly from cowprotocol-primitives-0.1.0 (already a workspace dependency) and the existing module.toml manifests rather than manual lookup.

@lgahdl lgahdl requested a review from mfw78 as a code owner July 9, 2026 22:22
@jean-neiverth

Copy link
Copy Markdown
Collaborator

Following up on the require_ws point above — eth_getLogs polling already works over HTTP, and I think that was the case even before #306, which extended the same poll-based approach to block following.

Wonder if this line could mislead operators who only have an HTTP endpoint available. Maybe it could be reworded to something like "WebSocket gives lower-latency streaming, but HTTP works for both block and log subscriptions via polling."

@lgahdl lgahdl linked an issue Jul 10, 2026 that may be closed by this pull request
@mfw78 mfw78 force-pushed the feat/124-multi-chain-deployment branch from eed7c47 to fb475c0 Compare July 14, 2026 02:19
@mfw78 mfw78 force-pushed the feat/m1-mock-venue branch 2 times, most recently from b8aeb1c to d94a4bd Compare July 14, 2026 03:33
@mfw78 mfw78 force-pushed the feat/124-multi-chain-deployment branch 3 times, most recently from daeac17 to c3c9027 Compare July 14, 2026 07:45
@mfw78 mfw78 force-pushed the feat/m1-mock-venue branch 2 times, most recently from b4734ce to e15aa66 Compare July 14, 2026 09:33
@mfw78 mfw78 force-pushed the feat/124-multi-chain-deployment branch 2 times, most recently from 3ed77e2 to e718f06 Compare July 14, 2026 10:05
@mfw78 mfw78 force-pushed the feat/m1-mock-venue branch from 4c508ec to 6f83ed6 Compare July 14, 2026 12:25
@mfw78 mfw78 force-pushed the feat/124-multi-chain-deployment branch from e718f06 to 6b9dc81 Compare July 14, 2026 12:26
mfw78 and others added 9 commits July 14, 2026 12:47
Introduce the world-neutral half of the poll loop in
nexum_sdk::keeper: a ConditionalSource trait (one watch in, one
outcome out, generic over the host, no venue-transport
pre-abstraction), a Tick carrying the dispatch instant, and the
RetryAction (try-next-block / backoff / drop) whose effects the
Retrier runs through the gate and watch-set stores. RetryAction
moves out of shepherd-sdk; the CoW crate re-exports it so no module
rewires.

shepherd-sdk keeps classify_api_error as the CoW errorType table but
returns the keeper action, and encodes two fixes in the one
classification path: classify_poll_error maps an unrecognised revert
selector with a structured payload to DontTryAgain instead of
re-polling every block forever, and DuplicatedOrder (both spellings)
classifies as already-submitted - the run records the
submitted: receipt and keeps the watch rather than dropping every
future tranche. classify_submit_error widens the table to the whole
CowApiError surface and gives Backoff its producer: a rate-limit
fault with server guidance gates the watch on the epoch clock.

cow::run composes the loop: watch-set scan -> gate check ->
source poll -> PollOutcome dispatch, driving submission through the
existing CowApiHost seam with the journal as the idempotency guard
and the retry ledger as the failure dispatch. Acceptance tests run
against the composed shepherd-sdk-test MockHost as integration
tests; no module is rewired yet.
RpcError.data is now Bytes; the test helper takes raw Vec<u8> and wraps
it (Vec -> Bytes is O(1)).
The run logged through the LoggingHost seam, which the
guest tracing capture cannot observe - module tests proving
behaviour identity for keeper ports assert on tracing events.
Route the submit-path diagnostics through the tracing macros with
the wording the legacy twap poll loop established, and give
ConditionalSource a defaulted label so shared log lines attribute
the strategy that produced them.
Reduce strategy.rs to decode and evaluate: log decoding persists
watches through the keeper watch set, and the
getTradeableOrderWithSignature evaluation moves behind
ConditionalSource. The hand-rolled gate keys, watch-update
lifecycle, submitted: markers, and submit-retry dispatch are
deleted in favour of cow::run over the keeper stores and
retry ledger, still on the legacy CowApiHost seam. The MockHost
dispatch tests are untouched - the behaviour-identity proof for
the port.
Rework MockLocalStore to mirror the runtime store's shape: namespaced
views over one shared row map, so identical key strings in sibling
namespaces never collide, plus store-wide entry and byte limits that
span namespaces the way one database file does. Fault injection stays
per-view.
Script per-call venue behaviour where MockCowApi replays one response:
a strictly-draining queue of submit outcomes with a configurable
steady-state fallback, per-route response sequences whose final entry
sticks (a terminal order status persists across re-polls), and venue
fault injection that overrides both without consuming them. MockHost
grows a defaulted venue type parameter so the composed host carries
either mock on the same CowApiHost seam. Acceptance tests drive the
keeper run across multi-tick retry, backoff, and outage
scenarios, and module-shaped strategy code against status sequences.
…124)

New docs/deployment/multi-chain.md covering the verbatim M5 grant
deliverable: per-chain [chains.<id>] + env-var wiring, CoW orderbook
URL slugs per network, per-chain contract addresses (CREATE2-stable
vs EthFlow per-network caveat), the [[subscription]] duplication
pattern with twap-monitor and ethflow-watcher examples, event topic
reference, and resource sizing guidance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfSsJPYDQ1GQGbnSVFxATx
…ents

Review catches:

- The EthFlow section claimed the address is per-network and told
  operators to hunt for a different mainnet address. The current
  production deployment is address-identical on every supported chain
  (cowprotocol's ETH_FLOW_PRODUCTION, from networks.prod.json) - the
  old text would have steered a mainnet port toward the legacy v1.0.0
  deployment and missed every OrderPlacement event. The real caveat is
  narrower and now stated as such: sameness is not a CREATE2 guarantee,
  legacy per-version deployments exist, verify on version bumps.
- "every chain the orderbook supports" was false (the Chain enum also
  carries BNB, Polygon, Avalanche, Linea, Plasma); the table is scoped
  to the chains this repo's modules target.
- Dropped the unsupported "Gnosis volume roughly equal to Mainnet"
  claim; require_ws violations are an ERROR log, not a warning; the
  RPC recommendation names a rate requirement instead of a specific
  provider's free tier.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfSsJPYDQ1GQGbnSVFxATx
@mfw78 mfw78 force-pushed the feat/124-multi-chain-deployment branch from 6b9dc81 to 255c4aa Compare July 14, 2026 12:47
@mfw78 mfw78 force-pushed the feat/m1-mock-venue branch 2 times, most recently from 02e6276 to 6076837 Compare July 14, 2026 23:33
Base automatically changed from feat/m1-mock-venue to dev/m1 July 14, 2026 23:36
@mfw78 mfw78 added the base: dev/m1 Open PR targets dev/m1 integration branch — merge AFTER develop PRs; develop merged in last. label Jul 15, 2026
@mfw78

mfw78 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Superseded by #351 — rebased onto current dev/m1 (dropped redundant train commits), Claude trailers + body footer stripped, authorship preserved (@lgahdl). Closing in favour of #351.

@mfw78 mfw78 closed this Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

base: dev/m1 Open PR targets dev/m1 integration branch — merge AFTER develop PRs; develop merged in last.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: multi-chain deployment patterns (Mainnet/Arbitrum/Base/Gnosis)

3 participants