ETH Price: $2,426.50 (-1.36%)

Transaction Decoder

Block:
17728006 at Jul-19-2023 03:04:47 PM +UTC
Transaction Fee:
0.003302522032921713 ETH $8.01
Gas Used:
99,003 Gas / 33.357797571 Gwei

Emitted Events:

192 frxETH.Approval( owner=[Sender] 0x6e74053a3798e0fc9a9775f7995316b27f21c4d2, spender=[Receiver] Fraxferry, value=500000000000000000 )
193 frxETH.Approval( owner=[Sender] 0x6e74053a3798e0fc9a9775f7995316b27f21c4d2, spender=[Receiver] Fraxferry, value=0 )
194 frxETH.Transfer( from=[Sender] 0x6e74053a3798e0fc9a9775f7995316b27f21c4d2, to=[Receiver] Fraxferry, value=500000000000000000 )
195 Fraxferry.Embark( sender=[Sender] 0x6e74053a3798e0fc9a9775f7995316b27f21c4d2, index=3, amount=500000000000000000, amountAfterFee=496000000000000000, timestamp=1689779087 )

Account State Difference:

  Address   Before After State Difference Code
0x5E842234...8E08CAa1f
0x6e74053a...27f21c4D2
2.476257776720616066 Eth
Nonce: 760
2.472955254687694353 Eth
Nonce: 761
0.003302522032921713
(beaverbuild)
2.477422206683087311 Eth2.477451907583087311 Eth0.0000297009
0x9f76b097...F6e7b3Fa9

Execution Trace

Fraxferry.embarkWithSignature( _amount=500000000000000000, recipient=0x6e74053a3798e0fC9a9775F7995316b27f21c4D2, deadline=1689779133073, approveMax=False, v=27, r=07019D724BD4C5B2B43F3710D3DF4B110AF527A9431E4C6DCC24656C63CFBA03, s=24C99EFF8B2BE05A9D0398352AB6F6BF08C4D0682FC540072A95FDEF7345DCDC )
  • frxETH.permit( owner=0x6e74053a3798e0fC9a9775F7995316b27f21c4D2, spender=0x9f76b097Cd95627bFbD8052A583127FF6e7b3Fa9, value=500000000000000000, deadline=1689779133073, v=27, r=07019D724BD4C5B2B43F3710D3DF4B110AF527A9431E4C6DCC24656C63CFBA03, s=24C99EFF8B2BE05A9D0398352AB6F6BF08C4D0682FC540072A95FDEF7345DCDC )
    • Null: 0x000...001.dd769518( )
    • frxETH.transferFrom( from=0x6e74053a3798e0fC9a9775F7995316b27f21c4D2, to=0x9f76b097Cd95627bFbD8052A583127FF6e7b3Fa9, amount=500000000000000000 ) => ( True )
      File 1 of 2: Fraxferry
      // SPDX-License-Identifier: GPL-2.0-or-later
      pragma solidity >=0.8.0;
      
      // Sources flattened with hardhat v2.12.5 https://hardhat.org
      
      // File @openzeppelin/contracts/token/ERC20/[email protected]
      
      // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
      
      
      /**
       * @dev Interface of the ERC20 standard as defined in the EIP.
       */
      interface IERC20 {
          /**
           * @dev Emitted when `value` tokens are moved from one account (`from`) to
           * another (`to`).
           *
           * Note that `value` may be zero.
           */
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          /**
           * @dev Emitted when the allowance of a `spender` for an `owner` is set by
           * a call to {approve}. `value` is the new allowance.
           */
          event Approval(address indexed owner, address indexed spender, uint256 value);
      
          /**
           * @dev Returns the amount of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
      
          /**
           * @dev Returns the amount of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
      
          /**
           * @dev Moves `amount` tokens from the caller's account to `to`.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transfer(address to, uint256 amount) external returns (bool);
      
          /**
           * @dev Returns the remaining number of tokens that `spender` will be
           * allowed to spend on behalf of `owner` through {transferFrom}. This is
           * zero by default.
           *
           * This value changes when {approve} or {transferFrom} are called.
           */
          function allowance(address owner, address spender) external view returns (uint256);
      
          /**
           * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * IMPORTANT: Beware that changing an allowance with this method brings the risk
           * that someone may use both the old and the new allowance by unfortunate
           * transaction ordering. One possible solution to mitigate this race
           * condition is to first reduce the spender's allowance to 0 and set the
           * desired value afterwards:
           * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
           *
           * Emits an {Approval} event.
           */
          function approve(address spender, uint256 amount) external returns (bool);
      
          /**
           * @dev Moves `amount` tokens from `from` to `to` using the
           * allowance mechanism. `amount` is then deducted from the caller's
           * allowance.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(
              address from,
              address to,
              uint256 amount
          ) external returns (bool);
      }
      
      
      // File @uniswap/v3-periphery/contracts/libraries/[email protected]
      
      
      library TransferHelper {
          /// @notice Transfers tokens from the targeted address to the given destination
          /// @notice Errors with 'STF' if transfer fails
          /// @param token The contract address of the token to be transferred
          /// @param from The originating address from which the tokens will be transferred
          /// @param to The destination address of the transfer
          /// @param value The amount to be transferred
          function safeTransferFrom(
              address token,
              address from,
              address to,
              uint256 value
          ) internal {
              (bool success, bytes memory data) =
                  token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
              require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
          }
      
          /// @notice Transfers tokens from msg.sender to a recipient
          /// @dev Errors with ST if transfer fails
          /// @param token The contract address of the token which will be transferred
          /// @param to The recipient of the transfer
          /// @param value The value of the transfer
          function safeTransfer(
              address token,
              address to,
              uint256 value
          ) internal {
              (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
              require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST');
          }
      
          /// @notice Approves the stipulated contract to spend the given allowance in the given token
          /// @dev Errors with 'SA' if transfer fails
          /// @param token The contract address of the token to be approved
          /// @param to The target of the approval
          /// @param value The amount of the given token the target will be allowed to spend
          function safeApprove(
              address token,
              address to,
              uint256 value
          ) internal {
              (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
              require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA');
          }
      
          /// @notice Transfers ETH to the recipient address
          /// @dev Fails with `STE`
          /// @param to The destination of the transfer
          /// @param value The value to be transferred
          function safeTransferETH(address to, uint256 value) internal {
              (bool success, ) = to.call{value: value}(new bytes(0));
              require(success, 'STE');
          }
      }
      
      
      // File @openzeppelin/contracts/token/ERC20/extensions/[email protected]
      
      // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
      
      
      /**
       * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
       * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
       *
       * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
       * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
       * need to send a transaction, and thus is not required to hold Ether at all.
       */
      interface IERC20Permit {
          /**
           * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
           * given ``owner``'s signed approval.
           *
           * IMPORTANT: The same issues {IERC20-approve} has related to transaction
           * ordering also apply here.
           *
           * Emits an {Approval} event.
           *
           * Requirements:
           *
           * - `spender` cannot be the zero address.
           * - `deadline` must be a timestamp in the future.
           * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
           * over the EIP712-formatted function arguments.
           * - the signature must use ``owner``'s current nonce (see {nonces}).
           *
           * For more information on the signature format, see the
           * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
           * section].
           */
          function permit(
              address owner,
              address spender,
              uint256 value,
              uint256 deadline,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external;
      
          /**
           * @dev Returns the current nonce for `owner`. This value must be
           * included whenever a signature is generated for {permit}.
           *
           * Every successful call to {permit} increases ``owner``'s nonce by one. This
           * prevents a signature from being used multiple times.
           */
          function nonces(address owner) external view returns (uint256);
      
          /**
           * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
           */
          // solhint-disable-next-line func-name-mixedcase
          function DOMAIN_SEPARATOR() external view returns (bytes32);
      }
      
      
      // File @openzeppelin/contracts/utils/math/[email protected]
      
      // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
      
      
      /**
       * @dev Standard math utilities missing in the Solidity language.
       */
      library Math {
          enum Rounding {
              Down, // Toward negative infinity
              Up, // Toward infinity
              Zero // Toward zero
          }
      
          /**
           * @dev Returns the largest of two numbers.
           */
          function max(uint256 a, uint256 b) internal pure returns (uint256) {
              return a > b ? a : b;
          }
      
          /**
           * @dev Returns the smallest of two numbers.
           */
          function min(uint256 a, uint256 b) internal pure returns (uint256) {
              return a < b ? a : b;
          }
      
          /**
           * @dev Returns the average of two numbers. The result is rounded towards
           * zero.
           */
          function average(uint256 a, uint256 b) internal pure returns (uint256) {
              // (a + b) / 2 can overflow.
              return (a & b) + (a ^ b) / 2;
          }
      
          /**
           * @dev Returns the ceiling of the division of two numbers.
           *
           * This differs from standard division with `/` in that it rounds up instead
           * of rounding down.
           */
          function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
              // (a + b - 1) / b can overflow on addition, so we distribute.
              return a == 0 ? 0 : (a - 1) / b + 1;
          }
      
          /**
           * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
           * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
           * with further edits by Uniswap Labs also under MIT license.
           */
          function mulDiv(
              uint256 x,
              uint256 y,
              uint256 denominator
          ) internal pure returns (uint256 result) {
              unchecked {
                  // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                  // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                  // variables such that product = prod1 * 2^256 + prod0.
                  uint256 prod0; // Least significant 256 bits of the product
                  uint256 prod1; // Most significant 256 bits of the product
                  assembly {
                      let mm := mulmod(x, y, not(0))
                      prod0 := mul(x, y)
                      prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                  }
      
                  // Handle non-overflow cases, 256 by 256 division.
                  if (prod1 == 0) {
                      return prod0 / denominator;
                  }
      
                  // Make sure the result is less than 2^256. Also prevents denominator == 0.
                  require(denominator > prod1);
      
                  ///////////////////////////////////////////////
                  // 512 by 256 division.
                  ///////////////////////////////////////////////
      
                  // Make division exact by subtracting the remainder from [prod1 prod0].
                  uint256 remainder;
                  assembly {
                      // Compute remainder using mulmod.
                      remainder := mulmod(x, y, denominator)
      
                      // Subtract 256 bit number from 512 bit number.
                      prod1 := sub(prod1, gt(remainder, prod0))
                      prod0 := sub(prod0, remainder)
                  }
      
                  // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                  // See https://cs.stackexchange.com/q/138556/92363.
      
                  // Does not overflow because the denominator cannot be zero at this stage in the function.
                  uint256 twos = denominator & (~denominator + 1);
                  assembly {
                      // Divide denominator by twos.
                      denominator := div(denominator, twos)
      
                      // Divide [prod1 prod0] by twos.
                      prod0 := div(prod0, twos)
      
                      // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                      twos := add(div(sub(0, twos), twos), 1)
                  }
      
                  // Shift in bits from prod1 into prod0.
                  prod0 |= prod1 * twos;
      
                  // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                  // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                  // four bits. That is, denominator * inv = 1 mod 2^4.
                  uint256 inverse = (3 * denominator) ^ 2;
      
                  // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                  // in modular arithmetic, doubling the correct bits in each step.
                  inverse *= 2 - denominator * inverse; // inverse mod 2^8
                  inverse *= 2 - denominator * inverse; // inverse mod 2^16
                  inverse *= 2 - denominator * inverse; // inverse mod 2^32
                  inverse *= 2 - denominator * inverse; // inverse mod 2^64
                  inverse *= 2 - denominator * inverse; // inverse mod 2^128
                  inverse *= 2 - denominator * inverse; // inverse mod 2^256
      
                  // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                  // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                  // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                  // is no longer required.
                  result = prod0 * inverse;
                  return result;
              }
          }
      
          /**
           * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
           */
          function mulDiv(
              uint256 x,
              uint256 y,
              uint256 denominator,
              Rounding rounding
          ) internal pure returns (uint256) {
              uint256 result = mulDiv(x, y, denominator);
              if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                  result += 1;
              }
              return result;
          }
      
          /**
           * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
           *
           * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
           */
          function sqrt(uint256 a) internal pure returns (uint256) {
              if (a == 0) {
                  return 0;
              }
      
              // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
              //
              // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
              // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
              //
              // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
              // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
              // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
              //
              // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
              uint256 result = 1 << (log2(a) >> 1);
      
              // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
              // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
              // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
              // into the expected uint128 result.
              unchecked {
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  return min(result, a / result);
              }
          }
      
          /**
           * @notice Calculates sqrt(a), following the selected rounding direction.
           */
          function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
              unchecked {
                  uint256 result = sqrt(a);
                  return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
              }
          }
      
          /**
           * @dev Return the log in base 2, rounded down, of a positive value.
           * Returns 0 if given 0.
           */
          function log2(uint256 value) internal pure returns (uint256) {
              uint256 result = 0;
              unchecked {
                  if (value >> 128 > 0) {
                      value >>= 128;
                      result += 128;
                  }
                  if (value >> 64 > 0) {
                      value >>= 64;
                      result += 64;
                  }
                  if (value >> 32 > 0) {
                      value >>= 32;
                      result += 32;
                  }
                  if (value >> 16 > 0) {
                      value >>= 16;
                      result += 16;
                  }
                  if (value >> 8 > 0) {
                      value >>= 8;
                      result += 8;
                  }
                  if (value >> 4 > 0) {
                      value >>= 4;
                      result += 4;
                  }
                  if (value >> 2 > 0) {
                      value >>= 2;
                      result += 2;
                  }
                  if (value >> 1 > 0) {
                      result += 1;
                  }
              }
              return result;
          }
      
          /**
           * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
           * Returns 0 if given 0.
           */
          function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
              unchecked {
                  uint256 result = log2(value);
                  return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
              }
          }
      
          /**
           * @dev Return the log in base 10, rounded down, of a positive value.
           * Returns 0 if given 0.
           */
          function log10(uint256 value) internal pure returns (uint256) {
              uint256 result = 0;
              unchecked {
                  if (value >= 10**64) {
                      value /= 10**64;
                      result += 64;
                  }
                  if (value >= 10**32) {
                      value /= 10**32;
                      result += 32;
                  }
                  if (value >= 10**16) {
                      value /= 10**16;
                      result += 16;
                  }
                  if (value >= 10**8) {
                      value /= 10**8;
                      result += 8;
                  }
                  if (value >= 10**4) {
                      value /= 10**4;
                      result += 4;
                  }
                  if (value >= 10**2) {
                      value /= 10**2;
                      result += 2;
                  }
                  if (value >= 10**1) {
                      result += 1;
                  }
              }
              return result;
          }
      
          /**
           * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
           * Returns 0 if given 0.
           */
          function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
              unchecked {
                  uint256 result = log10(value);
                  return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
              }
          }
      
          /**
           * @dev Return the log in base 256, rounded down, of a positive value.
           * Returns 0 if given 0.
           *
           * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
           */
          function log256(uint256 value) internal pure returns (uint256) {
              uint256 result = 0;
              unchecked {
                  if (value >> 128 > 0) {
                      value >>= 128;
                      result += 16;
                  }
                  if (value >> 64 > 0) {
                      value >>= 64;
                      result += 8;
                  }
                  if (value >> 32 > 0) {
                      value >>= 32;
                      result += 4;
                  }
                  if (value >> 16 > 0) {
                      value >>= 16;
                      result += 2;
                  }
                  if (value >> 8 > 0) {
                      result += 1;
                  }
              }
              return result;
          }
      
          /**
           * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
           * Returns 0 if given 0.
           */
          function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
              unchecked {
                  uint256 result = log256(value);
                  return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
              }
          }
      }
      
      
      // File contracts/Fraxferry/Fraxferry.sol
      
      
      // ====================================================================
      // |     ______                   _______                             |
      // |    / _____________ __  __   / ____(_____  ____ _____  ________   |
      // |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
      // |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
      // | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
      // |                                                                  |
      // ====================================================================
      // ============================ Fraxferry =============================
      // ====================================================================
      // Ferry that can be used to ship tokens between chains
      
      // Frax Finance: https://github.com/FraxFinance
      
      // Primary Author(s)
      // Dennis: https://github.com/denett
      
      /*
      ** Modus operandi:
      ** - User sends tokens to the contract. This transaction is stored in the contract.
      ** - Captain queries the source chain for transactions to ship.
      ** - Captain sends batch (start, end, hash) to start the trip,
      ** - Crewmembers check the batch and can dispute it if it is invalid.
      ** - Non disputed batches can be executed by the first officer by providing the transactions as calldata. 
      ** - Hash of the transactions must be equal to the hash in the batch. User receives their tokens on the other chain.
      ** - In case there was a fraudulent transaction (a hacker for example), the owner can cancel a single transaction, such that it will not be executed.
      ** - The owner can manually manage the tokens in the contract and must make sure it has enough funds.
      **
      ** What must happen for a false batch to be executed:
      ** - Captain is tricked into proposing a batch with a false hash
      ** - All crewmembers bots are offline/censured/compromised and no one disputes the proposal
      **
      ** Other risks:
      ** - Reorgs on the source chain. Avoided, by only returning the transactions on the source chain that are at least one hour old.
      ** - Rollbacks of optimistic rollups. Avoided by running a node.
      ** - Operators do not have enough time to pause the chain after a fake proposal. Avoided by requiring a minimal amount of time between sending the proposal and executing it.
      */
      
      
      
      
      contract Fraxferry {
         IERC20 immutable public token;
         IERC20 immutable public targetToken;
         uint immutable public chainid;
         uint immutable public targetChain;   
         
         address public owner;
         address public nominatedOwner;
         address public captain;
         address public firstOfficer;
         mapping(address => bool) public crewmembers;
         mapping(address => bool) public fee_exempt_addrs;
      
         bool public paused;
         
         uint public MIN_WAIT_PERIOD_ADD=3600; // Minimal 1 hour waiting
         uint public MIN_WAIT_PERIOD_EXECUTE=79200; // Minimal 22 hour waiting
         uint public FEE_RATE=10;      // 0.1% fee
         uint public FEE_MIN=5*1e18;   // 5 token min fee
         uint public FEE_MAX=100*1e18; // 100 token max fee
         
         uint constant MAX_FEE_RATE=100; // Max fee rate is 1%
         uint constant MAX_FEE_MIN=100e18; // Max minimum fee is 100 tokens
         uint constant MAX_FEE_MAX=1000e18; // Max fee is 1000 tokens
         
         uint constant public REDUCED_DECIMALS=1e10;
         
         Transaction[] public transactions;
         mapping(uint => bool) public cancelled;
         uint public executeIndex;
         Batch[] public batches;
         
         struct Transaction {
            address user;
            uint64 amount;
            uint32 timestamp;
         }
         
         struct Batch {
            uint64 start;
            uint64 end;
            uint64 departureTime;
            uint64 status;
            bytes32 hash;
         }
         
         struct BatchData {
            uint startTransactionNo;
            Transaction[] transactions;
         }
      
         constructor(address _token, uint _chainid, address _targetToken, uint _targetChain) {
            //require (block.chainid==_chainid,"Wrong chain");
            chainid=_chainid;
            token = IERC20(_token);
            targetToken = IERC20(_targetToken);
            owner = msg.sender;
            targetChain = _targetChain;
         }
         
         
         // ############## Events ##############
         
         event Embark(address indexed sender, uint index, uint amount, uint amountAfterFee, uint timestamp);
         event Disembark(uint start, uint end, bytes32 hash); 
         event Depart(uint batchNo,uint start,uint end,bytes32 hash); 
         event RemoveBatch(uint batchNo);
         event DisputeBatch(uint batchNo, bytes32 hash);
         event Cancelled(uint index, bool cancel);
         event Pause(bool paused);
         event OwnerNominated(address indexed newOwner);
         event OwnerChanged(address indexed previousOwner,address indexed newOwner);
         event SetCaptain(address indexed previousCaptain, address indexed newCaptain);   
         event SetFirstOfficer(address indexed previousFirstOfficer, address indexed newFirstOfficer);
         event SetCrewmember(address indexed crewmember,bool set); 
         event SetFee(uint previousFeeRate, uint feeRate,uint previousFeeMin, uint feeMin,uint previousFeeMax, uint feeMax);
         event SetMinWaitPeriods(uint previousMinWaitAdd,uint previousMinWaitExecute,uint minWaitAdd,uint minWaitExecute); 
         event FeeExemptToggled(address addr,bool is_fee_exempt); 
         
      
         // ############## Modifiers ##############
         
         modifier isOwner() {
            require (msg.sender==owner,"Not owner");
            _;
         }
         
         modifier isCaptain() {
            require (msg.sender==captain,"Not captain");
            _;
         }
         
         modifier isFirstOfficer() {
            require (msg.sender==firstOfficer,"Not first officer");
            _;
         }   
          
         modifier isCrewmember() {
            require (crewmembers[msg.sender] || msg.sender==owner || msg.sender==captain || msg.sender==firstOfficer,"Not crewmember");
            _;
         }
         
         modifier notPaused() {
            require (!paused,"Paused");
            _;
         } 
         
         // ############## Ferry actions ##############
         
         function embarkWithRecipient(uint amount, address recipient) public notPaused {
            amount = (amount/REDUCED_DECIMALS)*REDUCED_DECIMALS; // Round amount to fit in data structure
            uint fee;
            if(fee_exempt_addrs[msg.sender]) fee = 0;
            else {
               fee = Math.min(Math.max(FEE_MIN,amount*FEE_RATE/10000),FEE_MAX);
            }
            require (amount>fee,"Amount too low");
            require (amount/REDUCED_DECIMALS<=type(uint64).max,"Amount too high");
            TransferHelper.safeTransferFrom(address(token),msg.sender,address(this),amount); 
            uint64 amountAfterFee = uint64((amount-fee)/REDUCED_DECIMALS);
            emit Embark(recipient,transactions.length,amount,amountAfterFee*REDUCED_DECIMALS,block.timestamp);
            transactions.push(Transaction(recipient,amountAfterFee,uint32(block.timestamp)));   
         }
         
         function embark(uint amount) public {
            embarkWithRecipient(amount, msg.sender) ;
         }
      
         function embarkWithSignature(
            uint256 _amount,
            address recipient,
            uint256 deadline,
            bool approveMax,
            uint8 v,
            bytes32 r,
            bytes32 s
         ) public {
            uint amount = approveMax ? type(uint256).max : _amount;
            IERC20Permit(address(token)).permit(msg.sender, address(this), amount, deadline, v, r, s);
            embarkWithRecipient(amount,recipient);
         }   
         
         function depart(uint start, uint end, bytes32 hash) external notPaused isCaptain {
            require ((batches.length==0 && start==0) || (batches.length>0 && start==batches[batches.length-1].end+1),"Wrong start");
            require (end>=start && end<type(uint64).max,"Wrong end");
            batches.push(Batch(uint64(start),uint64(end),uint64(block.timestamp),0,hash));
            emit Depart(batches.length-1,start,end,hash);
         }
         
         function disembark(BatchData calldata batchData) external notPaused isFirstOfficer {
            Batch memory batch = batches[executeIndex++];
            require (batch.status==0,"Batch disputed");
            require (batch.start==batchData.startTransactionNo,"Wrong start");
            require (batch.start+batchData.transactions.length-1==batch.end,"Wrong size");
            require (block.timestamp-batch.departureTime>=MIN_WAIT_PERIOD_EXECUTE,"Too soon");
            
            bytes32 hash = keccak256(abi.encodePacked(targetChain, targetToken, chainid, token, batch.start));
            for (uint i=0;i<batchData.transactions.length;++i) {
               if (!cancelled[batch.start+i]) {
                  TransferHelper.safeTransfer(address(token),batchData.transactions[i].user,batchData.transactions[i].amount*REDUCED_DECIMALS);
               }
               hash = keccak256(abi.encodePacked(hash, batchData.transactions[i].user,batchData.transactions[i].amount));
            }
            require (batch.hash==hash,"Wrong hash");
            emit Disembark(batch.start,batch.end,hash);
         }
         
         function removeBatches(uint batchNo) external isOwner {
            require (executeIndex<=batchNo,"Batch already executed");
            while (batches.length>batchNo) batches.pop();
            emit RemoveBatch(batchNo);
         }
         
         function disputeBatch(uint batchNo, bytes32 hash) external isCrewmember {
            require (batches[batchNo].hash==hash,"Wrong hash");
            require (executeIndex<=batchNo,"Batch already executed");
            require (batches[batchNo].status==0,"Batch already disputed");
            batches[batchNo].status=1; // Set status on disputed
            _pause(true);
            emit DisputeBatch(batchNo,hash);
         }
         
         function pause() external isCrewmember {
            _pause(true);
         }
         
         function unPause() external isOwner {
            _pause(false);
         }   
         
         function _pause(bool _paused) internal {
            paused=_paused;
            emit Pause(_paused);
         } 
         
         function _jettison(uint index, bool cancel) internal {
            require (executeIndex==0 || index>batches[executeIndex-1].end,"Transaction already executed");
            cancelled[index]=cancel;
            emit Cancelled(index,cancel);
         }
         
         function jettison(uint index, bool cancel) external isOwner {
            _jettison(index,cancel);
         }
         
         function jettisonGroup(uint[] calldata indexes, bool cancel) external isOwner {
            for (uint i=0;i<indexes.length;++i) {
               _jettison(indexes[i],cancel);
            }
         }   
         
         // ############## Parameters management ##############
         
         function setFee(uint _FEE_RATE, uint _FEE_MIN, uint _FEE_MAX) external isOwner {
            require(_FEE_RATE<MAX_FEE_RATE);
            require(_FEE_MIN<MAX_FEE_MIN);
            require(_FEE_MAX<MAX_FEE_MAX);
            emit SetFee(FEE_RATE,_FEE_RATE,FEE_MIN,_FEE_MIN,FEE_MAX,_FEE_MAX);
            FEE_RATE=_FEE_RATE;
            FEE_MIN=_FEE_MIN;
            FEE_MAX=_FEE_MAX;
         }
         
         function setMinWaitPeriods(uint _MIN_WAIT_PERIOD_ADD, uint _MIN_WAIT_PERIOD_EXECUTE) external isOwner {
            require(_MIN_WAIT_PERIOD_ADD>=3600 && _MIN_WAIT_PERIOD_EXECUTE>=3600,"Period too short");
            emit SetMinWaitPeriods(MIN_WAIT_PERIOD_ADD, MIN_WAIT_PERIOD_EXECUTE,_MIN_WAIT_PERIOD_ADD, _MIN_WAIT_PERIOD_EXECUTE);
            MIN_WAIT_PERIOD_ADD=_MIN_WAIT_PERIOD_ADD;
            MIN_WAIT_PERIOD_EXECUTE=_MIN_WAIT_PERIOD_EXECUTE;
         }
         
         // ############## Roles management ##############
         
         function nominateNewOwner(address newOwner) external isOwner {
            nominatedOwner = newOwner;
            emit OwnerNominated(newOwner);
         }   
         
         function acceptOwnership() external {
            require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
            emit OwnerChanged(owner, nominatedOwner);
            owner = nominatedOwner;
            nominatedOwner = address(0);
         }
         
         function setCaptain(address newCaptain) external isOwner {
            emit SetCaptain(captain,newCaptain);
            captain=newCaptain;
         }
         
         function setFirstOfficer(address newFirstOfficer) external isOwner {
            emit SetFirstOfficer(firstOfficer,newFirstOfficer);
            firstOfficer=newFirstOfficer;
         }    
         
         function setCrewmember(address crewmember, bool set) external isOwner {
            crewmembers[crewmember]=set;
            emit SetCrewmember(crewmember,set);
         }   
      
         function toggleFeeExemptAddr(address addr) external isOwner {
            fee_exempt_addrs[addr] = !fee_exempt_addrs[addr];
            emit FeeExemptToggled(addr,fee_exempt_addrs[addr]);
         }   
        
         
         // ############## Token management ##############   
         
         function sendTokens(address receiver, uint amount) external isOwner {
            require (receiver!=address(0),"Zero address not allowed");
            TransferHelper.safeTransfer(address(token),receiver,amount);
         }   
         
         // Generic proxy
         function execute(address _to, uint256 _value, bytes calldata _data) external isOwner returns (bool, bytes memory) {
            require(_data.length==0 || _to.code.length>0,"Can not call a function on a EOA");
            (bool success, bytes memory result) = _to.call{value:_value}(_data);
            return (success, result);
         }   
         
         // ############## Views ##############
         function getNextBatch(uint _start, uint max) public view returns (uint start, uint end, bytes32 hash) {
            uint cutoffTime = block.timestamp-MIN_WAIT_PERIOD_ADD;
            if (_start<transactions.length && transactions[_start].timestamp<cutoffTime) {
               start=_start;
               end=start+max-1;
               if (end>=transactions.length) end=transactions.length-1;
               while(transactions[end].timestamp>=cutoffTime) end--;
               hash = getTransactionsHash(start,end);
            }
         }
         
         function getBatchData(uint start, uint end) public view returns (BatchData memory data) {
            data.startTransactionNo = start;
            data.transactions = new Transaction[](end-start+1);
            for (uint i=start;i<=end;++i) {
               data.transactions[i-start]=transactions[i];
            }
         }
         
         function getBatchAmount(uint start, uint end) public view returns (uint totalAmount) {
            for (uint i=start;i<=end;++i) {
               totalAmount+=transactions[i].amount;
            }
            totalAmount*=REDUCED_DECIMALS;
         }
         
         function getTransactionsHash(uint start, uint end) public view returns (bytes32) {
            bytes32 result = keccak256(abi.encodePacked(chainid, token, targetChain, targetToken, uint64(start)));
            for (uint i=start;i<=end;++i) {
               result = keccak256(abi.encodePacked(result, transactions[i].user,transactions[i].amount));
            }
            return result;
         }   
         
         function noTransactions() public view returns (uint) {
            return transactions.length;
         }
         
         function noBatches() public view returns (uint) {
            return batches.length;
         }
      }

      File 2 of 2: frxETH
      // SPDX-License-Identifier: GPL-2.0-or-later
      pragma solidity >=0.8.0;
      
      
      // ====================================================================
      // |     ______                   _______                             |
      // |    / _____________ __  __   / ____(_____  ____ _____  ________   |
      // |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
      // |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
      // | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
      // |                                                                  |
      // ====================================================================
      // ============================== frxETH ==============================
      // ====================================================================
      // Frax Finance: https://github.com/FraxFinance
      
      // Primary Author(s)
      // Jack Corddry: https://github.com/corddry
      // Nader Ghazvini: https://github.com/amirnader-ghazvini 
      
      // Reviewer(s) / Contributor(s)
      // Sam Kazemian: https://github.com/samkazemian
      // Dennis: https://github.com/denett
      // Travis Moore: https://github.com/FortisFortuna
      // Jamie Turley: https://github.com/jyturley
      
      /// @title Stablecoin pegged to Ether for use within the Frax ecosystem
      /** @notice Does not accrue ETH 2.0 staking yield: it must be staked at the sfrxETH contract first.
          ETH -> frxETH conversion is permanent, so a market will develop for the latter.
          Withdraws are not live (as of deploy time) so loosely pegged to eth but is possible will float */
      /// @dev frxETH adheres to EIP-712/EIP-2612 and can use permits
      
      // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol)
      
      // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
      
      /**
       * @dev Interface of the ERC20 standard as defined in the EIP.
       */
      interface IERC20 {
          /**
           * @dev Emitted when `value` tokens are moved from one account (`from`) to
           * another (`to`).
           *
           * Note that `value` may be zero.
           */
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          /**
           * @dev Emitted when the allowance of a `spender` for an `owner` is set by
           * a call to {approve}. `value` is the new allowance.
           */
          event Approval(address indexed owner, address indexed spender, uint256 value);
      
          /**
           * @dev Returns the amount of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
      
          /**
           * @dev Returns the amount of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
      
          /**
           * @dev Moves `amount` tokens from the caller's account to `to`.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transfer(address to, uint256 amount) external returns (bool);
      
          /**
           * @dev Returns the remaining number of tokens that `spender` will be
           * allowed to spend on behalf of `owner` through {transferFrom}. This is
           * zero by default.
           *
           * This value changes when {approve} or {transferFrom} are called.
           */
          function allowance(address owner, address spender) external view returns (uint256);
      
          /**
           * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * IMPORTANT: Beware that changing an allowance with this method brings the risk
           * that someone may use both the old and the new allowance by unfortunate
           * transaction ordering. One possible solution to mitigate this race
           * condition is to first reduce the spender's allowance to 0 and set the
           * desired value afterwards:
           * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
           *
           * Emits an {Approval} event.
           */
          function approve(address spender, uint256 amount) external returns (bool);
      
          /**
           * @dev Moves `amount` tokens from `from` to `to` using the
           * allowance mechanism. `amount` is then deducted from the caller's
           * allowance.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(
              address from,
              address to,
              uint256 amount
          ) external returns (bool);
      }
      
      // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
      
      /**
       * @dev Interface for the optional metadata functions from the ERC20 standard.
       *
       * _Available since v4.1._
       */
      interface IERC20Metadata is IERC20 {
          /**
           * @dev Returns the name of the token.
           */
          function name() external view returns (string memory);
      
          /**
           * @dev Returns the symbol of the token.
           */
          function symbol() external view returns (string memory);
      
          /**
           * @dev Returns the decimals places of the token.
           */
          function decimals() external view returns (uint8);
      }
      
      // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
      
      /**
       * @dev Provides information about the current execution context, including the
       * sender of the transaction and its data. While these are generally available
       * via msg.sender and msg.data, they should not be accessed in such a direct
       * manner, since when dealing with meta-transactions the account sending and
       * paying for execution may not be the actual sender (as far as an application
       * is concerned).
       *
       * This contract is only required for intermediate, library-like contracts.
       */
      abstract contract Context {
          function _msgSender() internal view virtual returns (address) {
              return msg.sender;
          }
      
          function _msgData() internal view virtual returns (bytes calldata) {
              return msg.data;
          }
      }
      
      /**
       * @dev Implementation of the {IERC20} interface.
       *
       * This implementation is agnostic to the way tokens are created. This means
       * that a supply mechanism has to be added in a derived contract using {_mint}.
       * For a generic mechanism see {ERC20PresetMinterPauser}.
       *
       * TIP: For a detailed writeup see our guide
       * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
       * to implement supply mechanisms].
       *
       * We have followed general OpenZeppelin Contracts guidelines: functions revert
       * instead returning `false` on failure. This behavior is nonetheless
       * conventional and does not conflict with the expectations of ERC20
       * applications.
       *
       * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
       * This allows applications to reconstruct the allowance for all accounts just
       * by listening to said events. Other implementations of the EIP may not emit
       * these events, as it isn't required by the specification.
       *
       * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
       * functions have been added to mitigate the well-known issues around setting
       * allowances. See {IERC20-approve}.
       */
      contract ERC20 is Context, IERC20, IERC20Metadata {
          mapping(address => uint256) private _balances;
      
          mapping(address => mapping(address => uint256)) private _allowances;
      
          uint256 private _totalSupply;
      
          string private _name;
          string private _symbol;
      
          /**
           * @dev Sets the values for {name} and {symbol}.
           *
           * The default value of {decimals} is 18. To select a different value for
           * {decimals} you should overload it.
           *
           * All two of these values are immutable: they can only be set once during
           * construction.
           */
          constructor(string memory name_, string memory symbol_) {
              _name = name_;
              _symbol = symbol_;
          }
      
          /**
           * @dev Returns the name of the token.
           */
          function name() public view virtual override returns (string memory) {
              return _name;
          }
      
          /**
           * @dev Returns the symbol of the token, usually a shorter version of the
           * name.
           */
          function symbol() public view virtual override returns (string memory) {
              return _symbol;
          }
      
          /**
           * @dev Returns the number of decimals used to get its user representation.
           * For example, if `decimals` equals `2`, a balance of `505` tokens should
           * be displayed to a user as `5.05` (`505 / 10 ** 2`).
           *
           * Tokens usually opt for a value of 18, imitating the relationship between
           * Ether and Wei. This is the value {ERC20} uses, unless this function is
           * overridden;
           *
           * NOTE: This information is only used for _display_ purposes: it in
           * no way affects any of the arithmetic of the contract, including
           * {IERC20-balanceOf} and {IERC20-transfer}.
           */
          function decimals() public view virtual override returns (uint8) {
              return 18;
          }
      
          /**
           * @dev See {IERC20-totalSupply}.
           */
          function totalSupply() public view virtual override returns (uint256) {
              return _totalSupply;
          }
      
          /**
           * @dev See {IERC20-balanceOf}.
           */
          function balanceOf(address account) public view virtual override returns (uint256) {
              return _balances[account];
          }
      
          /**
           * @dev See {IERC20-transfer}.
           *
           * Requirements:
           *
           * - `to` cannot be the zero address.
           * - the caller must have a balance of at least `amount`.
           */
          function transfer(address to, uint256 amount) public virtual override returns (bool) {
              address owner = _msgSender();
              _transfer(owner, to, amount);
              return true;
          }
      
          /**
           * @dev See {IERC20-allowance}.
           */
          function allowance(address owner, address spender) public view virtual override returns (uint256) {
              return _allowances[owner][spender];
          }
      
          /**
           * @dev See {IERC20-approve}.
           *
           * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
           * `transferFrom`. This is semantically equivalent to an infinite approval.
           *
           * Requirements:
           *
           * - `spender` cannot be the zero address.
           */
          function approve(address spender, uint256 amount) public virtual override returns (bool) {
              address owner = _msgSender();
              _approve(owner, spender, amount);
              return true;
          }
      
          /**
           * @dev See {IERC20-transferFrom}.
           *
           * Emits an {Approval} event indicating the updated allowance. This is not
           * required by the EIP. See the note at the beginning of {ERC20}.
           *
           * NOTE: Does not update the allowance if the current allowance
           * is the maximum `uint256`.
           *
           * Requirements:
           *
           * - `from` and `to` cannot be the zero address.
           * - `from` must have a balance of at least `amount`.
           * - the caller must have allowance for ``from``'s tokens of at least
           * `amount`.
           */
          function transferFrom(
              address from,
              address to,
              uint256 amount
          ) public virtual override returns (bool) {
              address spender = _msgSender();
              _spendAllowance(from, spender, amount);
              _transfer(from, to, amount);
              return true;
          }
      
          /**
           * @dev Atomically increases the allowance granted to `spender` by the caller.
           *
           * This is an alternative to {approve} that can be used as a mitigation for
           * problems described in {IERC20-approve}.
           *
           * Emits an {Approval} event indicating the updated allowance.
           *
           * Requirements:
           *
           * - `spender` cannot be the zero address.
           */
          function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
              address owner = _msgSender();
              _approve(owner, spender, allowance(owner, spender) + addedValue);
              return true;
          }
      
          /**
           * @dev Atomically decreases the allowance granted to `spender` by the caller.
           *
           * This is an alternative to {approve} that can be used as a mitigation for
           * problems described in {IERC20-approve}.
           *
           * Emits an {Approval} event indicating the updated allowance.
           *
           * Requirements:
           *
           * - `spender` cannot be the zero address.
           * - `spender` must have allowance for the caller of at least
           * `subtractedValue`.
           */
          function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
              address owner = _msgSender();
              uint256 currentAllowance = allowance(owner, spender);
              require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
              unchecked {
                  _approve(owner, spender, currentAllowance - subtractedValue);
              }
      
              return true;
          }
      
          /**
           * @dev Moves `amount` of tokens from `from` to `to`.
           *
           * This internal function is equivalent to {transfer}, and can be used to
           * e.g. implement automatic token fees, slashing mechanisms, etc.
           *
           * Emits a {Transfer} event.
           *
           * Requirements:
           *
           * - `from` cannot be the zero address.
           * - `to` cannot be the zero address.
           * - `from` must have a balance of at least `amount`.
           */
          function _transfer(
              address from,
              address to,
              uint256 amount
          ) internal virtual {
              require(from != address(0), "ERC20: transfer from the zero address");
              require(to != address(0), "ERC20: transfer to the zero address");
      
              _beforeTokenTransfer(from, to, amount);
      
              uint256 fromBalance = _balances[from];
              require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
              unchecked {
                  _balances[from] = fromBalance - amount;
                  // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
                  // decrementing then incrementing.
                  _balances[to] += amount;
              }
      
              emit Transfer(from, to, amount);
      
              _afterTokenTransfer(from, to, amount);
          }
      
          /** @dev Creates `amount` tokens and assigns them to `account`, increasing
           * the total supply.
           *
           * Emits a {Transfer} event with `from` set to the zero address.
           *
           * Requirements:
           *
           * - `account` cannot be the zero address.
           */
          function _mint(address account, uint256 amount) internal virtual {
              require(account != address(0), "ERC20: mint to the zero address");
      
              _beforeTokenTransfer(address(0), account, amount);
      
              _totalSupply += amount;
              unchecked {
                  // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
                  _balances[account] += amount;
              }
              emit Transfer(address(0), account, amount);
      
              _afterTokenTransfer(address(0), account, amount);
          }
      
          /**
           * @dev Destroys `amount` tokens from `account`, reducing the
           * total supply.
           *
           * Emits a {Transfer} event with `to` set to the zero address.
           *
           * Requirements:
           *
           * - `account` cannot be the zero address.
           * - `account` must have at least `amount` tokens.
           */
          function _burn(address account, uint256 amount) internal virtual {
              require(account != address(0), "ERC20: burn from the zero address");
      
              _beforeTokenTransfer(account, address(0), amount);
      
              uint256 accountBalance = _balances[account];
              require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
              unchecked {
                  _balances[account] = accountBalance - amount;
                  // Overflow not possible: amount <= accountBalance <= totalSupply.
                  _totalSupply -= amount;
              }
      
              emit Transfer(account, address(0), amount);
      
              _afterTokenTransfer(account, address(0), amount);
          }
      
          /**
           * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
           *
           * This internal function is equivalent to `approve`, and can be used to
           * e.g. set automatic allowances for certain subsystems, etc.
           *
           * Emits an {Approval} event.
           *
           * Requirements:
           *
           * - `owner` cannot be the zero address.
           * - `spender` cannot be the zero address.
           */
          function _approve(
              address owner,
              address spender,
              uint256 amount
          ) internal virtual {
              require(owner != address(0), "ERC20: approve from the zero address");
              require(spender != address(0), "ERC20: approve to the zero address");
      
              _allowances[owner][spender] = amount;
              emit Approval(owner, spender, amount);
          }
      
          /**
           * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
           *
           * Does not update the allowance amount in case of infinite allowance.
           * Revert if not enough allowance is available.
           *
           * Might emit an {Approval} event.
           */
          function _spendAllowance(
              address owner,
              address spender,
              uint256 amount
          ) internal virtual {
              uint256 currentAllowance = allowance(owner, spender);
              if (currentAllowance != type(uint256).max) {
                  require(currentAllowance >= amount, "ERC20: insufficient allowance");
                  unchecked {
                      _approve(owner, spender, currentAllowance - amount);
                  }
              }
          }
      
          /**
           * @dev Hook that is called before any transfer of tokens. This includes
           * minting and burning.
           *
           * Calling conditions:
           *
           * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
           * will be transferred to `to`.
           * - when `from` is zero, `amount` tokens will be minted for `to`.
           * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
           * - `from` and `to` are never both zero.
           *
           * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
           */
          function _beforeTokenTransfer(
              address from,
              address to,
              uint256 amount
          ) internal virtual {}
      
          /**
           * @dev Hook that is called after any transfer of tokens. This includes
           * minting and burning.
           *
           * Calling conditions:
           *
           * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
           * has been transferred to `to`.
           * - when `from` is zero, `amount` tokens have been minted for `to`.
           * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
           * - `from` and `to` are never both zero.
           *
           * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
           */
          function _afterTokenTransfer(
              address from,
              address to,
              uint256 amount
          ) internal virtual {}
      }
      
      // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/extensions/draft-ERC20Permit.sol)
      
      // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
      
      /**
       * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
       * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
       *
       * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
       * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
       * need to send a transaction, and thus is not required to hold Ether at all.
       */
      interface IERC20Permit {
          /**
           * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
           * given ``owner``'s signed approval.
           *
           * IMPORTANT: The same issues {IERC20-approve} has related to transaction
           * ordering also apply here.
           *
           * Emits an {Approval} event.
           *
           * Requirements:
           *
           * - `spender` cannot be the zero address.
           * - `deadline` must be a timestamp in the future.
           * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
           * over the EIP712-formatted function arguments.
           * - the signature must use ``owner``'s current nonce (see {nonces}).
           *
           * For more information on the signature format, see the
           * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
           * section].
           */
          function permit(
              address owner,
              address spender,
              uint256 value,
              uint256 deadline,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external;
      
          /**
           * @dev Returns the current nonce for `owner`. This value must be
           * included whenever a signature is generated for {permit}.
           *
           * Every successful call to {permit} increases ``owner``'s nonce by one. This
           * prevents a signature from being used multiple times.
           */
          function nonces(address owner) external view returns (uint256);
      
          /**
           * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
           */
          // solhint-disable-next-line func-name-mixedcase
          function DOMAIN_SEPARATOR() external view returns (bytes32);
      }
      
      // OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/ECDSA.sol)
      
      // OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
      
      // OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)
      
      /**
       * @dev Standard math utilities missing in the Solidity language.
       */
      library Math {
          enum Rounding {
              Down, // Toward negative infinity
              Up, // Toward infinity
              Zero // Toward zero
          }
      
          /**
           * @dev Returns the largest of two numbers.
           */
          function max(uint256 a, uint256 b) internal pure returns (uint256) {
              return a > b ? a : b;
          }
      
          /**
           * @dev Returns the smallest of two numbers.
           */
          function min(uint256 a, uint256 b) internal pure returns (uint256) {
              return a < b ? a : b;
          }
      
          /**
           * @dev Returns the average of two numbers. The result is rounded towards
           * zero.
           */
          function average(uint256 a, uint256 b) internal pure returns (uint256) {
              // (a + b) / 2 can overflow.
              return (a & b) + (a ^ b) / 2;
          }
      
          /**
           * @dev Returns the ceiling of the division of two numbers.
           *
           * This differs from standard division with `/` in that it rounds up instead
           * of rounding down.
           */
          function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
              // (a + b - 1) / b can overflow on addition, so we distribute.
              return a == 0 ? 0 : (a - 1) / b + 1;
          }
      
          /**
           * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
           * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
           * with further edits by Uniswap Labs also under MIT license.
           */
          function mulDiv(
              uint256 x,
              uint256 y,
              uint256 denominator
          ) internal pure returns (uint256 result) {
              unchecked {
                  // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                  // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                  // variables such that product = prod1 * 2^256 + prod0.
                  uint256 prod0; // Least significant 256 bits of the product
                  uint256 prod1; // Most significant 256 bits of the product
                  assembly {
                      let mm := mulmod(x, y, not(0))
                      prod0 := mul(x, y)
                      prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                  }
      
                  // Handle non-overflow cases, 256 by 256 division.
                  if (prod1 == 0) {
                      return prod0 / denominator;
                  }
      
                  // Make sure the result is less than 2^256. Also prevents denominator == 0.
                  require(denominator > prod1);
      
                  ///////////////////////////////////////////////
                  // 512 by 256 division.
                  ///////////////////////////////////////////////
      
                  // Make division exact by subtracting the remainder from [prod1 prod0].
                  uint256 remainder;
                  assembly {
                      // Compute remainder using mulmod.
                      remainder := mulmod(x, y, denominator)
      
                      // Subtract 256 bit number from 512 bit number.
                      prod1 := sub(prod1, gt(remainder, prod0))
                      prod0 := sub(prod0, remainder)
                  }
      
                  // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                  // See https://cs.stackexchange.com/q/138556/92363.
      
                  // Does not overflow because the denominator cannot be zero at this stage in the function.
                  uint256 twos = denominator & (~denominator + 1);
                  assembly {
                      // Divide denominator by twos.
                      denominator := div(denominator, twos)
      
                      // Divide [prod1 prod0] by twos.
                      prod0 := div(prod0, twos)
      
                      // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                      twos := add(div(sub(0, twos), twos), 1)
                  }
      
                  // Shift in bits from prod1 into prod0.
                  prod0 |= prod1 * twos;
      
                  // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                  // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                  // four bits. That is, denominator * inv = 1 mod 2^4.
                  uint256 inverse = (3 * denominator) ^ 2;
      
                  // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                  // in modular arithmetic, doubling the correct bits in each step.
                  inverse *= 2 - denominator * inverse; // inverse mod 2^8
                  inverse *= 2 - denominator * inverse; // inverse mod 2^16
                  inverse *= 2 - denominator * inverse; // inverse mod 2^32
                  inverse *= 2 - denominator * inverse; // inverse mod 2^64
                  inverse *= 2 - denominator * inverse; // inverse mod 2^128
                  inverse *= 2 - denominator * inverse; // inverse mod 2^256
      
                  // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                  // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                  // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                  // is no longer required.
                  result = prod0 * inverse;
                  return result;
              }
          }
      
          /**
           * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
           */
          function mulDiv(
              uint256 x,
              uint256 y,
              uint256 denominator,
              Rounding rounding
          ) internal pure returns (uint256) {
              uint256 result = mulDiv(x, y, denominator);
              if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                  result += 1;
              }
              return result;
          }
      
          /**
           * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
           *
           * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
           */
          function sqrt(uint256 a) internal pure returns (uint256) {
              if (a == 0) {
                  return 0;
              }
      
              // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
              //
              // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
              // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
              //
              // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
              // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
              // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
              //
              // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
              uint256 result = 1 << (log2(a) >> 1);
      
              // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
              // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
              // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
              // into the expected uint128 result.
              unchecked {
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  result = (result + a / result) >> 1;
                  return min(result, a / result);
              }
          }
      
          /**
           * @notice Calculates sqrt(a), following the selected rounding direction.
           */
          function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
              unchecked {
                  uint256 result = sqrt(a);
                  return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
              }
          }
      
          /**
           * @dev Return the log in base 2, rounded down, of a positive value.
           * Returns 0 if given 0.
           */
          function log2(uint256 value) internal pure returns (uint256) {
              uint256 result = 0;
              unchecked {
                  if (value >> 128 > 0) {
                      value >>= 128;
                      result += 128;
                  }
                  if (value >> 64 > 0) {
                      value >>= 64;
                      result += 64;
                  }
                  if (value >> 32 > 0) {
                      value >>= 32;
                      result += 32;
                  }
                  if (value >> 16 > 0) {
                      value >>= 16;
                      result += 16;
                  }
                  if (value >> 8 > 0) {
                      value >>= 8;
                      result += 8;
                  }
                  if (value >> 4 > 0) {
                      value >>= 4;
                      result += 4;
                  }
                  if (value >> 2 > 0) {
                      value >>= 2;
                      result += 2;
                  }
                  if (value >> 1 > 0) {
                      result += 1;
                  }
              }
              return result;
          }
      
          /**
           * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
           * Returns 0 if given 0.
           */
          function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
              unchecked {
                  uint256 result = log2(value);
                  return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
              }
          }
      
          /**
           * @dev Return the log in base 10, rounded down, of a positive value.
           * Returns 0 if given 0.
           */
          function log10(uint256 value) internal pure returns (uint256) {
              uint256 result = 0;
              unchecked {
                  if (value >= 10**64) {
                      value /= 10**64;
                      result += 64;
                  }
                  if (value >= 10**32) {
                      value /= 10**32;
                      result += 32;
                  }
                  if (value >= 10**16) {
                      value /= 10**16;
                      result += 16;
                  }
                  if (value >= 10**8) {
                      value /= 10**8;
                      result += 8;
                  }
                  if (value >= 10**4) {
                      value /= 10**4;
                      result += 4;
                  }
                  if (value >= 10**2) {
                      value /= 10**2;
                      result += 2;
                  }
                  if (value >= 10**1) {
                      result += 1;
                  }
              }
              return result;
          }
      
          /**
           * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
           * Returns 0 if given 0.
           */
          function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
              unchecked {
                  uint256 result = log10(value);
                  return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
              }
          }
      
          /**
           * @dev Return the log in base 256, rounded down, of a positive value.
           * Returns 0 if given 0.
           *
           * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
           */
          function log256(uint256 value) internal pure returns (uint256) {
              uint256 result = 0;
              unchecked {
                  if (value >> 128 > 0) {
                      value >>= 128;
                      result += 16;
                  }
                  if (value >> 64 > 0) {
                      value >>= 64;
                      result += 8;
                  }
                  if (value >> 32 > 0) {
                      value >>= 32;
                      result += 4;
                  }
                  if (value >> 16 > 0) {
                      value >>= 16;
                      result += 2;
                  }
                  if (value >> 8 > 0) {
                      result += 1;
                  }
              }
              return result;
          }
      
          /**
           * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
           * Returns 0 if given 0.
           */
          function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
              unchecked {
                  uint256 result = log256(value);
                  return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
              }
          }
      }
      
      /**
       * @dev String operations.
       */
      library Strings {
          bytes16 private constant _SYMBOLS = "0123456789abcdef";
          uint8 private constant _ADDRESS_LENGTH = 20;
      
          /**
           * @dev Converts a `uint256` to its ASCII `string` decimal representation.
           */
          function toString(uint256 value) internal pure returns (string memory) {
              unchecked {
                  uint256 length = Math.log10(value) + 1;
                  string memory buffer = new string(length);
                  uint256 ptr;
                  /// @solidity memory-safe-assembly
                  assembly {
                      ptr := add(buffer, add(32, length))
                  }
                  while (true) {
                      ptr--;
                      /// @solidity memory-safe-assembly
                      assembly {
                          mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                      }
                      value /= 10;
                      if (value == 0) break;
                  }
                  return buffer;
              }
          }
      
          /**
           * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
           */
          function toHexString(uint256 value) internal pure returns (string memory) {
              unchecked {
                  return toHexString(value, Math.log256(value) + 1);
              }
          }
      
          /**
           * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
           */
          function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
              bytes memory buffer = new bytes(2 * length + 2);
              buffer[0] = "0";
              buffer[1] = "x";
              for (uint256 i = 2 * length + 1; i > 1; --i) {
                  buffer[i] = _SYMBOLS[value & 0xf];
                  value >>= 4;
              }
              require(value == 0, "Strings: hex length insufficient");
              return string(buffer);
          }
      
          /**
           * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
           */
          function toHexString(address addr) internal pure returns (string memory) {
              return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
          }
      }
      
      /**
       * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
       *
       * These functions can be used to verify that a message was signed by the holder
       * of the private keys of a given address.
       */
      library ECDSA {
          enum RecoverError {
              NoError,
              InvalidSignature,
              InvalidSignatureLength,
              InvalidSignatureS,
              InvalidSignatureV // Deprecated in v4.8
          }
      
          function _throwError(RecoverError error) private pure {
              if (error == RecoverError.NoError) {
                  return; // no error: do nothing
              } else if (error == RecoverError.InvalidSignature) {
                  revert("ECDSA: invalid signature");
              } else if (error == RecoverError.InvalidSignatureLength) {
                  revert("ECDSA: invalid signature length");
              } else if (error == RecoverError.InvalidSignatureS) {
                  revert("ECDSA: invalid signature 's' value");
              }
          }
      
          /**
           * @dev Returns the address that signed a hashed message (`hash`) with
           * `signature` or error string. This address can then be used for verification purposes.
           *
           * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
           * this function rejects them by requiring the `s` value to be in the lower
           * half order, and the `v` value to be either 27 or 28.
           *
           * IMPORTANT: `hash` _must_ be the result of a hash operation for the
           * verification to be secure: it is possible to craft signatures that
           * recover to arbitrary addresses for non-hashed data. A safe way to ensure
           * this is by receiving a hash of the original message (which may otherwise
           * be too long), and then calling {toEthSignedMessageHash} on it.
           *
           * Documentation for signature generation:
           * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
           * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
           *
           * _Available since v4.3._
           */
          function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
              if (signature.length == 65) {
                  bytes32 r;
                  bytes32 s;
                  uint8 v;
                  // ecrecover takes the signature parameters, and the only way to get them
                  // currently is to use assembly.
                  /// @solidity memory-safe-assembly
                  assembly {
                      r := mload(add(signature, 0x20))
                      s := mload(add(signature, 0x40))
                      v := byte(0, mload(add(signature, 0x60)))
                  }
                  return tryRecover(hash, v, r, s);
              } else {
                  return (address(0), RecoverError.InvalidSignatureLength);
              }
          }
      
          /**
           * @dev Returns the address that signed a hashed message (`hash`) with
           * `signature`. This address can then be used for verification purposes.
           *
           * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
           * this function rejects them by requiring the `s` value to be in the lower
           * half order, and the `v` value to be either 27 or 28.
           *
           * IMPORTANT: `hash` _must_ be the result of a hash operation for the
           * verification to be secure: it is possible to craft signatures that
           * recover to arbitrary addresses for non-hashed data. A safe way to ensure
           * this is by receiving a hash of the original message (which may otherwise
           * be too long), and then calling {toEthSignedMessageHash} on it.
           */
          function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
              (address recovered, RecoverError error) = tryRecover(hash, signature);
              _throwError(error);
              return recovered;
          }
      
          /**
           * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
           *
           * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
           *
           * _Available since v4.3._
           */
          function tryRecover(
              bytes32 hash,
              bytes32 r,
              bytes32 vs
          ) internal pure returns (address, RecoverError) {
              bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
              uint8 v = uint8((uint256(vs) >> 255) + 27);
              return tryRecover(hash, v, r, s);
          }
      
          /**
           * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
           *
           * _Available since v4.2._
           */
          function recover(
              bytes32 hash,
              bytes32 r,
              bytes32 vs
          ) internal pure returns (address) {
              (address recovered, RecoverError error) = tryRecover(hash, r, vs);
              _throwError(error);
              return recovered;
          }
      
          /**
           * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
           * `r` and `s` signature fields separately.
           *
           * _Available since v4.3._
           */
          function tryRecover(
              bytes32 hash,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal pure returns (address, RecoverError) {
              // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
              // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
              // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
              // signatures from current libraries generate a unique signature with an s-value in the lower half order.
              //
              // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
              // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
              // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
              // these malleable signatures as well.
              if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
                  return (address(0), RecoverError.InvalidSignatureS);
              }
      
              // If the signature is valid (and not malleable), return the signer address
              address signer = ecrecover(hash, v, r, s);
              if (signer == address(0)) {
                  return (address(0), RecoverError.InvalidSignature);
              }
      
              return (signer, RecoverError.NoError);
          }
      
          /**
           * @dev Overload of {ECDSA-recover} that receives the `v`,
           * `r` and `s` signature fields separately.
           */
          function recover(
              bytes32 hash,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal pure returns (address) {
              (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
              _throwError(error);
              return recovered;
          }
      
          /**
           * @dev Returns an Ethereum Signed Message, created from a `hash`. This
           * produces hash corresponding to the one signed with the
           * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
           * JSON-RPC method as part of EIP-191.
           *
           * See {recover}.
           */
          function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
              // 32 is the length in bytes of hash,
              // enforced by the type signature above
              return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
          }
      
          /**
           * @dev Returns an Ethereum Signed Message, created from `s`. This
           * produces hash corresponding to the one signed with the
           * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
           * JSON-RPC method as part of EIP-191.
           *
           * See {recover}.
           */
          function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
              return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
          }
      
          /**
           * @dev Returns an Ethereum Signed Typed Data, created from a
           * `domainSeparator` and a `structHash`. This produces hash corresponding
           * to the one signed with the
           * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
           * JSON-RPC method as part of EIP-712.
           *
           * See {recover}.
           */
          function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
              return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
          }
      }
      
      /**
       * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
       *
       * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
       * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
       * they need in their contracts using a combination of `abi.encode` and `keccak256`.
       *
       * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
       * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
       * ({_hashTypedDataV4}).
       *
       * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
       * the chain id to protect against replay attacks on an eventual fork of the chain.
       *
       * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
       * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
       *
       * _Available since v3.4._
       */
      abstract contract EIP712 {
          /* solhint-disable var-name-mixedcase */
          // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
          // invalidate the cached domain separator if the chain id changes.
          bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
          uint256 private immutable _CACHED_CHAIN_ID;
          address private immutable _CACHED_THIS;
      
          bytes32 private immutable _HASHED_NAME;
          bytes32 private immutable _HASHED_VERSION;
          bytes32 private immutable _TYPE_HASH;
      
          /* solhint-enable var-name-mixedcase */
      
          /**
           * @dev Initializes the domain separator and parameter caches.
           *
           * The meaning of `name` and `version` is specified in
           * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
           *
           * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
           * - `version`: the current major version of the signing domain.
           *
           * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
           * contract upgrade].
           */
          constructor(string memory name, string memory version) {
              bytes32 hashedName = keccak256(bytes(name));
              bytes32 hashedVersion = keccak256(bytes(version));
              bytes32 typeHash = keccak256(
                  "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
              );
              _HASHED_NAME = hashedName;
              _HASHED_VERSION = hashedVersion;
              _CACHED_CHAIN_ID = block.chainid;
              _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
              _CACHED_THIS = address(this);
              _TYPE_HASH = typeHash;
          }
      
          /**
           * @dev Returns the domain separator for the current chain.
           */
          function _domainSeparatorV4() internal view returns (bytes32) {
              if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
                  return _CACHED_DOMAIN_SEPARATOR;
              } else {
                  return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
              }
          }
      
          function _buildDomainSeparator(
              bytes32 typeHash,
              bytes32 nameHash,
              bytes32 versionHash
          ) private view returns (bytes32) {
              return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
          }
      
          /**
           * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
           * function returns the hash of the fully encoded EIP712 message for this domain.
           *
           * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
           *
           * ```solidity
           * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
           *     keccak256("Mail(address to,string contents)"),
           *     mailTo,
           *     keccak256(bytes(mailContents))
           * )));
           * address signer = ECDSA.recover(digest, signature);
           * ```
           */
          function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
              return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
          }
      }
      
      // OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)
      
      /**
       * @title Counters
       * @author Matt Condon (@shrugs)
       * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
       * of elements in a mapping, issuing ERC721 ids, or counting request ids.
       *
       * Include with `using Counters for Counters.Counter;`
       */
      library Counters {
          struct Counter {
              // This variable should never be directly accessed by users of the library: interactions must be restricted to
              // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
              // this feature: see https://github.com/ethereum/solidity/issues/4637
              uint256 _value; // default: 0
          }
      
          function current(Counter storage counter) internal view returns (uint256) {
              return counter._value;
          }
      
          function increment(Counter storage counter) internal {
              unchecked {
                  counter._value += 1;
              }
          }
      
          function decrement(Counter storage counter) internal {
              uint256 value = counter._value;
              require(value > 0, "Counter: decrement overflow");
              unchecked {
                  counter._value = value - 1;
              }
          }
      
          function reset(Counter storage counter) internal {
              counter._value = 0;
          }
      }
      
      /**
       * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
       * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
       *
       * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
       * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
       * need to send a transaction, and thus is not required to hold Ether at all.
       *
       * _Available since v3.4._
       */
      abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
          using Counters for Counters.Counter;
      
          mapping(address => Counters.Counter) private _nonces;
      
          // solhint-disable-next-line var-name-mixedcase
          bytes32 private constant _PERMIT_TYPEHASH =
              keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
          /**
           * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
           * However, to ensure consistency with the upgradeable transpiler, we will continue
           * to reserve a slot.
           * @custom:oz-renamed-from _PERMIT_TYPEHASH
           */
          // solhint-disable-next-line var-name-mixedcase
          bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;
      
          /**
           * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
           *
           * It's a good idea to use the same `name` that is defined as the ERC20 token name.
           */
          constructor(string memory name) EIP712(name, "1") {}
      
          /**
           * @dev See {IERC20Permit-permit}.
           */
          function permit(
              address owner,
              address spender,
              uint256 value,
              uint256 deadline,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) public virtual override {
              require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
      
              bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));
      
              bytes32 hash = _hashTypedDataV4(structHash);
      
              address signer = ECDSA.recover(hash, v, r, s);
              require(signer == owner, "ERC20Permit: invalid signature");
      
              _approve(owner, spender, value);
          }
      
          /**
           * @dev See {IERC20Permit-nonces}.
           */
          function nonces(address owner) public view virtual override returns (uint256) {
              return _nonces[owner].current();
          }
      
          /**
           * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
           */
          // solhint-disable-next-line func-name-mixedcase
          function DOMAIN_SEPARATOR() external view override returns (bytes32) {
              return _domainSeparatorV4();
          }
      
          /**
           * @dev "Consume a nonce": return the current value and increment.
           *
           * _Available since v4.1._
           */
          function _useNonce(address owner) internal virtual returns (uint256 current) {
              Counters.Counter storage nonce = _nonces[owner];
              current = nonce.current();
              nonce.increment();
          }
      }
      
      // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol)
      
      /**
       * @dev Extension of {ERC20} that allows token holders to destroy both their own
       * tokens and those that they have an allowance for, in a way that can be
       * recognized off-chain (via event analysis).
       */
      abstract contract ERC20Burnable is Context, ERC20 {
          /**
           * @dev Destroys `amount` tokens from the caller.
           *
           * See {ERC20-_burn}.
           */
          function burn(uint256 amount) public virtual {
              _burn(_msgSender(), amount);
          }
      
          /**
           * @dev Destroys `amount` tokens from `account`, deducting from the caller's
           * allowance.
           *
           * See {ERC20-_burn} and {ERC20-allowance}.
           *
           * Requirements:
           *
           * - the caller must have allowance for ``accounts``'s tokens of at least
           * `amount`.
           */
          function burnFrom(address account, uint256 amount) public virtual {
              _spendAllowance(account, _msgSender(), amount);
              _burn(account, amount);
          }
      }
      
      // https://docs.synthetix.io/contracts/Owned
      // NO NEED TO AUDIT
      contract Owned {
          address public owner;
          address public nominatedOwner;
      
          constructor (address _owner) {
              require(_owner != address(0), "Owner address cannot be 0");
              owner = _owner;
              emit OwnerChanged(address(0), _owner);
          }
      
          function nominateNewOwner(address _owner) external onlyOwner {
              nominatedOwner = _owner;
              emit OwnerNominated(_owner);
          }
      
          function acceptOwnership() external {
              require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
              emit OwnerChanged(owner, nominatedOwner);
              owner = nominatedOwner;
              nominatedOwner = address(0);
          }
      
          modifier onlyOwner {
              require(msg.sender == owner, "Only the contract owner may perform this action");
              _;
          }
      
          event OwnerNominated(address newOwner);
          event OwnerChanged(address oldOwner, address newOwner);
      }
      
      /// @title Parent contract for frxETH.sol
      /** @notice Combines Openzeppelin's ERC20Permit and ERC20Burnable with Synthetix's Owned. 
          Also includes a list of authorized minters */
      /// @dev frxETH adheres to EIP-712/EIP-2612 and can use permits
      contract ERC20PermitPermissionedMint is ERC20Permit, ERC20Burnable, Owned {
          // Core
          address public timelock_address;
      
          // Minters
          address[] public minters_array; // Allowed to mint
          mapping(address => bool) public minters; // Mapping is also used for faster verification
      
          /* ========== CONSTRUCTOR ========== */
      
          constructor(
              address _creator_address,
              address _timelock_address,
              string memory _name,
              string memory _symbol
          ) 
          ERC20(_name, _symbol)
          ERC20Permit(_name) 
          Owned(_creator_address)
          {
            timelock_address = _timelock_address;
          }
      
          /* ========== MODIFIERS ========== */
      
          modifier onlyByOwnGov() {
              require(msg.sender == timelock_address || msg.sender == owner, "Not owner or timelock");
              _;
          }
      
          modifier onlyMinters() {
             require(minters[msg.sender] == true, "Only minters");
              _;
          } 
      
          /* ========== RESTRICTED FUNCTIONS ========== */
      
          // Used by minters when user redeems
          function minter_burn_from(address b_address, uint256 b_amount) public onlyMinters {
              super.burnFrom(b_address, b_amount);
              emit TokenMinterBurned(b_address, msg.sender, b_amount);
          }
      
          // This function is what other minters will call to mint new tokens 
          function minter_mint(address m_address, uint256 m_amount) public onlyMinters {
              super._mint(m_address, m_amount);
              emit TokenMinterMinted(msg.sender, m_address, m_amount);
          }
      
          // Adds whitelisted minters 
          function addMinter(address minter_address) public onlyByOwnGov {
              require(minter_address != address(0), "Zero address detected");
      
              require(minters[minter_address] == false, "Address already exists");
              minters[minter_address] = true; 
              minters_array.push(minter_address);
      
              emit MinterAdded(minter_address);
          }
      
          // Remove a minter 
          function removeMinter(address minter_address) public onlyByOwnGov {
              require(minter_address != address(0), "Zero address detected");
              require(minters[minter_address] == true, "Address nonexistant");
              
              // Delete from the mapping
              delete minters[minter_address];
      
              // 'Delete' from the array by setting the address to 0x0
              for (uint i = 0; i < minters_array.length; i++){ 
                  if (minters_array[i] == minter_address) {
                      minters_array[i] = address(0); // This will leave a null in the array and keep the indices the same
                      break;
                  }
              }
      
              emit MinterRemoved(minter_address);
          }
      
          function setTimelock(address _timelock_address) public onlyByOwnGov {
              require(_timelock_address != address(0), "Zero address detected"); 
              timelock_address = _timelock_address;
              emit TimelockChanged(_timelock_address);
          }
      
          /* ========== EVENTS ========== */
          
          event TokenMinterBurned(address indexed from, address indexed to, uint256 amount);
          event TokenMinterMinted(address indexed from, address indexed to, uint256 amount);
          event MinterAdded(address minter_address);
          event MinterRemoved(address minter_address);
          event TimelockChanged(address timelock_address);
      }
      
      contract frxETH is ERC20PermitPermissionedMint {
      
          /* ========== CONSTRUCTOR ========== */
          constructor(
            address _creator_address,
            address _timelock_address
          ) 
          ERC20PermitPermissionedMint(_creator_address, _timelock_address, "Frax Ether", "frxETH") 
          {}
      
      }