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

## [Unreleased]

### Added

- **BREAKING:** Add `sourceAmount` to `TransactionPayQuote` ([#7159](https://github.com/MetaMask/core/pull/7159))
- Add `estimate` and `max` properties to `fee.sourceNetwork` in `TransactionPayQuote`.
- Add `isTargetGasFeeToken` to `fee` in `TransactionPayQuote`.
- Add matching properties to `TransactionPayTotals`.
- Use fixed fiat rate for Polygon USDCe and Arbitrum USDC.

## [6.0.0]

### Fixed
Expand Down
8 changes: 8 additions & 0 deletions packages/transaction-pay-controller/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import type { Hex } from '@metamask/utils';

export const CONTROLLER_NAME = 'TransactionPayController';
export const CHAIN_ID_ARBITRUM = '0xa4b1' as Hex;
export const CHAIN_ID_POLYGON = '0x89' as Hex;

export const NATIVE_TOKEN_ADDRESS =
'0x0000000000000000000000000000000000000000' as Hex;

export const ARBITRUM_USDC_ADDRESS =
'0xaf88d065e77c8cC2239327C5EDb3A432268e5831' as Hex;

export const POLYGON_USDCE_ADDRESS =
'0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174' as Hex;

export enum TransactionPayStrategy {
Bridge = 'bridge',
Relay = 'relay',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ describe('Bridge Quotes Utils', () => {

calculateGasCostMock.mockReturnValue({
fiat: '0.1',
human: '0.051',
raw: '51000000000000',
usd: '0.2',
});

Expand Down Expand Up @@ -715,6 +717,8 @@ describe('Bridge Quotes Utils', () => {
it('returns target network fee in quote', async () => {
calculateTransactionGasCostMock.mockReturnValue({
fiat: '1.23',
human: '0.000123',
raw: '123000000000000',
usd: '2.34',
});

Expand All @@ -735,6 +739,8 @@ describe('Bridge Quotes Utils', () => {
it('for trade only', async () => {
calculateGasCostMock.mockReturnValue({
fiat: '1.23',
human: '0.000123',
raw: '123000000000000',
usd: '2.34',
});

Expand All @@ -756,15 +762,16 @@ describe('Bridge Quotes Utils', () => {

expect(quotes[0].fees).toMatchObject({
sourceNetwork: {
fiat: '1.23',
usd: '2.34',
estimate: { fiat: '1.23', usd: '2.34' },
},
});
});

it('for trade and approval', async () => {
calculateGasCostMock.mockReturnValue({
fiat: '1.23',
human: '0.000123',
raw: '123000000000000',
usd: '2.34',
});

Expand All @@ -790,8 +797,10 @@ describe('Bridge Quotes Utils', () => {

expect(quotes[0].fees).toMatchObject({
sourceNetwork: {
fiat: '2.46',
usd: '4.68',
estimate: {
fiat: '2.46',
usd: '4.68',
},
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type {
import { TransactionPayStrategy } from '../..';
import { projectLogger } from '../../logger';
import type {
FiatValue,
Amount,
PayStrategyGetBatchRequest,
PayStrategyGetQuotesRequest,
PayStrategyGetRefreshIntervalRequest,
Expand Down Expand Up @@ -497,46 +497,50 @@ function normalizeQuote(
);
}

const targetAmountMinimumFiat = calculateFiatValue(
const targetAmountMinimumFiat = calculateAmount(
quote.quote.minDestTokenAmount,
quote.quote.destAsset.decimals,
targetFiatRate.fiatRate,
targetFiatRate.usdRate,
);

const sourceAmountFiat = calculateFiatValue(
const sourceAmount = calculateAmount(
quote.quote.srcTokenAmount,
quote.quote.srcAsset.decimals,
sourceFiatRate.fiatRate,
sourceFiatRate.usdRate,
);

const targetAmountGoal = calculateFiatValue(
const targetAmount = calculateAmount(
request.targetAmountMinimum,
quote.quote.destAsset.decimals,
targetFiatRate.fiatRate,
targetFiatRate.usdRate,
);

const targetNetwork = calculateTransactionGasCost(transaction, messenger);
const sourceNetwork = calculateSourceNetworkFee(quote, messenger);

const sourceNetwork = {
estimate: calculateSourceNetworkFee(quote, messenger),
max: calculateSourceNetworkFee(quote, messenger, { isMax: true }),
};

return {
estimatedDuration: quote.estimatedProcessingTimeInSeconds,
dust: {
fiat: new BigNumber(targetAmountMinimumFiat.fiat)
.minus(targetAmountGoal.fiat)
.minus(targetAmount.fiat)
.toString(10),
usd: new BigNumber(targetAmountMinimumFiat.usd)
.minus(targetAmountGoal.usd)
.minus(targetAmount.usd)
.toString(10),
},
fees: {
provider: {
fiat: new BigNumber(sourceAmountFiat.fiat)
fiat: new BigNumber(sourceAmount.fiat)
.minus(targetAmountMinimumFiat.fiat)
.toString(10),
usd: new BigNumber(sourceAmountFiat.usd)
usd: new BigNumber(sourceAmount.usd)
.minus(targetAmountMinimumFiat.usd)
.toString(10),
},
Expand All @@ -545,53 +549,63 @@ function normalizeQuote(
},
original: quote,
request,
sourceAmount,
strategy: TransactionPayStrategy.Bridge,
};
}

/**
* Calculate fiat value from amount and fiat rates.
* Calculate amount from raw value and fiat rates.
*
* @param amount - Amount to convert.
* @param raw - Amount to convert.
* @param decimals - Token decimals.
* @param fiatRateFiat - Fiat rate.
* @param fiatRateUsd - USD rate.
* @returns Fiat value.
* @returns Amount object.
*/
function calculateFiatValue(
amount: string,
function calculateAmount(
raw: string,
decimals: number,
fiatRateFiat: string,
fiatRateUsd: string,
): FiatValue {
const amountHuman = new BigNumber(amount).shiftedBy(-decimals);
const usd = amountHuman.multipliedBy(fiatRateUsd).toString(10);
const fiat = amountHuman.multipliedBy(fiatRateFiat).toString(10);
): Amount {
const humanValue = new BigNumber(raw).shiftedBy(-decimals);
const human = humanValue.toString(10);

return { fiat, usd };
const usd = humanValue.multipliedBy(fiatRateUsd).toString(10);
const fiat = humanValue.multipliedBy(fiatRateFiat).toString(10);

return { fiat, human, raw, usd };
}

/**
* Calculate the source network fee for a bridge quote.
*
* @param quote - Bridge quote response.
* @param messenger - Controller messenger.
* @param options - Calculation options.
* @param options.isMax - Whether to calculate the maximum cost.
* @returns Estimated gas cost for the source network.
*/
function calculateSourceNetworkFee(
quote: TransactionPayBridgeQuote,
messenger: TransactionPayControllerMessenger,
): FiatValue {
{ isMax = false } = {},
): Amount {
const { approval, trade } = quote;

const approvalCost = approval
? calculateTransactionCost(approval as TxData, messenger)
: { fiat: '0', usd: '0' };
? calculateTransactionCost(approval as TxData, messenger, { isMax })
: { fiat: '0', human: '0', raw: '0', usd: '0' };

const tradeCost = calculateTransactionCost(trade as TxData, messenger);
const tradeCost = calculateTransactionCost(trade as TxData, messenger, {
isMax,
});

return {
fiat: new BigNumber(approvalCost.fiat).plus(tradeCost.fiat).toString(10),
human: new BigNumber(approvalCost.human).plus(tradeCost.human).toString(10),
raw: new BigNumber(approvalCost.raw).plus(tradeCost.raw).toString(10),
usd: new BigNumber(approvalCost.usd).plus(tradeCost.usd).toString(10),
};
}
Expand All @@ -601,17 +615,22 @@ function calculateSourceNetworkFee(
*
* @param transaction - Transaction parameters.
* @param messenger - Controller messenger
* @param options - Calculation options.
* @param options.isMax - Whether to calculate the maximum cost.
* @returns Estimated gas cost for a bridge transaction.
*/
function calculateTransactionCost(
transaction: TxData,
messenger: TransactionPayControllerMessenger,
): FiatValue {
const { effectiveGas, gasLimit } = transaction;
{ isMax }: { isMax: boolean },
): Amount {
const { effectiveGas: effectiveGasOriginal, gasLimit } = transaction;
const effectiveGas = isMax ? undefined : effectiveGasOriginal;

return calculateGasCost({
...transaction,
gas: effectiveGas || gasLimit || '0x0',
messenger,
isMax,
});
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
export const ARBITRUM_USDC_ADDRESS =
'0xaf88d065e77c8cC2239327C5EDb3A432268e5831';
export const CHAIN_ID_ARBITRUM = '0xa4b1';
export const CHAIN_ID_POLYGON = '0x89';
export const CHAIN_ID_HYPERCORE = '0x539';
export const RELAY_URL_BASE = 'https://api.relay.link';
export const RELAY_URL_QUOTE = `${RELAY_URL_BASE}/quote`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import type { TransactionMeta } from '@metamask/transaction-controller';
import type { Hex } from '@metamask/utils';
import { cloneDeep } from 'lodash';

import { RELAY_URL_QUOTE } from './constants';
import { getRelayQuotes } from './relay-quotes';
import type { RelayQuote } from './types';
import {
ARBITRUM_USDC_ADDRESS,
CHAIN_ID_ARBITRUM,
CHAIN_ID_POLYGON,
RELAY_URL_QUOTE,
} from './constants';
import { getRelayQuotes } from './relay-quotes';
import type { RelayQuote } from './types';
import { NATIVE_TOKEN_ADDRESS } from '../../constants';
NATIVE_TOKEN_ADDRESS,
} from '../../constants';
import { getMessengerMock } from '../../tests/messenger-mock';
import type {
GetDelegationTransactionCallback,
Expand Down Expand Up @@ -41,6 +41,9 @@ const QUOTE_REQUEST_MOCK: QuoteRequest = {

const QUOTE_MOCK = {
details: {
currencyIn: {
amountUsd: '1.24',
},
currencyOut: {
amountFormatted: '1.0',
amountUsd: '1.23',
Expand Down Expand Up @@ -122,13 +125,17 @@ describe('Relay Quotes Utils', () => {
});

calculateTransactionGasCostMock.mockReturnValue({
usd: '1.23',
fiat: '2.34',
human: '0.615',
raw: '6150000000000000',
usd: '1.23',
});

calculateGasCostMock.mockReturnValue({
usd: '3.45',
fiat: '4.56',
human: '1.725',
raw: '1725000000000000',
usd: '3.45',
});

getRemoteFeatureFlagControllerStateMock.mockReturnValue({
Expand Down Expand Up @@ -288,7 +295,7 @@ describe('Relay Quotes Utils', () => {
expect(result[0].estimatedDuration).toBe(300);
});

it('includes provider fee in quote', async () => {
it('includes provider fee from relayer fee', async () => {
successfulFetchMock.mockResolvedValue({
json: async () => QUOTE_MOCK,
} as never);
Expand All @@ -305,6 +312,26 @@ describe('Relay Quotes Utils', () => {
});
});

it('includes provider fee from usd change if greater', async () => {
const quote = cloneDeep(QUOTE_MOCK);
quote.details.currencyIn.amountUsd = '3.00';

successfulFetchMock.mockResolvedValue({
json: async () => quote,
} as never);

const result = await getRelayQuotes({
messenger,
requests: [QUOTE_REQUEST_MOCK],
transaction: TRANSACTION_META_MOCK,
});

expect(result[0].fees.provider).toStrictEqual({
usd: '1.77',
fiat: '3.54',
});
});

it('includes dust in quote', async () => {
successfulFetchMock.mockResolvedValue({
json: async () => QUOTE_MOCK,
Expand Down Expand Up @@ -334,8 +361,18 @@ describe('Relay Quotes Utils', () => {
});

expect(result[0].fees.sourceNetwork).toStrictEqual({
usd: '3.45',
fiat: '4.56',
estimate: {
fiat: '4.56',
human: '1.725',
raw: '1725000000000000',
usd: '3.45',
},
max: {
fiat: '4.56',
human: '1.725',
raw: '1725000000000000',
usd: '3.45',
},
});
});

Expand Down
Loading
Loading