Skip to content

Fix/bridge status controller mobile hardware wallets #5931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
4 changes: 4 additions & 0 deletions packages/bridge-status-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bump `@metamask/controller-utils` to `^11.10.0` ([#5935](https://github.com/MetaMask/core/pull/5935))
- Bump `@metamask/transaction-controller` to `^57.3.0` ([#5954](https://github.com/MetaMask/core/pull/5954))

### Fixed

- Properly prompt for confirmation on Ledger on Mobile for bridge transactions ([#5931](https://github.com/MetaMask/core/pull/5931))

## [29.1.0]

### Added
Expand Down
113 changes: 72 additions & 41 deletions packages/bridge-status-controller/src/bridge-status-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getActionType,
formatChainIdToCaip,
isCrossChain,
isHardwareWallet,
} from '@metamask/bridge-controller';
import type { TraceCallback } from '@metamask/controller-utils';
import { toHex } from '@metamask/controller-utils';
Expand All @@ -40,15 +41,15 @@ import {
REFRESH_INTERVAL_MS,
TraceName,
} from './constants';
import { type BridgeStatusControllerMessenger } from './types';
import type {
BridgeStatusControllerState,
StartPollingForBridgeTxStatusArgsSerialized,
FetchFunction,
BridgeClientId,
SolanaTransactionMeta,
BridgeHistoryItem,
} from './types';
import { type BridgeStatusControllerMessenger } from './types';
import { BridgeClientId } from './types';
import {
fetchBridgeTxStatus,
getStatusRequestWithSrcTxHash,
Expand Down Expand Up @@ -597,20 +598,22 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
readonly #handleApprovalTx = async (
isBridgeTx: boolean,
quoteResponse: QuoteResponse<string | TxData> & QuoteMetadata,
requireApproval = false,
): Promise<TransactionMeta | undefined> => {
const { approval } = quoteResponse;

if (approval) {
const approveTx = async () => {
await this.#handleUSDTAllowanceReset(quoteResponse);

const approvalTxMeta = await this.#handleEvmTransaction(
isBridgeTx
const approvalTxMeta = await this.#handleEvmTransaction({
transactionType: isBridgeTx
? TransactionType.bridgeApproval
: TransactionType.swapApproval,
approval,
trade: approval,
quoteResponse,
);
requireApproval,
});
if (!approvalTxMeta) {
throw new Error(
'Failed to submit bridge tx: approval txMeta is undefined',
Expand Down Expand Up @@ -638,39 +641,58 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
return undefined;
};

readonly #handleEvmSmartTransaction = async (
isBridgeTx: boolean,
trade: TxData,
quoteResponse: Omit<QuoteResponse, 'approval' | 'trade'> & QuoteMetadata,
approvalTxId?: string,
) => {
return await this.#handleEvmTransaction(
isBridgeTx ? TransactionType.bridge : TransactionType.swap,
readonly #handleEvmSmartTransaction = async ({
isBridgeTx,
trade,
quoteResponse,
approvalTxId,
requireApproval = false,
}: {
isBridgeTx: boolean;
trade: TxData;
quoteResponse: Omit<QuoteResponse, 'approval' | 'trade'> & QuoteMetadata;
approvalTxId?: string;
requireApproval?: boolean;
}) => {
return await this.#handleEvmTransaction({
transactionType: isBridgeTx
? TransactionType.bridge
: TransactionType.swap,
trade,
quoteResponse,
approvalTxId,
false, // Set to false to indicate we don't want to wait for hash
);
shouldWaitForHash: false, // Set to false to indicate we don't want to wait for hash
requireApproval,
});
};

/**
* Submits an EVM transaction to the TransactionController
*
* @param transactionType - The type of transaction to submit
* @param trade - The trade data to confirm
* @param quoteResponse - The quote response
* @param quoteResponse.quote - The quote
* @param approvalTxId - The tx id of the approval tx
* @param shouldWaitForHash - Whether to wait for the hash of the transaction
* @param params - The parameters for the transaction
* @param params.transactionType - The type of transaction to submit
* @param params.trade - The trade data to confirm
* @param params.quoteResponse - The quote response
* @param params.approvalTxId - The tx id of the approval tx
* @param params.shouldWaitForHash - Whether to wait for the hash of the transaction
* @param params.requireApproval - Whether to require approval for the transaction
* @returns The transaction meta
*/
readonly #handleEvmTransaction = async (
transactionType: TransactionType,
trade: TxData,
quoteResponse: Omit<QuoteResponse, 'approval' | 'trade'> & QuoteMetadata,
approvalTxId?: string,
readonly #handleEvmTransaction = async ({
transactionType,
trade,
quoteResponse,
approvalTxId,
shouldWaitForHash = true,
): Promise<TransactionMeta | undefined> => {
requireApproval = false,
}: {
transactionType: TransactionType;
trade: TxData;
quoteResponse: Omit<QuoteResponse, 'approval' | 'trade'> & QuoteMetadata;
approvalTxId?: string;
shouldWaitForHash?: boolean;
requireApproval?: boolean;
}): Promise<TransactionMeta | undefined> => {
const actionId = generateActionId().toString();

const selectedAccount = this.messagingSystem.call(
Expand All @@ -691,7 +713,7 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
const requestOptions = {
actionId,
networkClientId,
requireApproval: false,
requireApproval,
type: transactionType,
origin: 'metamask',
};
Expand Down Expand Up @@ -774,11 +796,11 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
const shouldResetApproval =
allowance.lt(quoteResponse.sentAmount.amount) && allowance.gt(0);
if (shouldResetApproval) {
await this.#handleEvmTransaction(
TransactionType.bridgeApproval,
{ ...quoteResponse.approval, data: getEthUsdtResetData() },
await this.#handleEvmTransaction({
transactionType: TransactionType.bridgeApproval,
trade: { ...quoteResponse.approval, data: getEthUsdtResetData() },
quoteResponse,
);
});
}
}
};
Expand Down Expand Up @@ -819,7 +841,7 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
submitTx = async (
quoteResponse: QuoteResponse<TxData | string> & QuoteMetadata,
isStxEnabledOnClient: boolean,
) => {
): Promise<TransactionMeta & Partial<SolanaTransactionMeta>> => {
let txMeta: (TransactionMeta & Partial<SolanaTransactionMeta>) | undefined;

const isBridgeTx = isCrossChain(
Expand Down Expand Up @@ -858,10 +880,17 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
!isSolanaChainId(quoteResponse.quote.srcChainId) &&
typeof quoteResponse.trade !== 'string'
) {
// For hardware wallets on Mobile, this is fixes an issue where the Ledger does not get prompted for the 2nd approval
// Extension does not have this issue
const requireApproval =
this.#clientId === BridgeClientId.MOBILE &&
isHardwareWallet(this.#getMultichainSelectedAccount());

// Set approval time and id if an approval tx is needed
const approvalTxMeta = await this.#handleApprovalTx(
isBridgeTx,
quoteResponse,
requireApproval,
);
approvalTime = approvalTxMeta?.time;
approvalTxId = approvalTxMeta?.id;
Expand All @@ -878,12 +907,13 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
},
},
async () =>
await this.#handleEvmSmartTransaction(
await this.#handleEvmSmartTransaction({
isBridgeTx,
quoteResponse.trade as TxData,
trade: quoteResponse.trade as TxData,
quoteResponse,
approvalTxId,
),
requireApproval,
}),
);
} else {
txMeta = await this.#trace(
Expand All @@ -897,12 +927,13 @@ export class BridgeStatusController extends StaticIntervalPollingController<Brid
},
},
async () =>
await this.#handleEvmTransaction(
TransactionType.bridge,
quoteResponse.trade as TxData,
await this.#handleEvmTransaction({
transactionType: TransactionType.bridge,
trade: quoteResponse.trade as TxData,
quoteResponse,
approvalTxId,
),
requireApproval,
}),
);
}
}
Expand Down
Loading