Skip to main content

A cross-chain token bridge

note

Clone the examples repo and look at the app before starting!

This dapp implements a cross-chain token bridge. It consists of two contracts, the TokenSender contract and the ERC20Bridged contract, which extends OpenZepplin's standard ERC20 contract with bridging functionality. In the default configuration, TokenSender is deployed on Ethereum and ERC20Bridged is deployed on Polygon, but this can be changed by updating the Cubist config file.

TokenSender is the "native-token" side of the cross-chain bridge. It receives payment in the native token of the chain on which it is deployed; in response, it issues ERC20 tokens from the ERC20Bridged contract. ERC20Bridged is the "wrapped-token" side of the bridge. It defines an ERC20 token---in the example deployment script, 'FooBarBaz' (ticker 'FBB')---that is minted in response to commands from TokenSender. In addition to all standard ERC20 functionality, users can burn these wrapped tokens in order to release native tokens from TokenSender.

TokenSender in slightly more detail

The TokenSender contract has just three methods:

  1. The constructor saves the address of the partner ERC20Bridged contract.
  2. bridgeSend is the method to which users send payments; its argument is the address on the wrapped-token chain that will receive the proceeds. This method first takes a 0.1% fee, then instructs ERC20Bridged to mint wrapped tokens corresponding to the balance and send them to the recipient.
  3. bridgeReceive is the method that is invoked when a user burns wrapped tokens (by interacting with ERC20Bridged on the wrapped-token chain) in order to release native tokens on the native-token chain. Because of the onlyOwner modifier, this method can only be invoked by the bridge owner.

Because TokenSender is intended to be a very simple example, it lacks features that a deployment-ready token bridge should have. Most notably, it does not implement fee accounting or a mechanism for sweeping fees. It may be a fun exercise to implement these yourself! (Hint: you'll probably need to add a state variable for tracking fees, and a method that (1) checks that the caller is authorized, (2) releases the accrued fees, and (3) updates the fee-tracking variable.)

ERC20Bridged in slightly more detail

The ERC20Bridged method is more complex, but most of that complexity is just the functionality inherited from the base contract (as a reminder, the base contract is just OpenZeppelin's standard ERC20 implementation).

The three methods that ERC20Bridged implements correspond to the functionality on the TokenSender side:

  1. The constructor sets up the underlying ERC20 contract (setting the name and symbol) and saves the address of the partner TokenSender contract.
  2. bridgeMint is the method that is called by the bridge provider in response to TokenSender's bridgeSend method: when it receives a cross-chain call indicating the recipient and the amount, it invokes the OpenZeppelin ERC20 contract's _mint method, which mints new tokens and issues them to the recipient. Because of the onlyOwner modifier, this method can only be invoked by the bridge owner.
  3. bridgeSend is the method that a user invokes in order to burn wrapped tokens and send native tokens via TokenSender. The arguments are the address on the native-token chain that will receive the proceeds, and the amount to send. This method first burns the tokens using the OpenZepplin ERC20 contract's _burn method (which will fail if the user does not own enough tokens), then makes a cross-chain call to the TokenSender contract's bridgeReceive method instructing it to send native tokens to the intended recipient.
note

The next Cubist release will support bridge providers beyond the Cubist Trusted Relayer---e.g., Axelar or Layer Zero. You can swap bridge providers just as you swap chains, with a single line in the configuration file. Contract us at [email protected] or on discord if you're interested in bridge provider support!