Skip to content
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

Added QR code #23428

Merged
merged 64 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
6fb6df3
Added QR code
albertolive Mar 12, 2024
36ec5f6
Improved code
albertolive Mar 13, 2024
355e5e0
Clean stuff
albertolive Mar 15, 2024
e4e8966
initial handshake work
albertolive Mar 20, 2024
8cd8b94
Merge branch 'develop' into MMI-4693-QR-code
albertolive Mar 21, 2024
6259201
Merge branch 'develop' into MMI-4693-QR-code
albertolive Mar 21, 2024
73a32e3
Improved code
albertolive Mar 21, 2024
c925859
Merge branch 'develop' into MMI-4693-QR-code
albertolive Mar 21, 2024
7a8356a
Improving code
albertolive Mar 21, 2024
5318444
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 5, 2024
abd835a
Continue working on it
albertolive Apr 5, 2024
14184b2
Merge branch 'MMI-4693-QR-code' of https://github.com/MetaMask/metama…
albertolive Apr 5, 2024
b81d203
Added logic to decrypt the connect request
albertolive Apr 12, 2024
c271122
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 15, 2024
c0a55f9
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 16, 2024
244f554
updated packages, lavamoat and qr code modal
albertolive Apr 17, 2024
53513b4
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 17, 2024
8793db8
Update LavaMoat policies
metamaskbot Apr 17, 2024
e3ae6de
improved qr modal component
albertolive Apr 18, 2024
4e55314
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 18, 2024
bccb5dc
Improved qr code modal
albertolive Apr 18, 2024
6a1e6ef
Added unit tests
albertolive Apr 19, 2024
114b00d
Added storybook story and improved tests
albertolive Apr 19, 2024
00554ba
Added sentry errors
albertolive Apr 24, 2024
ae44c3b
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 24, 2024
7f7950d
fixed issues
albertolive Apr 24, 2024
770b541
updated lavamoat
albertolive Apr 24, 2024
dff9172
Update LavaMoat policies
metamaskbot Apr 24, 2024
c5fc2f2
Fixed lavamoat
albertolive Apr 24, 2024
65268de
chore: fix lavamoat and small code change
zone-live Apr 24, 2024
c62c3f2
Merge branch 'develop' into MMI-4693-QR-code
zone-live Apr 24, 2024
996f2b7
Update LavaMoat policies
metamaskbot Apr 24, 2024
0b7f644
chore: fix audit failing and test
zone-live Apr 24, 2024
f7af556
adding back connectRequest as is needed to listen for new connectRequ…
albertolive Apr 25, 2024
8b081a3
Fixed tests, added e2e tests and improved code
albertolive Apr 26, 2024
58c563f
test
albertolive Apr 28, 2024
b394db3
test
albertolive Apr 28, 2024
471fc77
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 29, 2024
d2619e5
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 29, 2024
6acd368
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 29, 2024
8d52d28
FIx PW issue
albertolive Apr 29, 2024
c8f0c47
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 30, 2024
2a10cef
Merge branch 'develop' into MMI-4693-QR-code
albertolive Apr 30, 2024
0ad5890
remove console log
albertolive Apr 30, 2024
a1e90fb
Merge branch 'MMI-4693-QR-code' of https://github.com/MetaMask/metama…
albertolive Apr 30, 2024
0b42348
updated to tsx
albertolive Apr 30, 2024
5e113ab
move to tsx
albertolive Apr 30, 2024
3ff89a2
fix tsx
albertolive Apr 30, 2024
2124074
fixed tsx
albertolive Apr 30, 2024
5f9e120
fix lint
albertolive May 2, 2024
3703a12
Merge branch 'develop' into MMI-4693-QR-code
albertolive May 2, 2024
546abb6
lint issues
albertolive May 2, 2024
a4afe1b
fix lint
albertolive May 2, 2024
2e65fbe
fix lint
albertolive May 2, 2024
5eff79d
test
zone-live May 2, 2024
22b5e3a
test 2
zone-live May 2, 2024
74e41d2
clean up
zone-live May 2, 2024
38a9719
test 3
zone-live May 2, 2024
d63ae12
clean up test changes
zone-live May 2, 2024
5b29830
test logging element
zone-live May 2, 2024
014d577
test logging env var
zone-live May 2, 2024
9e19a21
clean up
zone-live May 2, 2024
8ecc17f
clean up
zone-live May 2, 2024
87761a1
updates visual snapshots
zone-live May 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions app/scripts/controllers/mmi-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
IInteractiveRefreshTokenChangeEvent,
Label,
Signature,
ConnectionRequest,
} from '../../../shared/constants/mmi-controller';
import AccountTracker from '../lib/account-tracker';
import AppStateController from './app-state';
Expand Down Expand Up @@ -106,6 +107,10 @@ export default class MMIController extends EventEmitter {

private updateTransactionHash: (txId: string, txHash: string) => void;

private setChannelId: (channelId: string) => void;

private setConnectionRequest: (payload: ConnectionRequest | null) => void;

public trackTransactionEvents: (
args: { transactionMeta: TransactionMeta },
// TODO: Replace `any` with type
Expand Down Expand Up @@ -147,6 +152,8 @@ export default class MMIController extends EventEmitter {
this.extension = opts.extension;

this.updateTransactionHash = opts.updateTransactionHash;
this.setChannelId = opts.setChannelId;
this.setConnectionRequest = opts.setConnectionRequest;

this.trackTransactionEvents = opts.trackTransactionEvents;
this.txStateManager = {
Expand Down Expand Up @@ -185,6 +192,20 @@ export default class MMIController extends EventEmitter {
await this.handleSigningEvents(signature, messageId, 'v4');
},
);

this.transactionUpdateController.on(
'handshake',
async ({ channelId }: { channelId: string }) => {
this.setChannelId(channelId);
},
);

this.transactionUpdateController.on(
'connection.request',
async (payload: ConnectionRequest) => {
this.setConnectionRequest(payload);
},
);
} // End of constructor

async persistKeyringsAfterRefreshTokenChange() {
Expand Down
8 changes: 8 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,10 @@ export default class MetamaskController extends EventEmitter {
this.txController.updateTransaction(txMeta, note),
updateTransactionHash: (id, hash) =>
this.txController.updateCustodialTransaction(id, { hash }),
setChannelId: (channelId) =>
this.institutionalFeaturesController.setChannelId(channelId),
setConnectionRequest: (payload) =>
this.institutionalFeaturesController.setConnectionRequest(payload),
});
///: END:ONLY_INCLUDE_IF

Expand Down Expand Up @@ -3356,6 +3360,10 @@ export default class MetamaskController extends EventEmitter {
this.institutionalFeaturesController.removeAddTokenConnectRequest.bind(
this.institutionalFeaturesController,
),
setConnectionRequest:
this.institutionalFeaturesController.setConnectionRequest.bind(
this.institutionalFeaturesController,
),
showInteractiveReplacementTokenBanner:
appStateController.showInteractiveReplacementTokenBanner.bind(
appStateController,
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,14 @@
"@lavamoat/lavadome-react": "0.0.17",
"@lavamoat/snow": "^2.0.1",
"@material-ui/core": "^4.11.0",
"@metamask-institutional/custody-controller": "^0.2.24",
"@metamask-institutional/custody-keyring": "^1.0.12",
"@metamask-institutional/extension": "^0.3.20",
"@metamask-institutional/institutional-features": "^1.2.15",
"@metamask-institutional/custody-controller": "^0.2.25",
"@metamask-institutional/custody-keyring": "^1.1.0",
"@metamask-institutional/extension": "^0.3.21",
"@metamask-institutional/institutional-features": "^1.3.0",
"@metamask-institutional/portfolio-dashboard": "^1.4.0",
"@metamask-institutional/rpc-allowlist": "1.0.3",
"@metamask-institutional/sdk": "^0.1.25",
"@metamask-institutional/transaction-update": "^0.1.38",
"@metamask-institutional/rpc-allowlist": "^1.0.3",
"@metamask-institutional/sdk": "^0.1.26",
"@metamask-institutional/transaction-update": "^0.2.0",
"@metamask/abi-utils": "^2.0.2",
"@metamask/accounts-controller": "^11.0.0",
"@metamask/address-book-controller": "^3.1.7",
Expand Down
8 changes: 8 additions & 0 deletions shared/constants/mmi-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export type MMIControllerOptions = {
// TODO: Replace `any` with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
updateTransaction: (txMeta: any) => void;
setChannelId: (channelId: string) => void;
setConnectionRequest: (payload: ConnectionRequest | null) => void;
};

export type ISignedEvent = {
Expand Down Expand Up @@ -89,3 +91,9 @@ export type NetworkConfiguration = {
chainId: string;
setActiveNetwork: (chainId: string) => void;
};

export type ConnectionRequest = {
payload: string;
traceId: string;
channelId: string;
};
19 changes: 18 additions & 1 deletion test/e2e/mmi/custodian-hooks/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export class CustodianTestClient implements ICustodianTestClient {
const diffTime = transactions.map((tx: { createdAt: string }) =>
Math.abs(
new Date(tx.createdAt).getTime() -
parseInt(signedTransactionTime, 10),
parseInt(signedTransactionTime, 10),
),
);
const min = Math.min(...diffTime);
Expand Down Expand Up @@ -346,4 +346,21 @@ export class CustodianTestClient implements ICustodianTestClient {
async rejectPersonalSignatureId(txId: string): Promise<string | RegExp> {
return txId;
}

public async postConnectionRequest(data: any) {
return (await axios
.post("https://neptune-custody.dev.metamask-institutional.io/qrcode/connection-request", data, {
headers: {
'Content-Type': 'application/json',
},
})
.then(function (response) {
expect(response.status).toBe(200);
return response.data;
})
.catch(function (error) {
console.log(error.response.data);
throw error;
})) as string;
}
}
57 changes: 40 additions & 17 deletions test/e2e/mmi/pageObjects/mmi-accountMenu-page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type Locator, type Page, test, expect } from '@playwright/test';
import { getCustodianInfoByName } from '../helpers/custodian-helper';
import { MMISaturnUIPage } from './mmi-saturn-ui-page';
import { CustodianTestClient } from '../custodian-hooks/hooks';

export class MMIAccountMenuPage {
readonly page: Page;
Expand Down Expand Up @@ -46,7 +47,7 @@ export class MMIAccountMenuPage {
.filter({ hasText: 'Select an account' });
}

async connectCustodian(name: string, visual?: boolean) {
async connectCustodian(name: string, visual?: boolean, qrCode?: boolean) {
await this.page
.getByRole('button', { name: /Add account or hardware wallet/iu })
.click();
Expand All @@ -61,6 +62,7 @@ export class MMIAccountMenuPage {
}

const custodian = await getCustodianInfoByName(name);

await this.page
.getByRole('list')
.locator('div')
Expand All @@ -69,24 +71,45 @@ export class MMIAccountMenuPage {
.getByTestId('custody-connect-button')
.click();

await expect(
this.page.getByText(/connect saturn custody accounts/iu),
).toBeVisible();
if (visual) {
await test.expect
.soft(this.page)
.toHaveScreenshot('custodian_connection_info.png', { fullPage: true });
if (qrCode) {
const spanElement = await this.page.$('span.hidden');

if (spanElement) {
let data = await spanElement.getAttribute('data-value');

while (!data) {
await new Promise(resolve => setTimeout(resolve, 1000));
data = await spanElement.getAttribute('data-value');
}

const client = new CustodianTestClient();
await client.setup();
await client.postConnectionRequest(data);
await this.page.getByTestId('select-all-accounts-selected-false').click();
await this.page.getByRole('button', { name: /connect/iu }).click();
await this.page.getByRole('button', { name: /close/iu }).first().click();
}
} else {
await expect(
this.page.getByText(/connect saturn custody accounts/iu),
).toBeVisible();
if (visual) {
await test.expect
.soft(this.page)
.toHaveScreenshot('custodian_connection_info.png', { fullPage: true });
}

const pagePromise = this.page.context().waitForEvent('page');
await this.page.getByRole('button', { name: /continue/iu }).click();
const saturnUI = await pagePromise;
await saturnUI.waitForLoadState();

const saturnUIPage = new MMISaturnUIPage(saturnUI);
await saturnUIPage.connectMMI();
await this.page.getByRole('button', { name: /cancel/iu }).click();
await this.page.getByRole('button', { name: /back/iu }).click();
}

const pagePromise = this.page.context().waitForEvent('page');
await this.page.getByRole('button', { name: /continue/iu }).click();
const saturnUI = await pagePromise;
await saturnUI.waitForLoadState();

const saturnUIPage = new MMISaturnUIPage(saturnUI);
await saturnUIPage.connectMMI();
await this.page.getByRole('button', { name: /cancel/iu }).click();
await this.page.getByRole('button', { name: /back/iu }).click();
}

async selectCustodyAccount(account: string) {
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/mmi/pageObjects/mmi-network-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class MMINetworkPage {
}

async selectNetwork(network: string) {
await this.page.getByText(network).first().click();
await this.page.waitForSelector(`text=${network}`, { state: 'visible' });
await this.page.click(`text=${network}`, { force: true });
}
}
65 changes: 65 additions & 0 deletions test/e2e/mmi/specs/qrCode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { type Page, type BrowserContext } from '@playwright/test';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

import { test } from '../helpers/extension-loader';
import { ChromeExtensionPage } from '../pageObjects/mmi-extension-page';
import { MMIMainMenuPage } from '../pageObjects/mmi-mainMenu-page';
import { MMINetworkPage } from '../pageObjects/mmi-network-page';
import { MMISignUpPage } from '../pageObjects/mmi-signup-page';
import { MMIMainPage } from '../pageObjects/mmi-main-page';
import { MMIAccountMenuPage } from '../pageObjects/mmi-accountMenu-page';
import { CustodianTestClient } from '../custodian-hooks/hooks';
import { SEPOLIA_DISPLAY_NAME } from '../helpers/utils';

test.describe('QR Code Connection Request', () => {
test('run the extension and add custodian accounts using the QR Code feature', async ({
page,
context,
}) => {
const client = new CustodianTestClient();
await client.setup();

// Getting extension id of MMI
const extensions = new ChromeExtensionPage(await context.newPage());
await extensions.goto();
await extensions.setDevMode();
const extensionId = await extensions.getExtensionId();
await extensions.close();

const signUp = new MMISignUpPage(
await context.newPage(),
extensionId as string,
);
await signUp.goto();
await signUp.start();
await signUp.authentication();
await signUp.info();
await signUp.close();

// Setup testnetwork in settings
const mainMenuPage = new MMIMainMenuPage(page, extensionId as string);
await mainMenuPage.goto();
await mainMenuPage.fillPassword();
await mainMenuPage.finishOnboarding();
await mainMenuPage.selectMenuOption('settings');
await mainMenuPage.selectSettings('Advance');
await mainMenuPage.switchTestNetwork();
await mainMenuPage.closeSettings();

// Check network
const networkPage = new MMINetworkPage(page);
await networkPage.open();
await networkPage.selectNetwork(
process.env.MMI_E2E_TEST_NETWORK || SEPOLIA_DISPLAY_NAME,
);

// Get account from and to name
const accountFrom = await client.getAccountFrom();

const accountsPopup = new MMIAccountMenuPage(page);
await accountsPopup.accountsMenu();
await accountsPopup.connectCustodian('Neptune Custody', false, true);
await accountsPopup.selectCustodyAccount(accountFrom);

const mainPage = new MMIMainPage(page);
await mainPage.bringToFront();
});
});
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions ui/components/institutional/qr-code-modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './qr-code-modal';
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { Meta, StoryFn } from '@storybook/react';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import QRCodeModal from './qr-code-modal';

const mockStore = configureMockStore();
const testData = {
metamask: {
institutionalFeatures: {
channelId: 'channel123',
connectionRequest: {
payload: 'encryptedPayload',
},
},
},
};

const store = mockStore(testData);

const meta: Meta<typeof QRCodeModal> = {
title: 'Components/QRCodeModal',
decorators: [
(storyFn: any) => <Provider store={store}>{storyFn()}</Provider>,
],
component: QRCodeModal,
argTypes: {
onClose: { action: 'closed' },
custodianName: { control: 'text' },
custodianURL: { control: 'text' },
},
};

export default meta;

const Template: StoryFn<typeof QRCodeModal> = (args) => (
<QRCodeModal {...args} />
);

export const Default = Template.bind({});
Default.args = {
custodianName: 'Test Custodian',
custodianURL: 'http://testcustodian.com',
};

export const WithError = Template.bind({});
WithError.args = {
...Default.args,
error: 'Failed to load data',
};