Skip to content

Stage - #61

Merged
1kresh merged 77 commits into
mainfrom
stage
Jul 7, 2026
Merged

Stage#61
1kresh merged 77 commits into
mainfrom
stage

Conversation

@1kresh

@1kresh 1kresh commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Merge stage into main.

oxsteins and others added 30 commits June 26, 2026 15:17
Rework the 3F Bridge Facilitator from a single-adapter, API-key model
into a multi-adapter, signed-payload model:

- Adapter-as-facilitator auth: drop the API key and offer-address.
  Create offers as signed payloads and list them with a per-adapter
  EIP-712 GetOffers Authorization header, authorized via the adapter's
  EIP-1271 isValidSignature. Adds GetOffersDigest (grunt-api domain,
  no verifyingContract, chainId=1) with golden + apitypes + live tests.
- Config takes an adapters list. Each adapter's vault and collateral are
  resolved on-chain at startup in two batched Multicalls, and the solver
  verifies it is the adapter's EIP-1271 offerSigner, dropping any it
  isn't and shutting down if none match (no redeem-only mode).
- Per-auction coverage: cover an auction's full requested amount in a
  single pass with one or more single-adapter offers (most-fundable
  first, each sized to the uncovered remainder), gated on live coverage
  so a fully-covered auction is never re-offered. The return floor
  (minRequestYieldBps) is enforced at selection. Offer dedup is keyed by
  (adapter, auction) and carries principal; redeem and reconcile run for
  every matched adapter, failing soft per adapter.
A solver whose Run returns a fatal error previously only propagated it
as the top-level return; with several solvers sharing the process the
healthy ones shut down cleanly while the failed one left no error in the
structured logs. Log it, attributed to the solver, before returning.
Update 3F-PLAN.md and the README 3F section for the multi-adapter,
signed-payload model (adapter-as-facilitator, per-auction coverage,
floor-at-selection), and add the implementation plan.
Replace offerAuction's re-size-every-round greedy loop with a single
pass: sizeOffer now returns each eligible adapter's capacity (no longer
clamped to the ask), and selectOffers ranks candidates by capacity and
assigns each the principal to offer (min(capacity, uncovered)) until the
auction's remainder is filled. offerAuction just submits the returned
offers. Drops the per-round candidate rebuild, the byAdapter map, and the
tried set; gives the future min-amount exposure param a single home.
…i domain

The signed GET /v1/offer requires chainId in the query: the 3F server
rebuilds the grunt-api EIP-712 domain from it to verify the signature and
routes the EIP-1271 check to that chain. Sign the GetOffers digest and send
the query chainId with the bot's operating chain (1 on mainnet, 11155111 on
Sepolia), not a hardcoded 1.

Also surface the server's response body in API errors (the generated client's
error is otherwise only the status line), and re-vendor the 3F OpenAPI spec
(doc-only drift; the generated client is unaffected).
FAILED (consume reverted) and NOT_ACCEPTED offers are not live coverage —
caching them at startup would wrongly suppress re-offering on those
auctions. Skip them in rebuildOfferCache so discovery re-offers (3F
retries failed offers too).
feat(3f): multi-adapter bridge facilitator with signed-payload auth
# Conflicts:
#	internal/solvers/bridgefacilitator/config.go
…floor

Regenerate the 3F adapter binding from core-mirror's ThreeFAdapter instead of
rfq's BridgeFacilitatorAdapter, and adapt the solver to its ABI:

- Makefile: vendor ThreeFAdapter from CORE_MIRROR_OUT; bindings map
  ThreeFAdapter:3f/adapter. Removed the BridgeFacilitatorAdapter binding/ABI.
- chainreader: read minRequestYield (ppm) and convert to bps (ppmToBps, ceil so
  the pre-screen never bids below the on-chain floor); drop the totalMaxCollateral
  read — the delegator's limitOf is the sleeve ceiling, already folded into fundable.
- sizer: drop the sleeve clamp (sleeveMax/outstanding); fundable carries the
  sleeve ceiling.
- docs/3F-PLAN: sync adapter source + exposure caps (no sleeve cap, ppm yield).
… requests, finalizeRequest)

The delegator-simplify ThreeFAdapter was refactored (44f50b56 "refactor: 3f")
after the initial binding, so re-target the solver at the current contract:

- bindings: regenerate from the refactored ABI (getMaxAssets, requests/requestIndex,
  finalizeRequest, multicall, setLimitsPerRequest, min/maxAssetsPerRequest,
  minYieldPerRequest). Scoped to the 3f adapter only.
- chainreader: fundable now reads the adapter's own getMaxAssets() (drops the
  delegator/vault limitOf+withdrawable math and the vaultDelegator cache). Enumerate
  the active request set via requests(0..49) — no length getter — with a shared
  collectRequests prefix helper. Read the per-request caps (minYieldPerRequest ppm,
  minAssetsPerRequest, maxAssetsPerRequest).
- sizer/selection: maxAssets/minAssets replace perRequestMax; enforce the minAssets
  floor (sizer pre-screen + post-clamp re-check in selectOffers). Concurrency is the
  MAX_REQUESTS const (50).
- redeemer: finalize via adapter.multicall(finalizeRequest...) in a single tx.
- tests + docs/3F-PLAN synced.
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.4.0 to 6.5.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](actions/setup-go@4a36011...924ae3a)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 6.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 9.2.1 to 9.3.0.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](golangci/golangci-lint-action@82606bf...ba0d7d2)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: 9.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Replace the requests(0..49) enumeration (one Multicall sub-call per possible
slot, every discover tick) with the adapter's new requestsLength() getter:

- chainreader: openCount is a single requestsLength() read folded into the
  liquidity/exposure multicall. The redeemer reads requestsLength() and
  enumerates exactly that many requests(i) (AllowFailure guards a finalize race).
- regenerate the adapter binding + ABI for requestsLength().
- docs/3F-PLAN synced.

Depends on the core-mirror requestsLength() getter (symbioticfi/core-mirror#10).
feat(3f): re-target solver at refactored ThreeFAdapter (getMaxAssets, requests, finalizeRequest)
…t-all)

ThreeFAdapter reverts TooLargeRequest when principal > maxAssetsPerRequest, so 0
means reject-all, not "no ceiling". The sizer skipped the clamp at 0 (carried
over from BridgeFacilitatorAdapter's perRequestMaxCollateral semantics), so once
fundable > 0 with an unconfigured maxAssetsPerRequest the bot would size to
fundable, sign an offer, and have the consume revert (nonce/gas wasted).

Always clamp by maxAssets (0 -> capacity 0 -> no bid), matching the contract.
minAssetsPerRequest / minYieldPerRequest are genuinely vacuous at 0 (their
lower-bound checks can't trip against a non-negative principal), so their
0 = no-floor handling is correct and unchanged. Comments corrected.
fix(3f): treat maxAssetsPerRequest as an always-active cap (0 = reject-all)
…onfig

Every background-refreshed cache the bid path reads now stamps a wall-clock
updatedAt only on a successful store: the monitor snapshot (Morpho markets/
positions + adapter/vault quotes) and the ops-loop cachedState (Executor
accounting, callback balance, loan/ETH rate, gas predictor). buildBid fails
closed with stale_state (error log naming each stale component and its age)
when any stamp exceeds intervals.maxStateAgeMs — a loop that keeps failing
while serving prior data stops bidding instead of running on old state.

parseConfig validates every background poll interval (opsPollMs,
monitorPollMs) is strictly < maxStateAgeMs, and executor/callback/adapter
now use parse.NonZeroAddress (matching the rfq/3f solvers).

Plan §3.3 records the rule: any future background state consumed by the bid
path must stamp updatedAt, join staleStateGate, and validate its interval.
alrxy and others added 29 commits July 6, 2026 17:11
Bumps the go group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github.com/ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) | `1.17.3` | `1.17.4` |
| [github.com/getsentry/sentry-go](https://github.com/getsentry/sentry-go) | `0.46.2` | `0.47.0` |
| [github.com/gorilla/websocket](https://github.com/gorilla/websocket) | `1.5.0` | `1.5.3` |
| [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) | `1.15.0` | `1.23.2` |
| [go.uber.org/zap](https://github.com/uber-go/zap) | `1.27.0` | `1.28.0` |



Updates `github.com/ethereum/go-ethereum` from 1.17.3 to 1.17.4
- [Release notes](https://github.com/ethereum/go-ethereum/releases)
- [Commits](ethereum/go-ethereum@v1.17.3...v1.17.4)

Updates `github.com/getsentry/sentry-go` from 0.46.2 to 0.47.0
- [Release notes](https://github.com/getsentry/sentry-go/releases)
- [Changelog](https://github.com/getsentry/sentry-go/blob/master/CHANGELOG.md)
- [Commits](getsentry/sentry-go@v0.46.2...v0.47.0)

Updates `github.com/gorilla/websocket` from 1.5.0 to 1.5.3
- [Release notes](https://github.com/gorilla/websocket/releases)
- [Commits](gorilla/websocket@v1.5.0...v1.5.3)

Updates `github.com/prometheus/client_golang` from 1.15.0 to 1.23.2
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](prometheus/client_golang@v1.15.0...v1.23.2)

Updates `go.uber.org/zap` from 1.27.0 to 1.28.0
- [Release notes](https://github.com/uber-go/zap/releases)
- [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md)
- [Commits](uber-go/zap@v1.27.0...v1.28.0)

---
updated-dependencies:
- dependency-name: github.com/ethereum/go-ethereum
  dependency-version: 1.17.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/getsentry/sentry-go
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: github.com/gorilla/websocket
  dependency-version: 1.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: go
- dependency-name: github.com/prometheus/client_golang
  dependency-version: 1.23.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
- dependency-name: go.uber.org/zap
  dependency-version: 1.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: oxsteins <oxsteins@gmail.com>
Signed-off-by: oxsteins <oxsteins@gmail.com>
refactor(rfq): updated strategy files in rfq
New config/rfq.mainnet.example.yaml mirroring rfq.hoodi.example.yaml for
Ethereum mainnet (chainId 1): the mevblocker RPC fallback, the RFQ reactor
(0x5eB54c47…35dac) and executor (0xe60E8421…69Cf), solverMode external, and
the LiquidLane adapter scope. The `adapters` entry documents that concrete
per-token adapter instances (deployed by factory 0x3b5Bb07d…1749460) are
filled at deploy; secrets/URLs stay ${ENV}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add chain.writeRpcUrl (env WRITE_RPC_URL). When set, it is dialed as a
separate client used ONLY to broadcast signed transactions — chain.Client
overrides SendTransaction to route through it — while every read (nonce, gas,
fee, receipts, block number) stays on rpcUrl. Empty reuses the primary, so
behaviour is unchanged. This lets fills be submitted through a private/
MEV-protected relay (e.g. mevblocker) while state is read from a normal RPC,
sidestepping the nonce/receipt-consistency pitfalls of splitting reads.

- config: ChainConfig.WriteRPCURL (optional, ${WRITE_RPC_URL}); env-expanded.
- chain.Dial: dials a second client when writeRpcUrl is set; Close() closes both.
- chain.Client.SendTransaction/Close override the promoted ethclient methods.
- tests: config env-expansion + optional; chain routing (only broadcasts hit
  the write endpoint; reads stay on the primary; empty reuses the primary).
- docs: config.example.yaml + rfq.mainnet.example.yaml document the split.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lane

NO-TASK vault-solver mainnet: RFQ profile + write-only RPC
…o-484aea1235

chore(deps): bump the go group across 1 directory with 5 updates
…ge/golangci/golangci-lint-action-9.3.0

chore(deps): bump golangci/golangci-lint-action from 9.2.1 to 9.3.0
…ge/actions/setup-go-6.5.0

chore(deps): bump actions/setup-go from 6.4.0 to 6.5.0
Signed-off-by: oxsteins <oxsteins@gmail.com>
Signed-off-by: oxsteins <oxsteins@gmail.com>
Signed-off-by: oxsteins <oxsteins@gmail.com>
Add mainnet to the stage (gprptest) and prod (symbiotic.fi, on main) deploy
matrices/jobs so merging to main deploys mainnet alongside hoodi + sepolia.
Deploy-time prereqs (signers, solver registration, DNS) are handled out of band.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NO-TASK Un-gate mainnet deploys (gprptest + symbiotic.fi)
Signed-off-by: oxsteins <oxsteins@gmail.com>
The mainnet deploy failed (Deployment vault-solver never Ready, 0/3, helm
rollback) because .github/chart/mainnet.yaml was EMPTY — hoodi/sepolia carry the
whole vault-solver config.yaml in their network file, so mainnet shipped a
ConfigMap with no config and the daemon exited at startup (config Validate
requires chain.rpcUrl / chainId / >=1 solver).

- Populate mainnet.yaml mirroring the known-good hoodi/sepolia profile: chainId 1,
  reactor 0x5eB5…35dac, writeRpcUrl  (private/MEV relay for sends),
  solverMode internal, empty adapters + permissionedTokens (mGLOBAL tabled on
  mainnet; concrete mF-ONE LiquidLane adapter instance TBD).
- deploy.yml EXECUTOR was a hoodi-vs-else binary, so mainnet silently got sepolia's
  executor — extend to a 3-way so mainnet gets 0xe60E…C69Cf.
- Skip the first/second/third (mGLOBAL) solvers on mainnet via
  if: inputs.network != 'mainnet' — only the permissionless daemon deploys there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chore: add LICENSE and fix docs/code
…config

fix(infra): vault-solver mainnet deploy (empty mainnet.yaml → crash)
@1kresh
1kresh merged commit 6a1faf3 into main Jul 7, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants