Skip to content

Commit

Permalink
feat: cross chain swaps - tx submit (#27262)
Browse files Browse the repository at this point in the history
## **Description**

This PR implements the following:

1. Submit bridge transaction for normal transactions
3. Submit bridge transaction for native gas tokens that don't require
approval
4. Submit bridge transaction for ERC20s that require approval

Does not fully:
1. Submit bridge transaction for smart transactions
- You can submit an STX, but the status screens don't make the most
sense right now.
- Improved STX support be handled by
#28460 and
MetaMask/core#4918

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/27262?quickstart=1)

## **Related issues**

- Targeting: #27522

## **Manual testing steps**

1. Go to Bridge
2. Fill in source/dest token and amounts
3. Get a quote
4. Execute Bridge

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->


https://github.com/user-attachments/assets/b73f917d-e3e4-468b-b0fa-29f41f559488




## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
infiniteflower authored Nov 21, 2024
1 parent 6023787 commit a1b6ba7
Show file tree
Hide file tree
Showing 27 changed files with 5,082 additions and 11 deletions.
4 changes: 4 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.

49 changes: 47 additions & 2 deletions app/scripts/controllers/bridge/bridge-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,27 @@ const messengerMock = {
publish: jest.fn(),
} as unknown as jest.Mocked<BridgeControllerMessenger>;

jest.mock('@ethersproject/contracts', () => {
return {
Contract: jest.fn(() => ({
allowance: jest.fn(() => '100000000000000000000'),
})),
};
});

jest.mock('@ethersproject/providers', () => {
return {
Web3Provider: jest.fn(),
};
});

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

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

beforeEach(() => {
Expand All @@ -43,6 +59,18 @@ describe('BridgeController', function () {
'extension-support': true,
'src-network-allowlist': [10, 534352],
'dest-network-allowlist': [137, 42161],
'approval-gas-multiplier': {
'137': 1.1,
'42161': 1.2,
'10': 1.3,
'534352': 1.4,
},
'bridge-gas-multiplier': {
'137': 2.1,
'42161': 2.2,
'10': 2.3,
'534352': 2.4,
},
});
nock(BRIDGE_API_BASE_URL)
.get('/getTokens?chainId=10')
Expand Down Expand Up @@ -507,7 +535,10 @@ describe('BridgeController', function () {
bridgeController,
'startPollingByNetworkClientId',
);
messengerMock.call.mockReturnValueOnce({ address: '0x123' } as never);
messengerMock.call.mockReturnValue({
address: '0x123',
provider: jest.fn(),
} as never);

bridgeController.updateBridgeQuoteRequestParams({
srcChainId: 1,
Expand Down Expand Up @@ -536,4 +567,18 @@ describe('BridgeController', function () {
}),
);
});

describe('getBridgeERC20Allowance', () => {
it('should return the atomic allowance of the ERC20 token contract', async () => {
messengerMock.call.mockReturnValue({
address: '0x123',
provider: jest.fn(),
} as never);
const allowance = await bridgeController.getBridgeERC20Allowance(
'0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
'0xa',
);
expect(allowance).toBe('100000000000000000000');
});
});
});
38 changes: 37 additions & 1 deletion app/scripts/controllers/bridge/bridge-controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { StateMetadata } from '@metamask/base-controller';
import { add0x, Hex } from '@metamask/utils';
import { StaticIntervalPollingController } from '@metamask/polling-controller';
import { NetworkClientId } from '@metamask/network-controller';
import { StateMetadata } from '@metamask/base-controller';
import { Contract } from '@ethersproject/contracts';
import { abiERC20 } from '@metamask/metamask-eth-abis';
import { Web3Provider } from '@ethersproject/providers';
import { BigNumber } from '@ethersproject/bignumber';
import {
fetchBridgeFeatureFlags,
fetchBridgeQuotes,
Expand All @@ -25,6 +29,7 @@ import {
DEFAULT_BRIDGE_CONTROLLER_STATE,
REFRESH_INTERVAL_MS,
RequestStatus,
METABRIDGE_CHAIN_TO_ADDRESS_MAP,
} from './constants';
import {
BridgeControllerState,
Expand Down Expand Up @@ -60,6 +65,8 @@ export default class BridgeController extends StaticIntervalPollingController<

this.setIntervalLength(REFRESH_INTERVAL_MS);

this.#abortController = new AbortController();
// Register action handlers
this.messagingSystem.registerActionHandler(
`${BRIDGE_CONTROLLER_NAME}:setBridgeFeatureFlags`,
this.setBridgeFeatureFlags.bind(this),
Expand All @@ -80,6 +87,10 @@ export default class BridgeController extends StaticIntervalPollingController<
`${BRIDGE_CONTROLLER_NAME}:resetState`,
this.resetState.bind(this),
);
this.messagingSystem.registerActionHandler(
`${BRIDGE_CONTROLLER_NAME}:getBridgeERC20Allowance`,
this.getBridgeERC20Allowance.bind(this),
);
}

_executePoll = async (
Expand Down Expand Up @@ -277,4 +288,29 @@ export default class BridgeController extends StaticIntervalPollingController<
chainId,
);
}

/**
*
* @param contractAddress - The address of the ERC20 token contract
* @param chainId - The hex chain ID of the bridge network
* @returns The atomic allowance of the ERC20 token contract
*/
getBridgeERC20Allowance = async (
contractAddress: string,
chainId: Hex,
): Promise<string> => {
const provider = this.#getSelectedNetworkClient()?.provider;
if (!provider) {
throw new Error('No provider found');
}

const web3Provider = new Web3Provider(provider);
const contract = new Contract(contractAddress, abiERC20, web3Provider);
const { address: walletAddress } = this.#getSelectedAccount();
const allowance = await contract.allowance(
walletAddress,
METABRIDGE_CHAIN_TO_ADDRESS_MAP[chainId],
);
return BigNumber.from(allowance).toString();
};
}
7 changes: 7 additions & 0 deletions app/scripts/controllers/bridge/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { zeroAddress } from 'ethereumjs-util';
import { Hex } from '@metamask/utils';
import { METABRIDGE_ETHEREUM_ADDRESS } from '../../../../shared/constants/bridge';
import { CHAIN_IDS } from '../../../../shared/constants/network';
import { BridgeControllerState, BridgeFeatureFlagsKey } from './types';

export const BRIDGE_CONTROLLER_NAME = 'BridgeController';
Expand Down Expand Up @@ -36,3 +39,7 @@ export const DEFAULT_BRIDGE_CONTROLLER_STATE: BridgeControllerState = {
quotesLoadingStatus: undefined,
quotesRefreshCount: 0,
};

export const METABRIDGE_CHAIN_TO_ADDRESS_MAP: Record<Hex, string> = {
[CHAIN_IDS.MAINNET]: METABRIDGE_ETHEREUM_ADDRESS,
};
2 changes: 2 additions & 0 deletions app/scripts/controllers/bridge/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export enum BridgeUserAction {
export enum BridgeBackgroundAction {
SET_FEATURE_FLAGS = 'setBridgeFeatureFlags',
RESET_STATE = 'resetState',
GET_BRIDGE_ERC20_ALLOWANCE = 'getBridgeERC20Allowance',
}

type BridgeControllerAction<FunctionName extends keyof BridgeController> = {
Expand All @@ -64,6 +65,7 @@ type BridgeControllerAction<FunctionName extends keyof BridgeController> = {
type BridgeControllerActions =
| BridgeControllerAction<BridgeBackgroundAction.SET_FEATURE_FLAGS>
| BridgeControllerAction<BridgeBackgroundAction.RESET_STATE>
| BridgeControllerAction<BridgeBackgroundAction.GET_BRIDGE_ERC20_ALLOWANCE>
| BridgeControllerAction<BridgeUserAction.SELECT_SRC_NETWORK>
| BridgeControllerAction<BridgeUserAction.SELECT_DEST_NETWORK>
| BridgeControllerAction<BridgeUserAction.UPDATE_QUOTE_PARAMS>;
Expand Down
5 changes: 5 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3985,6 +3985,11 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger,
`${BRIDGE_CONTROLLER_NAME}:${BridgeBackgroundAction.RESET_STATE}`,
),
[BridgeBackgroundAction.GET_BRIDGE_ERC20_ALLOWANCE]:
this.controllerMessenger.call.bind(
this.controllerMessenger,
`${BRIDGE_CONTROLLER_NAME}:${BridgeBackgroundAction.GET_BRIDGE_ERC20_ALLOWANCE}`,
),
[BridgeUserAction.SELECT_SRC_NETWORK]: this.controllerMessenger.call.bind(
this.controllerMessenger,
`${BRIDGE_CONTROLLER_NAME}:${BridgeUserAction.SELECT_SRC_NETWORK}`,
Expand Down
4 changes: 4 additions & 0 deletions shared/constants/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ export const BRIDGE_API_BASE_URL = process.env.BRIDGE_USE_DEV_APIS
: BRIDGE_PROD_API_BASE_URL;

export const BRIDGE_CLIENT_ID = 'extension';

export const ETH_USDT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7';
export const METABRIDGE_ETHEREUM_ADDRESS =
'0x0439e60F02a8900a951603950d8D4527f400C3f1';
1 change: 1 addition & 0 deletions shared/constants/metametrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,7 @@ export enum MetaMetricsNetworkEventSource {
Dapp = 'dapp',
DeprecatedNetworkModal = 'deprecated_network_modal',
NewAddNetworkFlow = 'new_add_network_flow',
Bridge = 'bridge',
}

export enum MetaMetricsSwapsEventSource {
Expand Down
2 changes: 2 additions & 0 deletions shared/constants/security-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export const SECURITY_PROVIDER_EXCLUDED_TRANSACTION_TYPES = [
TransactionType.swap,
TransactionType.swapApproval,
TransactionType.swapAndSend,
TransactionType.bridgeApproval,
TransactionType.bridge,
];

export const LOADING_SECURITY_ALERT_RESPONSE: SecurityAlertResponse = {
Expand Down
Loading

0 comments on commit a1b6ba7

Please sign in to comment.