Skip to content

Commit f60bea6

Browse files
committed
feat: oauth, seedless controller integration
1 parent 461d7b7 commit f60bea6

File tree

34 files changed

+2087
-11
lines changed

34 files changed

+2087
-11
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ module.exports = {
3737
'@typescript-eslint/no-explicit-any': 'error',
3838
// Under discussion
3939
'@typescript-eslint/no-duplicate-enum-values': 'off',
40+
'@typescript-eslint/no-parameter-properties': 'off',
4041
},
4142
},
4243
{

app.config.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ module.exports = {
1818
{
1919
subdomains: '*'
2020
}
21-
]
22-
]
21+
],
22+
'expo-apple-authentication',
23+
],
24+
ios: {
25+
usesAppleSignIn: true
26+
}
2327
};

app/core/AppConstants.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CoreTypes } from '@walletconnect/types';
22
import Device from '../util/device';
33
import { DEFAULT_SERVER_URL } from '@metamask/sdk-communication-layer';
4+
import { getBundleId } from 'react-native-device-info';
45

56
const DEVELOPMENT = 'development';
67
const PORTFOLIO_URL =
@@ -139,7 +140,9 @@ export default {
139140
'https://support.metamask.io/transactions-and-gas/transactions/smart-transactions/',
140141
STAKING_RISK_DISCLOSURE: 'https://consensys.io/staking-risk-disclosures',
141142
},
142-
DECODING_API_URL: process.env.DECODING_API_URL || 'https://signature-insights.api.cx.metamask.io/v1',
143+
DECODING_API_URL:
144+
process.env.DECODING_API_URL ||
145+
'https://signature-insights.api.cx.metamask.io/v1',
143146
ERRORS: {
144147
INFURA_BLOCKED_MESSAGE:
145148
'EthQuery - RPC Error - This service is not available in your country',
@@ -227,4 +230,17 @@ export default {
227230
VERSION: 'v1',
228231
DEFAULT_FETCH_INTERVAL: 15 * 60 * 1000, // 15 minutes
229232
},
233+
SEEDLESS_ONBOARDING: {
234+
AUTH_SERVER_URL: process.env.AUTH_SERVER_URL,
235+
IOS_APPLE_CLIENT_ID: getBundleId
236+
? getBundleId()
237+
: process.env.IOS_APPLE_CLIENT_ID,
238+
IOS_GOOGLE_CLIENT_ID: process.env.IOS_GOOGLE_CLIENT_ID,
239+
IOS_GOOGLE_REDIRECT_URI: process.env.IOS_GOOGLE_REDIRECT_URI,
240+
ANDROID_WEB_GOOGLE_CLIENT_ID: process.env.ANDROID_WEB_GOOGLE_CLIENT_ID,
241+
ANDROID_WEB_APPLE_CLIENT_ID: process.env.ANDROID_WEB_APPLE_CLIENT_ID,
242+
AUTH_CONNECTION_ID: process.env.AUTH_CONNECTION_ID,
243+
GROUPED_AUTH_CONNECTION_ID: process.env.GROUPED_AUTH_CONNECTION_ID,
244+
WEB3AUTH_NETWORK: process.env.WEB3AUTH_NETWORK,
245+
},
230246
} as const;

app/core/Engine/Engine.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ import { isProductSafetyDappScanningEnabled } from '../../util/phishingDetection
205205
import { appMetadataControllerInit } from './controllers/app-metadata-controller';
206206
import { InternalAccount } from '@metamask/keyring-internal-api';
207207
import { toFormattedAddress } from '../../util/address';
208+
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
209+
import { seedlessOnboardingControllerInit } from './controllers/seedless-onboarding-controller';
210+
///: END:ONLY_INCLUDE_IF
208211

209212
const NON_EMPTY = 'NON_EMPTY';
210213

@@ -1048,6 +1051,9 @@ export class Engine {
10481051
MultichainBalancesController: multichainBalancesControllerInit,
10491052
MultichainTransactionsController: multichainTransactionsControllerInit,
10501053
///: END:ONLY_INCLUDE_IF
1054+
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
1055+
SeedlessOnboardingController: seedlessOnboardingControllerInit,
1056+
///: END:ONLY_INCLUDE_IF
10511057
},
10521058
persistedState: initialState as EngineState,
10531059
existingControllersByName,
@@ -1059,7 +1065,10 @@ export class Engine {
10591065
const gasFeeController = controllersByName.GasFeeController;
10601066
const signatureController = controllersByName.SignatureController;
10611067
const transactionController = controllersByName.TransactionController;
1062-
1068+
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
1069+
const seedlessOnboardingController =
1070+
controllersByName.SeedlessOnboardingController;
1071+
///: END:ONLY_INCLUDE_IF
10631072
// Backwards compatibility for existing references
10641073
this.accountsController = accountsController;
10651074
this.gasFeeController = gasFeeController;
@@ -1400,6 +1409,9 @@ export class Engine {
14001409
BridgeController: bridgeController,
14011410
BridgeStatusController: bridgeStatusController,
14021411
EarnController: earnController,
1412+
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
1413+
SeedlessOnboardingController: seedlessOnboardingController,
1414+
///: END:ONLY_INCLUDE_IF
14031415
};
14041416

14051417
const childControllers = Object.assign({}, this.context);
@@ -2028,6 +2040,9 @@ export default {
20282040
BridgeController,
20292041
BridgeStatusController,
20302042
EarnController,
2043+
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
2044+
SeedlessOnboardingController,
2045+
///: END:ONLY_INCLUDE_IF
20312046
} = instance.datamodel.state;
20322047

20332048
return {
@@ -2078,6 +2093,9 @@ export default {
20782093
BridgeController,
20792094
BridgeStatusController,
20802095
EarnController,
2096+
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
2097+
SeedlessOnboardingController,
2098+
///: END:ONLY_INCLUDE_IF
20812099
};
20822100
},
20832101

app/core/Engine/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ export const BACKGROUND_STATE_CHANGE_EVENT_NAMES = [
7070
'BridgeController:stateChange',
7171
'BridgeStatusController:stateChange',
7272
'EarnController:stateChange',
73+
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
74+
'SeedlessOnboardingController:stateChange',
75+
///: END:ONLY_INCLUDE_IF
7376
] as const;
7477

7578
export const swapsSupportedChainIds = [
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { seedlessOnboardingControllerInit } from '.';
2+
import { ExtendedControllerMessenger } from '../../../ExtendedControllerMessenger';
3+
import { buildControllerInitRequestMock } from '../../utils/test-utils';
4+
import { ControllerInitRequest } from '../../types';
5+
import {
6+
SeedlessOnboardingController,
7+
SeedlessOnboardingControllerMessenger,
8+
SeedlessOnboardingControllerState,
9+
} from '@metamask/seedless-onboarding-controller';
10+
11+
jest.mock('@metamask/seedless-onboarding-controller', () => {
12+
const actualSeedlessOnboardingController = jest.requireActual(
13+
'@metamask/seedless-onboarding-controller',
14+
);
15+
return {
16+
controllerName: actualSeedlessOnboardingController.controllerName,
17+
getDefaultSeedlessOnboardingControllerState:
18+
actualSeedlessOnboardingController.getDefaultSeedlessOnboardingControllerState,
19+
SeedlessOnboardingController: jest.fn(),
20+
Web3AuthNetwork: actualSeedlessOnboardingController.Web3AuthNetwork,
21+
};
22+
});
23+
24+
describe('seedless onboarding controller init', () => {
25+
const seedlessOnboardingControllerClassMock = jest.mocked(
26+
SeedlessOnboardingController,
27+
);
28+
let initRequestMock: jest.Mocked<
29+
ControllerInitRequest<SeedlessOnboardingControllerMessenger>
30+
>;
31+
32+
beforeEach(() => {
33+
jest.resetAllMocks();
34+
const baseControllerMessenger = new ExtendedControllerMessenger();
35+
// Create controller init request mock
36+
initRequestMock = buildControllerInitRequestMock(baseControllerMessenger);
37+
});
38+
39+
it('returns controller instance', () => {
40+
expect(
41+
seedlessOnboardingControllerInit(initRequestMock).controller,
42+
).toBeInstanceOf(SeedlessOnboardingController);
43+
});
44+
45+
it('controller state should be default state when no initial state is passed in', () => {
46+
const defaultSeedlessOnboardingControllerState = jest
47+
.requireActual('@metamask/seedless-onboarding-controller')
48+
.getDefaultSeedlessOnboardingControllerState();
49+
50+
seedlessOnboardingControllerInit(initRequestMock);
51+
52+
const seedlessOnboardingControllerState =
53+
seedlessOnboardingControllerClassMock.mock.calls[0][0].state;
54+
55+
expect(seedlessOnboardingControllerState).toEqual(
56+
defaultSeedlessOnboardingControllerState,
57+
);
58+
});
59+
60+
it('controller state should be initial state when initial state is passed in', () => {
61+
const initialSeedlessOnboardingControllerState: Partial<SeedlessOnboardingControllerState> =
62+
{
63+
vault: undefined,
64+
nodeAuthTokens: undefined,
65+
};
66+
67+
initRequestMock.persistedState = {
68+
...initRequestMock.persistedState,
69+
SeedlessOnboardingController: initialSeedlessOnboardingControllerState,
70+
};
71+
72+
seedlessOnboardingControllerInit(initRequestMock);
73+
74+
const seedlessOnboardingControllerState =
75+
seedlessOnboardingControllerClassMock.mock.calls[0][0].state;
76+
77+
expect(seedlessOnboardingControllerState).toStrictEqual(
78+
initialSeedlessOnboardingControllerState,
79+
);
80+
});
81+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import './shim';
2+
import type { ControllerInitFunction } from '../../types';
3+
import {
4+
SeedlessOnboardingController,
5+
SeedlessOnboardingControllerState,
6+
Web3AuthNetwork,
7+
getDefaultSeedlessOnboardingControllerState,
8+
type SeedlessOnboardingControllerMessenger,
9+
} from '@metamask/seedless-onboarding-controller';
10+
import AppConstants from '../../../AppConstants';
11+
import { Encryptor, LEGACY_DERIVATION_OPTIONS } from '../../../Encryptor';
12+
import { EncryptionKey, EncryptionResult } from '../../../Encryptor/types';
13+
14+
const web3AuthNetwork = AppConstants.SEEDLESS_ONBOARDING.WEB3AUTH_NETWORK;
15+
16+
if (!web3AuthNetwork) {
17+
throw new Error(
18+
`Missing environment variables for SeedlessOnboardingController\n
19+
WEB3AUTH_NETWORK: ${web3AuthNetwork}\n`,
20+
);
21+
}
22+
23+
const encryptor = new Encryptor({
24+
keyDerivationOptions: LEGACY_DERIVATION_OPTIONS,
25+
});
26+
27+
/**
28+
* Initialize the SeedlessOnboardingController.
29+
*
30+
* @param request - The request object.
31+
* @returns The SeedlessOnboardingController.
32+
*/
33+
export const seedlessOnboardingControllerInit: ControllerInitFunction<
34+
SeedlessOnboardingController<EncryptionKey>,
35+
SeedlessOnboardingControllerMessenger
36+
> = (request) => {
37+
const { controllerMessenger, persistedState } = request;
38+
39+
const seedlessOnboardingControllerState =
40+
persistedState.SeedlessOnboardingController ??
41+
getDefaultSeedlessOnboardingControllerState();
42+
43+
const controller = new SeedlessOnboardingController({
44+
messenger: controllerMessenger,
45+
state:
46+
seedlessOnboardingControllerState as SeedlessOnboardingControllerState,
47+
encryptor: {
48+
...encryptor,
49+
decryptWithKey: async (key: EncryptionKey, encryptedString: string) =>
50+
encryptor.decryptWithKey(
51+
key,
52+
encryptedString as unknown as EncryptionResult,
53+
),
54+
},
55+
network: web3AuthNetwork as Web3AuthNetwork,
56+
});
57+
58+
return { controller };
59+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import Crypto from 'react-native-crypto';
2+
3+
global.crypto = {
4+
...Crypto,
5+
...global.crypto,
6+
};

app/core/Engine/messengers/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ import { getNotificationServicesControllerMessenger } from './notifications/noti
2727
import { getNotificationServicesPushControllerMessenger } from './notifications/notification-services-push-controller-messenger';
2828
import { getGasFeeControllerMessenger } from './gas-fee-controller-messenger/gas-fee-controller-messenger';
2929
import { getSignatureControllerMessenger } from './signature-controller-messenger';
30+
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
31+
import { getSeedlessOnboardingControllerMessenger } from './seedless-onboarding-controller-messenger';
32+
///: END:ONLY_INCLUDE_IF
33+
3034
/**
3135
* The messengers for the controllers that have been.
3236
*/
@@ -107,4 +111,10 @@ export const CONTROLLER_MESSENGERS = {
107111
getInitMessenger: noop,
108112
},
109113
///: END:ONLY_INCLUDE_IF
114+
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
115+
SeedlessOnboardingController: {
116+
getMessenger: getSeedlessOnboardingControllerMessenger,
117+
getInitMessenger: noop,
118+
},
119+
///: END:ONLY_INCLUDE_IF
110120
} as const;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { BaseControllerMessenger } from '../../types';
2+
3+
export type SeedlessOnboardingControllerMessenger = ReturnType<
4+
typeof getSeedlessOnboardingControllerMessenger
5+
>;
6+
7+
/**
8+
* Get the SeedlessOnboardingControllerMessenger for the SeedlessOnboardingController.
9+
*
10+
* @param baseControllerMessenger - The base controller messenger.
11+
* @returns The SeedlessOnboardingControllerMessenger.
12+
*/
13+
export function getSeedlessOnboardingControllerMessenger(
14+
baseControllerMessenger: BaseControllerMessenger,
15+
) {
16+
return baseControllerMessenger.getRestricted({
17+
name: 'SeedlessOnboardingController',
18+
allowedEvents: ['KeyringController:lock', 'KeyringController:unlock'],
19+
allowedActions: [],
20+
});
21+
}

0 commit comments

Comments
 (0)