Skip to content
Merged
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
4 changes: 4 additions & 0 deletions packages/bridge-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add and export `getQuotesReceivedProperties` utility to build the metrics payload for clients ([#7182](https://github.com/MetaMask/core/pull/7182))

## [61.0.0]

### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ Array [
"usd_quoted_gas": 0,
"usd_quoted_return": 100,
"warnings": Array [
"warning1",
"insufficient_balance",
],
},
],
Expand Down Expand Up @@ -494,7 +494,7 @@ Array [
"usd_quoted_gas": 0,
"usd_quoted_return": 100,
"warnings": Array [
"warning1",
"low_return",
],
},
],
Expand Down
99 changes: 96 additions & 3 deletions packages/bridge-controller/src/bridge-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,7 @@ describe('BridgeController', function () {
bridgeController.trackUnifiedSwapBridgeEvent(
UnifiedSwapBridgeEventName.QuotesReceived,
{
warnings: ['warning1'],
warnings: ['low_return'],
usd_quoted_gas: 0,
gas_included: false,
gas_included_7702: false,
Expand Down Expand Up @@ -2101,6 +2101,99 @@ describe('BridgeController', function () {
expect(quotes[1].nonEvmFeesInNative).toBe('0.00005'); // BTC fee as-is
});

it('should catch BTC chain fees errors and return undefined fees', async () => {
jest.useFakeTimers();
// Use the actual Solana mock which already has string trade type
const btcQuoteResponse = mockBridgeQuotesSolErc20.map((quote) => ({
...quote,
quote: {
...quote.quote,
srcChainId: ChainId.BTC,
},
})) as unknown as QuoteResponse[];

messengerMock.call.mockImplementation(
(
...args: Parameters<BridgeControllerMessenger['call']>
): ReturnType<BridgeControllerMessenger['call']> => {
const [actionType] = args;

if (actionType === 'AccountsController:getAccountByAddress') {
return {
type: 'btc:p2wpkh',
id: 'btc-account-1',
scopes: [BtcScope.Mainnet],
methods: [],
address: 'bc1q...',
metadata: {
name: 'BTC Account 1',
importTime: 1717334400,
keyring: {
type: 'Snap Keyring',
},
snap: {
id: 'btc-snap-id',
name: 'BTC Snap',
},
},
} as never;
}

if (actionType === 'SnapController:handleRequest') {
return new Promise((_resolve, reject) => {
reject(new Error('Failed to compute fees'));
});
}

return {
provider: jest.fn() as never,
} as never;
},
);

jest.spyOn(fetchUtils, 'fetchBridgeQuotes').mockResolvedValue({
quotes: btcQuoteResponse,
validationFailures: [],
});

const quoteParams = {
srcChainId: ChainId.BTC.toString(),
destChainId: '1',
srcTokenAddress: 'NATIVE',
destTokenAddress: '0x0000000000000000000000000000000000000000',
srcTokenAmount: '100000', // satoshis
walletAddress: 'bc1q...',
destWalletAddress: '0x5342',
slippage: 0.5,
};

await bridgeController.updateBridgeQuoteRequestParams(
quoteParams,
metricsContext,
);

// Wait for polling to start
jest.advanceTimersByTime(201);
await flushPromises();

// Wait for fetch to trigger
jest.advanceTimersByTime(295);
await flushPromises();

// Wait for fetch to complete
jest.advanceTimersByTime(2601);
await flushPromises();

// Final wait for fee calculation
jest.advanceTimersByTime(100);
await flushPromises();

const { quotes } = bridgeController.state;
expect(quotes).toHaveLength(2); // mockBridgeQuotesSolErc20 has 2 quotes
expect(quotes[0].nonEvmFeesInNative).toBeUndefined();
expect(quotes[1].nonEvmFeesInNative).toBeUndefined();
});

describe('trackUnifiedSwapBridgeEvent client-side calls', () => {
beforeEach(async () => {
jest.clearAllMocks();
Expand Down Expand Up @@ -2267,7 +2360,7 @@ describe('BridgeController', function () {
bridgeController.trackUnifiedSwapBridgeEvent(
UnifiedSwapBridgeEventName.QuotesReceived,
{
warnings: ['warning1'],
warnings: ['insufficient_balance'],
usd_quoted_gas: 0,
gas_included: false,
gas_included_7702: false,
Expand Down Expand Up @@ -2569,7 +2662,7 @@ describe('BridgeController', function () {
bridgeController.trackUnifiedSwapBridgeEvent(
UnifiedSwapBridgeEventName.QuotesReceived,
{
warnings: ['warning1'],
warnings: ['low_return'],
usd_quoted_gas: 0,
gas_included: false,
gas_included_7702: false,
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 @@ -13,6 +13,7 @@ export type {
RequestMetadata,
TxStatusData,
QuoteFetchData,
QuoteWarning,
} from './utils/metrics/types';

export {
Expand All @@ -21,6 +22,7 @@ export {
getSwapType,
isHardwareWallet,
isCustomSlippage,
getQuotesReceivedProperties,
} from './utils/metrics/properties';

export type {
Expand Down
107 changes: 105 additions & 2 deletions packages/bridge-controller/src/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ describe('Bridge Selectors', () => {
amount: string;
asset: Pick<BridgeAsset, 'address' | 'decimals' | 'assetId'>;
},
gasIncluded7702?: boolean,
): BridgeAppState => {
const chainId = 56;
const currencyRates = {
Expand All @@ -446,6 +447,10 @@ describe('Bridge Selectors', () => {
price: '1',
currency: 'BNB',
},
'0x0000000000000000000000000000000000000001': {
price: '1.5498387253001357',
currency: 'BNB',
},
},
} as unknown as Record<string, Record<string, MarketDataDetails>>;
const srcTokenAmount = new BigNumber('10') // $10 worth of src token
Expand Down Expand Up @@ -473,8 +478,8 @@ describe('Bridge Selectors', () => {
},
txFee,
},
gasIncluded: Boolean(txFee),
gasIncluded7702: false,
gasIncluded: Boolean(txFee) && !gasIncluded7702,
gasIncluded7702: Boolean(gasIncluded7702),
srcTokenAmount,
destTokenAmount: new BigNumber('9')
.dividedBy(marketData['0x38'][destAsset.address].price)
Expand Down Expand Up @@ -875,6 +880,104 @@ describe('Bridge Selectors', () => {
}
`);
});

it('when gasIncluded7702=true and is taken from dest token', () => {
const newState = getMockSwapState(
{
address: '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d',
decimals: 18,
assetId:
'eip155:1/erc20:0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d',
},
{
address: '0x0000000000000000000000000000000000000001',
decimals: 18,
assetId:
'eip155:1/erc20:0x0000000000000000000000000000000000000001',
},
{
amount: '1000000000000000000',
asset: {
address:
'eip155:1/erc20:0x0000000000000000000000000000000000000001',
decimals: 18,
assetId:
'eip155:1/erc20:0x0000000000000000000000000000000000000001',
},
},
true,
);

const { sortedQuotes } = selectBridgeQuotes(newState, mockClientParams);

const {
quote,
trade,
approval,
estimatedProcessingTimeInSeconds,
...quoteMetadata
} = sortedQuotes[0];
expect(quoteMetadata).toMatchInlineSnapshot(`
Object {
"adjustedReturn": Object {
"usd": "10.518641979781876096240273601395823616",
"valueInCurrency": "8.999999999999999949780980627632791914",
},
"cost": Object {
"usd": "1.168737997753541853682403691190760358912",
"valueInCurrency": "1.000000000000000050216414294183215375298",
},
"gasFee": Object {
"effective": Object {
"amount": "0.000008087",
"usd": "0.00521708544",
"valueInCurrency": "0.00446386226",
},
"max": Object {
"amount": "0.000016174",
"usd": "0.01043417088",
"valueInCurrency": "0.00892772452",
},
"total": Object {
"amount": "0.000008087",
"usd": "0.00521708544",
"valueInCurrency": "0.00446386226",
},
},
"includedTxFees": Object {
"amount": "1",
"usd": "999.831958465623542784",
"valueInCurrency": "855.479979591168903686",
},
"minToTokenAmount": Object {
"amount": "0.009994389353314869",
"usd": "9.992709880792782241436661998044855296",
"valueInCurrency": "8.549999999999999909517932616692707134",
},
"sentAmount": Object {
"amount": "11.689344272882887843",
"usd": "11.687379977535417949922677292586583974912",
"valueInCurrency": "9.999999999999999999997394921816007289298",
},
"swapRate": "0.00089999999999999999",
"toTokenAmount": Object {
"amount": "0.010520409845594599",
"usd": "10.518641979781876096240273601395823616",
"valueInCurrency": "8.999999999999999949780980627632791914",
},
"totalMaxNetworkFee": Object {
"amount": "0.000016174",
"usd": "0.01043417088",
"valueInCurrency": "0.00892772452",
},
"totalNetworkFee": Object {
"amount": "0.000008087",
"usd": "0.00521708544",
"valueInCurrency": "0.00446386226",
},
}
`);
});
});

it('should only fetch quotes once if balance is insufficient', () => {
Expand Down
Loading
Loading