Transaction Hash:
Block:
18255571 at Oct-01-2023 12:04:35 PM +UTC
Transaction Fee:
0.00050573077481223 ETH
$0.96
Gas Used:
78,210 Gas / 6.466318563 Gwei
Emitted Events:
1023 |
WalletSimple.Transacted( msgSender=[Sender] 0x538d72ded42a76a30f730292da939e0577f22f57, otherSigner=0x7e06Ec24...fbB70C85E, operation=142FC78AC400910E43249FEC299510FC600CD7BB563E6CEFC70C008EA523DC34, toAddress=0x44c1fa988bd174672bedfbc59b21cab2fcd431d7, value=307526510000000000, data=0x )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x44c1fA98...2FCD431d7 | 0.031907498446696016 Eth | 0.339434008446696016 Eth | 0.30752651 | ||
0x48EC5560...4CFDF6b0c | (Bitstamp: Wallet) | 15,224.633619233326547234 Eth | 15,224.326092723326547234 Eth | 0.30752651 | |
0x538d72dE...577f22F57 | (Bitstamp: Deployer) |
23.083796511418496711 Eth
Nonce: 120351
|
23.083290780643684481 Eth
Nonce: 120352
| 0.00050573077481223 | |
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 13.994764862325996812 Eth | 13.994773856475996812 Eth | 0.00000899415 |
Execution Trace
WalletSimple.sendMultiSig( toAddress=0x44c1fA988Bd174672BEdfBc59B21CAb2FCD431d7, value=307526510000000000, data=0x, expireTime=1696766649, sequenceId=117281, signature=0xCDACDDA13890C499D7C6B1A52939467E63B18435EA84EBA7D940950BB71B1E5F463E047AF313B6CAA4472E5CCC6A3B0D8AA4065625D202B6A154042C39D1DE2A1C )
WalletSimple.sendMultiSig( toAddress=0x44c1fA988Bd174672BEdfBc59B21CAb2FCD431d7, value=307526510000000000, data=0x, expireTime=1696766649, sequenceId=117281, signature=0xCDACDDA13890C499D7C6B1A52939467E63B18435EA84EBA7D940950BB71B1E5F463E047AF313B6CAA4472E5CCC6A3B0D8AA4065625D202B6A154042C39D1DE2A1C )
-
Null: 0x000...001.142fc78a( )
- ETH 0.30752651
0x44c1fa988bd174672bedfbc59b21cab2fcd431d7.CALL( )
-
File 1 of 2: WalletSimple
File 2 of 2: 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 2: 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"}}