Skip to content

feat: scaffold permissioned surplus pools#248

Open
kayibal wants to merge 4 commits into
mainfrom
feat/permissioned-surplus-scaffold
Open

feat: scaffold permissioned surplus pools#248
kayibal wants to merge 4 commits into
mainfrom
feat/permissioned-surplus-scaffold

Conversation

@kayibal

@kayibal kayibal commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

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 (an is_permissioned(&ProtocolComponent) -> bool predicate), ComponentScope { ExcludePermissioned, IncludeAll }, and filter_topology / filter_component_ids helpers (todo!()).
  • worker.rs / pool.rs / registry.rs — thread a per-worker permission context so public pools filter permissioned components out of their local graph in initialize_graph and process_event. The shared MarketState is not duplicated.
  • worker_pool_router/mod.rsPoolRole { Public, Surplus } on SolverPoolHandle; a role-aware combine_with_surplus wired into quote(). 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.rsSurplusInfo (order-level reporting aggregate) on OrderQuote, and a per-leg committed_amount_out on Swap that the encoder reads to derive the hook's maxExchangeRate. Both are #[serde(skip)] and never reach the public /v1/quote DTO.
  • solver.rsPoolConfig.role (serde) + FyndBuilder::permission_policy(...); assemble_components tags 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_surplus body (surplus-vs-public selection + per-leg stamping).
  • Role-aware response gating in solve_order (wait for a public and the surplus candidate, not just the first response).
  • filter_topology / filter_component_ids bodies.
  • Encoder hook calldata / signature.

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>
@github-actions

Copy link
Copy Markdown

No API Breaking Changes Detected

The PR title signals breaking changes, but cargo-semver-checks found none.
If the breaking change is behavioral, CLI, or config-level (not public Rust API), this is expected.
Otherwise, consider using fix: instead of feat: in the PR title.

@kayibal kayibal left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

Comment thread fynd-core/src/worker_pool_router/mod.rs Outdated
Comment thread fynd-core/src/worker_pool_router/mod.rs Outdated

@kayibal kayibal left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some smaller issues

Comment thread fynd-core/src/types/quote.rs Outdated
Comment thread fynd-core/src/types/quote.rs Outdated
Comment thread fynd-core/src/worker_pool/worker.rs Outdated
Comment thread fynd-core/src/worker_pool/worker.rs Outdated
- 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>
Comment thread fynd-core/src/worker_pool_router/mod.rs
Comment thread fynd-core/src/feed/permission.rs

@tamaralipows tamaralipows left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Comment thread fynd-core/src/encoding/encoder.rs Outdated
Comment thread fynd-core/src/worker_pool_router/mod.rs Outdated
Comment thread fynd-core/src/worker_pool_router/mod.rs Outdated
Comment thread fynd-rpc-types/src/lib.rs Outdated
Comment thread fynd-core/src/feed/permission.rs Outdated
Comment thread fynd-core/src/worker_pool_router/mod.rs Outdated
Comment thread fynd-core/src/types/quote.rs Outdated
/// 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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a lot of confusion comes from the word "pool" meaning two different things though the docs in this PR:

  1. AMM pool
  2. 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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

kayibal and others added 2 commits June 11, 2026 16:50
- 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>
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.

2 participants