Skip to content

feat: consume and return reserve value from the bridge API - #10241

Open
Julink-eth wants to merge 11 commits into
mainfrom
feat/consume-reserve-bridge-api-response
Open

Julink-eth wants to merge 11 commits into
mainfrom
feat/consume-reserve-bridge-api-response

Conversation

@Julink-eth

@Julink-eth Julink-eth commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Explanation

Add optional quote.feeData.reserve on V2 quotes so clients can consume the native minimum-balance reserve returned by the Bridge API (
va-mmcx-bridge-api#1327).
reserve is a sibling of feeData.network, not a FeeType. Fee aggregators iterate FeeType keys (network, relayer, metabridge, txFee), so treating reserve as a fee would inflate quote cost. It is a balance that must remain in the source account after execution, not an amount spent.
Normalize reserve in toNormalizedAmounts the same way as other amount fields, and preserve it when coercing V1 ↔ V2.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Touches bridge quote schema, V1/V2 coercion, and sent-amount calculation—user-visible swap/bridge flows—but the field is optional and explicitly excluded from fee aggregation.

Overview
Adds optional quote.feeData.reserve on V1 and V2 quote schemas so the controller can pass through the Bridge API’s native minimum-balance requirement. reserve sits beside feeData.network, not under FeeType, so fee rollups and sent-amount math do not treat it as spendable.

V1 ↔ V2 coercers copy network and reserve when present. Quote metadata keeps reserve through merge sanitization, toNormalizedAmounts, and toCurrencyValues (fiat derived without iterating FeeType). calcSentAmount omits reserve when summing source-side fees so wallet deduction stays correct.

Tests and the package changelog cover coercion, merge, normalization, currency, and sent-amount behavior.

Reviewed by Cursor Bugbot for commit 8d1cbbb. Bugbot is set up for automated code reviews on this repo. Configure here.

@Julink-eth
Julink-eth requested review from a team as code owners September 15, 2026 12:20
@Julink-eth
Julink-eth deployed to default-branch September 15, 2026 12:20 — with GitHub Actions Active

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Compatibility conversions, reserve preservation, currency normalization, and reverse-coercion coverage remain incomplete.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds optional native-balance reserve data to V2 bridge quotes for client consumption.

Changes:

  • Adds and normalizes feeData.reserve.
  • Preserves reserve data during V1/V2 coercion.
  • Adds tests and changelog documentation.
File summaries
File Summary
packages/bridge-controller/src/validators/quote.ts Defines the optional reserve field.
packages/bridge-controller/src/utils/quote-metadata/to-normalized-amounts.ts Normalizes reserve amounts.
packages/bridge-controller/src/utils/quote-metadata/merge.test.ts Tests quote metadata behavior.
packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts Migrates V1 fee data to V2.
packages/bridge-controller/src/coercers/quote-response-v2-to-v1.ts Converts reserve data back to V1.
packages/bridge-controller/src/coercers/quote-response-v1-to-v2.test.ts Tests V1-to-V2 migration.
packages/bridge-controller/CHANGELOG.md Documents the API addition.
Review details

Suppressed comments (5)

packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts:136

  • These arrays come from the V1 feeData, whose assets use the legacy BridgeAsset shape. Copying them directly skips the toBridgeAssetV2 conversion used for metabridge and txFee; a real V1 asset can therefore retain chainId/address and, for legacy icon, fail to provide the V2 iconUrl. Map both arrays and convert each entry's asset before returning the V2 quote.
      ...(migratedFeeData.network?.length && {
        network: migratedFeeData.network,
      }),
      ...(migratedFeeData.reserve?.length && {
        reserve: migratedFeeData.reserve,
      }),

packages/bridge-controller/src/coercers/quote-response-v2-to-v1.ts:123

  • The reverse coercer converts the existing V2 fee assets with toBridgeAssetV1, but the new reserve entries are copied unchanged. A V2 reserve asset has no required V1 chainId or address, so a V2-to-V1 conversion returns a non-V1-shaped reserve and legacy consumers cannot use its asset fields. Map the entries and call toBridgeAssetV1 on each asset.
      ...(feeData.reserve?.length && {
        reserve: feeData.reserve,
      }),

packages/bridge-controller/src/coercers/quote-response-v2-to-v1.ts:123

  • The new V2-to-V1 preservation branch is not covered by the added tests; they only exercise V1-to-V2. Add a V2 input containing feeData.reserve and assert that toQuoteResponseV1 preserves it (including the asset data), so this compatibility path cannot regress unnoticed.
      ...(feeData.reserve?.length && {
        reserve: feeData.reserve,
      }),

packages/bridge-controller/src/validators/quote.ts:188

  • This field is lost in the supported QuoteMetadataMigrationPhase.V1Data path: mergeQuoteMetadata removes feeData and reconstructs only metabridge/txFee, while toQuoteMetadataV2 has no legacy equivalent for reserve. A valid V2 quote therefore reaches clients without its reserve unless they use a later migration phase, so preserve this non-fee field explicitly during sanitization without adding it to FeeType aggregation.
      reserve: optional(array(AmountsAndAssetSchema)),

packages/bridge-controller/src/validators/quote.ts:188

  • Because reserve is intentionally not a FeeType, toCurrencyValues's Object.values(FeeType) loop never derives valueInCurrency for it from a backend usd value in migration phases 1.5 and 2. This makes the new amount inconsistent with network and prevents fiat display when the exchange rate is available; map reserve explicitly without adding it to fee aggregation.
      reserve: optional(array(AmountsAndAssetSchema)),
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Add test coverage for preserving feeData.reserve during V2-to-V1 coercion.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread packages/bridge-controller/src/coercers/quote-response-v2-to-v1.ts
@Julink-eth
Julink-eth requested a lite review from Copilot September 15, 2026 13:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The reserve fixture uses a mismatched asset, so the regression test does not exercise the new exclusion.

Review details

Suppressed comments (1)

packages/bridge-controller/src/utils/quote-metadata/calculators.test.ts:235

  • This fixture gives reserve a Stellar asset ID while srcAsset is an Optimism ERC-20. Because calcSentAmount filters fees with assetIdsMatch, the pre-change Object.values(feeData) implementation would already skip this reserve, so the test would pass even if the new exclusion were removed. Use a reserve asset matching srcAsset (or a native-source fixture) so this regression test actually exercises the changed behavior.
                  assetId: 'stellar:pubnet/slip44:148' as const,
                  symbol: 'XLM',
                  name: 'Stellar Lumens',
                  decimals: 7,
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

toCurrencyValues does not derive valueInCurrency for USD reserve data; add explicit handling and a test.

Review details

Suppressed comments (1)

packages/bridge-controller/src/validators/quote.ts:193

  • Because reserve is intentionally not a FeeType, toCurrencyValues's Object.values(FeeType) loop never derives valueInCurrency when a reserve carries a usd value. In V2-only/1.5 selector output, the reserve can therefore lack the user's fiat value even though all other AmountsAndAsset fee entries are converted. Handle feeData.reserve explicitly without adding it to FeeType, and cover that path with a test.
      reserve: optional(array(AmountsAndAssetSchema)),
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Fix the reserve currency-value index alignment issue before approval.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 12/12 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread packages/bridge-controller/src/utils/quote-metadata/to-currency-values.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

No unresolved review comments remain, and the changes include focused regression tests.

Review details
  • Files reviewed: 12/12 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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