Skip to content

feat: Read router fees from on-chain FeeCalculator#250

Open
dianacarvalho1 wants to merge 5 commits into
mainfrom
fee-taking/dc/ENG-6014-auto-update-fees
Open

feat: Read router fees from on-chain FeeCalculator#250
dianacarvalho1 wants to merge 5 commits into
mainfrom
fee-taking/dc/ENG-6014-auto-update-fees

Conversation

@dianacarvalho1

@dianacarvalho1 dianacarvalho1 commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Fynd now reads router fees from the FeeCalculator contract at startup and refreshes them every 5 minutes via a background fetcher, replacing the previously hardcoded fee values.

Done:

  • Resolve the FeeCalculator address from the Tycho Router, then read the default router fees, per-client fee overrides, and the fee-unit precision scale (MAX_FEE_BPS).
  • Apply fees per order by client address, falling back to the default fee when the client has no override. Uses the client fee receiver to identify the client if set, if not it uses the order sender.
  • Encode using only fetched on-chain values; return an error instead of encoding when fees are not yet loaded.
  • Add RouterFees/FeeRates types and a SharedRouterFees handle shared between the encoder and the fetcher
    • Track the last successful fetch time on RouterFees
  • Gate Solver::wait_until_ready and GET /v1/health on router fees having been loaded at least once.
  • Return 503 from /v1/health when the last successful fetch is over an hour old, so a liveness probe restarts the service.

@dianacarvalho1 dianacarvalho1 self-assigned this Jun 16, 2026
/// This is the calldata convention between Fynd and the router (`clientFeeBps`), independent
/// of the FeeCalculator's internal precision. The contract scales `clientFeeBps` into its own
/// fee units by `max_fee_units / LEGACY_BPS_DENOMINATOR`.
pub const LEGACY_BPS_DENOMINATOR: u64 = 10_000;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

note that when we resolve this mismatch between the router and the fee calculator (and do a new router deployment) Fynd won't be able to automatically update, the clients will need to update their Fynd version

Fynd now reads router fees from the FeeCalculator contract at startup and refreshes them every 5 minutes via a background fetcher, replacing the previously hardcoded fee values.

Done:
- Resolve the FeeCalculator address from the Tycho Router, then read the default router fees, per-client fee overrides, and the fee-unit precision scale (MAX_FEE_BPS).
- Apply fees per order by client address, falling back to the default fee when the client has no override. Uses the client fee receiver to identify the client if set, if not it uses the order sender.
- Encode using only fetched on-chain values; return an error instead of encoding when fees are not yet loaded.
- Add RouterFees/FeeRates types and a SharedRouterFees handle shared between the encoder and the fetcher.

Took 1 hour 34 minutes


Took 19 seconds

Took 11 seconds
@dianacarvalho1 dianacarvalho1 force-pushed the fee-taking/dc/ENG-6014-auto-update-fees branch from 56c57d9 to 6e06eda Compare June 16, 2026 10:49
@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.

@Troshchk Troshchk 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.

Thank you @dianacarvalho1! 💫
I left a couple of questions for my understanding

Comment thread fynd-core/src/encoding/encoder.rs
Comment thread fynd-core/src/solver.rs
Comment thread fynd-core/src/encoding/fee_fetcher.rs

@louise-poole louise-poole left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just some small comments - otherwise looks good! 👍

Comment on lines +411 to +418
println!(
"mainnet router fees: max_fee_units={}, default_on_output={}, \
default_on_client_fee={}, custom_clients={}",
fees.max_fee_units(),
default_rates.on_output(),
default_rates.on_client_fee(),
fees.custom_client_count(),
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We don't usually have a println in our tests... couldt hese not just be asserts?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I didn't want to have asserts because the contract fees might actually change for real and then we have a broken test 😕

Comment thread fynd-core/src/encoding/router_fees.rs Outdated
Comment thread fynd-core/src/encoding/router_fees.rs Outdated
Comment thread fynd-core/src/encoding/encoder.rs Outdated
Comment thread fynd-core/src/encoding/fee_fetcher.rs Outdated
- Track the last successful fetch time on RouterFees
- Gate Solver::wait_until_ready and GET /v1/health on router fees having been loaded at least once.
- Return 503 from /v1/health when the last successful fetch is over an hour old, so a liveness probe restarts the service.

Took 41 minutes

@Troshchk Troshchk 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.

Thank you!

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

Thank you! I've double checked the logic and it all looks perfect. Glad this has also been added to the health check

Comment on lines +198 to +213
for (client, fees) in page.clients.into_iter().zip(page.fees) {
// Resolve each field against the defaults here, mirroring
// FeeCalculator._getFeeInfo, so the stored pair is the effective rate.
let on_output = if fees.hasCustomFeeOnOutput {
fees.feeBpsOnOutput
} else {
default_fee_on_output
};
let on_client_fee = if fees.hasCustomFeeOnClientFee {
fees.feeBpsOnClientFee
} else {
default_fee_on_client_fee
};
custom_fees
.insert(Bytes::from(client.as_slice().to_vec()), (on_output, on_client_fee));
}

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.

Comment thread fynd-rpc/src/api/handlers.rs Outdated
Add RouterFees::fallback() (0.1 bps on output) and initialise SharedRouterFees with it, so encoding never fails when on-chain fees haven't been
fetched yet.
Drop the router fee readiness and staleness checks from /v1/health, plus the now-dead ROUTER_FEE_STALE_THRESHOLD and fetch-timestamp tracking.

Took 12 minutes
@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: cargo libc is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?cargo/reqwest@0.12.28cargo/futures@0.3.32cargo/actix-web@4.13.0cargo/tempfile@3.27.0cargo/sha2@0.11.0cargo/alloy@1.8.3cargo/alloy-chains@0.2.34cargo/tokio@1.52.3cargo/metrics@0.24.6cargo/reqwest@0.13.4cargo/chrono@0.4.45cargo/uuid@1.23.3cargo/tycho-simulation@0.313.0cargo/tycho-execution@0.313.0cargo/sha2@0.10.9cargo/sha2@0.9.9cargo/tokio-tungstenite@0.20.1cargo/zstd@0.13.3cargo/num_cpus@1.17.0cargo/dialoguer@0.11.0cargo/opentelemetry@0.27.1cargo/opentelemetry-otlp@0.27.0cargo/tracing-opentelemetry@0.28.0cargo/metrics-exporter-prometheus@0.16.2cargo/wiremock@0.6.5cargo/opentelemetry_sdk@0.27.1cargo/tokio-tungstenite@0.28.0cargo/libc@0.2.186

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/libc@0.2.186. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: cargo openssl is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?cargo/reqwest@0.12.28cargo/tokio-tungstenite@0.20.1cargo/tokio-tungstenite@0.28.0cargo/openssl@0.10.81

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/openssl@0.10.81. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: cargo tokio is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: Cargo.lockcargo/tokio@1.52.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/tokio@1.52.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: cargo zerocopy is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?cargo/reqwest@0.12.28cargo/alloy@1.8.3cargo/alloy-chains@0.2.34cargo/metrics@0.24.6cargo/reqwest@0.13.4cargo/tycho-simulation@0.313.0cargo/tycho-execution@0.313.0cargo/tokio-tungstenite@0.20.1cargo/opentelemetry-otlp@0.27.0cargo/metrics-exporter-prometheus@0.16.2cargo/opentelemetry_sdk@0.27.1cargo/tokio-tungstenite@0.28.0cargo/zerocopy@0.8.52

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore cargo/zerocopy@0.8.52. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

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.

4 participants