ERC1155 NFT Bridging
BOBA ERC1155 bridges consists of two bridge contracts. The L1ERC1155Bridge contract is deployed on L1 and the L2ERC1155Bridge contract is deployed on L2. It supports native L1 ERC1155 tokens and native L2 ERC1155 tokens to be moved back and forth. These two contracts have not been audited, exercise caution when using this on mainnet.
- Native L1 ERC1155 token: the original token contract was deployed on L1
- Native L2 ERC1155 token: the original token contract was deployed on L2
Bridging a token to Boba takes several minutes, and bridging a token from Boba to Ethereum takes 7 days. Not all tokens are bridgeable - developers must use specialized token contracts (e.g. L2StandardERC1155.sol) to enable this functionality.

Assuming you have already deployed an ERC1155 token contract on L1, and you wish to transfer those tokens to L2, please make sure that your L1 ERC1155 token contract is ERC1155 compatible. Your contract must implement
ERC165
and ERC721
interfaces. We will check the interface before registering your token contracts to our bridges.bytes4 erc1155 = 0xd9b67a26;
require(ERC165Checker.supportsInterface(_l1Contract, erc1155), "L1 token is not ERC1155 compatible");
After verifying the interface, please deploy L2StandardERC1155 on Boba. The
L1_ERC1155_TOKEN_CONTRACT_ADDRESS
is the address of your token on Ethereum.const Factory__L2StandardERC1155 = new ethers.ContractFactory(
L2StandardERC1155.abi,
L2StandardERC1155.bytecode,
L2Wallet
)
const L2StandardERC1155 = await Factory__L2StandardERC1155.deploy(
L2_ERC1155_BRIDGE_ADDRESS, // L2 ERC1155 Bridge Address
L1_ERC1155_TOKEN_CONTRACT_ADDRESS, // Your L1 Token Address
URI
)
await L2StandardERC1155.deployTransaction.wait()
If you want to deploy your own L2 ERC1155 token contract, please follow requirements:
- The following functions in your L2 ERC1155 contract should be overriden byfunction mint(address _to, uint256 _tokenId, uint256 _amount, bytes memory _data) public virtual override onlyL2Bridge {_mint(_to, _tokenId, _amount, _data);emit Mint(_to, _tokenId, _amount);}function mintBatch(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) public virtual override onlyL2Bridge {_mintBatch(_to, _tokenIds, _amounts, _data);emit MintBatch(_to, _tokenIds, _amounts);}function burn(address _from, uint256 _tokenId, uint256 _amount) public virtual override onlyL2Bridge {_burn(_from, _tokenId, _amount);emit Burn(_from, _tokenId, _amount);}function burnBatch(address _from, uint256[] memory _tokenIds, uint256[] memory _amounts) public virtual override onlyL2Bridge {_burnBatch(_from, _tokenIds, _amounts);emit BurnBatch(_from, _tokenIds, _amounts);}
- In your L2 ERC1155 token contract, you must add the following code to bypass our interface check in our bridge
pragma solidity >0.7.5;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "./IL2StandardERC1155.sol";
contract L2StandardERC1155 is IL2StandardERC1155, ERC1155 {
// [This is mandatory]
// This is your L1 token contract address. Only one pair of your token contracts can be registered in the bridges.
address public override l1Contract;
// [This is not mandatory] You can use other names or other ways to only allow l2 bridge to mint and burn tokens
// This is L2 brigde contract address
address public l2Bridge;
// [This is not mandatory]
// Only l2Brigde (L2 bridge) can mint or burn tokens
modifier onlyL2Bridge {
require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn");
_;
}
// [This is mandatory]
// You must export this interface
function supportsInterface(bytes4 _interfaceId) public view override(IERC165, ERC1155) returns (bool) {
bytes4 bridgingSupportedInterface = IL2StandardERC1155.l1Contract.selector
^ IL2StandardERC1155.mint.selector
^ IL2StandardERC1155.burn.selector
^ IL2StandardERC1155.mintBatch.selector
^ IL2StandardERC1155.burnBatch.selector;
return _interfaceId == bridgingSupportedInterface || super.supportsInterface(_interfaceId);
}
// [The input is mandatory] The input must be `address _to, uint256 _tokenId, uint256 _amount, bytes memory _data`
// [SECURITY] Make sure that only L2 ERC1155 bridge can mint tokens
function mint(address _to, uint256 _tokenId, uint256 _amount, bytes memory _data) public virtual override onlyL2Bridge {
_mint(_to, _tokenId, _amount, _data);
emit Mint(_to, _tokenId, _amount);
}
// [The input is mandatory] The input must be `address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data`
// [SECURITY] Make sure that only L2 ERC1155 bridge can mint tokens
function mintBatch(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) public virtual override onlyL2Bridge {
_mintBatch(_to, _tokenIds, _amounts, _data);
emit MintBatch(_to, _tokenIds, _amounts);
}
// [The input is mandatory] The input must be `address _from, uint256 _tokenId, uint256 _amount`
// [SECURITY] Make sure that only L2 ERC1155 bridge can burn tokens
function burn(address _from, uint256 _tokenId, uint256 _amount) public virtual override onlyL2Bridge {
_burn(_from, _tokenId, _amount);
emit Burn(_from, _tokenId, _amount);
}
// [The input is mandatory] The input must be `address _from, uint256[] memory _tokenIds, uint256[] memory _amounts`
// [SECURITY] Make sure that only L2 ERC1155 bridge can burn tokens
function burnBatch(address _from, uint256[] memory _tokenIds, uint256[] memory _amounts) public virtual override onlyL2Bridge {
_burnBatch(_from, _tokenIds, _amounts);
emit BurnBatch(_from, _tokenIds, _amounts);
}
}
NOTE: Once you have your L2 ERC1155 token contract address, please contact us so we can register that address in the L1 and L2 bridges.

Deploy your ERC115 token on Boba and then deploy L1StandardERC1155 on Ethereum. The
L1_ERC1155_TOKEN_CONTRACT_ADDRESS
is the address of your token on Boba.const Factory__L1StandardERC1155 = new ethers.ContractFactory(
L1StandardERC1155.abi,
L1StandardERC1155.bytecode,
L1Wallet
)
const L1StandardERC1155 = await Factory__L1StandardERC1155.deploy(
L1_ERC1155_BRIDGE_ADDRESS, // L1 ERC1155 Bridge Address
L2_ERC1155_TOKEN_CONTRACT_ADDRESS, // Your L2 Token Address
URI
)
await L1StandardERC1155.deployTransaction.wait()
If you want to deploy your own L1 ERC1155 token contract, please follow requirements:
- The following functions in your L1 ERC1155 contract should be overriden byfunction mint(address _to, uint256 _tokenId, uint256 _amount, bytes memory _data) public virtual override onlyL1Bridge {_mint(_to, _tokenId, _amount, _data);emit Mint(_to, _tokenId, _amount);}function mintBatch(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) public virtual override onlyL1Bridge {_mintBatch(_to, _tokenIds, _amounts, _data);emit MintBatch(_to, _tokenIds, _amounts);}function burn(address _from, uint256 _tokenId, uint256 _amount) public virtual override onlyL1Bridge {_burn(_from, _tokenId, _amount);emit Burn(_from, _tokenId, _amount);}function burnBatch(address _from, uint256[] memory _tokenIds, uint256[] memory _amounts) public virtual override onlyL1Bridge {_burnBatch(_from, _tokenIds, _amounts);emit BurnBatch(_from, _tokenIds, _amounts);}
- In your L1 ERC1155 token contract, you must add the following code to bypass our interface check in our bridge
pragma solidity >0.7.5;
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "./IL1StandardERC1155.sol";
contract L1StandardERC1155 is IL1StandardERC1155, ERC1155 {
// [This is mandatory]
// This is your L2 token contract address. Only one pair of your token contracts can be registered in the bridges.
address public override l2Contract;
// [This is not mandatory] You can use other names or other ways to only allow l2 bridge to mint and burn tokens
// This is L1 brigde contract address
address public l1Bridge;
// [This is not mandatory]
// Only l1Brigde (L1 bridge) can mint or burn tokens
modifier onlyL1Bridge {
require(msg.sender == l1Bridge, "Only L1 Bridge can mint and burn");
_;
}
// [This is mandatory]
// You must export this interface
function supportsInterface(bytes4 _interfaceId) public view override(IERC165, ERC1155) returns (bool) {
bytes4 bridgingSupportedInterface = IL1StandardERC1155.l1Contract.selector
^ IL1StandardERC1155.mint.selector
^ IL1StandardERC1155.burn.selector
^ IL1StandardERC1155.mintBatch.selector
^ IL1StandardERC1155.burnBatch.selector;
return _interfaceId == bridgingSupportedInterface || super.supportsInterface(_interfaceId);
}
// [The input is mandatory] The input must be `address _to, uint256 _tokenId, uint256 _amount, bytes memory _data`
// [SECURITY] Make sure that only L1 ERC1155 bridge can mint tokens
function mint(address _to, uint256 _tokenId, uint256 _amount, bytes memory _data) public virtual override onlyL1Bridge {
_mint(_to, _tokenId, _amount, _data);
emit Mint(_to, _tokenId, _amount);
}
// [The input is mandatory] The input must be `address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data`
// [SECURITY] Make sure that only L1 ERC1155 bridge can mint tokens
function mintBatch(address _to, uint256[] memory _tokenIds, uint256[] memory _amounts, bytes memory _data) public virtual override onlyL1Bridge {
_mintBatch(_to, _tokenIds, _amounts, _data);
emit MintBatch(_to, _tokenIds, _amounts);
}
// [The input is mandatory] The input must be `address _from, uint256 _tokenId, uint256 _amount`
// [SECURITY] Make sure that only L1 ERC1155 bridge can burn tokens
function burn(address _from, uint256 _tokenId, uint256 _amount) public virtual override onlyL1Bridge {
_burn(_from, _tokenId, _amount);
emit Burn(_from, _tokenId, _amount);
}
// [The input is mandatory] The input must be `address _from, uint256[] memory _tokenIds, uint256[] memory _amounts`
// [SECURITY] Make sure that only L1 ERC1155 bridge can burn tokens
function burnBatch(address _from, uint256[] memory _tokenIds, uint256[] memory _amounts) public virtual override onlyL1Bridge {
_burnBatch(_from, _tokenIds, _amounts);
emit BurnBatch(_from, _tokenIds, _amounts);
}
}
NOTE: Once you have your L1 token contract address, please contact us so we can register that address in the L1 and L2 bridges.

First, users transfer their token to the L1 Bridge, starting with an approval.
const approveTx = await L1Token.setApprovalForAll(L1_ERC1155_BRIDGE_ADDRESS, true)
await approveTx.wait()
Users then call the
deposit
or depositTo
function to deposit token to L2. The token arrives on L2 after eight L1 blocks.const tx = await L1ERC1155Brige.deposit(
L1_ERC1155_TOKEN_CONTRACT_ADDRESS,
TOKEN_ID,
TOKEN_AMOUNT,
DATA, // event data - you can pass `0x` if you don't want to emit any data in the events
9999999 // L2 gas
)
await tx.wait()
Prior to the exit, the L2 Bridge burns the L2 tokens, so the first step is for the user to approve the transaction.
const approveTx = await L2Token.setApprovalForAll(L1_ERC1155_BRIDGE_ADDRESS, true)
await approveTx.wait()
Users have to approve the Boba for the exit fee next. They then call the
withdraw
or withdrawTo
function to exit the tokens from Boba to Ethereum. The token will arrive on L1 after the seven days.const exitFee = await BOBABillingContract.exitFee()
const approveBOBATx = await L2BOBAToken.approve(
L2ERC1155Brige.address,
exitFee
)
await approveBOBATx.wait()
const tx = await L1ERC1155Brige.withdraw(
L2_ERC1155_TOKEN_CONTRACT_ADDRESS,
TOKEN_ID,
TOKEN_AMOUNT,
DATA, // event data - you can pass `0x` if you don't want to emit any data in the events
9999999 // L2 gas
)
await tx.wait()
Users have to transfer their tokens to the L2 Bridge, so they start by approving the transaction.
const approveTx = await L2Token.setApprovalForAll(L2_ERC1155_BRIDGE_ADDRESS, TOKEN_ID)
await approveTx.wait()
Users have to approve the Boba for the exit fee next. They then call the
withdraw
or withdrawTo
function to exit tokens from L2. The token will arrive on L1 after the seven days.const exitFee = await BOBABillingContract.exitFee()
const approveBOBATx = await L2BOBAToken.approve(
L2ERC1155Brige.address,
exitFee
)
await approveBOBATx.wait()
const tx = await L2ERC1155Brige.withdraw(
L2_ERC1155_TOKEN_CONTRACT_ADDRESS,
TOKEN_ID,
TOKEN_AMOUNT,
DATA, // event data - you can pass `0x` if you don't want to emit any data in the events
9999999 // L2 gas
)
await tx.wait()
The L1 Bridge has to burn the L1 tokens, so the user needs to approve the transaction first.
const approveTx = await L2BOBAToken.setApprovalForAll(L1_ERC1155_BRIDGE_ADDRESS, true)
await approveTx.wait()
Users then call the
deposit
or depositTo
function to deposit tokens to L2. The token arrives on L2 after eight L1 blocks.const tx = await L1ERC1155Brige.deposit(
L1_ERC1155_TOKEN_CONTRACT_ADDRESS,
TOKEN_ID,
TOKEN_AMOUNT,
DATA, // event data - you can pass `0x` if you don't want to emit any data in the events
9999999 // L2 gas
)
await tx.wait()
To bridge tokens from Alt L2s to Alt L1, you need to add the BOBA as the value to cover the exit fee.
const exitFee = await BOBABillingContract.exitFee()
const tx = await L2ERC1155Brige.withdraw(
L2_ERC1155_TOKEN_CONTRACT_ADDRESS,
TOKEN_ID,
TOKEN_AMOUNT,
DATA, // event data - you can pass `0x` if you don't want to emit any data in the events
9999999, // L2 gas
{value: exitFee} // exit fee
)
await tx.wait()

Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Layer | Contract Name | Contract Address |
---|---|---|
L1 | Proxy__L1ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
L2 | Proxy__L2ERC1155Bridge | 0x1dF39152AC0e81aB100341cACC4dE4c372A550cb |
Last modified 1mo ago