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

chore: wire up bridge input actions [METABRIDGE-866] #25813

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c3182f8
chore: fetch feature flags from account-overview page
micaelae Jun 25, 2024
0ba8534
chore: fetch feature flags from token-overview page
micaelae Jun 25, 2024
d47bef3
chore: use enums as bridge controller actions
micaelae Jul 3, 2024
f0af7fe
chore: add new bridge route
micaelae Jun 25, 2024
9a8382f
chore: create placeholder bridge page
micaelae Jun 25, 2024
38aeb2f
chore: route to bridge page when FF is turned on
micaelae Jun 25, 2024
c1a2ea9
fix: sentry e2e tests
micaelae Jun 29, 2024
f9cbaf8
fix: wallet-created integration test
micaelae Jul 12, 2024
ac2ff08
fix: add type for event
micaelae Jul 18, 2024
862e87a
fix: early return
micaelae Jul 18, 2024
6d218ae
fix: mitigate e2e test flakiness
micaelae Jul 18, 2024
9bdf782
test: add bridge e2e tests for routing
micaelae Jun 24, 2024
045ca2d
refactor: extract test constants
micaelae Jul 18, 2024
dda7956
test: add currency rates
micaelae Jul 18, 2024
b2e7e47
refactor: reorganize bridge controller
micaelae Jul 24, 2024
d035314
chore: upgrade BridgeController to BaseController V2
micaelae Jul 24, 2024
f155cae
chore: read bridge chains from feature flags and add selectors
micaelae Jun 25, 2024
95462ee
chore: add bridge selected token and amount selectors/actions
micaelae Jun 27, 2024
fa361d6
chore: add bridge SELECT_DEST_NETWORK action
micaelae Jul 4, 2024
4523b21
chore: set bridge destTopAssets from swaps-api response
micaelae Jul 4, 2024
7e2b725
chore: add bridge SELECT_SRC_NETWORK action
micaelae Jul 9, 2024
de1b189
chore: set src network on Bridge page load
micaelae Jul 12, 2024
b6447a1
fix: e2e tests
micaelae Jul 16, 2024
e3d2cfd
fix: flaky sentry tests
micaelae Jul 25, 2024
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
29 changes: 0 additions & 29 deletions app/scripts/controllers/bridge.test.ts

This file was deleted.

36 changes: 0 additions & 36 deletions app/scripts/controllers/bridge.ts

This file was deleted.

122 changes: 122 additions & 0 deletions app/scripts/controllers/bridge/bridge-controller.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import nock from 'nock';
import { BRIDGE_API_BASE_URL } from '../../../../shared/constants/bridge';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import { SWAPS_API_V2_BASE_URL } from '../../../../shared/constants/swaps';
import BridgeController from './bridge-controller';
import { BridgeControllerMessenger } from './types';
import { DEFAULT_BRIDGE_CONTROLLER_STATE } from './constants';

const EMPTY_INIT_STATE = {
bridgeState: DEFAULT_BRIDGE_CONTROLLER_STATE,
};

const messengerMock = {
call: jest.fn(),
registerActionHandler: jest.fn(),
registerInitialEventPayload: jest.fn(),
publish: jest.fn(),
} as unknown as jest.Mocked<BridgeControllerMessenger>;

describe('BridgeController', function () {
let bridgeController: BridgeController;

beforeAll(function () {
bridgeController = new BridgeController({ messenger: messengerMock });
});

beforeEach(() => {
jest.clearAllMocks();
nock(BRIDGE_API_BASE_URL)
.get('/getAllFeatureFlags')
.reply(200, {
'extension-support': true,
'src-network-allowlist': [10, 534352],
'dest-network-allowlist': [137, 42161],
});
nock(BRIDGE_API_BASE_URL)
.get('/getTokens?chainId=10')
.reply(200, [
{
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
symbol: 'ABC',
decimals: 16,
},
{
address: '0x1291478912',
symbol: 'DEF',
decimals: 16,
},
]);
nock(SWAPS_API_V2_BASE_URL)
.get('/networks/10/topAssets')
.reply(200, [
{
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
symbol: 'ABC',
},
]);
});

it('constructor should setup correctly', function () {
expect(bridgeController.state).toStrictEqual(EMPTY_INIT_STATE);
});

it('setBridgeFeatureFlags should fetch and set the bridge feature flags', async function () {
const expectedFeatureFlagsResponse = {
extensionSupport: true,
destNetworkAllowlist: [CHAIN_IDS.POLYGON, CHAIN_IDS.ARBITRUM],
srcNetworkAllowlist: [CHAIN_IDS.OPTIMISM, CHAIN_IDS.SCROLL],
};
expect(bridgeController.state).toStrictEqual(EMPTY_INIT_STATE);

await bridgeController.setBridgeFeatureFlags();
expect(bridgeController.state.bridgeState.bridgeFeatureFlags).toStrictEqual(
expectedFeatureFlagsResponse,
);
});

it('selectDestNetwork should set the bridge dest tokens and top assets', async function () {
await bridgeController.selectDestNetwork('0xa');
expect(bridgeController.state.bridgeState.destTokens).toStrictEqual({
'0x0000000000000000000000000000000000000000': {
address: '0x0000000000000000000000000000000000000000',
decimals: 18,
iconUrl: './images/eth_logo.svg',
name: 'Ether',
symbol: 'ETH',
},
'0x1f9840a85d5af5bf1d1762f925bdaddc4201f984': {
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
symbol: 'ABC',
decimals: 16,
},
});
expect(bridgeController.state.bridgeState.destTopAssets).toStrictEqual([
{ address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', symbol: 'ABC' },
]);
});

it('selectSrcNetwork should set the bridge src tokens and top assets', async function () {
await bridgeController.selectSrcNetwork('0xa');
expect(bridgeController.state.bridgeState.srcTokens).toStrictEqual({
'0x0000000000000000000000000000000000000000': {
address: '0x0000000000000000000000000000000000000000',
decimals: 18,
iconUrl: './images/eth_logo.svg',
name: 'Ether',
symbol: 'ETH',
},
'0x1f9840a85d5af5bf1d1762f925bdaddc4201f984': {
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
symbol: 'ABC',
decimals: 16,
},
});
expect(bridgeController.state.bridgeState.srcTopAssets).toStrictEqual([
{
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
symbol: 'ABC',
},
]);
});
});
92 changes: 92 additions & 0 deletions app/scripts/controllers/bridge/bridge-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { BaseController, StateMetadata } from '@metamask/base-controller';
import { Hex } from '@metamask/utils';
import {
fetchBridgeFeatureFlags,
fetchBridgeTokens,
} from '../../../../ui/pages/bridge/bridge.util';
import { fetchTopAssetsList } from '../../../../ui/pages/swaps/swaps.util';
import {
BRIDGE_CONTROLLER_NAME,
DEFAULT_BRIDGE_CONTROLLER_STATE,
} from './constants';
import { BridgeControllerState, BridgeControllerMessenger } from './types';

const metadata: StateMetadata<{ bridgeState: BridgeControllerState }> = {
bridgeState: {
persist: false,
anonymous: false,
},
};

export default class BridgeController extends BaseController<
typeof BRIDGE_CONTROLLER_NAME,
{ bridgeState: BridgeControllerState },
BridgeControllerMessenger
> {
constructor({ messenger }: { messenger: BridgeControllerMessenger }) {
super({
name: BRIDGE_CONTROLLER_NAME,
metadata,
messenger,
state: { bridgeState: DEFAULT_BRIDGE_CONTROLLER_STATE },
});

this.messagingSystem.registerActionHandler(
`${BRIDGE_CONTROLLER_NAME}:setBridgeFeatureFlags`,
this.setBridgeFeatureFlags.bind(this),
);
this.messagingSystem.registerActionHandler(
`${BRIDGE_CONTROLLER_NAME}:selectSrcNetwork`,
this.selectSrcNetwork.bind(this),
);
this.messagingSystem.registerActionHandler(
`${BRIDGE_CONTROLLER_NAME}:selectDestNetwork`,
this.selectDestNetwork.bind(this),
);
}

resetState = () => {
this.update((_state) => {
_state.bridgeState = {
...DEFAULT_BRIDGE_CONTROLLER_STATE,
};
});
};

setBridgeFeatureFlags = async () => {
const { bridgeState } = this.state;
const bridgeFeatureFlags = await fetchBridgeFeatureFlags();
this.update((_state) => {
_state.bridgeState = { ...bridgeState, bridgeFeatureFlags };
});
};

selectSrcNetwork = async (chainId: Hex) => {
await this.#setTopAssets(chainId, 'srcTopAssets');
await this.#setTokens(chainId, 'srcTokens');
};

selectDestNetwork = async (chainId: Hex) => {
await this.#setTopAssets(chainId, 'destTopAssets');
await this.#setTokens(chainId, 'destTokens');
};

#setTopAssets = async (
chainId: Hex,
stateKey: 'srcTopAssets' | 'destTopAssets',
) => {
const { bridgeState } = this.state;
const topAssets = await fetchTopAssetsList(chainId);
this.update((_state) => {
_state.bridgeState = { ...bridgeState, [stateKey]: topAssets };
});
};

#setTokens = async (chainId: Hex, stateKey: 'srcTokens' | 'destTokens') => {
const { bridgeState } = this.state;
const tokens = await fetchBridgeTokens(chainId);
this.update((_state) => {
_state.bridgeState = { ...bridgeState, [stateKey]: tokens };
});
};
}
15 changes: 15 additions & 0 deletions app/scripts/controllers/bridge/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BridgeControllerState, BridgeFeatureFlagsKey } from './types';

export const BRIDGE_CONTROLLER_NAME = 'BridgeController';

export const DEFAULT_BRIDGE_CONTROLLER_STATE: BridgeControllerState = {
bridgeFeatureFlags: {
[BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: false,
[BridgeFeatureFlagsKey.NETWORK_SRC_ALLOWLIST]: [],
[BridgeFeatureFlagsKey.NETWORK_DEST_ALLOWLIST]: [],
},
srcTokens: {},
srcTopAssets: [],
destTokens: {},
destTopAssets: [],
};
63 changes: 63 additions & 0 deletions app/scripts/controllers/bridge/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
ControllerStateChangeEvent,
RestrictedControllerMessenger,
} from '@metamask/base-controller';
import { Hex } from '@metamask/utils';
import { SwapsTokenObject } from '../../../../shared/constants/swaps';
import BridgeController from './bridge-controller';
import { BRIDGE_CONTROLLER_NAME } from './constants';

export enum BridgeFeatureFlagsKey {
EXTENSION_SUPPORT = 'extensionSupport',
NETWORK_SRC_ALLOWLIST = 'srcNetworkAllowlist',
NETWORK_DEST_ALLOWLIST = 'destNetworkAllowlist',
}

export type BridgeFeatureFlags = {
[BridgeFeatureFlagsKey.EXTENSION_SUPPORT]: boolean;
[BridgeFeatureFlagsKey.NETWORK_SRC_ALLOWLIST]: Hex[];
[BridgeFeatureFlagsKey.NETWORK_DEST_ALLOWLIST]: Hex[];
};

export type BridgeControllerState = {
bridgeFeatureFlags: BridgeFeatureFlags;
srcTokens: Record<string, SwapsTokenObject>;
srcTopAssets: { address: string }[];
destTokens: Record<string, SwapsTokenObject>;
destTopAssets: { address: string }[];
};

export enum BridgeUserAction {
SELECT_SRC_NETWORK = 'selectSrcNetwork',
SELECT_DEST_NETWORK = 'selectDestNetwork',
}
export enum BridgeBackgroundAction {
SET_FEATURE_FLAGS = 'setBridgeFeatureFlags',
}

type BridgeControllerAction<FunctionName extends keyof BridgeController> = {
type: `${typeof BRIDGE_CONTROLLER_NAME}:${FunctionName}`;
handler: BridgeController[FunctionName];
};

// Maps to BridgeController function names
type BridgeControllerActions =
| BridgeControllerAction<BridgeBackgroundAction.SET_FEATURE_FLAGS>
| BridgeControllerAction<BridgeUserAction.SELECT_SRC_NETWORK>
| BridgeControllerAction<BridgeUserAction.SELECT_DEST_NETWORK>;

type BridgeControllerEvents = ControllerStateChangeEvent<
typeof BRIDGE_CONTROLLER_NAME,
BridgeControllerState
>;

/**
* The messenger for the BridgeController.
*/
export type BridgeControllerMessenger = RestrictedControllerMessenger<
typeof BRIDGE_CONTROLLER_NAME,
BridgeControllerActions,
BridgeControllerEvents,
never,
never
>;
6 changes: 6 additions & 0 deletions app/scripts/lib/setupSentry.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,13 @@ export const SENTRY_BACKGROUND_STATE = {
bridgeState: {
bridgeFeatureFlags: {
extensionSupport: false,
destNetworkAllowlist: [],
srcNetworkAllowlist: [],
},
destTokens: {},
destTopAssets: [],
srcTokens: {},
srcTopAssets: [],
},
},
CronjobController: {
Expand Down
Loading