Part III of III: How MetaMask Bridge Really Works

Part III in our trilogy of How MetaMask Really Works. This time we are going to show that the Bridge function — which we touched on last time — relies heavily on closed source contracts across a variety of blockchains.

DataFinnovation - ChainArgos - 4AC
ChainArgos
6 min read3 days ago

--

“Ok, but where is the meat in all this?”

MetaMask, Consensys and a lot of the crypto commentariat more generally are either lying about how MetaMask works or they do not understand their own frankly-not-very-complicated software.

The Bridge Contracts

We can get the bridge contract addresses here on Github:

  • Eth mainnet 0x0439e60F02a8900a951603950d8D4527f400C3f1
  • Optimism 0xB90357f2b86dbfD59c3502215d4060f71DF8ca0e
  • BSC 0xaEc23140408534b378bf5832defc426dF8604B59
  • Polygon 0x3A0b42cE6166abB05d30DdF12E726c95a83D7a16
  • ZKsync_era 0x357B5935482AD8a4A2e181e0132aBd1882E16520`
  • Base 0xa20ECbC821fB54064aa7B5C6aC81173b8b34Df71
  • Arbitrum 0x23981fC34e69eeDFE2BD9a0a9fCb0719Fe09DbFC
  • Avalanche 0x29106d08382d3c73bF477A94333C61Db1142E1B6
  • Linea 0xE3d0d2607182Af5B24f5C3C2E4990A053aDd64e3

We already covered the Eth mainnet contract, so here we will look a Optimism, BSC and Avalanche because it is far more unlikely that Consensys is in bed with all of them.

We will work through a full transaction on BSC, yet the analysis there however, is true for the rest as well.

Here is the bridge() function on the contract:

/**
* @notice Performs a bridge
* @param adapterId Identifier of the aggregator to be used for the bridge
* @param srcToken Identifier of the source chain
* @param amount Amount of tokens to be transferred from the destination chain
* @param data Dynamic data which is passed in to the delegatecall made to the adapter
* @dev pausable and nonreentrant
*/
function bridge(
string calldata adapterId,
address srcToken,
uint256 amount,
bytes calldata data
) external payable override whenNotPaused nonReentrant {
address adapter = adapters[adapterId];
require(adapter != address(0), "ADAPTER_NOT_FOUND");

// Move ERC20 funds to the spender
if (srcToken != Constants.NATIVE_TOKEN) {
IERC20(srcToken).safeTransferFrom(
msg.sender,
address(spender),
amount
);
} else {
require(msg.value == amount, "MSGVALUE_AMOUNT_MISMATCH");
}

spender.bridge{value: msg.value}(
adapter,
abi.encodePacked(
// bridge signature
IAdapter.bridge.selector,
abi.encode(msg.sender),
data
)
);
}

First notice it can be paused — not far down in the lines of code “whenNotPaused” confirms that it can be paused.

Similarly, nobody says “whenSwitchedOn” if there was no way to switch something off.

Yes, this contract has an owner with central control.

We will discuss what else the owner can do below.

Then look at how bridge() works — it takes as input:

  1. An adapterId
  2. A token and amount
  3. A binary blob

Then it:

  1. Looks up the adapter’s smart contract address
  2. Sends the tokens to a spender contract
  3. Calls that spender, passing in the adapter address and binary blob.

This is very similar to How Swap Works and is almost certainly a custodial process.

A Sample Transaction

Now look at transaction 0x06dc87b6344f161e7ee594cfda3cc8cecb43647a1882abadc38e3e8146c9f540 on BSC.

We can see the input data as:

We can go to the bridge contract and call the adapters() function passing in lifiAdapterV2.

This gives us 0x7Ac070f096C6e20931c3Dc54F927446be232618B, a closed source contract.

So most of MetaMask’s Bridge function is closed source.

Cheers?

You just passed custody of your assets to code you cannot read.

Congratulations!

The Owner

Each of these bridges has an owner.

Here are 3 of them:

  • Optimism 0xFc88419AEa3B142622CD2921cb0ce8a19a9aCDFB
  • BSC 0x87CB611Caf60545B18aba38059D87a36eac20032
  • Avalanche 0x5E17Bf05787c09e156fdeF4Af0b7E8C66c1ff160

The owner can pause the bridge as discussed above, and it can also call these functions:

/**
* @notice Sets the adapter for an aggregator. It can't be changed later.
* @param adapterId Aggregator's identifier
* @param adapterAddress Address of the contract that contains the logic for this aggregator
*/
function setAdapter(
string calldata adapterId,
address adapterAddress
) external override onlyOwner {
require(adapterAddress.isContract(), "ADAPTER_IS_NOT_A_CONTRACT");
require(!adapterRemoved[adapterId], "ADAPTER_REMOVED");
require(adapters[adapterId] == address(0), "ADAPTER_EXISTS");
require(bytes(adapterId).length > 0, "INVALID_ADAPTED_ID");

adapters[adapterId] = adapterAddress;
emit AdapterSet(adapterId, adapterAddress);
}

/**
* @notice Removes the adapter for an existing aggregator. This can't be undone.
* @param adapterId Adapter's identifier
*/
function removeAdapter(
string calldata adapterId
) external override onlyOwner {
require(adapters[adapterId] != address(0), "ADAPTER_DOES_NOT_EXIST");
delete adapters[adapterId];
adapterRemoved[adapterId] = true;
emit AdapterRemoved(adapterId);
}

All the bridge() function does is look up, and then pass control to, an adapter contract.

And the owner can change the adapterId-to-address mapping pretty much at will.

Of cource these actions need to be coordinated with the MetaMask add-in for any of this to work (i.e. so there is a mapping for the adapterId passed in to bridge()).

It is all one monolithic block of not-open-source software (that’s pronounced as “closed source”).

The System & Commentary

This is, again, absolute proof MetaMask is both closed source and custodial.

The bridge() function literally sends the user’s tokens to a closed source contract for processing which is functionally identical to wiring money to a bank — it is a black box you are trusting to do what you asked.

MetaMask is sometimes reported as the most-used web3 wallet, so it would appear that a lot of web3 users don’t care about decentralization or transparency, which is not exactly news.

But this also shows how false and misleading statements by MetaMask abound and how most of the crypto commentariat are clowns.

In a piece on The Block twitter.com/drnicka is quoted as saying:

For me, it’s about custodiality — the degree to which users are in sovereign control of their assets. If they are through and through no custody of funds; no broker-dealer

CoinDesk quotes Consensys’s head regulatory person twitter.com/billhughesdc as saying:

The fact that they’re looking at open source protocol developers, certainly with an eye on building a case to enforce, really struck us as way out of bounds

And in this Unchained interview real lawyers — who surely have been misinformed about how this all works — liken MetaMask to an internet browser:

SEC fundamentally misconceives the technology and what it is that the software is doing and so to give you sort of an example I think you have to think about the difference between an internet browser on the one hand versus erade on the other okay if I log into [an e-trade] account and I put money in and I buy stock that they hold that they have actual constructive custody of and they charge a fee for my trades into and out of stock you can see why the SEC would say that that [e- trade] is acting as a broker dealer and needs to comply with broker dealer regulations they have custody of my money they have custody of my stock there are potential abuses that could happen if somebody has my money or my stock and so they need to meet certain uh compliance requirements but the software we’re talking about [metamask] I have custody of my money I have my private Keys No One Else does okay when I use an internet browser to go buy a…

Please, please, please tell us we are fundamentally misconceiving the technology and need it explained better.

Of course there is a lot more coverage, but we aren’t going to waste your time.

One last bit worth noting is that MetaMask itself describes the tech even today as open source:

And so does their parent company Consensys:

Again, of course there are more examples of these false and misleading statements, but by this point are you surprised anymore?

--

--