Skip to content

Commit

Permalink
Use single controller method to add transactions (#20007)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewwalsh0 authored Jul 25, 2023
1 parent 4b9a4d3 commit fdc3558
Show file tree
Hide file tree
Showing 7 changed files with 534 additions and 790 deletions.
268 changes: 117 additions & 151 deletions app/scripts/controllers/transactions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,22 +315,6 @@ export default class TransactionController extends EventEmitter {
});
}

/**
* Adds a tx to the txlist
*
* @param txMeta
* @fires ${txMeta.id}:unapproved
*/
addTransaction(txMeta) {
this.txStateManager.addTransaction(txMeta);
this.emit(`${txMeta.id}:unapproved`, txMeta);
this._trackTransactionMetricsEvent(
txMeta,
TransactionMetaMetricsEvent.added,
txMeta.actionId,
);
}

/**
* Wipes the transactions for a given account
*
Expand All @@ -341,64 +325,52 @@ export default class TransactionController extends EventEmitter {
}

/**
* Add a new unapproved transaction to the pipeline
* Add a new unapproved transaction
*
* @returns {Promise<string>} the hash of the transaction after being submitted to the network
* @param {object} txParams - txParams for the transaction
* @param {object} opts - with the key origin to put the origin on the txMeta
* @param {object} txParams - Standard parameters for an Ethereum transaction
* @param {object} opts - Options
* @param {string} opts.actionId - Unique ID to prevent duplicate requests
* @param {string} opts.method - RPC method that requested the transaction
* @param {string} opts.origin - Origin of the transaction request, such as the hostname of a dApp
* @param {boolean} opts.requireApproval - Whether the transaction requires approval by the user
* @param {object[]} opts.sendFlowHistory - Associated history to store with the transaction
* @param {object} opts.swaps - Options specific to swap transactions
* @param {boolean} opts.swaps.hasApproveTx - Whether this transaction required an approval transaction
* @param {boolean} opts.swaps.meta - Additional metadata to store for the transaction
* @param {TransactionType} opts.type - Type of transaction to add, such as 'cancel' or 'swap'
* @returns {Promise<{transactionMeta: TransactionMeta, result: Promise<string>}>} An object containing the transaction metadata, and a promise that resolves to the transaction hash after being submitted to the network
*/
async newUnapprovedTransaction(txParams, opts = {}) {
log.debug(
`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`,
);

const { txMeta: initialTxMeta, isExisting } = await this._createTransaction(
opts.method,
txParams,
opts.origin,
undefined,
undefined,
opts.id,
);

const txId = initialTxMeta.id;
const isCompleted = this._isTransactionCompleted(initialTxMeta);

const finishedPromise = isCompleted
? Promise.resolve(initialTxMeta)
: this._waitForTransactionFinished(txId);

if (!isExisting && !isCompleted) {
try {
await this._requestTransactionApproval(initialTxMeta);
} catch (error) {
// Errors generated from final status using finished event
}
}
async addTransaction(
txParams,
{
actionId,
method,
origin,
requireApproval,
sendFlowHistory,
swaps: { hasApproveTx, meta } = {},
type,
} = {},
) {
log.debug(`MetaMaskController addTransaction ${JSON.stringify(txParams)}`);

const finalTxMeta = await finishedPromise;
const finalStatus = finalTxMeta?.status;
const { txMeta, isExisting } = await this._createTransaction(txParams, {
actionId,
method,
origin,
sendFlowHistory,
swaps: { hasApproveTx, meta },
type,
});

switch (finalStatus) {
case TransactionStatus.submitted:
return finalTxMeta.hash;
case TransactionStatus.rejected:
throw cleanErrorStack(
ethErrors.provider.userRejectedRequest(
'MetaMask Tx Signature: User denied transaction signature.',
),
);
case TransactionStatus.failed:
throw cleanErrorStack(ethErrors.rpc.internal(finalTxMeta.err.message));
default:
throw cleanErrorStack(
ethErrors.rpc.internal(
`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(
finalTxMeta?.txParams,
)}`,
),
);
}
return {
transactionMeta: txMeta,
result: this._processApproval(txMeta, {
isExisting,
requireApproval,
actionId,
}),
};
}

/**
Expand Down Expand Up @@ -694,7 +666,7 @@ export default class TransactionController extends EventEmitter {
updateTxMeta.loadingDefaults = false;

// The history note used here 'Added new unapproved transaction.' is confusing update call only updated the gas defaults.
// We need to improve `this.addTransaction` to accept history note and change note here.
// We need to improve `this._addTransaction` to accept history note and change note here.
this.txStateManager.updateTransaction(
updateTxMeta,
'Added new unapproved transaction.',
Expand All @@ -705,58 +677,6 @@ export default class TransactionController extends EventEmitter {

// ====================================================================================================================================================

/**
* Validates and generates a txMeta with defaults and puts it in txStateManager
* store.
*
* actionId is used to uniquely identify a request to create a transaction.
* Only 1 transaction will be created for multiple requests with same actionId.
* actionId is fix used for making this action idempotent to deal with scenario when
* action is invoked multiple times with same parameters in MV3 due to service worker re-activation.
*
* @param txMethodType
* @param txParams
* @param origin
* @param transactionType
* @param sendFlowHistory
* @param actionId
* @param options
*/
async addUnapprovedTransaction(
txMethodType,
txParams,
origin,
transactionType,
sendFlowHistory = [],
actionId,
options,
) {
const { txMeta, isExisting } = await this._createTransaction(
txMethodType,
txParams,
origin,
transactionType,
sendFlowHistory,
actionId,
options,
);
if (isExisting) {
const isCompleted = this._isTransactionCompleted(txMeta);

return isCompleted
? txMeta
: await this._waitForTransactionFinished(txMeta.id);
}

if (options?.requireApproval === false) {
await this._updateAndApproveTransaction(txMeta, actionId);
} else {
await this._requestTransactionApproval(txMeta, { actionId });
}

return txMeta;
}

/**
* Adds the tx gas defaults: gas && gasPrice
*
Expand Down Expand Up @@ -1122,7 +1042,7 @@ export default class TransactionController extends EventEmitter {
newTxMeta.estimatedBaseFee = estimatedBaseFee;
}

this.addTransaction(newTxMeta);
this._addTransaction(newTxMeta);
await this._approveTransaction(newTxMeta.id, actionId, {
hasApprovalRequest: false,
});
Expand Down Expand Up @@ -1182,7 +1102,7 @@ export default class TransactionController extends EventEmitter {
newTxMeta.estimatedBaseFee = estimatedBaseFee;
}

this.addTransaction(newTxMeta);
this._addTransaction(newTxMeta);
await this._approveTransaction(newTxMeta.id, actionId);
return newTxMeta;
}
Expand Down Expand Up @@ -1593,21 +1513,14 @@ export default class TransactionController extends EventEmitter {
}

async _createTransaction(
txMethodType,
txParams,
origin,
transactionType,
sendFlowHistory = [],
actionId,
options,
{ actionId, method, origin, sendFlowHistory = [], swaps, type },
) {
if (
transactionType !== undefined &&
!VALID_UNAPPROVED_TRANSACTION_TYPES.includes(transactionType)
type !== undefined &&
!VALID_UNAPPROVED_TRANSACTION_TYPES.includes(type)
) {
throw new Error(
`TransactionController - invalid transactionType value: ${transactionType}`,
);
throw new Error(`TransactionController - invalid type value: ${type}`);
}

// If a transaction is found with the same actionId, do not create a new speed-up transaction.
Expand Down Expand Up @@ -1665,40 +1578,32 @@ export default class TransactionController extends EventEmitter {
}
}

const { type } = await determineTransactionType(
const { type: determinedType } = await determineTransactionType(
normalizedTxParams,
this.query,
);
txMeta.type = transactionType || type;
txMeta.type = type || determinedType;

// ensure value
txMeta.txParams.value = txMeta.txParams.value
? addHexPrefix(txMeta.txParams.value)
: '0x0';

if (txMethodType && this.securityProviderRequest) {
if (method && this.securityProviderRequest) {
const securityProviderResponse = await this.securityProviderRequest(
txMeta,
txMethodType,
method,
);

txMeta.securityProviderResponse = securityProviderResponse;
}

this.addTransaction(txMeta);
this._addTransaction(txMeta);

txMeta = await this.addTransactionGasDefaults(txMeta);

if (
[TransactionType.swap, TransactionType.swapApproval].includes(
transactionType,
)
) {
txMeta = await this._createSwapsTransaction(
options?.swaps,
transactionType,
txMeta,
);
if ([TransactionType.swap, TransactionType.swapApproval].includes(type)) {
txMeta = await this._createSwapsTransaction(swaps, type, txMeta);
}

return { txMeta, isExisting: false };
Expand Down Expand Up @@ -1830,6 +1735,51 @@ export default class TransactionController extends EventEmitter {
await this._approveTransaction(txMeta.id, actionId);
}

async _processApproval(txMeta, { actionId, isExisting, requireApproval }) {
const txId = txMeta.id;
const isCompleted = this._isTransactionCompleted(txMeta);

const finishedPromise = isCompleted
? Promise.resolve(txMeta)
: this._waitForTransactionFinished(txId);

if (!isExisting && !isCompleted) {
try {
if (requireApproval === false) {
await this._updateAndApproveTransaction(txMeta, actionId);
} else {
await this._requestTransactionApproval(txMeta, { actionId });
}
} catch (error) {
// Errors generated from final status using finished event
}
}

const finalTxMeta = await finishedPromise;
const finalStatus = finalTxMeta?.status;

switch (finalStatus) {
case TransactionStatus.submitted:
return finalTxMeta.hash;
case TransactionStatus.rejected:
throw cleanErrorStack(
ethErrors.provider.userRejectedRequest(
'MetaMask Tx Signature: User denied transaction signature.',
),
);
case TransactionStatus.failed:
throw cleanErrorStack(ethErrors.rpc.internal(finalTxMeta.err.message));
default:
throw cleanErrorStack(
ethErrors.rpc.internal(
`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(
finalTxMeta?.txParams,
)}`,
),
);
}
}

/**
* sets the tx status to approved
* auto fills the nonce
Expand Down Expand Up @@ -2762,6 +2712,22 @@ export default class TransactionController extends EventEmitter {
);
}

/**
* Adds a tx to the txlist
*
* @param txMeta
* @fires ${txMeta.id}:unapproved
*/
_addTransaction(txMeta) {
this.txStateManager.addTransaction(txMeta);
this.emit(`${txMeta.id}:unapproved`, txMeta);
this._trackTransactionMetricsEvent(
txMeta,
TransactionMetaMetricsEvent.added,
txMeta.actionId,
);
}

// Approvals

async _requestTransactionApproval(
Expand Down
Loading

0 comments on commit fdc3558

Please sign in to comment.