Bridge ERC20 through the Custom Gateway
Scroll Alpha Testnet is now deprecated.
This guide will walk through how to use Scroll's bridge for ERC20s that need custom functionality using the Custom Gateway.
There is no need for a particular implementation for a token to be compatible with L2. If you already have a token, feel free to skip this step. If you want to deploy a new token, you can use the following contract of a simple ERC20 token that mints 1 million tokens to the deployer when launched.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract L1Token is ERC20 {
constructor() ERC20("My Token L1", "MTL1")
{
_mint(msg.sender, 1_000_000 ether);
}
}
The next step is launching the token on Scroll Alpha which represents the original Goerli token. This token can implement custom logic to match the same logic as the L1 token or even add more features on top of it.
For this to work:
- The token must implement the IScrollStandardERC20 interface in order to be compatible with the bridge.
- The contract should provide the gateway address and the counterpart token addresses (the L1 token we just launched) under the
gateway()
andcounterpart()
functions. It should also allow the L2 gateway to call the tokenmint()
andburn()
functions that will be called when a token is deposited and withdrawn.
The following is a complete example of a token compatible with the bridge. As the constructor, you should pass the
0xa07Cb742657294C339fB4d5d6CdF3fdBeE8C1c68
address as the official Scroll gateway and the address of the token we just launched on Goerli.// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@scroll-tech/[email protected]/libraries/token/IScrollERC20Extension.sol";
contract L2Token is ERC20, IScrollERC20Extension {
// We store the gateway and the L1 token address to provide the gateway() and counterpart() functions which are needed from the Scroll Standard ERC20 interface
address _gateway;
address _counterpart;
// In the constructor we pass as parameter the Custom L2 Gateway and the L1 token address as parameters
constructor(
address gateway_,
address counterpart_
) ERC20("My Token L2", "MTL2") {
_gateway = gateway_;
_counterpart = counterpart_;
}
function gateway() public view returns (address)
{
return _gateway;
}
function counterpart() external view returns (address)
{
return _counterpart;
}
// We allow minting only to the Gateway so it can mint new tokens when bridged from L1
function transferAndCall(
address receiver,
uint256 amount,
bytes calldata data
) external returns (bool success)
{
transfer(receiver, amount);
data;
return true;
}
// We allow minting only to the Gateway so it can mint new tokens when bridged from L1
function mint(address _to, uint256 _amount) external onlyGateway
{
_mint(_to, _amount);
}
// Similarly to minting, the Gateway is able to burn tokens whem bridged from L2 to L1
function burn(address _from, uint256 _amount) external onlyGateway
{
_burn(_from, _amount);
}
modifier onlyGateway() {
require(gateway() == _msgSender(), "Ownable: caller is not the gateway");
_;
}
}
You need to contact the Scroll team to add the token to
L2CustomERC20Gateway
contract in Scroll and L1CustomERC20Gateway
contract in L1. In addition, follow the instructions on the token lists repository to add your token to the Scroll official frontend.Once your token has been approved by the Scroll team, you should be able to deposit tokens from L1. To do so you must approve the
0x920f906B814597cF5DC76F95100F09CBAF9c5748
address that hosts the L1CustomGateway contract on Goerli. Then, deposit tokens by calling the depositERC20
function from the L1CustomGateway
contract. You can do this using the bridge, goerli scan, or a smart contract.You will follow similar steps to send tokens back from L2 to L1. First, approve the L2CustomGateway on
0xa07Cb742657294C339fB4d5d6CdF3fdBeE8C1c68
and then withdraw the tokens calling the withdrawERC20
from the L2CustomGateway
contract.Adding your token to the Scroll official bridge is the recommended way of bridging tokens. This will make them easier to discover and safer for holders. However, it will still need approval from the Scroll team. If you want to launch a custom token without going through the official approval process, you can launch a custom gateway yourself. To do so, you would need to launch a
L1CustomERC20Gateway
contract on L1 and a L2CustomERC20Gateway
on L2.Let’s start by launching the following contract on Goerli Testnet.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@scroll-tech/[email protected]/L1/gateways/L1CustomERC20Gateway.sol";
contract MyL1Gateway is L1CustomERC20Gateway
{
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal override nonReentrant {
super._deposit(_token, _to, _amount, _data, _gasLimit);
/*custom logic goes here*/
}
}
Now let’s launch the counterpart contract on Scroll Alpha L2.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@scroll-tech/[email protected]/L2/gateways/L2CustomERC20Gateway.sol";
contract MyL2Gateway is L2CustomERC20Gateway
{
function _withdraw(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal override nonReentrant {
super._withdraw(_token, _to, _amount, _data, _gasLimit);
/*custom logic goes here*/
}
}
Once the contracts are launched, call the following functions to initialize the contracts and bind them to the corresponding tokens and gateway on the other side of the bridge.
First, call the
initialize
function on the MyL1Gateway
contract with the following parameters:_counterpart
: The address ofMyL2Gateway
we just launched on Scroll Alpha._router
: Set it to0xe5E30E7c24e4dFcb281A682562E53154C15D3332
, the Scroll router contract on Goerli._messenger
: Set it to0x5260e38080BFe97e6C4925d9209eCc5f964373b6
, the Scroll messenger contract on Goerli.
A custom gate can host multiple token bridges. In this case, we will only be allowing bridging between L1Token and L2Token by calling the
updateTokenMapping
function on the MyL1Gateway
contract with the following parameters:_l1Token
: The address of theL1Token
contract we previously launched on Goerli._l2Token
: The address of theL2Token
contract we previously launched on Scroll Alpha.
Now let’s switch to the Scroll Alpha chain and initialize
MyL2Gateway
in a similar way.First, we call the
initialize
function from MyL2Gateway
:_counterpart
: The address ofMyL1Gateway
we just launched on Goerli._router
: Set it to0x6d79Aa2e4Fbf80CF8543Ad97e294861853fb0649
, the Scroll router contract on Scroll Alpha._messenger
: Set it0xb75d7e84517e1504C151B270255B087Fd746D34C
, the Scroll messenger contract on Scroll Alpha.
And then call the
updateTokenMapping
on the MyL2Gateway
contract:_l2Token
: The address of theL2Token
contract we previously launched on Scroll Alpha._l1Token
: The address of theL1Token
contract we previously launched on Goerli.
We can now call
depositERC20
from MyL1Gateway
and withdrawERC20
from MyL2Gateway
in a similar way to the official Scroll bridge.