FillRelayer

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
  }
}}

Tags:
ERC20, Token, Mintable, Factory|addr:0xde194359d624c45718537625442e46a1e94529c6|verified:true|block:23626878|tx:0x11a2cac0d3e9bb1bcf7e0e7182e18170e93bcff1c92df446b32ecd137baefccd|first_check:1761064709

Submitted on: 2025-10-21 18:38:29

Comments

Log in to comment.

No comments yet.