Multichain Token Development
Multichain token — not just deploying same ERC-20 to multiple networks. This is architectural solution with serious consequences for supply management, security and UX. Incorrectly designed multichain token creates illusion of unified asset with actually fragmented supply, opens bridge attack surface and complicates governance. Let's see how to do this right.
Multichain Token Models
Before coding, choose architectural model. There are three, fundamentally different.
Lock & Mint (Canonical Model)
Token exists natively on one chain (home chain, usually Ethereum). All other chains have wrapped versions. Bridge locks tokens on home chain and mints wrapped on destination chain. On reverse bridge — burning wrapped, unlocking original.
Pros: unified canonical supply, simple mental model for users.
Cons: if bridge is hacked — attacker can mint wrapped tokens without backing. This happened to Wormhole ($320M in 2022) and Ronin Bridge ($625M).
Burn & Mint (Omnichain Model)
On transfer token burns on source chain and mints on destination chain. Total supply globally constant. This approach used by LayerZero OFT (Omnichain Fungible Token) and Axelar ITS.
Pros: no locked liquidity on one chain, token equivalence across networks.
Cons: transaction not atomic — token destroyed on source, but may not mint on destination on failure. Need recovery mechanism.
Liquidity Pool Model
Independent tokens on each chain connected via AMM-pools in bridge protocols (Stargate, Synapse). Native swap bridge, not wrapped tokens.
Pros: instant (atomic swap from pool), no wrapped tokens.
Cons: requires bootstrap liquidity on each chain, slippage on unbalanced pools.
Implementation via LayerZero OFT
LayerZero became de-facto standard for new multichain tokens. OFT (Omnichain Fungible Token) — burn & mint model with messaging via LayerZero Endpoint.
Basic OFT Implementation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import { OFT } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is OFT {
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint, // LayerZero Endpoint address for current network
address _delegate
) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {}
// Minting only at deploy or via governance
function mint(address _to, uint256 _amount) external onlyOwner {
_mint(_to, _amount);
}
}
Deploy this contract on home chain and mint all supply. On other chains deploy same contract but without initial mint — tokens come via bridge.
OFT Configuration After Deploy
After deploy on all chains need to link contracts via setPeer:
// On each chain for each other network
function configurePeers() external onlyOwner {
// eid = endpoint ID in LayerZero system
// Ethereum mainnet: 30101, Arbitrum: 30110, Base: 30184, BSC: 30102
// If we're on Ethereum, register Arbitrum peer
oft.setPeer(30110, bytes32(uint256(uint160(ARBITRUM_OFT_ADDRESS))));
// Register Base peer
oft.setPeer(30184, bytes32(uint256(uint160(BASE_OFT_ADDRESS))));
}
Sending Tokens Between Chains
// Prepare parameters for cross-chain transfer
function bridgeTokens(
uint32 _dstEid, // destination endpoint ID
address _recipient,
uint256 _amount
) external payable {
SendParam memory sendParam = SendParam({
dstEid: _dstEid,
to: bytes32(uint256(uint160(_recipient))),
amountLD: _amount,
minAmountLD: (_amount * 995) / 1000, // 0.5% slippage tolerance
extraOptions: OptionsBuilder.newOptions()
.addExecutorLzReceiveOption(200000, 0), // gas on destination
composeMsg: "",
oftCmd: ""
});
// Get quote on native fee to pay for LayerZero messaging
MessagingFee memory fee = oft.quoteSend(sendParam, false);
oft.send{ value: fee.nativeFee }(sendParam, fee, payable(msg.sender));
}
Supply Management Between Chains
This is most complex aspect. Need monitoring:
| Metric | How to Track |
|---|---|
| Supply per chain | Call totalSupply() on each deploy |
| Circulating supply | Sum all totalSupply() minus locked in bridge contracts |
| Bridge flow | OFTSent / OFTReceived events |
| Pending messages | LayerZero Scan API |
For Lock & Mint model (if using custom bridge) need invariant check: sum(wrapped supplies) <= locked_on_home_chain. Monitor via Tenderly or own script with PagerDuty alerting.
Multichain Token Security
DVN (Decentralized Verifier Network) Configuration
LayerZero V2 allows configuring which verifiers must confirm message before execution. Minimal safe configuration — 2 of N verifiers:
// ULN (Ultra Light Node) configuration setup
UlnConfig memory ulnConfig = UlnConfig({
confirmations: 15, // blocks for finality
requiredDVNCount: 2, // minimum 2 DVN must confirm
optionalDVNCount: 0,
optionalDVNThreshold: 0,
requiredDVNs: [LAYERZERO_DVN, GOOGLE_CLOUD_DVN],
optionalDVNs: new address[](0)
});
For production recommend LayerZero DVN + one independent DVN (Google Cloud, Nethermind, p2p.org).
Rate Limiting
Even with reliable bridge — add rate limiting at token contract level as last line of defense:
// Limit maximum transfer per period
mapping(uint256 => uint256) public dailyBridgeVolume; // day => volume
uint256 public constant MAX_DAILY_BRIDGE = 1_000_000e18; // 1M tokens/day
modifier withRateLimit(uint256 amount) {
uint256 today = block.timestamp / 1 days;
require(
dailyBridgeVolume[today] + amount <= MAX_DAILY_BRIDGE,
"Daily bridge limit exceeded"
);
dailyBridgeVolume[today] += amount;
_;
}
If bridge is hacked — rate limit gives time to react, limiting damage.
Pause Mechanism
Contract should have pause function controlled by multisig with short timelock (or without for emergencies). On detecting anomalous bridge activity — instant stop of transfers.
Deploy and Infrastructure
Deploy Sequence
- Deploy on home chain, mint supply
- Deploy on all destination chains
- Call
setPeeron each chain for each pair - Verification: test bridge transfer small amount
- Monitoring: setup alerts on bridge events
Networks and LayerZero Endpoint IDs (Current)
| Network | Endpoint ID | Chain ID |
|---|---|---|
| Ethereum | 30101 | 1 |
| Arbitrum One | 30110 | 42161 |
| Base | 30184 | 8453 |
| Optimism | 30111 | 10 |
| BSC | 30102 | 56 |
| Polygon | 30109 | 137 |
| Avalanche | 30106 | 43114 |
Deploy Script via Hardhat
// scripts/deploy-oft.ts
import { ethers } from "hardhat";
import { EndpointId } from "@layerzerolabs/lz-definitions";
async function main() {
const [deployer] = await ethers.getSigners();
const lzEndpoint = getEndpointForNetwork(network.name);
const OFT = await ethers.getContractFactory("MyToken");
const oft = await OFT.deploy(
"My Token",
"MYT",
lzEndpoint,
deployer.address
);
await oft.waitForDeployment();
console.log(`OFT deployed to: ${await oft.getAddress()}`);
// Minting only on home chain
if (network.name === "ethereum") {
await oft.mint(deployer.address, ethers.parseEther("100000000"));
}
}
Governance and Multichain Token
If token used for governance — problem arises: how count voting power with distributed supply? Three approaches:
Hub-and-spoke: voting only on home chain. Users on other chains must bridge tokens back. Simple, but UX bad.
Cross-chain voting: off-chain snapshot votes on all chains, aggregation via LayerZero or Axelar. Uses Snapshot + XCHAIN voting.
Dedicated governance token: separate non-transferable governance token on one chain, distribution token — multichain. More complex, but architecturally cleaner.
Multichain token — not feature, it's commitment. Each new chain adds operational load, audit surface and risks. Add networks only where there's real liquidity and users.







