Skip to content

Adds OAuth Controller required for seedless onboarding #14889

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

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8051e3b
feat: initial commit seedless integration
ieow Apr 23, 2025
af381bc
fix: add patch
ieow Apr 23, 2025
664ac70
fix: getting seedless controller initial test pass
ieow Apr 23, 2025
6143a4a
feat: simple tests
ieow Apr 23, 2025
53c367d
rename: to authConnection
ieow Apr 23, 2025
7ad5fd4
update: upload seedless controller package
ieow Apr 24, 2025
ac5607e
fix: update with seedless contorller
ieow Apr 24, 2025
c6f4590
fix: lint
ieow Apr 24, 2025
f8952b1
fix: update comment
ieow Apr 24, 2025
cef712b
fix: use baseHandler
ieow Apr 24, 2025
ab32e19
fix: seedless
ieow Apr 24, 2025
3833daf
fix: use env
ieow Apr 24, 2025
ef332ad
working review
chaitanyapotti Apr 24, 2025
de8f5ac
fix: use env for AuthConnectionId
ieow Apr 24, 2025
1c1e958
fix: update env constant
ieow Apr 24, 2025
14ab169
fix: fomatting
ieow Apr 24, 2025
3599963
fix: redux state for seedless controller
ieow Apr 24, 2025
db56659
fix: add env to test
ieow Apr 24, 2025
5824e0c
fix: add Error class for oauth2login
ieow Apr 25, 2025
a5c3b42
fix: apple login
ieow Apr 25, 2025
adea362
fix: add JsDoc
ieow Apr 25, 2025
563849e
fix: tests
ieow Apr 25, 2025
9c7b458
fix: rename file
ieow Apr 25, 2025
c8e5b1a
fix: rename to OAuthService
ieow Apr 25, 2025
d3bdd54
fix: rename oauth2 to oauth
ieow Apr 25, 2025
e4f0af6
Update app/core/OAuthService/OAuthLoginHandlers/androidHandlers/apple.ts
ieow Apr 25, 2025
d49a2fc
Update app/core/OAuthService/OAuthLoginHandlers/baseHandler.ts
ieow Apr 25, 2025
0fd98b5
fix: update AuthResponse data type
ieow Apr 25, 2025
31f3725
fix: fix engine test
ieow Apr 25, 2025
f52172f
fix: address comment
ieow Apr 25, 2025
b2b2511
fix: renable test and format document
ieow Apr 25, 2025
28d92de
fix: tests and error constructor
ieow Apr 26, 2025
2f4b52e
fix: remove from redux state
ieow Apr 27, 2025
fa26464
fix: add feature flag
ieow Apr 27, 2025
3fdaafc
fix: better type handling
ieow Apr 28, 2025
5ed2216
Merge branch 'main' into feat/main-seedless-onboarding
ieow Apr 28, 2025
098d6cf
fix: update snapshot for engine initialState
ieow Apr 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ module.exports = {
'@typescript-eslint/no-explicit-any': 'error',
// Under discussion
'@typescript-eslint/no-duplicate-enum-values': 'off',
'@typescript-eslint/no-parameter-properties': 'off',
},
},
{
Expand Down
2 changes: 2 additions & 0 deletions app/constants/deeplinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export enum ACTIONS {
SELL = 'sell',
SELL_CRYPTO = 'sell-crypto',
EMPTY = '',
OAUTH2_REDIRECT = 'oauth2-redirect',
}

export const PREFIXES = {
Expand All @@ -43,5 +44,6 @@ export const PREFIXES = {
[ACTIONS.SELL]: '',
[ACTIONS.BUY_CRYPTO]: '',
[ACTIONS.SELL_CRYPTO]: '',
[ACTIONS.OAUTH2_REDIRECT]: '',
METAMASK: 'metamask://',
};
20 changes: 19 additions & 1 deletion app/core/Engine/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ import { getIsQuicknodeEndpointUrl } from './controllers/network-controller/util
import { appMetadataControllerInit } from './controllers/app-metadata-controller';
import { InternalAccount } from '@metamask/keyring-internal-api';
import { toFormattedAddress } from '../../util/address';
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
import { seedlessOnboardingControllerInit } from './controllers/seedless-onboarding-controller';
///: END:ONLY_INCLUDE_IF

const NON_EMPTY = 'NON_EMPTY';

Expand Down Expand Up @@ -1164,6 +1167,9 @@ export class Engine {
MultichainBalancesController: multichainBalancesControllerInit,
MultichainTransactionsController: multichainTransactionsControllerInit,
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
SeedlessOnboardingController: seedlessOnboardingControllerInit,
///: END:ONLY_INCLUDE_IF
},
persistedState: initialState as EngineState,
existingControllersByName,
Expand All @@ -1175,7 +1181,10 @@ export class Engine {
const gasFeeController = controllersByName.GasFeeController;
const signatureController = controllersByName.SignatureController;
const transactionController = controllersByName.TransactionController;

///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
const seedlessOnboardingController =
controllersByName.SeedlessOnboardingController;
///: END:ONLY_INCLUDE_IF
// Backwards compatibility for existing references
this.accountsController = accountsController;
this.gasFeeController = gasFeeController;
Expand Down Expand Up @@ -1516,6 +1525,9 @@ export class Engine {
BridgeController: bridgeController,
BridgeStatusController: bridgeStatusController,
EarnController: earnController,
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
SeedlessOnboardingController: seedlessOnboardingController,
///: END:ONLY_INCLUDE_IF
};

const childControllers = Object.assign({}, this.context);
Expand Down Expand Up @@ -2114,6 +2126,9 @@ export default {
BridgeController,
BridgeStatusController,
EarnController,
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
SeedlessOnboardingController,
///: END:ONLY_INCLUDE_IF
} = instance.datamodel.state;

return {
Expand Down Expand Up @@ -2164,6 +2179,9 @@ export default {
BridgeController,
BridgeStatusController,
EarnController,
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
SeedlessOnboardingController,
///: END:ONLY_INCLUDE_IF
};
},

Expand Down
3 changes: 3 additions & 0 deletions app/core/Engine/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export const BACKGROUND_STATE_CHANGE_EVENT_NAMES = [
'BridgeController:stateChange',
'BridgeStatusController:stateChange',
'EarnController:stateChange',
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
'SeedlessOnboardingController:stateChange',
///: END:ONLY_INCLUDE_IF
] as const;

export const swapsSupportedChainIds = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { seedlessOnboardingControllerInit } from '.';
import { ExtendedControllerMessenger } from '../../../ExtendedControllerMessenger';
import { buildControllerInitRequestMock } from '../../utils/test-utils';
import { ControllerInitRequest } from '../../types';
import {
SeedlessOnboardingController,
SeedlessOnboardingControllerMessenger,
SeedlessOnboardingControllerState,
} from '@metamask/seedless-onboarding-controller';

jest.mock('@metamask/seedless-onboarding-controller', () => {
const actualSeedlessOnboardingController = jest.requireActual(
'@metamask/seedless-onboarding-controller',
);
return {
controllerName: actualSeedlessOnboardingController.controllerName,
getDefaultSeedlessOnboardingControllerState:
actualSeedlessOnboardingController.getDefaultSeedlessOnboardingControllerState,
SeedlessOnboardingController: jest.fn(),
Web3AuthNetwork: actualSeedlessOnboardingController.Web3AuthNetwork,
};
});

describe('seedless onboarding controller init', () => {
const seedlessOnboardingControllerClassMock = jest.mocked(
SeedlessOnboardingController,
);
let initRequestMock: jest.Mocked<
ControllerInitRequest<SeedlessOnboardingControllerMessenger>
>;

beforeEach(() => {
jest.resetAllMocks();
const baseControllerMessenger = new ExtendedControllerMessenger();
// Create controller init request mock
initRequestMock = buildControllerInitRequestMock(baseControllerMessenger);
});

it('returns controller instance', () => {
expect(
seedlessOnboardingControllerInit(initRequestMock).controller,
).toBeInstanceOf(SeedlessOnboardingController);
});

it('controller state should be default state when no initial state is passed in', () => {
const defaultSeedlessOnboardingControllerState = jest
.requireActual('@metamask/seedless-onboarding-controller')
.getDefaultSeedlessOnboardingControllerState();

seedlessOnboardingControllerInit(initRequestMock);

const seedlessOnboardingControllerState =
seedlessOnboardingControllerClassMock.mock.calls[0][0].state;

expect(seedlessOnboardingControllerState).toEqual(
defaultSeedlessOnboardingControllerState,
);
});

it('controller state should be initial state when initial state is passed in', () => {
const initialSeedlessOnboardingControllerState: Partial<SeedlessOnboardingControllerState> =
{
vault: undefined,
nodeAuthTokens: undefined,
};

initRequestMock.persistedState = {
...initRequestMock.persistedState,
SeedlessOnboardingController: initialSeedlessOnboardingControllerState,
};

seedlessOnboardingControllerInit(initRequestMock);

const seedlessOnboardingControllerState =
seedlessOnboardingControllerClassMock.mock.calls[0][0].state;

expect(seedlessOnboardingControllerState).toStrictEqual(
initialSeedlessOnboardingControllerState,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { ControllerInitFunction } from '../../types';
import {
SeedlessOnboardingController,
SeedlessOnboardingControllerState,
Web3AuthNetwork,
getDefaultSeedlessOnboardingControllerState,
type SeedlessOnboardingControllerMessenger,
} from '@metamask/seedless-onboarding-controller';
import { Encryptor, LEGACY_DERIVATION_OPTIONS } from '../../../Encryptor';

export const web3AuthNetwork = process.env.Web3AuthNetwork as Web3AuthNetwork;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
export const web3AuthNetwork = process.env.Web3AuthNetwork as Web3AuthNetwork;
export const web3AuthNetwork = process.env.WEB3_AUTH_NETWORK as Web3AuthNetwork;

Also, should this be moved into a constants file or just use the env var inline


if (!web3AuthNetwork) {
throw new Error('Missing environment variables');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
throw new Error('Missing environment variables');
throw new Error('Missing environment variable WEB3_AUTH_NETWORK');

}

const encryptor = new Encryptor({
keyDerivationOptions: LEGACY_DERIVATION_OPTIONS,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not familiar with the different types of derivation options but I know we're using it in many places. @ccharly Can you take a look at this if it's fine?

});

/**
* Initialize the SeedlessOnboardingController.
*
* @param request - The request object.
* @returns The SeedlessOnboardingController.
*/
export const seedlessOnboardingControllerInit: ControllerInitFunction<
SeedlessOnboardingController,
SeedlessOnboardingControllerMessenger
> = (request) => {
const { controllerMessenger, persistedState } = request;

const seedlessOnboardingControllerState =
persistedState.SeedlessOnboardingController ??
getDefaultSeedlessOnboardingControllerState();

const controller = new SeedlessOnboardingController({
messenger: controllerMessenger,
state:
seedlessOnboardingControllerState as SeedlessOnboardingControllerState,
encryptor,
network: web3AuthNetwork,
});

return { controller };
};
10 changes: 10 additions & 0 deletions app/core/Engine/messengers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import { getNotificationServicesControllerMessenger } from './notifications/noti
import { getNotificationServicesPushControllerMessenger } from './notifications/notification-services-push-controller-messenger';
import { getGasFeeControllerMessenger } from './gas-fee-controller-messenger/gas-fee-controller-messenger';
import { getSignatureControllerMessenger } from './signature-controller-messenger';
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
import { getSeedlessOnboardingControllerMessenger } from './seedless-onboarding-controller-messenger';
///: END:ONLY_INCLUDE_IF

/**
* The messengers for the controllers that have been.
*/
Expand Down Expand Up @@ -107,4 +111,10 @@ export const CONTROLLER_MESSENGERS = {
getInitMessenger: noop,
},
///: END:ONLY_INCLUDE_IF
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
SeedlessOnboardingController: {
getMessenger: getSeedlessOnboardingControllerMessenger,
getInitMessenger: noop,
},
///: END:ONLY_INCLUDE_IF
} as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BaseControllerMessenger } from '../../types';

export type SeedlessOnboardingControllerMessenger = ReturnType<
typeof getSeedlessOnboardingControllerMessenger
>;

/**
* Get the SeedlessOnboardingControllerMessenger for the SeedlessOnboardingController.
*
* @param baseControllerMessenger - The base controller messenger.
* @returns The SeedlessOnboardingControllerMessenger.
*/
export function getSeedlessOnboardingControllerMessenger(
baseControllerMessenger: BaseControllerMessenger,
) {
return baseControllerMessenger.getRestricted({
name: 'SeedlessOnboardingController',
allowedEvents: [],
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't have visibility to the seedless onboarding controller. Does the controller use actions or events of other controllers?

allowedActions: [],
});
}
23 changes: 22 additions & 1 deletion app/core/Engine/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,14 @@ import {
EarnControllerEvents,
EarnControllerState,
} from '@metamask/earn-controller';
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
import {
SeedlessOnboardingController,
SeedlessOnboardingControllerState,
SeedlessOnboardingControllerEvents,
} from '@metamask/seedless-onboarding-controller';
///: END:ONLY_INCLUDE_IF

import { Hex } from '@metamask/utils';

import { CONTROLLER_MESSENGERS } from './messengers';
Expand Down Expand Up @@ -403,6 +411,9 @@ type GlobalEvents =
| BridgeControllerEvents
| BridgeStatusControllerEvents
| EarnControllerEvents
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
| SeedlessOnboardingControllerEvents
///: END:ONLY_INCLUDE_IF
| AppMetadataControllerEvents;

/**
Expand Down Expand Up @@ -477,6 +488,9 @@ export type Controllers = {
BridgeController: BridgeController;
BridgeStatusController: BridgeStatusController;
EarnController: EarnController;
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
SeedlessOnboardingController: SeedlessOnboardingController;
///: END:ONLY_INCLUDE_IF
};

/**
Expand Down Expand Up @@ -540,6 +554,9 @@ export type EngineState = {
BridgeController: BridgeControllerState;
BridgeStatusController: BridgeStatusControllerState;
EarnController: EarnControllerState;
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
SeedlessOnboardingController: SeedlessOnboardingControllerState;
///: END:ONLY_INCLUDE_IF
};

/** Controller names */
Expand Down Expand Up @@ -591,7 +608,11 @@ export type ControllersToInitialize =
| 'MultichainNetworkController'
| 'TransactionController'
| 'GasFeeController'
| 'SignatureController';
| 'SignatureController'
///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
| 'SeedlessOnboardingController'
///: END:ONLY_INCLUDE_IF
| 'AppMetadataController';

/**
* Callback that returns a controller messenger for a specific controller.
Expand Down
Loading