Description:
ERC20 token contract with Mintable, Factory capabilities. Standard implementation for fungible tokens on Ethereum.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/FillRelayer.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function mint(address account, uint amount) external;
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
}
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
return sub(a, b, "SafeMath: subtraction overflow");
}
function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;
return c;
}
}
library SafeERC20 {
using Address for address;
using SafeMath for uint256;
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
function safeTransfer(IERC20 token, address to, uint256 value) internal {
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(SELECTOR, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'SafeERC20: TRANSFER_FAILED');
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
function safeApprove(IERC20 token, address spender, uint256 value) internal {
require((value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).add(value);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function callOptionalReturn(IERC20 token, bytes memory data) private {
require(address(token).isContract(), "SafeERC20: call to non-contract");
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
/**
* @title FillRelayer
* @notice Dedicated fill relay contract - deployed on destination chain for relayers to execute cross-chain fills
*/
contract FillRelayer {
using SafeERC20 for IERC20;
// Anti-replay attack protection
mapping(bytes32 => bool) public filledRelays;
// whitelist
mapping(address => bool) public authorizedRelayers;
address public owner;
// Error definitions
error RecipientError();
error AmountError();
error CallError();
error RelayAlreadyFilled();
error NotAuthorized();
error ZeroAddressError();
error RelayerNotFoundError();
// Event definitions
event FillRelay(
address indexed relayer,
address indexed recipient,
address indexed outputToken,
uint256 outputAmount,
uint256 originChainId,
bytes32 depositHash,
bytes message,
bool isRefund,
uint timestamp
);
constructor() {
owner = msg.sender;
authorizedRelayers[msg.sender] = true; // Owner is authorized by default
}
receive() external payable {}
function addAuthorizedRelayer(address relayer) external {
require(msg.sender == owner, "Only owner");
if (relayer == address(0)) {
revert ZeroAddressError();
}
authorizedRelayers[relayer] = true;
}
function removeAuthorizedRelayer(address relayer) external {
require(msg.sender == owner, "Only owner");
if (!authorizedRelayers[relayer]) {
revert RelayerNotFoundError();
}
authorizedRelayers[relayer] = false;
}
/**
* @notice Check if this is a 69 Fill Relay contract
*/
function isFillRelayer69() public pure returns (bool) {
return true;
}
/**
* @notice Check if an operation would be a refund based on originChainId
* @param originChainId Origin chain ID to check
* @return bool Whether this would be a refund operation
*/
function isRefundOperation(uint256 originChainId) public view returns (bool) {
return originChainId == block.chainid;
}
/**
* @notice Fill relay function - relayers execute transfers on destination chain or refund on origin chain
* @param recipient Recipient address
* @param outputToken Output token address (0x0 for ETH)
* @param outputAmount Output amount
* @param originChainId Origin chain ID (if equals block.chainid, this is a refund operation)
* @param depositHash Origin chain deposit hash
* @param message Additional message
*/
function fillRelay(
address recipient,
address outputToken,
uint256 outputAmount,
uint256 originChainId,
bytes32 depositHash,
bytes memory message
) external payable {
// Check authorization
if (!authorizedRelayers[msg.sender]) {
revert NotAuthorized();
}
if (recipient == address(0)) {
revert RecipientError();
}
if (outputAmount == 0) {
revert AmountError();
}
// Determine if this is a refund operation
bool isRefund = originChainId == block.chainid;
// Anti-replay attack check
bytes32 relayHash;
assembly {
let ptr := mload(0x40)
mstore(ptr, originChainId)
mstore(add(ptr, 0x20), depositHash)
mstore(add(ptr, 0x40), recipient)
mstore(add(ptr, 0x60), outputToken)
relayHash := keccak256(ptr, 0x80)
}
if (filledRelays[relayHash]) {
revert RelayAlreadyFilled();
}
filledRelays[relayHash] = true;
// ETH transfer
if (outputToken == address(0)) {
if (msg.value != outputAmount) {
revert AmountError();
}
(bool ok,) = recipient.call{value: outputAmount}("");
if (!ok) {
revert CallError();
}
} else {
// ERC20 token transfer
IERC20(outputToken).safeTransferFrom(msg.sender, recipient, outputAmount);
}
emit FillRelay(
msg.sender,
recipient,
outputToken,
outputAmount,
originChainId,
depositHash,
message,
isRefund,
block.timestamp
);
}
/**
* @notice Check if a specific relay has been filled
* @param originChainId Origin chain ID
* @param depositHash Origin chain deposit hash
* @param recipient Recipient address
* @param outputToken Output token address
* @return bool Whether the relay has been filled
*/
function isRelayFilled(
uint256 originChainId,
bytes32 depositHash,
address recipient,
address outputToken
) external view returns (bool) {
bytes32 relayHash;
assembly {
let ptr := mload(0x40)
mstore(ptr, originChainId)
mstore(add(ptr, 0x20), depositHash)
mstore(add(ptr, 0x40), recipient)
mstore(add(ptr, 0x60), outputToken)
relayHash := keccak256(ptr, 0x80)
}
return filledRelays[relayHash];
}
}
"
}
},
"settings": {
"remappings": [
"ds-test/=lib/solmate/lib/ds-test/src/",
"forge-std/=lib/forge-std/src/",
"solmate/=lib/solmate/src/"
],
"optimizer": {
"enabled": false,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "paris",
"viaIR": false
}
}}
Submitted on: 2025-10-21 18:38:29
Comments
Log in to comment.
No comments yet.