Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"js/ts.tsdk.path": "node_modules/typescript/lib"
}
5 changes: 4 additions & 1 deletion packages/bridge-controller/src/bridge-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
getDefaultBridgeControllerState,
isCrossChain,
isEthUsdt,
isNativeAddress,
isNonEvmChainId,
isSolanaChainId,
} from './utils/bridge';
Expand Down Expand Up @@ -579,7 +580,9 @@ export class BridgeController extends StaticIntervalPollingController<BridgePoll
)
.filter(
(assetId: CaipAssetType | undefined): assetId is CaipAssetType =>
!selectIsAssetExchangeRateInState(exchangeRateSources, assetId),
!selectIsAssetExchangeRateInState(exchangeRateSources, assetId) ||
// Always fetch native asset exchange rates
isNativeAddress(assetId),
),
);

Expand Down
2 changes: 2 additions & 0 deletions packages/bridge-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ export {
selectBridgeFeatureFlags,
selectMinimumBalanceForRentExemptionInSOL,
selectTokenWarnings,
selectMetadataV2,
selectUsdToFiatExchangeRate,
} from './selectors';

export { DEFAULT_FEATURE_FLAG_CONFIG } from './constants/bridge';
Expand Down
59 changes: 51 additions & 8 deletions packages/bridge-controller/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ import {
} from './utils/caip-formatters';
import { processFeatureFlags } from './utils/feature-flags';
import { sumAmounts } from './utils/number-formatters';
import { calcBatchFees } from './utils/quote-metadata/calculators';
import {
calcBatchFees,
calcQuoteMetadataV2,
} from './utils/quote-metadata/calculators';
import { calcQuoteMetadata } from './utils/quote-metadata/calculators';
import { mergeQuoteMetadata } from './utils/quote-metadata/merge';
import type { QuoteMetadata } from './utils/quote-metadata/types';
Expand Down Expand Up @@ -94,6 +97,7 @@ const createBridgeSelector = createSelector_.withTypes<BridgeAppState>();
type BridgeQuotesClientParams = {
sortOrder: SortOrder;
selectedQuote: (QuoteResponse & QuoteMetadata) | null;
migrationPhase: '1' | '1.5' | '2';
};

type EvmTokenExchangeRate = { price?: number; currency?: string };
Expand Down Expand Up @@ -355,13 +359,52 @@ const selectMetadata = createBridgeSelector(
},
);

export const selectUsdToFiatExchangeRate = createBridgeSelector(
[
selectExchangeRateSources,
({ quoteRequest }) =>
getNativeAssetForChainId(quoteRequest[0]?.srcChainId ?? 1)?.assetId,
],
(exchangeRateSources, nativeAssetId) => {
const exchangeRate = selectExchangeRateByAssetId(
exchangeRateSources,
nativeAssetId,
);
return new BigNumber(exchangeRate?.exchangeRate ?? 0).div(
exchangeRate?.usdExchangeRate ?? 0,
);
},
);

export const selectMetadataV2 = createBridgeSelector(
[({ quotes }) => quotes, selectUsdToFiatExchangeRate],
(quotes, usdToFiatExchangeRate) => {
return quotes.map((quote) =>
calcQuoteMetadataV2(quote, usdToFiatExchangeRate),
);
},
);

// Selects cross-chain swap quotes including their metadata
const selectBridgeQuotesWithMetadata = createBridgeSelector(
[selectMetadata, ({ quotes }) => quotes],
(quoteMetadata, quotes) =>
quotes.map((quote, index) =>
mergeQuoteMetadata(quote, quoteMetadata[index]),
),
[
selectMetadata,
selectMetadataV2,
({ quotes }) => quotes,
(_, { migrationPhase }: BridgeQuotesClientParams) => migrationPhase,
],
(quoteMetadata, quoteMetadataV2, quotes, migrationPhase) =>
quotes.map((quote, index) => {
// if phase 1, skip toQuoteMetadataV2(quoteMetadataV2) so new metadata keys have old data
// if phase 1.5, add quoteMetadataV2, so new metadata keys can have both old and new data
// if phase 2, skip quoteMetadata, so new metadata keys have new data and old data can be removed
return mergeQuoteMetadata(
quote,
quoteMetadata[index],
migrationPhase,
quoteMetadataV2?.[index],
);
}),
);

const selectSortedBridgeQuotes = createBridgeSelector(
Expand Down Expand Up @@ -458,8 +501,8 @@ export const selectIsQuoteExpired = createBridgeSelector(
(isQuoteGoingToRefresh, quotesLastFetched, refreshRate, currentTimeInMs) =>
Boolean(
!isQuoteGoingToRefresh &&
quotesLastFetched &&
currentTimeInMs - quotesLastFetched > refreshRate,
quotesLastFetched &&
currentTimeInMs - quotesLastFetched > refreshRate,
),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { QuoteResponse } from '../../validators/quote-response';
import { isNativeAddress } from '../bridge';
import { calcTokenAmount } from '../number-formatters';
import type { QuoteMetadata, TokenAmountValues } from './types';
import { FeeType, toQuoteResponseV1, toQuoteResponseV2 } from '../..';

export const calcNonEvmTotalNetworkFee = (
bridgeQuote: QuoteResponse & NonEvmFees,
Expand Down Expand Up @@ -421,6 +422,7 @@ export const calcQuoteMetadata = (
nativeExchangeRate = {},
} = options;

// const quote = toQuoteResponseV2(toQuoteResponseV1(baseQuote));
const sentAmount = calcSentAmount(quote.quote, srcTokenExchangeRate);
const toTokenAmount = calcToAmount(
quote.quote.dest.amount,
Expand Down Expand Up @@ -507,3 +509,44 @@ export const calcQuoteMetadata = (
}),
};
};

export const calcQuoteMetadataV2 = (
quote: QuoteResponse,
usdToFiatExchangeRate: BigNumber,
): DeepPartial<QuoteResponse> => {
// Calculate fiat based on usd value
return {
quote: {
src: {
valueInCurrency: usdToFiatExchangeRate
.times(quote.quote.src.usd ?? '0')
.toFixed(),
},
dest: {
valueInCurrency: usdToFiatExchangeRate
.times(quote.quote.dest.usd ?? '0')
.toFixed(),
minAmountValueInCurrency: usdToFiatExchangeRate
.times(quote.quote.dest.minAmountUsd ?? '0')
.toFixed(),
},
feeData: Object.fromEntries(
Object.values(FeeType).map((feeType) => [
feeType,
quote.quote.feeData[feeType]?.map((fee) => ({
valueInCurrency: usdToFiatExchangeRate
.times(fee.usd ?? '0')
.toFixed(),
})),
]),
),
priceData: {
priceImpact: {
valueInCurrency: usdToFiatExchangeRate
.times(quote.quote.priceData?.priceImpact?.usd ?? '0')
.toFixed(),
},
},
},
};
};
23 changes: 23 additions & 0 deletions packages/bridge-controller/src/utils/quote-metadata/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,24 @@ import type { QuoteResponseV1 } from '../../validators/quote-response-v1';
import { toNormalizedAmounts } from './to-normalized-amounts';
import { toQuoteMetadataV2 } from './to-quote-metadata-v2';
import type { QuoteMetadata } from './types';
import type { DeepPartial } from '../../types';

/**
* Merges legacy {@link QuoteMetadata} values into the {@link QuoteResponse}
*
* @param quoteResponse - The {@link QuoteResponse} or {@link QuoteResponseV1} to merge the metadata into
* @param legacyQuoteMetadata - The {@link QuoteMetadata} values to merge
* @param migrationPhase - The migration phase
* @param fiatQuoteMetadata - The {@link QuoteMetadataV2} values to merge
* @returns The {@link QuoteResponse} with the metadata merged in
*/
export function mergeQuoteMetadata<
QuoteType extends QuoteResponse | QuoteResponseV1 = QuoteResponse,
>(
quoteResponse: QuoteType,
legacyQuoteMetadata: QuoteMetadata,
migrationPhase: '1' | '1.5' | '2' = '1',
fiatQuoteMetadata?: DeepPartial<QuoteResponse>,
): QuoteType & QuoteMetadata {
if (is(quoteResponse, QuoteResponseSchemaV1)) {
return merge({}, quoteResponse, legacyQuoteMetadata);
Expand All @@ -30,6 +35,24 @@ export function mergeQuoteMetadata<
quoteResponse,
);
const normalizedAmountsV2 = toNormalizedAmounts(quoteResponse);

if (migrationPhase === '2') {
// TODO Phase 2 of migration only uses metadata from the API response
// @ts-expect-error - TODO: fix this
return merge({}, quoteResponse, normalizedAmountsV2, fiatQuoteMetadata);
}

if (migrationPhase === '1.5') {
return merge(
{},
legacyQuoteMetadatV2,
quoteResponse,
normalizedAmountsV2,
fiatQuoteMetadata,
legacyQuoteMetadata, // return for client testing
);
}

// Phase 1 of migration uses calcQuoteMetadata's results
return merge(
{},
Expand Down
Loading