feat(bot): regime-derived profit-proportional bidding + vendor crypto (WP 7913) #19

Merged
buddysan merged 2 commits from perf/bidding-vendor into main 2026-09-16 14:51:49 +00:00
Owner

Summary

Phase 4 lane, second PR (WP 7913 + the bot/vendor signing primitives used by the next PR).

bot/bidding.pyOpportunity → Bid, profit-proportional and regime-derived (architecture §2.4/§4.2, docs/research/arc-arb-learnings.md §4.4):

base_part  = max(next_base_fee(extra_data) × headroom, 20 gwei floor)
net        = gross − ceil(gas × base_part / 1e12)
affordable = floor((net − min_profit − 1) × 1e12 / gas)     # strictly keeps the margin after ceil-rounded cost
lower      = max(floor, rival_tip_floor × (1 + bump))        # rival = percentile of index-k tips, rolling window
priority   = clamp(profit_fraction × net → wei/gas, lower, min(cap, affordable)); never maxFee == priority

TipRegime ingests full blocks (same effective-tip rule as docs/research/probes/ordering_regime_probe.py), refuses to bid on a cold window (regime_min_blocks, "never tuned once"), and refresh_from_pool tries the announcing WS source first, then every other read endpoint — a fast WS head is not yet imported by the local node (~120ms lag, measured in PR #15).

Gas rate — no duplicate table. Reuses the already-committed [chain].gas_token_erc20 / gas_wei_per_erc20_unit (revenue-director's search config, verified 1e12 for 0x3600…0000) instead of a private [[bidding.gas_rate]] list — one source of truth, an Opportunity on any other base token is a hard error.

bot/vendor/ (keccak-256, secp256k1 with RFC 6979 + EIP-2 low-s + recovery, RLP) — spec-derived, no third-party code. Rationale in bot/vendor/README.md: cast mktx costs 189–703 ms per signature on LXC 140 (load ~10, 4 vCPU) vs ~1 ms in-process; CEO-approved 2026-09-16 given a pluggable Signer still lands (next PR) with CastSigner as the shell-out fallback. Every primitive is pinned to cast-generated vectors (Foundry 1.8.1) plus the EIP-155 spec vector — bit-exact signatures proven in the next PR's test_submit.py.

Property tests (bot/tests/test_bidding.py): floor ≤ priority ≤ cap, priority ≤ affordable ceiling, margin always kept (net_after_bid > min_profit), monotone in net profit, max_fee > priority always, all values int. 500 randomized cases over floor/cap/fraction/regime combinations.

Test plan

  • make lint test green locally (238 tests, whole repo) and on LXC 140 (Python 3.12.3)
  • bot/tests/test_vendor_crypto.py: keccak vectors vs cast keccak; secp256k1 sign/verify/recover vs cast wallet sign --no-hash; RLP canonical examples + the EIP-155 spec's own raw tx, round-tripped and re-signed to reproduce its published r,s,v
  • config/bot.example.toml's [bidding] section itself parses and validates (test_from_dict_and_env_override's final assertion loads the real committed file)

🤖 Generated with Claude Code

## Summary Phase 4 lane, second PR (WP 7913 + the `bot/vendor` signing primitives used by the next PR). **`bot/bidding.py`** — `Opportunity → Bid`, profit-proportional **and** regime-derived (architecture §2.4/§4.2, `docs/research/arc-arb-learnings.md` §4.4): ``` base_part = max(next_base_fee(extra_data) × headroom, 20 gwei floor) net = gross − ceil(gas × base_part / 1e12) affordable = floor((net − min_profit − 1) × 1e12 / gas) # strictly keeps the margin after ceil-rounded cost lower = max(floor, rival_tip_floor × (1 + bump)) # rival = percentile of index-k tips, rolling window priority = clamp(profit_fraction × net → wei/gas, lower, min(cap, affordable)); never maxFee == priority ``` `TipRegime` ingests full blocks (same effective-tip rule as `docs/research/probes/ordering_regime_probe.py`), refuses to bid on a cold window (`regime_min_blocks`, "never tuned once"), and `refresh_from_pool` tries the announcing WS source first, then every other read endpoint — a fast WS head is not yet imported by the local node (~120ms lag, measured in PR #15). **Gas rate — no duplicate table.** Reuses the already-committed `[chain].gas_token_erc20` / `gas_wei_per_erc20_unit` (revenue-director's search config, verified `1e12` for `0x3600…0000`) instead of a private `[[bidding.gas_rate]]` list — one source of truth, an Opportunity on any other base token is a hard error. **`bot/vendor/`** (keccak-256, secp256k1 with RFC 6979 + EIP-2 low-s + recovery, RLP) — spec-derived, no third-party code. Rationale in `bot/vendor/README.md`: `cast mktx` costs 189–703 ms per signature on LXC 140 (load ~10, 4 vCPU) vs ~1 ms in-process; CEO-approved 2026-09-16 given a pluggable `Signer` still lands (next PR) with `CastSigner` as the shell-out fallback. Every primitive is pinned to `cast`-generated vectors (Foundry 1.8.1) plus the EIP-155 spec vector — bit-exact signatures proven in the next PR's `test_submit.py`. **Property tests** (`bot/tests/test_bidding.py`): floor ≤ priority ≤ cap, priority ≤ affordable ceiling, margin always kept (`net_after_bid > min_profit`), monotone in net profit, `max_fee > priority` always, all values `int`. 500 randomized cases over floor/cap/fraction/regime combinations. ## Test plan - [x] `make lint test` green locally (238 tests, whole repo) and on LXC 140 (Python 3.12.3) - [x] `bot/tests/test_vendor_crypto.py`: keccak vectors vs `cast keccak`; secp256k1 sign/verify/recover vs `cast wallet sign --no-hash`; RLP canonical examples + the EIP-155 spec's own raw tx, round-tripped and re-signed to reproduce its published `r,s,v` - [x] `config/bot.example.toml`'s `[bidding]` section itself parses and validates (`test_from_dict_and_env_override`'s final assertion loads the real committed file) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(bot): regime-derived profit-proportional bidding + in-repo keccak/secp256k1/RLP (WP 7913)
All checks were successful
ci / forge-tests (pull_request) Successful in 4s
ci / python-tests (pull_request) Successful in 39s
3ceccdbd24
bot/bidding.py
- Opportunity → Bid per architecture §2.4/§4.2 and learnings §4.4: priority = clamp(profit_fraction × net,
  max(floor, rival_tip_floor × (1+bump)), min(cap, affordable)), affordable = (net − min_profit − 1)/gas so
  the margin is always kept; max_fee = max(next_base_fee(extra_data) × 1.25, 20 gwei) + priority — never a
  full-ceiling bid (maxFee == priority). Pure integer math; gas-wei → base units via a configured rational
  per base token (1/1e12 for 0x3600…0000, verified); unknown base token is a hard error.
- TipRegime: rolling window of the last N blocks' index-0/index-k effective tips (same rule as
  docs/research/probes/ordering_regime_probe.py) and the next base fee from extra_data; a cold window
  (< regime_min_blocks) refuses to bid ("never tuned once"). refresh_from_pool tries the announcing WS
  source first and every read endpoint after — a fast WS head is not yet imported by the local node.
- Opportunity validation (§2.3, §4.1, §4.4, optional `contention`), opportunity_id, route_key; Bid
  carries block + registry_hash (§4.8). Config: [bidding] in config/bot.toml, every key required, env
  ARC_MEV_BIDDING_<KEY> overrides (§4.7).
- Property tests: floor ≤ priority ≤ cap, ≤ affordable ceiling, monotone in net profit, margin kept,
  max_fee > priority, ints only.

bot/vendor/ (keccak.py, secp256k1.py, rlp.py, README.md with provenance)
- spec-derived, dependency-free primitives the stdlib lacks; pinned to `cast` (Foundry 1.8.1) vectors
  and the EIP-155 spec vector; RFC 6979 deterministic nonces, EIP-2 low-s, public-key recovery.
  Rationale: `cast mktx` per tx costs 190-700 ms on LXC 140 vs ~1 ms in-process (measured; CEO-approved).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: BuddySan <[email protected]>
Merge branch 'main' into perf/bidding-vendor
Some checks failed
ci / forge-tests (pull_request) Successful in 6s
ci / python-tests (pull_request) Successful in 36s
ci / forge-tests (push) Failing after 1s
ci / python-tests (push) Successful in 36s
dfc107b29a
fortiblox-dev left a comment

automerger v2 auto-approved: all 7 gates pass. Approver: fortiblox-dev (opposite-identity rule). Gates: mergeable=true,label=auto-merge,author=buddysan,identity=ok,status_checks=not_required,signatures=upstream_BP_only,opt_in=repo_marker.

automerger v2 auto-approved: all 7 gates pass. Approver: fortiblox-dev (opposite-identity rule). Gates: mergeable=true,label=auto-merge,author=buddysan,identity=ok,status_checks=not_required,signatures=upstream_BP_only,opt_in=repo_marker.
buddysan deleted branch perf/bidding-vendor 2026-09-16 14:51:49 +00:00
Author
Owner

Auto-merged by forgejo-automerger at 2026-09-16 14:51:49 UTC. Approver: fortiblox-dev (opposite-identity rule, Option C upgrade 2026-05-13). Merger: buddysan via fast-forward-only (NOT admin force_merge). Criteria passed: mergeable=true,label=auto-merge,author=buddysan,identity=ok,status_checks=not_required,signatures=upstream_BP_only,opt_in=repo_marker. Branch dfc107b29a merged onto main and feature branch deleted. See project_forgejo_automerger memory for rationale.

Auto-merged by **forgejo-automerger** at 2026-09-16 14:51:49 UTC. **Approver:** `fortiblox-dev` (opposite-identity rule, Option C upgrade 2026-05-13). **Merger:** `buddysan` via `fast-forward-only` (NOT admin `force_merge`). **Criteria passed:** `mergeable=true,label=auto-merge,author=buddysan,identity=ok,status_checks=not_required,signatures=upstream_BP_only,opt_in=repo_marker`. Branch `dfc107b29a` merged onto `main` and feature branch deleted. See `project_forgejo_automerger` memory for rationale.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
fortiblox/arc-mev-bot!19
No description provided.