feat: scaffold permissioned surplus pools#248
Conversation
Adds the structure for routing through permissioned (fair-flow hook) pools that only Fynd accesses, capturing surplus above the best public-market rate while quoting the user the public rate. Public worker pools gain a component filter (PermissionPolicy + ComponentScope) to exclude permissioned components from their graph; a Surplus-role pool includes them. WorkerPoolRouter gains PoolRole and a role-aware combine_with_surplus step. OrderQuote carries an order-level SurplusInfo (reporting aggregate) and Swap carries a per-leg committed_amount_out the encoder reads to derive the hook maxExchangeRate; both are serde(skip) and never reach the public DTO. Function bodies are todo!(): the surplus selection, role-aware response gating, and the topology/event filters are specified in comments but not implemented. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No API Breaking Changes DetectedThe PR title signals breaking changes, but |
There was a problem hiding this comment.
🤖 Scaffold review — interface & plan focus. Two seams worth deciding before combine_with_surplus is implemented; both stem from the surplus path replacing rather than augmenting the existing ranked-candidate flow. Not blockers for the scaffold.
kayibal
left a comment
There was a problem hiding this comment.
some smaller issues
- Encapsulate graph filtering in PermissionContext (moved into feed/permission.rs). Workers now delegate via filter_topology/scope_event instead of matching on scope/policy inline. - combine_with_surplus augments rather than replaces the ranked-candidate flow: it takes the public ranking from rank_quotes (the committed reference and price-guard fallback chain) and returns it with the surplus winner prepended, instead of a single quote that dropped the fallbacks. - Drop /v1/quote HTTP references from SurplusInfo/OrderQuote docs (core is layer-agnostic); reframe order-level surplus as informational, with per-leg Swap::committed_amount_out as the value the encoder reads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks @kayibal - the structure itself looks sound and pretty close to what I envisioned. Just a couple comments here that could improve understandability. If we take care of those I think the generalization is okay 👍
| /// output. The single surplus pool additionally routes through permissioned pools and may beat the | ||
| /// public reference, in which case the protocol captures the surplus (`egAmount`). | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| pub enum PoolRole { |
There was a problem hiding this comment.
I think a lot of confusion comes from the word "pool" meaning two different things though the docs in this PR:
- AMM pool
- Worker pool - a group of solver workers that route across many AMM pools
I guess this is a Fynd-wide problem.
Even a simple naming convention like "component" for AMM pools and "pool" for worker pools (which the codebase already partially does with ProtocolComponent) would reduce confusion
There was a problem hiding this comment.
Agreed -- this is a Fynd-wide naming issue beyond this PR. In the docs touched here I now call AMM pools 'components' and reserve 'pool' for worker pools (the PoolRole doc spells out 'a group of workers'). Happy to file a follow-up for a repo-wide convention.
- Standardize terminology: drop 'fair-flow' and 'Fynd-exclusive' in favour of 'permissioned' (one note explains it means Fynd-only); prefer 'surplus' over 'egAmount' in prose; refer to AMM pools as components. - Rename PoolRole::Surplus -> PoolRole::All (a role names the liquidity scope; 'surplus' is the outcome). The 'surplus' config value still maps to it. - Trim combine_with_surplus doc: drop the formula block and conservativeness proof (kept in the design plan) and describe what the function does; replace 'haircut' with concrete wording. - Trim SurplusInfo to two sentences; clarify the order-level surplus is informational and the encoder reads the per-leg Swap::committed_amount_out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aligns the code with the docs after standardizing on 'surplus': SurplusInfo.eg_amount and the OrderQuote::eg_amount() getter become surplus_amount. The design plan is updated to match (egAmount only noted once as the on-chain hook variable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What this is
A scaffold (structure only,
todo!()bodies) for routing through permissioned "fair-flow hook" pools that only Fynd can access. The trade executes through the permissioned pool, the user is quoted the best public-market rate, and the protocol captures the surplus (egAmount) above that rate. On-chain hook contracts and permissioned-pool ingestion are out of scope; this is the off-chain solving side.Open it to review the structure and the design encoded in the doc comments — no logic is implemented yet.
What's in the diff
fynd-core/src/feed/permission.rs(new) —PermissionPolicy(anis_permissioned(&ProtocolComponent) -> boolpredicate),ComponentScope { ExcludePermissioned, IncludeAll }, andfilter_topology/filter_component_idshelpers (todo!()).worker.rs/pool.rs/registry.rs— thread a per-worker permission context so public pools filter permissioned components out of their local graph ininitialize_graphandprocess_event. The sharedMarketStateis not duplicated.worker_pool_router/mod.rs—PoolRole { Public, Surplus }onSolverPoolHandle; a role-awarecombine_with_surpluswired intoquote(). Its doc comment carries the full per-leg attribution math (pro-rata haircut, the concavity proof that the user is never quoted worse than the public route, rounding/clamp rules).types/quote.rs—SurplusInfo(order-level reporting aggregate) onOrderQuote, and a per-legcommitted_amount_outonSwapthat the encoder reads to derive the hook'smaxExchangeRate. Both are#[serde(skip)]and never reach the public/v1/quoteDTO.solver.rs—PoolConfig.role(serde) +FyndBuilder::permission_policy(...);assemble_componentstags each pool with its role and scope.encoder.rs,fynd-rpc-types,worker_pools.toml— a TODO to read the committed amount into the encode path, a NOTE that surplus fields stay out of the DTO, and a commented[pools.surplus]example.Not implemented (next, on top of this scaffold)
combine_with_surplusbody (surplus-vs-public selection + per-leg stamping).solve_order(wait for a public and the surplus candidate, not just the first response).filter_topology/filter_component_idsbodies.