ETH Price: $2,414.26 (-0.74%)

Transaction Decoder

Block:
20039014 at Jun-07-2024 09:19:47 AM +UTC
Transaction Fee:
0.00103394589061989 ETH $2.50
Gas Used:
84,927 Gas / 12.17452507 Gwei

Emitted Events:

Account State Difference:

  Address   Before After State Difference Code
0x2260FAC5...93bc2C599
(beaverbuild)
7.514861302031547323 Eth7.514900161110429815 Eth0.000038859078882492
0xD6F926D0...df4811c71
4.272142246105641088 Eth
Nonce: 1159
4.271108300215021198 Eth
Nonce: 1160
0.00103394589061989
0xE94aD792...b8DFfd688

Execution Trace

WalletSimple.sendMultiSigToken( toAddress=0xECa3F33B796c4e9FC03bf760d3557A429D68290B, value=59095, tokenContractAddress=0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, expireTime=1718356771, sequenceId=1987, signature=0xC32A768DEDB12BE1E7F18BFEA8F83174A2A8CF0F650D3C61F248F072D22CCEE263D205F1B7DA6699A362B8BA28276055AEEC343D54B0D4FDF5ED4C871F8504FB1B )
  • WalletSimple.sendMultiSigToken( toAddress=0xECa3F33B796c4e9FC03bf760d3557A429D68290B, value=59095, tokenContractAddress=0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, expireTime=1718356771, sequenceId=1987, signature=0xC32A768DEDB12BE1E7F18BFEA8F83174A2A8CF0F650D3C61F248F072D22CCEE263D205F1B7DA6699A362B8BA28276055AEEC343D54B0D4FDF5ED4C871F8504FB1B )
    • Null: 0x000...001.9c412418( )
    • WBTC.transfer( _to=0xECa3F33B796c4e9FC03bf760d3557A429D68290B, _value=59095 ) => ( True )
      File 1 of 3: WalletSimple
      {"ERC20Interface.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\npragma solidity 0.7.5;\n\n/**\n * Contract that exposes the needed erc20 token functions\n */\n\nabstract contract ERC20Interface {\n  // Send _value amount of tokens to address _to\n  function transfer(address _to, uint256 _value)\n    public\n    virtual\n    returns (bool success);\n\n  // Get the account balance of another account with address _owner\n  function balanceOf(address _owner)\n    public\n    virtual\n    view\n    returns (uint256 balance);\n}\n"},"Forwarder.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity 0.7.5;\nimport \u0027./TransferHelper.sol\u0027;\nimport \u0027./ERC20Interface.sol\u0027;\n\n/**\n * Contract that will forward any incoming Ether to the creator of the contract\n *\n */\ncontract Forwarder {\n  // Address to which any funds sent to this contract will be forwarded\n  address public parentAddress;\n  event ForwarderDeposited(address from, uint256 value, bytes data);\n\n  /**\n   * Initialize the contract, and sets the destination address to that of the creator\n   */\n  function init(address _parentAddress) external onlyUninitialized {\n    parentAddress = _parentAddress;\n    uint256 value = address(this).balance;\n\n    if (value == 0) {\n      return;\n    }\n\n    (bool success, ) = parentAddress.call{ value: value }(\u0027\u0027);\n    require(success, \u0027Flush failed\u0027);\n    // NOTE: since we are forwarding on initialization,\n    // we don\u0027t have the context of the original sender.\n    // We still emit an event about the forwarding but set\n    // the sender to the forwarder itself\n    emit ForwarderDeposited(address(this), value, msg.data);\n  }\n\n  /**\n   * Modifier that will execute internal code block only if the sender is the parent address\n   */\n  modifier onlyParent {\n    require(msg.sender == parentAddress, \u0027Only Parent\u0027);\n    _;\n  }\n\n  /**\n   * Modifier that will execute internal code block only if the contract has not been initialized yet\n   */\n  modifier onlyUninitialized {\n    require(parentAddress == address(0x0), \u0027Already initialized\u0027);\n    _;\n  }\n\n  /**\n   * Default function; Gets called when data is sent but does not match any other function\n   */\n  fallback() external payable {\n    flush();\n  }\n\n  /**\n   * Default function; Gets called when Ether is deposited with no data, and forwards it to the parent address\n   */\n  receive() external payable {\n    flush();\n  }\n\n  /**\n   * Execute a token transfer of the full balance from the forwarder token to the parent address\n   * @param tokenContractAddress the address of the erc20 token contract\n   */\n  function flushTokens(address tokenContractAddress) external onlyParent {\n    ERC20Interface instance = ERC20Interface(tokenContractAddress);\n    address forwarderAddress = address(this);\n    uint256 forwarderBalance = instance.balanceOf(forwarderAddress);\n    if (forwarderBalance == 0) {\n      return;\n    }\n\n    TransferHelper.safeTransfer(\n      tokenContractAddress,\n      parentAddress,\n      forwarderBalance\n    );\n  }\n\n  /**\n   * Flush the entire balance of the contract to the parent address.\n   */\n  function flush() public {\n    uint256 value = address(this).balance;\n\n    if (value == 0) {\n      return;\n    }\n\n    (bool success, ) = parentAddress.call{ value: value }(\u0027\u0027);\n    require(success, \u0027Flush failed\u0027);\n    emit ForwarderDeposited(msg.sender, value, msg.data);\n  }\n}\n"},"TransferHelper.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\n\npragma solidity \u003e=0.7.5;\n\n// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false\nlibrary TransferHelper {\n    function safeApprove(\n        address token,\n        address to,\n        uint256 value\n    ) internal {\n        // bytes4(keccak256(bytes(\u0027approve(address,uint256)\u0027)));\n        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));\n        require(\n            success \u0026\u0026 (data.length == 0 || abi.decode(data, (bool))),\n            \u0027TransferHelper::safeApprove: approve failed\u0027\n        );\n    }\n\n    function safeTransfer(\n        address token,\n        address to,\n        uint256 value\n    ) internal {\n        // bytes4(keccak256(bytes(\u0027transfer(address,uint256)\u0027)));\n        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));\n        require(\n            success \u0026\u0026 (data.length == 0 || abi.decode(data, (bool))),\n            \u0027TransferHelper::safeTransfer: transfer failed\u0027\n        );\n    }\n\n    function safeTransferFrom(\n        address token,\n        address from,\n        address to,\n        uint256 value\n    ) internal {\n        // bytes4(keccak256(bytes(\u0027transferFrom(address,address,uint256)\u0027)));\n        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));\n        require(\n            success \u0026\u0026 (data.length == 0 || abi.decode(data, (bool))),\n            \u0027TransferHelper::transferFrom: transferFrom failed\u0027\n        );\n    }\n\n    function safeTransferETH(address to, uint256 value) internal {\n        (bool success, ) = to.call{value: value}(new bytes(0));\n        require(success, \u0027TransferHelper::safeTransferETH: ETH transfer failed\u0027);\n    }\n}\n"},"WalletSimple.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity 0.7.5;\nimport \u0027./TransferHelper.sol\u0027;\nimport \u0027./Forwarder.sol\u0027;\nimport \u0027./ERC20Interface.sol\u0027;\n\n/**\n *\n * WalletSimple\n * ============\n *\n * Basic multi-signer wallet designed for use in a co-signing environment where 2 signatures are required to move funds.\n * Typically used in a 2-of-3 signing configuration. Uses ecrecover to allow for 2 signatures in a single transaction.\n *\n * The first signature is created on the operation hash (see Data Formats) and passed to sendMultiSig/sendMultiSigToken\n * The signer is determined by verifyMultiSig().\n *\n * The second signature is created by the submitter of the transaction and determined by msg.signer.\n *\n * Data Formats\n * ============\n *\n * The signature is created with ethereumjs-util.ecsign(operationHash).\n * Like the eth_sign RPC call, it packs the values as a 65-byte array of [r, s, v].\n * Unlike eth_sign, the message is not prefixed.\n *\n * The operationHash the result of keccak256(prefix, toAddress, value, data, expireTime).\n * For ether transactions, `prefix` is \"ETHER\".\n * For token transaction, `prefix` is \"ERC20\" and `data` is the tokenContractAddress.\n *\n *\n */\ncontract WalletSimple {\n  // Events\n  event Deposited(address from, uint256 value, bytes data);\n  event SafeModeActivated(address msgSender);\n  event Transacted(\n    address msgSender, // Address of the sender of the message initiating the transaction\n    address otherSigner, // Address of the signer (second signature) used to initiate the transaction\n    bytes32 operation, // Operation hash (see Data Formats)\n    address toAddress, // The address the transaction was sent to\n    uint256 value, // Amount of Wei sent to the address\n    bytes data // Data sent when invoking the transaction\n  );\n\n  event BatchTransfer(address sender, address recipient, uint256 value);\n  // this event shows the other signer and the operation hash that they signed\n  // specific batch transfer events are emitted in Batcher\n  event BatchTransacted(\n    address msgSender, // Address of the sender of the message initiating the transaction\n    address otherSigner, // Address of the signer (second signature) used to initiate the transaction\n    bytes32 operation // Operation hash (see Data Formats)\n  );\n\n  // Public fields\n  mapping(address =\u003e bool) public signers; // The addresses that can co-sign transactions on the wallet\n  bool public safeMode = false; // When active, wallet may only send to signer addresses\n  bool public initialized = false; // True if the contract has been initialized\n\n  // Internal fields\n  uint256 private constant MAX_SEQUENCE_ID_INCREASE = 10000;\n  uint256 constant SEQUENCE_ID_WINDOW_SIZE = 10;\n  uint256[SEQUENCE_ID_WINDOW_SIZE] recentSequenceIds;\n\n  /**\n   * Set up a simple multi-sig wallet by specifying the signers allowed to be used on this wallet.\n   * 2 signers will be required to send a transaction from this wallet.\n   * Note: The sender is NOT automatically added to the list of signers.\n   * Signers CANNOT be changed once they are set\n   *\n   * @param allowedSigners An array of signers on the wallet\n   */\n  function init(address[] calldata allowedSigners) external onlyUninitialized {\n    require(allowedSigners.length == 3, \u0027Invalid number of signers\u0027);\n\n    for (uint8 i = 0; i \u003c allowedSigners.length; i++) {\n      require(allowedSigners[i] != address(0), \u0027Invalid signer\u0027);\n      signers[allowedSigners[i]] = true;\n    }\n    initialized = true;\n  }\n\n  /**\n   * Get the network identifier that signers must sign over\n   * This provides protection signatures being replayed on other chains\n   * This must be a virtual function because chain-specific contracts will need\n   *    to override with their own network ids. It also can\u0027t be a field\n   *    to allow this contract to be used by proxy with delegatecall, which will\n   *    not pick up on state variables\n   */\n  function getNetworkId() internal virtual pure returns (string memory) {\n    return \u0027ETHER\u0027;\n  }\n\n  /**\n   * Get the network identifier that signers must sign over for token transfers\n   * This provides protection signatures being replayed on other chains\n   * This must be a virtual function because chain-specific contracts will need\n   *    to override with their own network ids. It also can\u0027t be a field\n   *    to allow this contract to be used by proxy with delegatecall, which will\n   *    not pick up on state variables\n   */\n  function getTokenNetworkId() internal virtual pure returns (string memory) {\n    return \u0027ERC20\u0027;\n  }\n\n  /**\n   * Get the network identifier that signers must sign over for batch transfers\n   * This provides protection signatures being replayed on other chains\n   * This must be a virtual function because chain-specific contracts will need\n   *    to override with their own network ids. It also can\u0027t be a field\n   *    to allow this contract to be used by proxy with delegatecall, which will\n   *    not pick up on state variables\n   */\n  function getBatchNetworkId() internal virtual pure returns (string memory) {\n    return \u0027ETHER-Batch\u0027;\n  }\n\n  /**\n   * Determine if an address is a signer on this wallet\n   * @param signer address to check\n   * returns boolean indicating whether address is signer or not\n   */\n  function isSigner(address signer) public view returns (bool) {\n    return signers[signer];\n  }\n\n  /**\n   * Modifier that will execute internal code block only if the sender is an authorized signer on this wallet\n   */\n  modifier onlySigner {\n    require(isSigner(msg.sender), \u0027Non-signer in onlySigner method\u0027);\n    _;\n  }\n\n  /**\n   * Modifier that will execute internal code block only if the contract has not been initialized yet\n   */\n  modifier onlyUninitialized {\n    require(!initialized, \u0027Contract already initialized\u0027);\n    _;\n  }\n\n  /**\n   * Gets called when a transaction is received with data that does not match any other method\n   */\n  fallback() external payable {\n    if (msg.value \u003e 0) {\n      // Fire deposited event if we are receiving funds\n      Deposited(msg.sender, msg.value, msg.data);\n    }\n  }\n\n  /**\n   * Gets called when a transaction is received with ether and no data\n   */\n  receive() external payable {\n    if (msg.value \u003e 0) {\n      // Fire deposited event if we are receiving funds\n      Deposited(msg.sender, msg.value, msg.data);\n    }\n  }\n\n  /**\n   * Execute a multi-signature transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.\n   * Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.\n   *\n   * @param toAddress the destination address to send an outgoing transaction\n   * @param value the amount in Wei to be sent\n   * @param data the data to send to the toAddress when invoking the transaction\n   * @param expireTime the number of seconds since 1970 for which this transaction is valid\n   * @param sequenceId the unique sequence id obtainable from getNextSequenceId\n   * @param signature see Data Formats\n   */\n  function sendMultiSig(\n    address toAddress,\n    uint256 value,\n    bytes calldata data,\n    uint256 expireTime,\n    uint256 sequenceId,\n    bytes calldata signature\n  ) external onlySigner {\n    // Verify the other signer\n    bytes32 operationHash = keccak256(\n      abi.encodePacked(\n        getNetworkId(),\n        toAddress,\n        value,\n        data,\n        expireTime,\n        sequenceId\n      )\n    );\n\n    address otherSigner = verifyMultiSig(\n      toAddress,\n      operationHash,\n      signature,\n      expireTime,\n      sequenceId\n    );\n\n    // Success, send the transaction\n    (bool success, ) = toAddress.call{ value: value }(data);\n    require(success, \u0027Call execution failed\u0027);\n\n    emit Transacted(\n      msg.sender,\n      otherSigner,\n      operationHash,\n      toAddress,\n      value,\n      data\n    );\n  }\n\n  /**\n   * Execute a batched multi-signature transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.\n   * Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.\n   * The recipients and values to send are encoded in two arrays, where for index i, recipients[i] will be sent values[i].\n   *\n   * @param recipients The list of recipients to send to\n   * @param values The list of values to send to\n   * @param expireTime the number of seconds since 1970 for which this transaction is valid\n   * @param sequenceId the unique sequence id obtainable from getNextSequenceId\n   * @param signature see Data Formats\n   */\n  function sendMultiSigBatch(\n    address[] calldata recipients,\n    uint256[] calldata values,\n    uint256 expireTime,\n    uint256 sequenceId,\n    bytes calldata signature\n  ) external onlySigner {\n    require(recipients.length != 0, \u0027Not enough recipients\u0027);\n    require(\n      recipients.length == values.length,\n      \u0027Unequal recipients and values\u0027\n    );\n    require(recipients.length \u003c 256, \u0027Too many recipients, max 255\u0027);\n\n    // Verify the other signer\n    bytes32 operationHash = keccak256(\n      abi.encodePacked(\n        getBatchNetworkId(),\n        recipients,\n        values,\n        expireTime,\n        sequenceId\n      )\n    );\n\n    // the first parameter (toAddress) is used to ensure transactions in safe mode only go to a signer\n    // if in safe mode, we should use normal sendMultiSig to recover, so this check will always fail if in safe mode\n    require(!safeMode, \u0027Batch in safe mode\u0027);\n    address otherSigner = verifyMultiSig(\n      address(0x0),\n      operationHash,\n      signature,\n      expireTime,\n      sequenceId\n    );\n\n    batchTransfer(recipients, values);\n    emit BatchTransacted(msg.sender, otherSigner, operationHash);\n  }\n\n  /**\n   * Transfer funds in a batch to each of recipients\n   * @param recipients The list of recipients to send to\n   * @param values The list of values to send to recipients.\n   *  The recipient with index i in recipients array will be sent values[i].\n   *  Thus, recipients and values must be the same length\n   */\n  function batchTransfer(\n    address[] calldata recipients,\n    uint256[] calldata values\n  ) internal {\n    for (uint256 i = 0; i \u003c recipients.length; i++) {\n      require(address(this).balance \u003e= values[i], \u0027Insufficient funds\u0027);\n\n      (bool success, ) = recipients[i].call{ value: values[i] }(\u0027\u0027);\n      require(success, \u0027Call failed\u0027);\n\n      emit BatchTransfer(msg.sender, recipients[i], values[i]);\n    }\n  }\n\n  /**\n   * Execute a multi-signature token transfer from this wallet using 2 signers: one from msg.sender and the other from ecrecover.\n   * Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.\n   *\n   * @param toAddress the destination address to send an outgoing transaction\n   * @param value the amount in tokens to be sent\n   * @param tokenContractAddress the address of the erc20 token contract\n   * @param expireTime the number of seconds since 1970 for which this transaction is valid\n   * @param sequenceId the unique sequence id obtainable from getNextSequenceId\n   * @param signature see Data Formats\n   */\n  function sendMultiSigToken(\n    address toAddress,\n    uint256 value,\n    address tokenContractAddress,\n    uint256 expireTime,\n    uint256 sequenceId,\n    bytes calldata signature\n  ) external onlySigner {\n    // Verify the other signer\n    bytes32 operationHash = keccak256(\n      abi.encodePacked(\n        getTokenNetworkId(),\n        toAddress,\n        value,\n        tokenContractAddress,\n        expireTime,\n        sequenceId\n      )\n    );\n\n    verifyMultiSig(toAddress, operationHash, signature, expireTime, sequenceId);\n\n    TransferHelper.safeTransfer(tokenContractAddress, toAddress, value);\n  }\n\n  /**\n   * Execute a token flush from one of the forwarder addresses. This transfer needs only a single signature and can be done by any signer\n   *\n   * @param forwarderAddress the address of the forwarder address to flush the tokens from\n   * @param tokenContractAddress the address of the erc20 token contract\n   */\n  function flushForwarderTokens(\n    address payable forwarderAddress,\n    address tokenContractAddress\n  ) external onlySigner {\n    Forwarder forwarder = Forwarder(forwarderAddress);\n    forwarder.flushTokens(tokenContractAddress);\n  }\n\n  /**\n   * Do common multisig verification for both eth sends and erc20token transfers\n   *\n   * @param toAddress the destination address to send an outgoing transaction\n   * @param operationHash see Data Formats\n   * @param signature see Data Formats\n   * @param expireTime the number of seconds since 1970 for which this transaction is valid\n   * @param sequenceId the unique sequence id obtainable from getNextSequenceId\n   * returns address that has created the signature\n   */\n  function verifyMultiSig(\n    address toAddress,\n    bytes32 operationHash,\n    bytes calldata signature,\n    uint256 expireTime,\n    uint256 sequenceId\n  ) private returns (address) {\n    address otherSigner = recoverAddressFromSignature(operationHash, signature);\n\n    // Verify if we are in safe mode. In safe mode, the wallet can only send to signers\n    require(!safeMode || isSigner(toAddress), \u0027External transfer in safe mode\u0027);\n\n    // Verify that the transaction has not expired\n    require(expireTime \u003e= block.timestamp, \u0027Transaction expired\u0027);\n\n    // Try to insert the sequence ID. Will revert if the sequence id was invalid\n    tryInsertSequenceId(sequenceId);\n\n    require(isSigner(otherSigner), \u0027Invalid signer\u0027);\n\n    require(otherSigner != msg.sender, \u0027Signers cannot be equal\u0027);\n\n    return otherSigner;\n  }\n\n  /**\n   * Irrevocably puts contract into safe mode. When in this mode, transactions may only be sent to signing addresses.\n   */\n  function activateSafeMode() external onlySigner {\n    safeMode = true;\n    SafeModeActivated(msg.sender);\n  }\n\n  /**\n   * Gets signer\u0027s address using ecrecover\n   * @param operationHash see Data Formats\n   * @param signature see Data Formats\n   * returns address recovered from the signature\n   */\n  function recoverAddressFromSignature(\n    bytes32 operationHash,\n    bytes memory signature\n  ) private pure returns (address) {\n    require(signature.length == 65, \u0027Invalid signature - wrong length\u0027);\n\n    // We need to unpack the signature, which is given as an array of 65 bytes (like eth.sign)\n    bytes32 r;\n    bytes32 s;\n    uint8 v;\n\n    // solhint-disable-next-line\n    assembly {\n      r := mload(add(signature, 32))\n      s := mload(add(signature, 64))\n      v := and(mload(add(signature, 65)), 255)\n    }\n    if (v \u003c 27) {\n      v += 27; // Ethereum versions are 27 or 28 as opposed to 0 or 1 which is submitted by some signing libs\n    }\n\n    // protect against signature malleability\n    // S value must be in the lower half orader\n    // reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/051d340171a93a3d401aaaea46b4b62fa81e5d7c/contracts/cryptography/ECDSA.sol#L53\n    require(\n      uint256(s) \u003c=\n        0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\n      \"ECDSA: invalid signature \u0027s\u0027 value\"\n    );\n\n    // note that this returns 0 if the signature is invalid\n    // Since 0x0 can never be a signer, when the recovered signer address\n    // is checked against our signer list, that 0x0 will cause an invalid signer failure\n    return ecrecover(operationHash, v, r, s);\n  }\n\n  /**\n   * Verify that the sequence id has not been used before and inserts it. Throws if the sequence ID was not accepted.\n   * We collect a window of up to 10 recent sequence ids, and allow any sequence id that is not in the window and\n   * greater than the minimum element in the window.\n   * @param sequenceId to insert into array of stored ids\n   */\n  function tryInsertSequenceId(uint256 sequenceId) private onlySigner {\n    // Keep a pointer to the lowest value element in the window\n    uint256 lowestValueIndex = 0;\n    // fetch recentSequenceIds into memory for function context to avoid unnecessary sloads\n    uint256[SEQUENCE_ID_WINDOW_SIZE] memory _recentSequenceIds = recentSequenceIds;\n    for (uint256 i = 0; i \u003c SEQUENCE_ID_WINDOW_SIZE; i++) {\n      require(_recentSequenceIds[i] != sequenceId, \u0027Sequence ID already used\u0027);\n\n      if (_recentSequenceIds[i] \u003c _recentSequenceIds[lowestValueIndex]) {\n        lowestValueIndex = i;\n      }\n    }\n\n    // The sequence ID being used is lower than the lowest value in the window\n    // so we cannot accept it as it may have been used before\n    require(\n      sequenceId \u003e _recentSequenceIds[lowestValueIndex],\n      \u0027Sequence ID below window\u0027\n    );\n\n    // Block sequence IDs which are much higher than the lowest value\n    // This prevents people blocking the contract by using very large sequence IDs quickly\n    require(\n      sequenceId \u003c=\n        (_recentSequenceIds[lowestValueIndex] + MAX_SEQUENCE_ID_INCREASE),\n      \u0027Sequence ID above maximum\u0027\n    );\n\n    recentSequenceIds[lowestValueIndex] = sequenceId;\n  }\n\n  /**\n   * Gets the next available sequence ID for signing when using executeAndConfirm\n   * returns the sequenceId one higher than the highest currently stored\n   */\n  function getNextSequenceId() public view returns (uint256) {\n    uint256 highestSequenceId = 0;\n    for (uint256 i = 0; i \u003c SEQUENCE_ID_WINDOW_SIZE; i++) {\n      if (recentSequenceIds[i] \u003e highestSequenceId) {\n        highestSequenceId = recentSequenceIds[i];\n      }\n    }\n    return highestSequenceId + 1;\n  }\n}\n"}}

      File 2 of 3: WBTC
      pragma solidity 0.4.24;
      
      // File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol
      
      /**
       * @title ERC20Basic
       * @dev Simpler version of ERC20 interface
       * See https://github.com/ethereum/EIPs/issues/179
       */
      contract ERC20Basic {
        function totalSupply() public view returns (uint256);
        function balanceOf(address _who) public view returns (uint256);
        function transfer(address _to, uint256 _value) public returns (bool);
        event Transfer(address indexed from, address indexed to, uint256 value);
      }
      
      // File: openzeppelin-solidity/contracts/math/SafeMath.sol
      
      /**
       * @title SafeMath
       * @dev Math operations with safety checks that throw on error
       */
      library SafeMath {
      
        /**
        * @dev Multiplies two numbers, throws on overflow.
        */
        function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
          // Gas optimization: this is cheaper than asserting 'a' not being zero, but the
          // benefit is lost if 'b' is also tested.
          // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
          if (_a == 0) {
            return 0;
          }
      
          c = _a * _b;
          assert(c / _a == _b);
          return c;
        }
      
        /**
        * @dev Integer division of two numbers, truncating the quotient.
        */
        function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
          // assert(_b > 0); // Solidity automatically throws when dividing by 0
          // uint256 c = _a / _b;
          // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
          return _a / _b;
        }
      
        /**
        * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
        */
        function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {
          assert(_b <= _a);
          return _a - _b;
        }
      
        /**
        * @dev Adds two numbers, throws on overflow.
        */
        function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
          c = _a + _b;
          assert(c >= _a);
          return c;
        }
      }
      
      // File: openzeppelin-solidity/contracts/token/ERC20/BasicToken.sol
      
      /**
       * @title Basic token
       * @dev Basic version of StandardToken, with no allowances.
       */
      contract BasicToken is ERC20Basic {
        using SafeMath for uint256;
      
        mapping(address => uint256) internal balances;
      
        uint256 internal totalSupply_;
      
        /**
        * @dev Total number of tokens in existence
        */
        function totalSupply() public view returns (uint256) {
          return totalSupply_;
        }
      
        /**
        * @dev Transfer token for a specified address
        * @param _to The address to transfer to.
        * @param _value The amount to be transferred.
        */
        function transfer(address _to, uint256 _value) public returns (bool) {
          require(_value <= balances[msg.sender]);
          require(_to != address(0));
      
          balances[msg.sender] = balances[msg.sender].sub(_value);
          balances[_to] = balances[_to].add(_value);
          emit Transfer(msg.sender, _to, _value);
          return true;
        }
      
        /**
        * @dev Gets the balance of the specified address.
        * @param _owner The address to query the the balance of.
        * @return An uint256 representing the amount owned by the passed address.
        */
        function balanceOf(address _owner) public view returns (uint256) {
          return balances[_owner];
        }
      
      }
      
      // File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol
      
      /**
       * @title ERC20 interface
       * @dev see https://github.com/ethereum/EIPs/issues/20
       */
      contract ERC20 is ERC20Basic {
        function allowance(address _owner, address _spender)
          public view returns (uint256);
      
        function transferFrom(address _from, address _to, uint256 _value)
          public returns (bool);
      
        function approve(address _spender, uint256 _value) public returns (bool);
        event Approval(
          address indexed owner,
          address indexed spender,
          uint256 value
        );
      }
      
      // File: openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol
      
      /**
       * @title Standard ERC20 token
       *
       * @dev Implementation of the basic standard token.
       * https://github.com/ethereum/EIPs/issues/20
       * Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
       */
      contract StandardToken is ERC20, BasicToken {
      
        mapping (address => mapping (address => uint256)) internal allowed;
      
      
        /**
         * @dev Transfer tokens from one address to another
         * @param _from address The address which you want to send tokens from
         * @param _to address The address which you want to transfer to
         * @param _value uint256 the amount of tokens to be transferred
         */
        function transferFrom(
          address _from,
          address _to,
          uint256 _value
        )
          public
          returns (bool)
        {
          require(_value <= balances[_from]);
          require(_value <= allowed[_from][msg.sender]);
          require(_to != address(0));
      
          balances[_from] = balances[_from].sub(_value);
          balances[_to] = balances[_to].add(_value);
          allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
          emit Transfer(_from, _to, _value);
          return true;
        }
      
        /**
         * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
         * 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
         * @param _spender The address which will spend the funds.
         * @param _value The amount of tokens to be spent.
         */
        function approve(address _spender, uint256 _value) public returns (bool) {
          allowed[msg.sender][_spender] = _value;
          emit Approval(msg.sender, _spender, _value);
          return true;
        }
      
        /**
         * @dev Function to check the amount of tokens that an owner allowed to a spender.
         * @param _owner address The address which owns the funds.
         * @param _spender address The address which will spend the funds.
         * @return A uint256 specifying the amount of tokens still available for the spender.
         */
        function allowance(
          address _owner,
          address _spender
         )
          public
          view
          returns (uint256)
        {
          return allowed[_owner][_spender];
        }
      
        /**
         * @dev Increase the amount of tokens that an owner allowed to a spender.
         * approve should be called when allowed[_spender] == 0. To increment
         * allowed value is better to use this function to avoid 2 calls (and wait until
         * the first transaction is mined)
         * From MonolithDAO Token.sol
         * @param _spender The address which will spend the funds.
         * @param _addedValue The amount of tokens to increase the allowance by.
         */
        function increaseApproval(
          address _spender,
          uint256 _addedValue
        )
          public
          returns (bool)
        {
          allowed[msg.sender][_spender] = (
            allowed[msg.sender][_spender].add(_addedValue));
          emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
          return true;
        }
      
        /**
         * @dev Decrease the amount of tokens that an owner allowed to a spender.
         * approve should be called when allowed[_spender] == 0. To decrement
         * allowed value is better to use this function to avoid 2 calls (and wait until
         * the first transaction is mined)
         * From MonolithDAO Token.sol
         * @param _spender The address which will spend the funds.
         * @param _subtractedValue The amount of tokens to decrease the allowance by.
         */
        function decreaseApproval(
          address _spender,
          uint256 _subtractedValue
        )
          public
          returns (bool)
        {
          uint256 oldValue = allowed[msg.sender][_spender];
          if (_subtractedValue >= oldValue) {
            allowed[msg.sender][_spender] = 0;
          } else {
            allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
          }
          emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
          return true;
        }
      
      }
      
      // File: openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol
      
      /**
       * @title DetailedERC20 token
       * @dev The decimals are only for visualization purposes.
       * All the operations are done using the smallest and indivisible token unit,
       * just as on Ethereum all the operations are done in wei.
       */
      contract DetailedERC20 is ERC20 {
        string public name;
        string public symbol;
        uint8 public decimals;
      
        constructor(string _name, string _symbol, uint8 _decimals) public {
          name = _name;
          symbol = _symbol;
          decimals = _decimals;
        }
      }
      
      // File: openzeppelin-solidity/contracts/ownership/Ownable.sol
      
      /**
       * @title Ownable
       * @dev The Ownable contract has an owner address, and provides basic authorization control
       * functions, this simplifies the implementation of "user permissions".
       */
      contract Ownable {
        address public owner;
      
      
        event OwnershipRenounced(address indexed previousOwner);
        event OwnershipTransferred(
          address indexed previousOwner,
          address indexed newOwner
        );
      
      
        /**
         * @dev The Ownable constructor sets the original `owner` of the contract to the sender
         * account.
         */
        constructor() public {
          owner = msg.sender;
        }
      
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
          require(msg.sender == owner);
          _;
        }
      
        /**
         * @dev Allows the current owner to relinquish control of the contract.
         * @notice Renouncing to ownership will leave the contract without an owner.
         * It will not be possible to call the functions with the `onlyOwner`
         * modifier anymore.
         */
        function renounceOwnership() public onlyOwner {
          emit OwnershipRenounced(owner);
          owner = address(0);
        }
      
        /**
         * @dev Allows the current owner to transfer control of the contract to a newOwner.
         * @param _newOwner The address to transfer ownership to.
         */
        function transferOwnership(address _newOwner) public onlyOwner {
          _transferOwnership(_newOwner);
        }
      
        /**
         * @dev Transfers control of the contract to a newOwner.
         * @param _newOwner The address to transfer ownership to.
         */
        function _transferOwnership(address _newOwner) internal {
          require(_newOwner != address(0));
          emit OwnershipTransferred(owner, _newOwner);
          owner = _newOwner;
        }
      }
      
      // File: openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol
      
      /**
       * @title Mintable token
       * @dev Simple ERC20 Token example, with mintable token creation
       * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol
       */
      contract MintableToken is StandardToken, Ownable {
        event Mint(address indexed to, uint256 amount);
        event MintFinished();
      
        bool public mintingFinished = false;
      
      
        modifier canMint() {
          require(!mintingFinished);
          _;
        }
      
        modifier hasMintPermission() {
          require(msg.sender == owner);
          _;
        }
      
        /**
         * @dev Function to mint tokens
         * @param _to The address that will receive the minted tokens.
         * @param _amount The amount of tokens to mint.
         * @return A boolean that indicates if the operation was successful.
         */
        function mint(
          address _to,
          uint256 _amount
        )
          public
          hasMintPermission
          canMint
          returns (bool)
        {
          totalSupply_ = totalSupply_.add(_amount);
          balances[_to] = balances[_to].add(_amount);
          emit Mint(_to, _amount);
          emit Transfer(address(0), _to, _amount);
          return true;
        }
      
        /**
         * @dev Function to stop minting new tokens.
         * @return True if the operation was successful.
         */
        function finishMinting() public onlyOwner canMint returns (bool) {
          mintingFinished = true;
          emit MintFinished();
          return true;
        }
      }
      
      // File: openzeppelin-solidity/contracts/token/ERC20/BurnableToken.sol
      
      /**
       * @title Burnable Token
       * @dev Token that can be irreversibly burned (destroyed).
       */
      contract BurnableToken is BasicToken {
      
        event Burn(address indexed burner, uint256 value);
      
        /**
         * @dev Burns a specific amount of tokens.
         * @param _value The amount of token to be burned.
         */
        function burn(uint256 _value) public {
          _burn(msg.sender, _value);
        }
      
        function _burn(address _who, uint256 _value) internal {
          require(_value <= balances[_who]);
          // no need to require value <= totalSupply, since that would imply the
          // sender's balance is greater than the totalSupply, which *should* be an assertion failure
      
          balances[_who] = balances[_who].sub(_value);
          totalSupply_ = totalSupply_.sub(_value);
          emit Burn(_who, _value);
          emit Transfer(_who, address(0), _value);
        }
      }
      
      // File: openzeppelin-solidity/contracts/lifecycle/Pausable.sol
      
      /**
       * @title Pausable
       * @dev Base contract which allows children to implement an emergency stop mechanism.
       */
      contract Pausable is Ownable {
        event Pause();
        event Unpause();
      
        bool public paused = false;
      
      
        /**
         * @dev Modifier to make a function callable only when the contract is not paused.
         */
        modifier whenNotPaused() {
          require(!paused);
          _;
        }
      
        /**
         * @dev Modifier to make a function callable only when the contract is paused.
         */
        modifier whenPaused() {
          require(paused);
          _;
        }
      
        /**
         * @dev called by the owner to pause, triggers stopped state
         */
        function pause() public onlyOwner whenNotPaused {
          paused = true;
          emit Pause();
        }
      
        /**
         * @dev called by the owner to unpause, returns to normal state
         */
        function unpause() public onlyOwner whenPaused {
          paused = false;
          emit Unpause();
        }
      }
      
      // File: openzeppelin-solidity/contracts/token/ERC20/PausableToken.sol
      
      /**
       * @title Pausable token
       * @dev StandardToken modified with pausable transfers.
       **/
      contract PausableToken is StandardToken, Pausable {
      
        function transfer(
          address _to,
          uint256 _value
        )
          public
          whenNotPaused
          returns (bool)
        {
          return super.transfer(_to, _value);
        }
      
        function transferFrom(
          address _from,
          address _to,
          uint256 _value
        )
          public
          whenNotPaused
          returns (bool)
        {
          return super.transferFrom(_from, _to, _value);
        }
      
        function approve(
          address _spender,
          uint256 _value
        )
          public
          whenNotPaused
          returns (bool)
        {
          return super.approve(_spender, _value);
        }
      
        function increaseApproval(
          address _spender,
          uint _addedValue
        )
          public
          whenNotPaused
          returns (bool success)
        {
          return super.increaseApproval(_spender, _addedValue);
        }
      
        function decreaseApproval(
          address _spender,
          uint _subtractedValue
        )
          public
          whenNotPaused
          returns (bool success)
        {
          return super.decreaseApproval(_spender, _subtractedValue);
        }
      }
      
      // File: openzeppelin-solidity/contracts/ownership/Claimable.sol
      
      /**
       * @title Claimable
       * @dev Extension for the Ownable contract, where the ownership needs to be claimed.
       * This allows the new owner to accept the transfer.
       */
      contract Claimable is Ownable {
        address public pendingOwner;
      
        /**
         * @dev Modifier throws if called by any account other than the pendingOwner.
         */
        modifier onlyPendingOwner() {
          require(msg.sender == pendingOwner);
          _;
        }
      
        /**
         * @dev Allows the current owner to set the pendingOwner address.
         * @param newOwner The address to transfer ownership to.
         */
        function transferOwnership(address newOwner) public onlyOwner {
          pendingOwner = newOwner;
        }
      
        /**
         * @dev Allows the pendingOwner address to finalize the transfer.
         */
        function claimOwnership() public onlyPendingOwner {
          emit OwnershipTransferred(owner, pendingOwner);
          owner = pendingOwner;
          pendingOwner = address(0);
        }
      }
      
      // File: openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol
      
      /**
       * @title SafeERC20
       * @dev Wrappers around ERC20 operations that throw on failure.
       * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
       * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
       */
      library SafeERC20 {
        function safeTransfer(
          ERC20Basic _token,
          address _to,
          uint256 _value
        )
          internal
        {
          require(_token.transfer(_to, _value));
        }
      
        function safeTransferFrom(
          ERC20 _token,
          address _from,
          address _to,
          uint256 _value
        )
          internal
        {
          require(_token.transferFrom(_from, _to, _value));
        }
      
        function safeApprove(
          ERC20 _token,
          address _spender,
          uint256 _value
        )
          internal
        {
          require(_token.approve(_spender, _value));
        }
      }
      
      // File: openzeppelin-solidity/contracts/ownership/CanReclaimToken.sol
      
      /**
       * @title Contracts that should be able to recover tokens
       * @author SylTi
       * @dev This allow a contract to recover any ERC20 token received in a contract by transferring the balance to the contract owner.
       * This will prevent any accidental loss of tokens.
       */
      contract CanReclaimToken is Ownable {
        using SafeERC20 for ERC20Basic;
      
        /**
         * @dev Reclaim all ERC20Basic compatible tokens
         * @param _token ERC20Basic The address of the token contract
         */
        function reclaimToken(ERC20Basic _token) external onlyOwner {
          uint256 balance = _token.balanceOf(this);
          _token.safeTransfer(owner, balance);
        }
      
      }
      
      // File: contracts/utils/OwnableContract.sol
      
      // empty block is used as this contract just inherits others.
      contract OwnableContract is CanReclaimToken, Claimable { } /* solhint-disable-line no-empty-blocks */
      
      // File: contracts/token/WBTC.sol
      
      contract WBTC is StandardToken, DetailedERC20("Wrapped BTC", "WBTC", 8),
          MintableToken, BurnableToken, PausableToken, OwnableContract {
      
          function burn(uint value) public onlyOwner {
              super.burn(value);
          }
      
          function finishMinting() public onlyOwner returns (bool) {
              return false;
          }
      
          function renounceOwnership() public onlyOwner {
              revert("renouncing ownership is blocked");
          }
      }

      File 3 of 3: WalletSimple
      {"ERC20Interface.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\npragma solidity 0.7.5;\n\n/**\n * Contract that exposes the needed erc20 token functions\n */\n\nabstract contract ERC20Interface {\n  // Send _value amount of tokens to address _to\n  function transfer(address _to, uint256 _value)\n    public\n    virtual\n    returns (bool success);\n\n  // Get the account balance of another account with address _owner\n  function balanceOf(address _owner)\n    public\n    virtual\n    view\n    returns (uint256 balance);\n}\n"},"Forwarder.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity 0.7.5;\nimport \u0027./TransferHelper.sol\u0027;\nimport \u0027./ERC20Interface.sol\u0027;\n\n/**\n * Contract that will forward any incoming Ether to the creator of the contract\n *\n */\ncontract Forwarder {\n  // Address to which any funds sent to this contract will be forwarded\n  address public parentAddress;\n  event ForwarderDeposited(address from, uint256 value, bytes data);\n\n  /**\n   * Initialize the contract, and sets the destination address to that of the creator\n   */\n  function init(address _parentAddress) external onlyUninitialized {\n    parentAddress = _parentAddress;\n    uint256 value = address(this).balance;\n\n    if (value == 0) {\n      return;\n    }\n\n    (bool success, ) = parentAddress.call{ value: value }(\u0027\u0027);\n    require(success, \u0027Flush failed\u0027);\n    // NOTE: since we are forwarding on initialization,\n    // we don\u0027t have the context of the original sender.\n    // We still emit an event about the forwarding but set\n    // the sender to the forwarder itself\n    emit ForwarderDeposited(address(this), value, msg.data);\n  }\n\n  /**\n   * Modifier that will execute internal code block only if the sender is the parent address\n   */\n  modifier onlyParent {\n    require(msg.sender == parentAddress, \u0027Only Parent\u0027);\n    _;\n  }\n\n  /**\n   * Modifier that will execute internal code block only if the contract has not been initialized yet\n   */\n  modifier onlyUninitialized {\n    require(parentAddress == address(0x0), \u0027Already initialized\u0027);\n    _;\n  }\n\n  /**\n   * Default function; Gets called when data is sent but does not match any other function\n   */\n  fallback() external payable {\n    flush();\n  }\n\n  /**\n   * Default function; Gets called when Ether is deposited with no data, and forwards it to the parent address\n   */\n  receive() external payable {\n    flush();\n  }\n\n  /**\n   * Execute a token transfer of the full balance from the forwarder token to the parent address\n   * @param tokenContractAddress the address of the erc20 token contract\n   */\n  function flushTokens(address tokenContractAddress) external onlyParent {\n    ERC20Interface instance = ERC20Interface(tokenContractAddress);\n    address forwarderAddress = address(this);\n    uint256 forwarderBalance = instance.balanceOf(forwarderAddress);\n    if (forwarderBalance == 0) {\n      return;\n    }\n\n    TransferHelper.safeTransfer(\n      tokenContractAddress,\n      parentAddress,\n      forwarderBalance\n    );\n  }\n\n  /**\n   * Flush the entire balance of the contract to the parent address.\n   */\n  function flush() public {\n    uint256 value = address(this).balance;\n\n    if (value == 0) {\n      return;\n    }\n\n    (bool success, ) = parentAddress.call{ value: value }(\u0027\u0027);\n    require(success, \u0027Flush failed\u0027);\n    emit ForwarderDeposited(msg.sender, value, msg.data);\n  }\n}\n"},"TransferHelper.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\n\npragma solidity \u003e=0.7.5;\n\n// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false\nlibrary TransferHelper {\n    function safeApprove(\n        address token,\n        address to,\n        uint256 value\n    ) internal {\n        // bytes4(keccak256(bytes(\u0027approve(address,uint256)\u0027)));\n        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));\n        require(\n            success \u0026\u0026 (data.length == 0 || abi.decode(data, (bool))),\n            \u0027TransferHelper::safeApprove: approve failed\u0027\n        );\n    }\n\n    function safeTransfer(\n        address token,\n        address to,\n        uint256 value\n    ) internal {\n        // bytes4(keccak256(bytes(\u0027transfer(address,uint256)\u0027)));\n        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));\n        require(\n            success \u0026\u0026 (data.length == 0 || abi.decode(data, (bool))),\n            \u0027TransferHelper::safeTransfer: transfer failed\u0027\n        );\n    }\n\n    function safeTransferFrom(\n        address token,\n        address from,\n        address to,\n        uint256 value\n    ) internal {\n        // bytes4(keccak256(bytes(\u0027transferFrom(address,address,uint256)\u0027)));\n        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));\n        require(\n            success \u0026\u0026 (data.length == 0 || abi.decode(data, (bool))),\n            \u0027TransferHelper::transferFrom: transferFrom failed\u0027\n        );\n    }\n\n    function safeTransferETH(address to, uint256 value) internal {\n        (bool success, ) = to.call{value: value}(new bytes(0));\n        require(success, \u0027TransferHelper::safeTransferETH: ETH transfer failed\u0027);\n    }\n}\n"},"WalletSimple.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity 0.7.5;\nimport \u0027./TransferHelper.sol\u0027;\nimport \u0027./Forwarder.sol\u0027;\nimport \u0027./ERC20Interface.sol\u0027;\n\n/**\n *\n * WalletSimple\n * ============\n *\n * Basic multi-signer wallet designed for use in a co-signing environment where 2 signatures are required to move funds.\n * Typically used in a 2-of-3 signing configuration. Uses ecrecover to allow for 2 signatures in a single transaction.\n *\n * The first signature is created on the operation hash (see Data Formats) and passed to sendMultiSig/sendMultiSigToken\n * The signer is determined by verifyMultiSig().\n *\n * The second signature is created by the submitter of the transaction and determined by msg.signer.\n *\n * Data Formats\n * ============\n *\n * The signature is created with ethereumjs-util.ecsign(operationHash).\n * Like the eth_sign RPC call, it packs the values as a 65-byte array of [r, s, v].\n * Unlike eth_sign, the message is not prefixed.\n *\n * The operationHash the result of keccak256(prefix, toAddress, value, data, expireTime).\n * For ether transactions, `prefix` is \"ETHER\".\n * For token transaction, `prefix` is \"ERC20\" and `data` is the tokenContractAddress.\n *\n *\n */\ncontract WalletSimple {\n  // Events\n  event Deposited(address from, uint256 value, bytes data);\n  event SafeModeActivated(address msgSender);\n  event Transacted(\n    address msgSender, // Address of the sender of the message initiating the transaction\n    address otherSigner, // Address of the signer (second signature) used to initiate the transaction\n    bytes32 operation, // Operation hash (see Data Formats)\n    address toAddress, // The address the transaction was sent to\n    uint256 value, // Amount of Wei sent to the address\n    bytes data // Data sent when invoking the transaction\n  );\n\n  event BatchTransfer(address sender, address recipient, uint256 value);\n  // this event shows the other signer and the operation hash that they signed\n  // specific batch transfer events are emitted in Batcher\n  event BatchTransacted(\n    address msgSender, // Address of the sender of the message initiating the transaction\n    address otherSigner, // Address of the signer (second signature) used to initiate the transaction\n    bytes32 operation // Operation hash (see Data Formats)\n  );\n\n  // Public fields\n  mapping(address =\u003e bool) public signers; // The addresses that can co-sign transactions on the wallet\n  bool public safeMode = false; // When active, wallet may only send to signer addresses\n  bool public initialized = false; // True if the contract has been initialized\n\n  // Internal fields\n  uint256 private constant MAX_SEQUENCE_ID_INCREASE = 10000;\n  uint256 constant SEQUENCE_ID_WINDOW_SIZE = 10;\n  uint256[SEQUENCE_ID_WINDOW_SIZE] recentSequenceIds;\n\n  /**\n   * Set up a simple multi-sig wallet by specifying the signers allowed to be used on this wallet.\n   * 2 signers will be required to send a transaction from this wallet.\n   * Note: The sender is NOT automatically added to the list of signers.\n   * Signers CANNOT be changed once they are set\n   *\n   * @param allowedSigners An array of signers on the wallet\n   */\n  function init(address[] calldata allowedSigners) external onlyUninitialized {\n    require(allowedSigners.length == 3, \u0027Invalid number of signers\u0027);\n\n    for (uint8 i = 0; i \u003c allowedSigners.length; i++) {\n      require(allowedSigners[i] != address(0), \u0027Invalid signer\u0027);\n      signers[allowedSigners[i]] = true;\n    }\n    initialized = true;\n  }\n\n  /**\n   * Get the network identifier that signers must sign over\n   * This provides protection signatures being replayed on other chains\n   * This must be a virtual function because chain-specific contracts will need\n   *    to override with their own network ids. It also can\u0027t be a field\n   *    to allow this contract to be used by proxy with delegatecall, which will\n   *    not pick up on state variables\n   */\n  function getNetworkId() internal virtual pure returns (string memory) {\n    return \u0027ETHER\u0027;\n  }\n\n  /**\n   * Get the network identifier that signers must sign over for token transfers\n   * This provides protection signatures being replayed on other chains\n   * This must be a virtual function because chain-specific contracts will need\n   *    to override with their own network ids. It also can\u0027t be a field\n   *    to allow this contract to be used by proxy with delegatecall, which will\n   *    not pick up on state variables\n   */\n  function getTokenNetworkId() internal virtual pure returns (string memory) {\n    return \u0027ERC20\u0027;\n  }\n\n  /**\n   * Get the network identifier that signers must sign over for batch transfers\n   * This provides protection signatures being replayed on other chains\n   * This must be a virtual function because chain-specific contracts will need\n   *    to override with their own network ids. It also can\u0027t be a field\n   *    to allow this contract to be used by proxy with delegatecall, which will\n   *    not pick up on state variables\n   */\n  function getBatchNetworkId() internal virtual pure returns (string memory) {\n    return \u0027ETHER-Batch\u0027;\n  }\n\n  /**\n   * Determine if an address is a signer on this wallet\n   * @param signer address to check\n   * returns boolean indicating whether address is signer or not\n   */\n  function isSigner(address signer) public view returns (bool) {\n    return signers[signer];\n  }\n\n  /**\n   * Modifier that will execute internal code block only if the sender is an authorized signer on this wallet\n   */\n  modifier onlySigner {\n    require(isSigner(msg.sender), \u0027Non-signer in onlySigner method\u0027);\n    _;\n  }\n\n  /**\n   * Modifier that will execute internal code block only if the contract has not been initialized yet\n   */\n  modifier onlyUninitialized {\n    require(!initialized, \u0027Contract already initialized\u0027);\n    _;\n  }\n\n  /**\n   * Gets called when a transaction is received with data that does not match any other method\n   */\n  fallback() external payable {\n    if (msg.value \u003e 0) {\n      // Fire deposited event if we are receiving funds\n      Deposited(msg.sender, msg.value, msg.data);\n    }\n  }\n\n  /**\n   * Gets called when a transaction is received with ether and no data\n   */\n  receive() external payable {\n    if (msg.value \u003e 0) {\n      // Fire deposited event if we are receiving funds\n      Deposited(msg.sender, msg.value, msg.data);\n    }\n  }\n\n  /**\n   * Execute a multi-signature transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.\n   * Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.\n   *\n   * @param toAddress the destination address to send an outgoing transaction\n   * @param value the amount in Wei to be sent\n   * @param data the data to send to the toAddress when invoking the transaction\n   * @param expireTime the number of seconds since 1970 for which this transaction is valid\n   * @param sequenceId the unique sequence id obtainable from getNextSequenceId\n   * @param signature see Data Formats\n   */\n  function sendMultiSig(\n    address toAddress,\n    uint256 value,\n    bytes calldata data,\n    uint256 expireTime,\n    uint256 sequenceId,\n    bytes calldata signature\n  ) external onlySigner {\n    // Verify the other signer\n    bytes32 operationHash = keccak256(\n      abi.encodePacked(\n        getNetworkId(),\n        toAddress,\n        value,\n        data,\n        expireTime,\n        sequenceId\n      )\n    );\n\n    address otherSigner = verifyMultiSig(\n      toAddress,\n      operationHash,\n      signature,\n      expireTime,\n      sequenceId\n    );\n\n    // Success, send the transaction\n    (bool success, ) = toAddress.call{ value: value }(data);\n    require(success, \u0027Call execution failed\u0027);\n\n    emit Transacted(\n      msg.sender,\n      otherSigner,\n      operationHash,\n      toAddress,\n      value,\n      data\n    );\n  }\n\n  /**\n   * Execute a batched multi-signature transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.\n   * Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.\n   * The recipients and values to send are encoded in two arrays, where for index i, recipients[i] will be sent values[i].\n   *\n   * @param recipients The list of recipients to send to\n   * @param values The list of values to send to\n   * @param expireTime the number of seconds since 1970 for which this transaction is valid\n   * @param sequenceId the unique sequence id obtainable from getNextSequenceId\n   * @param signature see Data Formats\n   */\n  function sendMultiSigBatch(\n    address[] calldata recipients,\n    uint256[] calldata values,\n    uint256 expireTime,\n    uint256 sequenceId,\n    bytes calldata signature\n  ) external onlySigner {\n    require(recipients.length != 0, \u0027Not enough recipients\u0027);\n    require(\n      recipients.length == values.length,\n      \u0027Unequal recipients and values\u0027\n    );\n    require(recipients.length \u003c 256, \u0027Too many recipients, max 255\u0027);\n\n    // Verify the other signer\n    bytes32 operationHash = keccak256(\n      abi.encodePacked(\n        getBatchNetworkId(),\n        recipients,\n        values,\n        expireTime,\n        sequenceId\n      )\n    );\n\n    // the first parameter (toAddress) is used to ensure transactions in safe mode only go to a signer\n    // if in safe mode, we should use normal sendMultiSig to recover, so this check will always fail if in safe mode\n    require(!safeMode, \u0027Batch in safe mode\u0027);\n    address otherSigner = verifyMultiSig(\n      address(0x0),\n      operationHash,\n      signature,\n      expireTime,\n      sequenceId\n    );\n\n    batchTransfer(recipients, values);\n    emit BatchTransacted(msg.sender, otherSigner, operationHash);\n  }\n\n  /**\n   * Transfer funds in a batch to each of recipients\n   * @param recipients The list of recipients to send to\n   * @param values The list of values to send to recipients.\n   *  The recipient with index i in recipients array will be sent values[i].\n   *  Thus, recipients and values must be the same length\n   */\n  function batchTransfer(\n    address[] calldata recipients,\n    uint256[] calldata values\n  ) internal {\n    for (uint256 i = 0; i \u003c recipients.length; i++) {\n      require(address(this).balance \u003e= values[i], \u0027Insufficient funds\u0027);\n\n      (bool success, ) = recipients[i].call{ value: values[i] }(\u0027\u0027);\n      require(success, \u0027Call failed\u0027);\n\n      emit BatchTransfer(msg.sender, recipients[i], values[i]);\n    }\n  }\n\n  /**\n   * Execute a multi-signature token transfer from this wallet using 2 signers: one from msg.sender and the other from ecrecover.\n   * Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.\n   *\n   * @param toAddress the destination address to send an outgoing transaction\n   * @param value the amount in tokens to be sent\n   * @param tokenContractAddress the address of the erc20 token contract\n   * @param expireTime the number of seconds since 1970 for which this transaction is valid\n   * @param sequenceId the unique sequence id obtainable from getNextSequenceId\n   * @param signature see Data Formats\n   */\n  function sendMultiSigToken(\n    address toAddress,\n    uint256 value,\n    address tokenContractAddress,\n    uint256 expireTime,\n    uint256 sequenceId,\n    bytes calldata signature\n  ) external onlySigner {\n    // Verify the other signer\n    bytes32 operationHash = keccak256(\n      abi.encodePacked(\n        getTokenNetworkId(),\n        toAddress,\n        value,\n        tokenContractAddress,\n        expireTime,\n        sequenceId\n      )\n    );\n\n    verifyMultiSig(toAddress, operationHash, signature, expireTime, sequenceId);\n\n    TransferHelper.safeTransfer(tokenContractAddress, toAddress, value);\n  }\n\n  /**\n   * Execute a token flush from one of the forwarder addresses. This transfer needs only a single signature and can be done by any signer\n   *\n   * @param forwarderAddress the address of the forwarder address to flush the tokens from\n   * @param tokenContractAddress the address of the erc20 token contract\n   */\n  function flushForwarderTokens(\n    address payable forwarderAddress,\n    address tokenContractAddress\n  ) external onlySigner {\n    Forwarder forwarder = Forwarder(forwarderAddress);\n    forwarder.flushTokens(tokenContractAddress);\n  }\n\n  /**\n   * Do common multisig verification for both eth sends and erc20token transfers\n   *\n   * @param toAddress the destination address to send an outgoing transaction\n   * @param operationHash see Data Formats\n   * @param signature see Data Formats\n   * @param expireTime the number of seconds since 1970 for which this transaction is valid\n   * @param sequenceId the unique sequence id obtainable from getNextSequenceId\n   * returns address that has created the signature\n   */\n  function verifyMultiSig(\n    address toAddress,\n    bytes32 operationHash,\n    bytes calldata signature,\n    uint256 expireTime,\n    uint256 sequenceId\n  ) private returns (address) {\n    address otherSigner = recoverAddressFromSignature(operationHash, signature);\n\n    // Verify if we are in safe mode. In safe mode, the wallet can only send to signers\n    require(!safeMode || isSigner(toAddress), \u0027External transfer in safe mode\u0027);\n\n    // Verify that the transaction has not expired\n    require(expireTime \u003e= block.timestamp, \u0027Transaction expired\u0027);\n\n    // Try to insert the sequence ID. Will revert if the sequence id was invalid\n    tryInsertSequenceId(sequenceId);\n\n    require(isSigner(otherSigner), \u0027Invalid signer\u0027);\n\n    require(otherSigner != msg.sender, \u0027Signers cannot be equal\u0027);\n\n    return otherSigner;\n  }\n\n  /**\n   * Irrevocably puts contract into safe mode. When in this mode, transactions may only be sent to signing addresses.\n   */\n  function activateSafeMode() external onlySigner {\n    safeMode = true;\n    SafeModeActivated(msg.sender);\n  }\n\n  /**\n   * Gets signer\u0027s address using ecrecover\n   * @param operationHash see Data Formats\n   * @param signature see Data Formats\n   * returns address recovered from the signature\n   */\n  function recoverAddressFromSignature(\n    bytes32 operationHash,\n    bytes memory signature\n  ) private pure returns (address) {\n    require(signature.length == 65, \u0027Invalid signature - wrong length\u0027);\n\n    // We need to unpack the signature, which is given as an array of 65 bytes (like eth.sign)\n    bytes32 r;\n    bytes32 s;\n    uint8 v;\n\n    // solhint-disable-next-line\n    assembly {\n      r := mload(add(signature, 32))\n      s := mload(add(signature, 64))\n      v := and(mload(add(signature, 65)), 255)\n    }\n    if (v \u003c 27) {\n      v += 27; // Ethereum versions are 27 or 28 as opposed to 0 or 1 which is submitted by some signing libs\n    }\n\n    // protect against signature malleability\n    // S value must be in the lower half orader\n    // reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/051d340171a93a3d401aaaea46b4b62fa81e5d7c/contracts/cryptography/ECDSA.sol#L53\n    require(\n      uint256(s) \u003c=\n        0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\n      \"ECDSA: invalid signature \u0027s\u0027 value\"\n    );\n\n    // note that this returns 0 if the signature is invalid\n    // Since 0x0 can never be a signer, when the recovered signer address\n    // is checked against our signer list, that 0x0 will cause an invalid signer failure\n    return ecrecover(operationHash, v, r, s);\n  }\n\n  /**\n   * Verify that the sequence id has not been used before and inserts it. Throws if the sequence ID was not accepted.\n   * We collect a window of up to 10 recent sequence ids, and allow any sequence id that is not in the window and\n   * greater than the minimum element in the window.\n   * @param sequenceId to insert into array of stored ids\n   */\n  function tryInsertSequenceId(uint256 sequenceId) private onlySigner {\n    // Keep a pointer to the lowest value element in the window\n    uint256 lowestValueIndex = 0;\n    // fetch recentSequenceIds into memory for function context to avoid unnecessary sloads\n    uint256[SEQUENCE_ID_WINDOW_SIZE] memory _recentSequenceIds = recentSequenceIds;\n    for (uint256 i = 0; i \u003c SEQUENCE_ID_WINDOW_SIZE; i++) {\n      require(_recentSequenceIds[i] != sequenceId, \u0027Sequence ID already used\u0027);\n\n      if (_recentSequenceIds[i] \u003c _recentSequenceIds[lowestValueIndex]) {\n        lowestValueIndex = i;\n      }\n    }\n\n    // The sequence ID being used is lower than the lowest value in the window\n    // so we cannot accept it as it may have been used before\n    require(\n      sequenceId \u003e _recentSequenceIds[lowestValueIndex],\n      \u0027Sequence ID below window\u0027\n    );\n\n    // Block sequence IDs which are much higher than the lowest value\n    // This prevents people blocking the contract by using very large sequence IDs quickly\n    require(\n      sequenceId \u003c=\n        (_recentSequenceIds[lowestValueIndex] + MAX_SEQUENCE_ID_INCREASE),\n      \u0027Sequence ID above maximum\u0027\n    );\n\n    recentSequenceIds[lowestValueIndex] = sequenceId;\n  }\n\n  /**\n   * Gets the next available sequence ID for signing when using executeAndConfirm\n   * returns the sequenceId one higher than the highest currently stored\n   */\n  function getNextSequenceId() public view returns (uint256) {\n    uint256 highestSequenceId = 0;\n    for (uint256 i = 0; i \u003c SEQUENCE_ID_WINDOW_SIZE; i++) {\n      if (recentSequenceIds[i] \u003e highestSequenceId) {\n        highestSequenceId = recentSequenceIds[i];\n      }\n    }\n    return highestSequenceId + 1;\n  }\n}\n"}}