Conversation
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)
# Conflicts: # Makefile
…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.
feat: rfq strategy
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>
feat: add uniswapx plan
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
feat: 3f strategy
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merge stage into main.