1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-24 19:10:22 +01:00

Trigger transaction popup using ApprovalController (#18400)

This commit is contained in:
Matthew Walsh 2023-04-11 14:18:43 +01:00 committed by GitHub
parent 0b1354e446
commit bb0dff9443
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 200 additions and 27 deletions

View File

@ -727,7 +727,6 @@ export function setupController(
}
function getUnapprovedTransactionCount() {
const unapprovedTxCount = controller.txController.getUnapprovedTxCount();
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager;
const { unapprovedEncryptionPublicKeyMsgCount } =
controller.encryptionPublicKeyManager;
@ -736,7 +735,6 @@ export function setupController(
const waitingForUnlockCount =
controller.appStateController.waitingForUnlock.length;
return (
unapprovedTxCount +
unapprovedDecryptMsgCount +
unapprovedEncryptionPublicKeyMsgCount +
pendingApprovalCount +

View File

@ -52,7 +52,10 @@ import {
determineTransactionType,
isEIP1559Transaction,
} from '../../../../shared/modules/transaction.utils';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import {
ORIGIN_METAMASK,
MESSAGE_TYPE,
} from '../../../../shared/constants/app';
import {
calcGasTotal,
getSwapsTokensReceivedFromTxMeta,
@ -156,6 +159,7 @@ export default class TransactionController extends EventEmitter {
this.getAccountType = opts.getAccountType;
this.getTokenStandardAndDetails = opts.getTokenStandardAndDetails;
this.securityProviderRequest = opts.securityProviderRequest;
this.messagingSystem = opts.messenger;
this.memStore = new ObservableStore({});
@ -798,6 +802,7 @@ export default class TransactionController extends EventEmitter {
this.txStateManager.getTransactionWithActionId(actionId);
if (existingTxMeta) {
this.emit('newUnapprovedTx', existingTxMeta);
this._requestApproval(existingTxMeta);
existingTxMeta = await this.addTransactionGasDefaults(existingTxMeta);
return existingTxMeta;
}
@ -870,6 +875,7 @@ export default class TransactionController extends EventEmitter {
this.addTransaction(txMeta);
this.emit('newUnapprovedTx', txMeta);
this._requestApproval(txMeta);
txMeta = await this.addTransactionGasDefaults(txMeta);
@ -1355,6 +1361,7 @@ export default class TransactionController extends EventEmitter {
try {
// approve
this.txStateManager.setTxStatusApproved(txId);
this._acceptApproval(txMeta);
// get next nonce
const fromAddress = txMeta.txParams.from;
// wait for a nonce
@ -1734,6 +1741,7 @@ export default class TransactionController extends EventEmitter {
async cancelTransaction(txId, actionId) {
const txMeta = this.txStateManager.getTransaction(txId);
this.txStateManager.setTxStatusRejected(txId);
this._rejectApproval(txMeta);
this._trackTransactionMetricsEvent(
txMeta,
TransactionMetaMetricsEvent.rejected,
@ -2596,4 +2604,54 @@ export default class TransactionController extends EventEmitter {
},
);
}
_requestApproval(txMeta) {
const id = this._getApprovalId(txMeta);
const { origin } = txMeta;
const type = MESSAGE_TYPE.TRANSACTION;
const requestData = { txId: txMeta.id };
this.messagingSystem
.call(
'ApprovalController:addRequest',
{
id,
origin,
type,
requestData,
},
true,
)
.catch(() => {
// Intentionally ignored as promise not currently used
});
}
_acceptApproval(txMeta) {
const id = this._getApprovalId(txMeta);
try {
this.messagingSystem.call('ApprovalController:acceptRequest', id);
} catch (error) {
log.error('Failed to accept transaction approval request', error);
}
}
_rejectApproval(txMeta) {
const id = this._getApprovalId(txMeta);
try {
this.messagingSystem.call(
'ApprovalController:rejectRequest',
id,
new Error('Rejected'),
);
} catch (error) {
log.error('Failed to reject transaction approval request', error);
}
}
_getApprovalId(txMeta) {
return String(txMeta.id);
}
}

View File

@ -29,7 +29,10 @@ import {
GasRecommendations,
} from '../../../../shared/constants/gas';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import {
MESSAGE_TYPE,
ORIGIN_METAMASK,
} from '../../../../shared/constants/app';
import { NetworkStatus } from '../../../../shared/constants/network';
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils';
import TransactionController from '.';
@ -52,7 +55,8 @@ describe('Transaction Controller', function () {
fromAccount,
fragmentExists,
networkStatusStore,
getCurrentChainId;
getCurrentChainId,
messengerMock;
beforeEach(function () {
fragmentExists = false;
@ -76,6 +80,7 @@ describe('Transaction Controller', function () {
blockTrackerStub.getLatestBlock = noop;
getCurrentChainId = sinon.stub().callsFake(() => currentChainId);
messengerMock = { call: sinon.stub().returns(Promise.resolve()) };
txController = new TransactionController({
provider,
@ -108,6 +113,7 @@ describe('Transaction Controller', function () {
getAccountType: () => 'MetaMask',
getDeviceModel: () => 'N/A',
securityProviderRequest: () => undefined,
messenger: messengerMock,
});
txController.nonceTracker.getNonceLock = () =>
Promise.resolve({ nextNonce: 0, releaseLock: noop });
@ -489,6 +495,67 @@ describe('Transaction Controller', function () {
{ message: 'MetaMask is having trouble connecting to the network' },
);
});
it('should create an approval request', async function () {
const txMeta = await txController.addUnapprovedTransaction(
undefined,
{
from: selectedAddress,
to: recipientAddress,
},
ORIGIN_METAMASK,
);
assert.equal(messengerMock.call.callCount, 1);
assert.deepEqual(messengerMock.call.getCall(0).args, [
'ApprovalController:addRequest',
{
id: String(txMeta.id),
origin: ORIGIN_METAMASK,
requestData: { txId: txMeta.id },
type: MESSAGE_TYPE.TRANSACTION,
},
true, // Show popup
]);
});
it('should still create an approval request when called twice with same actionId', async function () {
await txController.addUnapprovedTransaction(
undefined,
{
from: selectedAddress,
to: recipientAddress,
},
ORIGIN_METAMASK,
undefined,
undefined,
'12345',
);
const secondTxMeta = await txController.addUnapprovedTransaction(
undefined,
{
from: selectedAddress,
to: recipientAddress,
},
undefined,
undefined,
undefined,
'12345',
);
assert.equal(messengerMock.call.callCount, 2);
assert.deepEqual(messengerMock.call.getCall(1).args, [
'ApprovalController:addRequest',
{
id: String(secondTxMeta.id),
origin: ORIGIN_METAMASK,
requestData: { txId: secondTxMeta.id },
type: MESSAGE_TYPE.TRANSACTION,
},
true, // Show popup
]);
});
});
describe('#createCancelTransaction', function () {
@ -997,9 +1064,11 @@ describe('Transaction Controller', function () {
});
describe('#approveTransaction', function () {
it('does not overwrite set values', async function () {
const originalValue = '0x01';
const txMeta = {
let originalValue, txMeta, signStub, pubStub;
beforeEach(function () {
originalValue = '0x01';
txMeta = {
id: '1',
status: TransactionStatus.unapproved,
metamaskNetworkId: currentNetworkId,
@ -1019,17 +1088,22 @@ describe('Transaction Controller', function () {
providerResultStub.eth_gasPrice = wrongValue;
providerResultStub.eth_estimateGas = '0x5209';
const signStub = sinon
signStub = sinon
.stub(txController, 'signTransaction')
.callsFake(() => Promise.resolve());
const pubStub = sinon
.stub(txController, 'publishTransaction')
.callsFake(() => {
txController.setTxHash('1', originalValue);
txController.txStateManager.setTxStatusSubmitted('1');
});
pubStub = sinon.stub(txController, 'publishTransaction').callsFake(() => {
txController.setTxHash('1', originalValue);
txController.txStateManager.setTxStatusSubmitted('1');
});
});
afterEach(function () {
signStub.restore();
pubStub.restore();
});
it('does not overwrite set values', async function () {
await txController.approveTransaction(txMeta.id);
const result = txController.txStateManager.getTransaction(txMeta.id);
const params = result.txParams;
@ -1042,8 +1116,21 @@ describe('Transaction Controller', function () {
TransactionStatus.submitted,
'should have reached the submitted status.',
);
signStub.restore();
pubStub.restore();
});
it('should accept the approval request', async function () {
await txController.approveTransaction(txMeta.id);
assert.equal(messengerMock.call.callCount, 1);
assert.deepEqual(messengerMock.call.getCall(0).args, [
'ApprovalController:acceptRequest',
txMeta.id,
]);
});
it('should not throw if accepting approval request throws', async function () {
messengerMock.call.throws();
await txController.approveTransaction(txMeta.id);
});
});
@ -1108,7 +1195,7 @@ describe('Transaction Controller', function () {
});
describe('#cancelTransaction', function () {
it('should emit a status change to rejected', function (done) {
beforeEach(function () {
txController.txStateManager._addTransactionsToState([
{
id: 0,
@ -1181,7 +1268,9 @@ describe('Transaction Controller', function () {
history: [{}],
},
]);
});
it('should emit a status change to rejected', function (done) {
txController.once('tx:status-update', (txId, status) => {
try {
assert.equal(
@ -1198,6 +1287,22 @@ describe('Transaction Controller', function () {
txController.cancelTransaction(0);
});
it('should reject the approval request', function () {
txController.cancelTransaction(0);
assert.equal(messengerMock.call.callCount, 1);
assert.deepEqual(messengerMock.call.getCall(0).args, [
'ApprovalController:rejectRequest',
'0',
new Error('Rejected'),
]);
});
it('should not throw if rejecting approval request throws', async function () {
messengerMock.call.throws();
txController.cancelTransaction(0);
});
});
describe('#createSpeedUpTransaction', function () {

View File

@ -1007,8 +1007,15 @@ export default class MetamaskController extends EventEmitter {
getDeviceModel: this.getDeviceModel.bind(this),
getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this),
securityProviderRequest: this.securityProviderRequest.bind(this),
messenger: this.controllerMessenger.getRestricted({
name: 'TransactionController',
allowedActions: [
`${this.approvalController.name}:addRequest`,
`${this.approvalController.name}:acceptRequest`,
`${this.approvalController.name}:rejectRequest`,
],
}),
});
this.txController.on('newUnapprovedTx', () => opts.showUserConfirmation());
this.txController.on(`tx:status-update`, async (txId, status) => {
if (

View File

@ -53,6 +53,7 @@ export const MESSAGE_TYPE = {
PERSONAL_SIGN: 'personal_sign',
SEND_METADATA: 'metamask_sendDomainMetadata',
SWITCH_ETHEREUM_CHAIN: 'wallet_switchEthereumChain',
TRANSACTION: 'transaction',
WALLET_REQUEST_PERMISSIONS: 'wallet_requestPermissions',
WATCH_ASSET: 'wallet_watchAsset',
WATCH_ASSET_LEGACY: 'metamask_watchAsset',

View File

@ -1535,7 +1535,18 @@
"origin": "tmashuang.github.io"
}
],
"desktopEnabled": false
"desktopEnabled": false,
"pendingApprovals": {
"testApprovalId": {
"id": "testApprovalId",
"time": 1528133319641,
"origin": "metamask",
"type": "transaction",
"requestData": { "txId": "testTransactionId" },
"requestState": { "test": "value" }
}
},
"pendingApprovalCount": 1
},
"send": {
"amountMode": "INPUT",

View File

@ -484,21 +484,14 @@ export function getCurrentCurrency(state) {
export function getTotalUnapprovedCount(state) {
const {
unapprovedMsgCount = 0,
unapprovedPersonalMsgCount = 0,
unapprovedDecryptMsgCount = 0,
unapprovedEncryptionPublicKeyMsgCount = 0,
unapprovedTypedMessagesCount = 0,
pendingApprovalCount = 0,
} = state.metamask;
return (
unapprovedMsgCount +
unapprovedPersonalMsgCount +
unapprovedDecryptMsgCount +
unapprovedEncryptionPublicKeyMsgCount +
unapprovedTypedMessagesCount +
getUnapprovedTxCount(state) +
pendingApprovalCount +
getSuggestedAssetCount(state)
);