Cross-chain asset bridge for Selendra Network using Sygma Protocol (ChainSafe Systems)
This repository contains the Sygma Protocol bridge implementation for Selendra Network. It enables secure cross-chain asset transfers between Selendra and other EVM-compatible chains using Multi-Party Computation (MPC) security.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BRIDGE ARCHITECTURE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββ βββββββββββββββ βββββββββββββββββββββββββββββββ β
β β User βββββΆβ Bridge βββββΆβ FeeRouter β β
β β deposit() β β Contract β β βββββββββββ βββββββββββββ β β
β ββββββββββββββββ β (420 LOC) β β β Basic β β Percentageβ β β
β βββββββββββββββ β βFeeHandlerβ βFeeHandler β β β
β β β βββββββββββ βββββββββββββ β β
β βΌ βββββββββββββββββββββββββββββββ β
β βββββββββββββββ β
β β Resource ID βββββΆ Maps to Handler β
β βββββββββββββββ β
β β β
β βββββββββββββββββββΌββββββββββββββββββ β
β βΌ βΌ βΌ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β ERC20Handlerβ β ERC721Handlerβ β GMP Handler β β
β β (lock/mint) β β (NFTs) β β (messages) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββ β
β β ERC20Safe β β Holds locked tokens β
β βββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Chain A (Selendra) Chain B (Ethereum/BSC/etc)
ββββββββββββββββ ββββββββββββββββ
β deposit() β ββDepositβββΆ β β
β emit event β Event β MPC β
ββββββββββββββββ β Relayers β
β (TSS) β
ββββββββββββββββ β β
βexecuteProposalβ βββSignedββββ β β
β verify(MPC) β Proposal ββββββββββββββββ
ββββββββββββββββ
| Contract | Purpose | Lines |
|---|---|---|
| Bridge.sol | Main entry point - deposits, proposals, MPC verification | 420 |
| ERC20Handler.sol | Handle ERC20 lock/release or mint/burn | 165 |
| BasicFeeHandler.sol | Flat fee collection | 133 |
| PercentageERC20FeeHandler.sol | Percentage-based fee | ~150 |
| FeeHandlerRouter.sol | Route fees to correct handler | ~80 |
| AccessControlSegregator.sol | Fine-grained access control | ~100 |
// Bridge uses MPC address for proposal verification
address public _MPCAddress;
function verify(Proposal[] memory proposals, bytes calldata signature) public view returns (bool) {
// EIP-712 typed data signing
address signer = _hashTypedDataV4(...).recover(signature);
return signer == _MPCAddress;
}// Domain IDs
enum DomainId {
local = 0,
selendra = 1, // Mainnet
selendraTestnet = 10, // Testnet
}Each token gets a unique 32-byte resourceID that maps to a handler address:
mapping(bytes32 => address) public _resourceIDToHandlerAddress;- Lock/Release (lr): Tokens locked on source, released on destination
- Mint/Burn (mb): Tokens burned on source, minted on destination
| Network | RPC URL | Chain ID | Domain ID |
|---|---|---|---|
| Selendra Mainnet | https://rpc.selendra.org | 1961 | 1 |
| Selendra Testnet | https://rpc-testnet.selendra.org | 1953 | 10 |
cd bridge-contracts
npm install# Set your private key
export PRIVATE_KEY=your_private_key
# Deploy to testnet
npx hardhat run scripts/01.deloy_brigde.ts --network selendraTestnet
# Deploy to mainnet
npx hardhat run scripts/01.deloy_brigde.ts --network selendra- Deploy AccessControlSegregator (13 admin functions)
- Deploy Bridge (domainId, accessControlAddr)
- Deploy DefaultMessageReceiver
- Deploy ERC20Handler (bridgeAddr, messageReceiverAddr)
- Deploy FeeHandlerRouter (bridgeAddr)
- Deploy BasicFeeHandler (bridgeAddr, feeRouterAddr)
- Deploy PercentageERC20FeeHandler (bridgeAddr, feeRouterAddr)
- Configure Bridge:
Bridge.adminChangeFeeHandler(feeRouterAddr) - For each token:
setupErc20()andbridgeRegister()
# Edit scripts/02.setup_erc20.ts with token details
npx hardhat run scripts/02.setup_erc20.ts --network selendraTestnet# Edit scripts/03.bridge_resgister.ts with addresses
npx hardhat run scripts/03.bridge_resgister.ts --network selendraTestnet| Feature | Implementation |
|---|---|
| Pausing | adminPauseTransfers() / adminUnpauseTransfers() |
| Access Control | Function-level permissions via AccessControlSegregator |
| Nonce Tracking | Bitmap for efficient storage of used nonces |
| MPC Keygen | startKeygen() β endKeygen() β MPC address set |
| Key Refresh | refreshKey(hash) for topology changes |
| Retry Failed | retry(txHash) for failed deposits |
import { ethers } from "ethers";
const bridge = new ethers.Contract(BRIDGE_ADDRESS, BRIDGE_ABI, provider);
// Fetch deposit count per domain
const depositCount = await bridge._depositCounts(destinationDomainID);
// Check if proposal executed
const isExecuted = await bridge.isProposalExecuted(domainID, nonce);
// Get fee for transfer
const feeHandler = new ethers.Contract(
FEE_HANDLER_ADDRESS,
FEE_HANDLER_ABI,
provider
);
const [fee, tokenAddress] = await feeHandler.calculateFee(
sender,
fromDomainID,
destinationDomainID,
resourceID,
depositData,
feeData
);// Listen for deposits
bridge.on("Deposit", (destDomain, resourceID, nonce, user, data, response) => {
console.log(`Deposit #${nonce} to domain ${destDomain} by ${user}`);
});
// Listen for executions
bridge.on("ProposalExecution", (originDomain, nonce, dataHash, response) => {
console.log(`Proposal #${nonce} from domain ${originDomain} executed`);
});// 1. Approve token spending
const token = new ethers.Contract(TOKEN_ADDRESS, ERC20_ABI, signer);
await token.approve(ERC20_HANDLER_ADDRESS, amount);
// 2. Encode deposit data
const depositData = ethers.AbiCoder.defaultAbiCoder().encode(
["uint256", "uint256", "bytes"],
[amount, recipientAddress.length, recipientAddress]
);
// 3. Call deposit
const bridge = new ethers.Contract(BRIDGE_ADDRESS, BRIDGE_ABI, signer);
const tx = await bridge.deposit(
destinationDomainID,
resourceID,
depositData,
"0x", // feeData
{ value: bridgeFee }
);
await tx.wait();- Deploy contracts to mainnet with real addresses
- Add ERC721 Handler for NFT bridging
- Add GMP Handler for arbitrary message passing
- Create Subgraph to index bridge events
- Write comprehensive tests
- Document deployed contract addresses
LGPL-3.0-only
Based on Sygma Protocol by ChainSafe Systems.