Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 6 additions & 2 deletions app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ module.exports = {
{
subdomains: '*'
}
]
]
],
'expo-apple-authentication',
],
ios: {
usesAppleSignIn: true
}
};
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 = '',
OAUTH_REDIRECT = 'oauth-redirect',
}

export const PREFIXES = {
Expand All @@ -43,5 +44,6 @@ export const PREFIXES = {
[ACTIONS.SELL]: '',
[ACTIONS.BUY_CRYPTO]: '',
[ACTIONS.SELL_CRYPTO]: '',
[ACTIONS.OAUTH_REDIRECT]: '',
METAMASK: 'metamask://',
};
18 changes: 17 additions & 1 deletion app/core/AppConstants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CoreTypes } from '@walletconnect/types';
import Device from '../util/device';
import { DEFAULT_SERVER_URL } from '@metamask/sdk-communication-layer';
import { getBundleId } from 'react-native-device-info';

const DEVELOPMENT = 'development';
const PORTFOLIO_URL =
Expand Down Expand Up @@ -140,7 +141,9 @@ export default {
STAKING_RISK_DISCLOSURE: 'https://consensys.io/staking-risk-disclosures',
ADD_SOLANA_ACCOUNT_PRIVACY_POLICY: 'https://support.metamask.io/configure/accounts/how-to-add-accounts-in-your-wallet/#solana-accounts'
},
DECODING_API_URL: process.env.DECODING_API_URL || 'https://signature-insights.api.cx.metamask.io/v1',
DECODING_API_URL:
process.env.DECODING_API_URL ||
'https://signature-insights.api.cx.metamask.io/v1',
ERRORS: {
INFURA_BLOCKED_MESSAGE:
'EthQuery - RPC Error - This service is not available in your country',
Expand Down Expand Up @@ -228,4 +231,17 @@ export default {
VERSION: 'v1',
DEFAULT_FETCH_INTERVAL: 15 * 60 * 1000, // 15 minutes
},
SEEDLESS_ONBOARDING: {
AUTH_SERVER_URL: process.env.AUTH_SERVER_URL,
IOS_APPLE_CLIENT_ID: getBundleId
? getBundleId()
: process.env.IOS_APPLE_CLIENT_ID,
IOS_GOOGLE_CLIENT_ID: process.env.IOS_GOOGLE_CLIENT_ID,
IOS_GOOGLE_REDIRECT_URI: process.env.IOS_GOOGLE_REDIRECT_URI,
ANDROID_WEB_GOOGLE_CLIENT_ID: process.env.ANDROID_WEB_GOOGLE_CLIENT_ID,
ANDROID_WEB_APPLE_CLIENT_ID: process.env.ANDROID_WEB_APPLE_CLIENT_ID,
AUTH_CONNECTION_ID: process.env.AUTH_CONNECTION_ID,
GROUPED_AUTH_CONNECTION_ID: process.env.GROUPED_AUTH_CONNECTION_ID,
WEB3AUTH_NETWORK: process.env.WEB3AUTH_NETWORK,
},
} as const;
21 changes: 20 additions & 1 deletion app/core/Engine/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ import { InternalAccount } from '@metamask/keyring-internal-api';
import { toFormattedAddress } from '../../util/address';
import { BRIDGE_API_BASE_URL } from '../../constants/bridge';

///: BEGIN:ONLY_INCLUDE_IF(seedless-onboarding)
import { seedlessOnboardingControllerInit } from './controllers/seedless-onboarding-controller';
///: END:ONLY_INCLUDE_IF

const NON_EMPTY = 'NON_EMPTY';

const encryptor = new Encryptor({
Expand Down Expand Up @@ -1050,6 +1054,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 @@ -1061,7 +1068,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 @@ -1402,6 +1412,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 @@ -2030,6 +2043,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 @@ -2080,6 +2096,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,59 @@
import './shim';
import type { ControllerInitFunction } from '../../types';
import {
SeedlessOnboardingController,
SeedlessOnboardingControllerState,
Web3AuthNetwork,
getDefaultSeedlessOnboardingControllerState,
type SeedlessOnboardingControllerMessenger,
} from '@metamask/seedless-onboarding-controller';
import AppConstants from '../../../AppConstants';
import { Encryptor, LEGACY_DERIVATION_OPTIONS } from '../../../Encryptor';
import { EncryptionKey, EncryptionResult } from '../../../Encryptor/types';

const web3AuthNetwork = AppConstants.SEEDLESS_ONBOARDING.WEB3AUTH_NETWORK;

if (!web3AuthNetwork) {
throw new Error(
`Missing environment variables for SeedlessOnboardingController\n
WEB3AUTH_NETWORK: ${web3AuthNetwork}\n`,
);
}

const encryptor = new Encryptor({
keyDerivationOptions: LEGACY_DERIVATION_OPTIONS,
});

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

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

const controller = new SeedlessOnboardingController({
messenger: controllerMessenger,
state:
seedlessOnboardingControllerState as SeedlessOnboardingControllerState,
encryptor: {
...encryptor,
decryptWithKey: async (key: EncryptionKey, encryptedString: string) =>
encryptor.decryptWithKey(
key,
encryptedString as unknown as EncryptionResult,
),
},
network: web3AuthNetwork as Web3AuthNetwork,
});

return { controller };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// eslint-disable-next-line import/no-nodejs-modules
import Crypto from 'crypto';

global.crypto = {
...Crypto,
...global.crypto,
};
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: ['KeyringController:lock', 'KeyringController:unlock'],
allowedActions: [],
});
}
Loading
Loading