feat: consume and return reserve value from the bridge API - #10241
Julink-eth wants to merge 11 commits into
Conversation
There was a problem hiding this comment.
🟡 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 legacyBridgeAssetshape. Copying them directly skips thetoBridgeAssetV2conversion used formetabridgeandtxFee; a real V1 asset can therefore retainchainId/addressand, for legacyicon, fail to provide the V2iconUrl. 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 V1chainIdoraddress, so a V2-to-V1 conversion returns a non-V1-shaped reserve and legacy consumers cannot use its asset fields. Map the entries and calltoBridgeAssetV1on 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.reserveand assert thattoQuoteResponseV1preserves 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.V1Datapath:mergeQuoteMetadataremovesfeeDataand reconstructs onlymetabridge/txFee, whiletoQuoteMetadataV2has no legacy equivalent forreserve. 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 toFeeTypeaggregation.
reserve: optional(array(AmountsAndAssetSchema)),
packages/bridge-controller/src/validators/quote.ts:188
- Because
reserveis intentionally not aFeeType,toCurrencyValues'sObject.values(FeeType)loop never derivesvalueInCurrencyfor it from a backendusdvalue in migration phases 1.5 and 2. This makes the new amount inconsistent withnetworkand prevents fiat display when the exchange rate is available; mapreserveexplicitly 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.
There was a problem hiding this comment.
🔵 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
reservea Stellar asset ID whilesrcAssetis an Optimism ERC-20. BecausecalcSentAmountfilters fees withassetIdsMatch, the pre-changeObject.values(feeData)implementation would already skip this reserve, so the test would pass even if the new exclusion were removed. Use a reserve asset matchingsrcAsset(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
There was a problem hiding this comment.
🔵 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
reserveis intentionally not aFeeType,toCurrencyValues'sObject.values(FeeType)loop never derivesvalueInCurrencywhen a reserve carries ausdvalue. In V2-only/1.5 selector output, the reserve can therefore lack the user's fiat value even though all otherAmountsAndAssetfee entries are converted. HandlefeeData.reserveexplicitly without adding it toFeeType, 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
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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
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.reserveon V1 and V2 quote schemas so the controller can pass through the Bridge API’s native minimum-balance requirement.reservesits besidefeeData.network, not underFeeType, so fee rollups and sent-amount math do not treat it as spendable.V1 ↔ V2 coercers copy
networkandreservewhen present. Quote metadata keepsreservethrough merge sanitization,toNormalizedAmounts, andtoCurrencyValues(fiat derived without iteratingFeeType).calcSentAmountomitsreservewhen 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.