// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// MINIMAL ERC20 INTERFACE FOR QANX TRANSFERABILITY
interface TransferableERC20 {
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}
contract Signed {
// THE ADDRESSES PERMITTED TO SIGN WITHDRAWAL REQUESTS UP TO X AMOUNT
mapping(address => uint256) internal signers;
// SET NO LIMIT SIGNER ON DEPLOYMENT
constructor() {
signers[msg.sender] = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
}
// METHOD TO SET WITHDRAWAL SIGNER ADDRESSES
function setSigner(address signer, uint256 limit) external {
require(signer != address(0) && signers[msg.sender] == 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
signers[signer] = limit;
}
// METHOD TO VERIFY WITHDRAWAL SIGNATURE OF A GIVEN TXID & AMOUNT
function verifySignature(bytes32 txid, bytes memory signature, uint256 amount) internal view returns (bool) {
// SIGNATURE VARIABLES FOR ECRECOVER
bytes32 r;
bytes32 vs;
// SPLIT SIGNATURE INTO r + vs
assembly {
r := mload(add(signature, 32))
vs := mload(add(signature, 64))
}
// DETERMINE s AND v FROM vs
bytes32 s = vs & 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
uint8 v = 27 + uint8(uint256(vs) >> 255);
// RECOVER & VERIFY SIGNER IDENTITY RELATED TO AMOUNT
return amount < signers[ecrecover(txid, v, r, s)];
}
}
contract BridgeQANX is Signed {
// POINTS TO THE OFFICIAL QANX CONTRACT
TransferableERC20 private _qanx = TransferableERC20(0xAAA7A10a8ee237ea61E8AC46C50A8Db8bCC1baaa);
// STORES NONCES FOR CROSS-CHAIN TRANSFERS (msg.sender => depositChainId => withdrawChainId = nonce)
mapping(address => mapping(uint256 => mapping(uint256 => uint256))) private _nonces;
// FETCH NONCE OF SENDER BASED ON CHAIN IDS
function getNonce(address sender, uint256 depositChainId, uint256 withdrawChainId) external view returns (uint256) {
return _nonces[sender][depositChainId][withdrawChainId];
}
// SEND IN TOKENS ON THE SOURCE CHAIN OF THE BRIDGE
function bridgeSend(address beneficiary, uint256 amount, uint256 withdrawChainId) external returns (bytes32) {
// CALCULATE TXID AND INCREMENT NONCE
bytes32 txid = keccak256(abi.encode(msg.sender, block.chainid, withdrawChainId, _nonces[msg.sender][block.chainid][withdrawChainId]++, beneficiary, amount));
// TRANSFER TOKENS FROM MSG SENDER TO THIS CONTRACT FOR THE AMOUNT TO BE BRIDGED
require(_qanx.transferFrom(msg.sender, address(this), amount));
// RETURN TXID
return txid;
}
// WITHDRAW TOKENS ON THE TARGET CHAIN OF THE BRIDGE
function bridgeWithdraw(address beneficiary, uint256 amount, uint256 depositChainId, bytes calldata signature) external returns (bool) {
// CALCULATE TXID AND INCREMENT NONCE
bytes32 txid = keccak256(abi.encode(msg.sender, depositChainId, block.chainid, _nonces[msg.sender][depositChainId][block.chainid]++, beneficiary, amount));
// VERIFY SIGNATURE
require(verifySignature(txid, signature, amount), "ERR_SIG");
// COLLECT FEE
uint256 fee = amount / 100 * feePercentage;
feesCollected += fee;
// TRANSFER TOKENS TO BENEFICIARY
require(_qanx.transfer(beneficiary, amount - fee), "ERR_TXN");
return true;
}
// FEE PERCENTAGE AND TOTAL COLLECTED FEES
uint256 private feePercentage;
uint256 private feesCollected;
// FEE TRANSPARENCY FUNCTION
function getFeeInfo() external view returns (uint256[2] memory) {
return [feePercentage, feesCollected];
}
// SETTER FOR FEE PERCENTAGE (MAX 5%)
function setFeePercentage(uint8 _feePercentage) external {
require(signers[msg.sender] > 0 && _feePercentage <= 5);
feePercentage = _feePercentage;
}
// METHOD TO WITHDRAW TOTAL COLLECTED FEES SO FAR
function withdrawFees(address beneficiary) external {
require(signers[msg.sender] > 0);
require(_qanx.transfer(beneficiary, feesCollected), "ERR_TXN");
feesCollected = 0;
}
}
{
"compilationTarget": {
"BridgeQANX.sol": "BridgeQANX"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"withdrawChainId","type":"uint256"}],"name":"bridgeSend","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"beneficiary","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"depositChainId","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"bridgeWithdraw","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getFeeInfo","outputs":[{"internalType":"uint256[2]","name":"","type":"uint256[2]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"depositChainId","type":"uint256"},{"internalType":"uint256","name":"withdrawChainId","type":"uint256"}],"name":"getNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"_feePercentage","type":"uint8"}],"name":"setFeePercentage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"uint256","name":"limit","type":"uint256"}],"name":"setSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"beneficiary","type":"address"}],"name":"withdrawFees","outputs":[],"stateMutability":"nonpayable","type":"function"}]