Skip to content
Open
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
2 changes: 1 addition & 1 deletion eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2239,7 +2239,7 @@
},
"packages/transaction-pay-controller/src/utils/transaction.ts": {
"no-restricted-syntax": {
"count": 2
"count": 6
}
},
"packages/user-operation-controller/src/UserOperationController.test.ts": {
Expand Down
3 changes: 3 additions & 0 deletions packages/transaction-pay-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **BREAKING:** Re-parse required tokens when asset state changes ([#8714](https://github.com/MetaMask/core/pull/8714))
- Adds `AssetsControllerStateChangeEvent`, `CurrencyRateStateChange`, `TokenRatesControllerStateChangeEvent`, and `TokensControllerStateChangeEvent` to `AllowedEvents`.
- Consumers must grant these events when creating the controller messenger.
- Bump `@metamask/gas-fee-controller` from `^26.1.1` to `^26.2.0` ([#8722](https://github.com/MetaMask/core/pull/8722))
- Bump `@metamask/transaction-controller` from `^65.1.0` to `^65.2.0` ([#8722](https://github.com/MetaMask/core/pull/8722))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import type {
import { getStrategyOrder } from './utils/feature-flags';
import { updateQuotes } from './utils/quotes';
import { updateSourceAmounts } from './utils/source-amounts';
import { getTransaction, pollTransactionChanges } from './utils/transaction';
import {
getTransaction,
subscribeAssetChanges,
subscribeTransactionChanges,
} from './utils/transaction';

jest.mock('./actions/update-fiat-payment');
jest.mock('./actions/update-payment-token');
Expand All @@ -41,7 +45,10 @@ describe('TransactionPayController', () => {
const getTransactionMock = jest.mocked(getTransaction);
const updateSourceAmountsMock = jest.mocked(updateSourceAmounts);
const updateQuotesMock = jest.mocked(updateQuotes);
const pollTransactionChangesMock = jest.mocked(pollTransactionChanges);
const subscribeTransactionChangesMock = jest.mocked(
subscribeTransactionChanges,
);
const subscribeAssetChangesMock = jest.mocked(subscribeAssetChanges);
const getStrategyOrderMock = jest.mocked(getStrategyOrder);
let messenger: TransactionPayControllerMessenger;
let getKeyringControllerStateMock: jest.Mock;
Expand Down Expand Up @@ -80,6 +87,21 @@ describe('TransactionPayController', () => {
updateQuotesMock.mockResolvedValue(true);
});

describe('constructor', () => {
it('subscribes to rate changes for in-flight retry', () => {
const controller = createController();

expect(subscribeAssetChangesMock).toHaveBeenCalledWith(
messenger,
expect.any(Function),
expect.any(Function),
);

const getControllerState = subscribeAssetChangesMock.mock.calls[0][1];
expect(getControllerState()).toBe(controller.state);
});
});

describe('updatePaymentToken', () => {
it('calls util', () => {
createController().updatePaymentToken({
Expand Down Expand Up @@ -674,7 +696,7 @@ describe('TransactionPayController', () => {
).toBeDefined();

const removeTransactionDataCallback =
pollTransactionChangesMock.mock.calls[0][2];
subscribeTransactionChangesMock.mock.calls[0][2];

removeTransactionDataCallback(TRANSACTION_ID_MOCK);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import type {
import { getStrategyOrder } from './utils/feature-flags';
import { updateQuotes } from './utils/quotes';
import { updateSourceAmounts } from './utils/source-amounts';
import { getTransaction, pollTransactionChanges } from './utils/transaction';
import {
getTransaction,
subscribeAssetChanges,
subscribeTransactionChanges,
} from './utils/transaction';

const MESSENGER_EXPOSED_METHODS = [
'getDelegationTransaction',
Expand Down Expand Up @@ -87,12 +91,18 @@ export class TransactionPayController extends BaseController<
MESSENGER_EXPOSED_METHODS,
);

pollTransactionChanges(
subscribeTransactionChanges(
messenger,
this.#updateTransactionData.bind(this),
this.#removeTransactionData.bind(this),
);

subscribeAssetChanges(
messenger,
() => this.state,
this.#updateTransactionData.bind(this),
);

// eslint-disable-next-line no-new
new QuoteRefresher({
getStrategies: this.#getStrategiesWithFallback.bind(this),
Expand Down
16 changes: 14 additions & 2 deletions packages/transaction-pay-controller/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import type { AssetsControllerGetStateForTransactionPayAction } from '@metamask/assets-controller';
import type {
AssetsControllerGetStateForTransactionPayAction,
AssetsControllerStateChangeEvent,
} from '@metamask/assets-controller';
import type {
CurrencyRateControllerGetStateAction,
CurrencyRateStateChange,
TokenBalancesControllerGetStateAction,
} from '@metamask/assets-controllers';
import type { TokenRatesControllerGetStateAction } from '@metamask/assets-controllers';
import type { TokensControllerGetStateAction } from '@metamask/assets-controllers';
import type { TokenRatesControllerStateChangeEvent } from '@metamask/assets-controllers';
import type {
TokensControllerGetStateAction,
TokensControllerStateChangeEvent,
} from '@metamask/assets-controllers';
import type { AccountTrackerControllerGetStateAction } from '@metamask/assets-controllers';
import type { ControllerStateChangeEvent } from '@metamask/base-controller';
import type { ControllerGetStateAction } from '@metamask/base-controller';
Expand Down Expand Up @@ -82,7 +90,11 @@ export type AllowedActions =
| TransactionControllerUpdateTransactionAction;

export type AllowedEvents =
| AssetsControllerStateChangeEvent
| BridgeStatusControllerStateChangeEvent
| CurrencyRateStateChange
| TokenRatesControllerStateChangeEvent
| TokensControllerStateChangeEvent
| TransactionControllerStateChangeEvent
| TransactionControllerUnapprovedTransactionAddedEvent;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { toHex } from '@metamask/controller-utils';
import { abiERC20 } from '@metamask/metamask-eth-abis';
import type { TransactionMeta } from '@metamask/transaction-controller';
import type { Hex } from '@metamask/utils';
import { createModuleLogger } from '@metamask/utils';

import { projectLogger } from '../logger';
import type {
TransactionPayControllerMessenger,
TransactionPayRequiredToken,
Expand All @@ -15,6 +17,8 @@ import {
getTokenInfo,
} from './token';

const log = createModuleLogger(projectLogger, 'required-tokens');

const FOUR_BYTE_TOKEN_TRANSFER = '0xa9059cbb';

/**
Expand Down Expand Up @@ -61,19 +65,28 @@ function getTokenTransferToken(
const { data, to } = getTokenTransferData(transaction) ?? {};

if (!to || !data) {
log('No token transfer detected', {
transactionId: transaction.id,
});
return undefined;
}

let transferAmount: Hex | undefined;
let decodeError: unknown;

try {
const result = new Interface(abiERC20).decodeFunctionData('transfer', data);
transferAmount = toHex(result._value);
} catch {
// Intentionally empty
} catch (error) {
decodeError = error;
}

if (transferAmount === undefined) {
log('Failed to decode transfer calldata', {
transactionId: transaction.id,
to,
error: decodeError,
});
return undefined;
}

Expand Down Expand Up @@ -105,6 +118,16 @@ function buildRequiredToken(
const tokenBalance = getTokenBalance(messenger, from, chainId, tokenAddress);

if (tokenDecimals === undefined || !symbol || fiatRates === undefined) {
log('Missing token data', {
transactionId: transaction.id,
chainId,
tokenAddress,
missing: {
decimals: tokenDecimals === undefined,
symbol: !symbol,
fiatRates: fiatRates === undefined,
},
});
return undefined;
}

Expand Down
Loading
Loading