Latest 25 from a total of 5,112 transactions
| Transaction Hash |
Method
|
Block
|
From
|
|
To
|
||||
|---|---|---|---|---|---|---|---|---|---|
| Transfer | 24508687 | 7 hrs ago | IN | 0.01170929 ETH | 0.00000086 | ||||
| Transfer | 24506467 | 15 hrs ago | IN | 0.01175536 ETH | 0.00000116 | ||||
| Transfer | 24499857 | 37 hrs ago | IN | 0.0083445 ETH | 0.00000197 | ||||
| Transfer | 24497016 | 46 hrs ago | IN | 0.0069511 ETH | 0.00000113 | ||||
| Transfer | 24494320 | 2 days ago | IN | 0.008618 ETH | 0.00000103 | ||||
| Transfer | 24492924 | 2 days ago | IN | 0.01131566 ETH | 0.00000172 | ||||
| Transfer | 24491764 | 2 days ago | IN | 1.22183374 ETH | 0.00000789 | ||||
| Transfer | 24490248 | 2 days ago | IN | 0.01044236 ETH | 0.00000131 | ||||
| Transfer | 24489595 | 2 days ago | IN | 0.00762679 ETH | 0.00000134 | ||||
| Transfer | 24488816 | 3 days ago | IN | 0.00532117 ETH | 0.00000083 | ||||
| Transfer | 24488500 | 3 days ago | IN | 0.00506547 ETH | 0.00000088 | ||||
| Transfer | 24488421 | 3 days ago | IN | 0.04252904 ETH | 0.00000082 | ||||
| Transfer | 24488033 | 3 days ago | IN | 0.00573302 ETH | 0.00000147 | ||||
| Transfer | 24486044 | 3 days ago | IN | 0.03137923 ETH | 0.00000531 | ||||
| Transfer | 24484399 | 3 days ago | IN | 0.01279272 ETH | 0.00000296 | ||||
| Transfer | 24484379 | 3 days ago | IN | 0.01914354 ETH | 0.00000252 | ||||
| Transfer | 24481782 | 4 days ago | IN | 0.00728106 ETH | 0.00000088 | ||||
| Transfer | 24480838 | 4 days ago | IN | 0.00972265 ETH | 0.00000074 | ||||
| Transfer | 24476557 | 4 days ago | IN | 0.06263927 ETH | 0.00000094 | ||||
| Transfer | 24476110 | 4 days ago | IN | 0.00514462 ETH | 0.00000096 | ||||
| Transfer | 24473932 | 5 days ago | IN | 0.00671487 ETH | 0.00000085 | ||||
| Transfer | 24473677 | 5 days ago | IN | 0.00503723 ETH | 0.00000082 | ||||
| Transfer | 24470948 | 5 days ago | IN | 0.198945 ETH | 0.00000113 | ||||
| Transfer | 24470637 | 5 days ago | IN | 0.01984545 ETH | 0.00000102 | ||||
| Transfer | 24470352 | 5 days ago | IN | 0.01436823 ETH | 0.00000236 |
Latest 25 internal transactions (View All)
| Parent Transaction Hash | Method | Block |
From
|
|
To
|
||
|---|---|---|---|---|---|---|---|
| Deposit | 24372119 | 19 days ago | 800 ETH | ||||
| Transfer | 24371748 | 19 days ago | 800 ETH | ||||
| Deposit | 24221377 | 40 days ago | 224 ETH | ||||
| Transfer | 24221202 | 40 days ago | 224 ETH | ||||
| Transfer | 24190862 | 44 days ago | 2,176.00164 ETH | ||||
| Transfer | 24190862 | 44 days ago | 77.331937 ETH | ||||
| Transfer | 23985088 | 73 days ago | 150.2860529 ETH | ||||
| Transfer | 23985087 | 73 days ago | 2,592 ETH | ||||
| Deposit | 23578251 | 130 days ago | 448 ETH | ||||
| Transfer | 23577305 | 130 days ago | 448 ETH | ||||
| Deposit | 23549579 | 134 days ago | 96 ETH | ||||
| Transfer | 23549438 | 134 days ago | 96 ETH | ||||
| Deposit | 23543074 | 135 days ago | 96 ETH | ||||
| Transfer | 23542988 | 135 days ago | 96 ETH | ||||
| Transfer | 23497884 | 141 days ago | 161.320089 ETH | ||||
| Transfer | 23497884 | 141 days ago | 7,680.02411016 ETH | ||||
| Deposit | 23378217 | 158 days ago | 64 ETH | ||||
| Transfer | 23377874 | 158 days ago | 64 ETH | ||||
| Deposit | 23372520 | 159 days ago | 320 ETH | ||||
| Transfer | 23372139 | 159 days ago | 320 ETH | ||||
| Deposit | 23276163 | 172 days ago | 64 ETH | ||||
| Transfer | 23248965 | 176 days ago | 64 ETH | ||||
| Deposit | 23185007 | 185 days ago | 64 ETH | ||||
| Transfer | 23184352 | 185 days ago | 64 ETH | ||||
| Transfer | 23098766 | 197 days ago | 3,232 ETH |
Cross-Chain Transactions
Latest 25 blocks (From a total of 94 blocks with 2.58 Ether produced)
| Block | Transaction | Difficulty | Gas Used | Reward | |
|---|---|---|---|---|---|
| 23922526 | 82 days ago | 34 | 0.00 TH | 1,605,809 (2.68%) | 0.004025715630531763 ETH |
| 23860416 | 90 days ago | 12 | 0.00 TH | 543,095 (1.21%) | 0.000831758561948 ETH |
| 23518343 | 138 days ago | 31 | 0.00 TH | 1,564,421 (3.48%) | 0.003191587409065818 ETH |
| 23477461 | 144 days ago | 74 | 0.00 TH | 3,202,865 (7.12%) | 0.007087463821038504 ETH |
| 23423268 | 152 days ago | 120 | 0.00 TH | 7,971,259 (17.77%) | 0.018577153991218101 ETH |
| 23314709 | 167 days ago | 23 | 0.00 TH | 1,002,362 (2.23%) | 0.002503078890749548 ETH |
| 23309387 | 168 days ago | 22 | 0.00 TH | 1,002,965 (2.23%) | 0.001827524980756227 ETH |
| 23300790 | 169 days ago | 21 | 0.00 TH | 966,999 (2.15%) | 0.001954901627359205 ETH |
| 23296792 | 169 days ago | 47 | 0.00 TH | 2,044,812 (4.55%) | 0.00434031795546445 ETH |
| 23232322 | 178 days ago | 51 | 0.00 TH | 3,070,735 (6.82%) | 0.006147942280985881 ETH |
| 23162621 | 188 days ago | 40 | 0.00 TH | 2,695,167 (6.01%) | 0.004185379533275109 ETH |
| 23140431 | 191 days ago | 83 | 0.00 TH | 4,793,594 (10.66%) | 0.0078048045330517 ETH |
| 23133982 | 192 days ago | 35 | 0.00 TH | 1,972,954 (4.39%) | 0.003232729958233383 ETH |
| 23067187 | 201 days ago | 32 | 0.00 TH | 1,733,206 (3.86%) | 0.002723084463336616 ETH |
| 23040381 | 205 days ago | 45 | 0.00 TH | 1,964,730 (4.38%) | 0.004067177173262328 ETH |
| 22976993 | 214 days ago | 64 | 0.00 TH | 3,749,177 (8.35%) | 0.00766307415208758 ETH |
| 22946106 | 218 days ago | 51 | 0.00 TH | 2,778,902 (7.61%) | 0.005289470129673001 ETH |
| 22762735 | 244 days ago | 24 | 0.00 TH | 845,057 (2.35%) | 0.00204432229209746 ETH |
| 22753545 | 245 days ago | 32 | 0.00 TH | 1,180,317 (3.28%) | 0.00237480608202639 ETH |
| 22637868 | 261 days ago | 24 | 0.00 TH | 1,160,763 (3.22%) | 0.002444782983450052 ETH |
| 22633213 | 262 days ago | 65 | 0.00 TH | 8,188,750 (22.77%) | 0.029459392358013944 ETH |
| 22627513 | 263 days ago | 36 | 0.00 TH | 1,502,496 (4.18%) | 0.00316475126750556 ETH |
| 22626635 | 263 days ago | 63 | 0.00 TH | 3,925,795 (10.95%) | 0.007625298283930821 ETH |
| 22619717 | 264 days ago | 32 | 0.00 TH | 1,831,716 (5.09%) | 0.004912860194107082 ETH |
| 22617179 | 264 days ago | 26 | 0.00 TH | 1,795,350 (4.99%) | 0.003511793106396977 ETH |
Latest 25 from a total of 77519 withdrawals (39,814.616200322 ETH withdrawn)
| Validator Index | Block | Amount | |
|---|---|---|---|
| 2208597 | 24510848 | 40 mins ago | 0.008236962 ETH |
| 2208596 | 24510848 | 40 mins ago | 0.008245686 ETH |
| 2208595 | 24510848 | 40 mins ago | 0.008232404 ETH |
| 2208594 | 24510848 | 40 mins ago | 0.008236791 ETH |
| 2208593 | 24510848 | 40 mins ago | 32.005975564 ETH |
| 2208592 | 24510848 | 40 mins ago | 0.008269659 ETH |
| 2208591 | 24510847 | 40 mins ago | 0.008246032 ETH |
| 2122867 | 24505886 | 17 hrs ago | 0.016635461 ETH |
| 2122866 | 24505886 | 17 hrs ago | 0.016657037 ETH |
| 2122864 | 24505886 | 17 hrs ago | 0.016663906 ETH |
| 2122863 | 24505886 | 17 hrs ago | 0.016658994 ETH |
| 2122862 | 24505886 | 17 hrs ago | 0.016676703 ETH |
| 2122861 | 24505886 | 17 hrs ago | 0.016681238 ETH |
| 2122860 | 24505886 | 17 hrs ago | 0.016613749 ETH |
| 2122859 | 24505885 | 17 hrs ago | 0.016659209 ETH |
| 2122857 | 24505885 | 17 hrs ago | 0.016659086 ETH |
| 2117670 | 24505579 | 18 hrs ago | 32.061139146 ETH |
| 2117669 | 24505579 | 18 hrs ago | 0.016665982 ETH |
| 2090364 | 24504071 | 23 hrs ago | 0.016622687 ETH |
| 2090363 | 24504071 | 23 hrs ago | 0.016666 ETH |
| 2090036 | 24504051 | 23 hrs ago | 0.016620832 ETH |
| 2090034 | 24504051 | 23 hrs ago | 0.016627166 ETH |
| 2090033 | 24504051 | 23 hrs ago | 0.016646265 ETH |
| 2090030 | 24504051 | 23 hrs ago | 0.064539444 ETH |
| 2071930 | 24503102 | 26 hrs ago | 32.000062643 ETH |
Minimal Proxy Contract for 0xe8e847cf573fc8ed75621660a36affd18c543d7e
Contract Source Code (Solidity Multiple files format)
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.5;
import './TransferHelper.sol';
import './Forwarder.sol';
import './ERC20Interface.sol';
/**
*
* WalletSimple
* ============
*
* Basic multi-signer wallet designed for use in a co-signing environment where 2 signatures are required to move funds.
* Typically used in a 2-of-3 signing configuration. Uses ecrecover to allow for 2 signatures in a single transaction.
*
* The first signature is created on the operation hash (see Data Formats) and passed to sendMultiSig/sendMultiSigToken
* The signer is determined by verifyMultiSig().
*
* The second signature is created by the submitter of the transaction and determined by msg.signer.
*
* Data Formats
* ============
*
* The signature is created with ethereumjs-util.ecsign(operationHash).
* Like the eth_sign RPC call, it packs the values as a 65-byte array of [r, s, v].
* Unlike eth_sign, the message is not prefixed.
*
* The operationHash the result of keccak256(prefix, toAddress, value, data, expireTime).
* For ether transactions, `prefix` is "ETHER".
* For token transaction, `prefix` is "ERC20" and `data` is the tokenContractAddress.
*
*
*/
contract WalletSimple {
// Events
event Deposited(address from, uint256 value, bytes data);
event SafeModeActivated(address msgSender);
event Transacted(
address msgSender, // Address of the sender of the message initiating the transaction
address otherSigner, // Address of the signer (second signature) used to initiate the transaction
bytes32 operation, // Operation hash (see Data Formats)
address toAddress, // The address the transaction was sent to
uint256 value, // Amount of Wei sent to the address
bytes data // Data sent when invoking the transaction
);
event BatchTransfer(address sender, address recipient, uint256 value);
// this event shows the other signer and the operation hash that they signed
// specific batch transfer events are emitted in Batcher
event BatchTransacted(
address msgSender, // Address of the sender of the message initiating the transaction
address otherSigner, // Address of the signer (second signature) used to initiate the transaction
bytes32 operation // Operation hash (see Data Formats)
);
// Public fields
mapping(address => bool) public signers; // The addresses that can co-sign transactions on the wallet
bool public safeMode = false; // When active, wallet may only send to signer addresses
bool public initialized = false; // True if the contract has been initialized
// Internal fields
uint256 private constant MAX_SEQUENCE_ID_INCREASE = 10000;
uint256 constant SEQUENCE_ID_WINDOW_SIZE = 10;
uint256[SEQUENCE_ID_WINDOW_SIZE] recentSequenceIds;
/**
* Set up a simple multi-sig wallet by specifying the signers allowed to be used on this wallet.
* 2 signers will be required to send a transaction from this wallet.
* Note: The sender is NOT automatically added to the list of signers.
* Signers CANNOT be changed once they are set
*
* @param allowedSigners An array of signers on the wallet
*/
function init(address[] calldata allowedSigners) external onlyUninitialized {
require(allowedSigners.length == 3, 'Invalid number of signers');
for (uint8 i = 0; i < allowedSigners.length; i++) {
require(allowedSigners[i] != address(0), 'Invalid signer');
signers[allowedSigners[i]] = true;
}
initialized = true;
}
/**
* Get the network identifier that signers must sign over
* This provides protection signatures being replayed on other chains
* This must be a virtual function because chain-specific contracts will need
* to override with their own network ids. It also can't be a field
* to allow this contract to be used by proxy with delegatecall, which will
* not pick up on state variables
*/
function getNetworkId() internal virtual pure returns (string memory) {
return 'ETHER';
}
/**
* Get the network identifier that signers must sign over for token transfers
* This provides protection signatures being replayed on other chains
* This must be a virtual function because chain-specific contracts will need
* to override with their own network ids. It also can't be a field
* to allow this contract to be used by proxy with delegatecall, which will
* not pick up on state variables
*/
function getTokenNetworkId() internal virtual pure returns (string memory) {
return 'ERC20';
}
/**
* Get the network identifier that signers must sign over for batch transfers
* This provides protection signatures being replayed on other chains
* This must be a virtual function because chain-specific contracts will need
* to override with their own network ids. It also can't be a field
* to allow this contract to be used by proxy with delegatecall, which will
* not pick up on state variables
*/
function getBatchNetworkId() internal virtual pure returns (string memory) {
return 'ETHER-Batch';
}
/**
* Determine if an address is a signer on this wallet
* @param signer address to check
* returns boolean indicating whether address is signer or not
*/
function isSigner(address signer) public view returns (bool) {
return signers[signer];
}
/**
* Modifier that will execute internal code block only if the sender is an authorized signer on this wallet
*/
modifier onlySigner {
require(isSigner(msg.sender), 'Non-signer in onlySigner method');
_;
}
/**
* Modifier that will execute internal code block only if the contract has not been initialized yet
*/
modifier onlyUninitialized {
require(!initialized, 'Contract already initialized');
_;
}
/**
* Gets called when a transaction is received with data that does not match any other method
*/
fallback() external payable {
if (msg.value > 0) {
// Fire deposited event if we are receiving funds
Deposited(msg.sender, msg.value, msg.data);
}
}
/**
* Gets called when a transaction is received with ether and no data
*/
receive() external payable {
if (msg.value > 0) {
// Fire deposited event if we are receiving funds
Deposited(msg.sender, msg.value, msg.data);
}
}
/**
* Execute a multi-signature transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
* Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
*
* @param toAddress the destination address to send an outgoing transaction
* @param value the amount in Wei to be sent
* @param data the data to send to the toAddress when invoking the transaction
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @param signature see Data Formats
*/
function sendMultiSig(
address toAddress,
uint256 value,
bytes calldata data,
uint256 expireTime,
uint256 sequenceId,
bytes calldata signature
) external onlySigner {
// Verify the other signer
bytes32 operationHash = keccak256(
abi.encodePacked(
getNetworkId(),
toAddress,
value,
data,
expireTime,
sequenceId
)
);
address otherSigner = verifyMultiSig(
toAddress,
operationHash,
signature,
expireTime,
sequenceId
);
// Success, send the transaction
(bool success, ) = toAddress.call{ value: value }(data);
require(success, 'Call execution failed');
emit Transacted(
msg.sender,
otherSigner,
operationHash,
toAddress,
value,
data
);
}
/**
* Execute a batched multi-signature transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
* Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
* The recipients and values to send are encoded in two arrays, where for index i, recipients[i] will be sent values[i].
*
* @param recipients The list of recipients to send to
* @param values The list of values to send to
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @param signature see Data Formats
*/
function sendMultiSigBatch(
address[] calldata recipients,
uint256[] calldata values,
uint256 expireTime,
uint256 sequenceId,
bytes calldata signature
) external onlySigner {
require(recipients.length != 0, 'Not enough recipients');
require(
recipients.length == values.length,
'Unequal recipients and values'
);
require(recipients.length < 256, 'Too many recipients, max 255');
// Verify the other signer
bytes32 operationHash = keccak256(
abi.encodePacked(
getBatchNetworkId(),
recipients,
values,
expireTime,
sequenceId
)
);
// the first parameter (toAddress) is used to ensure transactions in safe mode only go to a signer
// if in safe mode, we should use normal sendMultiSig to recover, so this check will always fail if in safe mode
require(!safeMode, 'Batch in safe mode');
address otherSigner = verifyMultiSig(
address(0x0),
operationHash,
signature,
expireTime,
sequenceId
);
batchTransfer(recipients, values);
emit BatchTransacted(msg.sender, otherSigner, operationHash);
}
/**
* Transfer funds in a batch to each of recipients
* @param recipients The list of recipients to send to
* @param values The list of values to send to recipients.
* The recipient with index i in recipients array will be sent values[i].
* Thus, recipients and values must be the same length
*/
function batchTransfer(
address[] calldata recipients,
uint256[] calldata values
) internal {
for (uint256 i = 0; i < recipients.length; i++) {
require(address(this).balance >= values[i], 'Insufficient funds');
(bool success, ) = recipients[i].call{ value: values[i] }('');
require(success, 'Call failed');
emit BatchTransfer(msg.sender, recipients[i], values[i]);
}
}
/**
* Execute a multi-signature token transfer from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
* Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
*
* @param toAddress the destination address to send an outgoing transaction
* @param value the amount in tokens to be sent
* @param tokenContractAddress the address of the erc20 token contract
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @param signature see Data Formats
*/
function sendMultiSigToken(
address toAddress,
uint256 value,
address tokenContractAddress,
uint256 expireTime,
uint256 sequenceId,
bytes calldata signature
) external onlySigner {
// Verify the other signer
bytes32 operationHash = keccak256(
abi.encodePacked(
getTokenNetworkId(),
toAddress,
value,
tokenContractAddress,
expireTime,
sequenceId
)
);
verifyMultiSig(toAddress, operationHash, signature, expireTime, sequenceId);
TransferHelper.safeTransfer(tokenContractAddress, toAddress, value);
}
/**
* Execute a token flush from one of the forwarder addresses. This transfer needs only a single signature and can be done by any signer
*
* @param forwarderAddress the address of the forwarder address to flush the tokens from
* @param tokenContractAddress the address of the erc20 token contract
*/
function flushForwarderTokens(
address payable forwarderAddress,
address tokenContractAddress
) external onlySigner {
Forwarder forwarder = Forwarder(forwarderAddress);
forwarder.flushTokens(tokenContractAddress);
}
/**
* Do common multisig verification for both eth sends and erc20token transfers
*
* @param toAddress the destination address to send an outgoing transaction
* @param operationHash see Data Formats
* @param signature see Data Formats
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* returns address that has created the signature
*/
function verifyMultiSig(
address toAddress,
bytes32 operationHash,
bytes calldata signature,
uint256 expireTime,
uint256 sequenceId
) private returns (address) {
address otherSigner = recoverAddressFromSignature(operationHash, signature);
// Verify if we are in safe mode. In safe mode, the wallet can only send to signers
require(!safeMode || isSigner(toAddress), 'External transfer in safe mode');
// Verify that the transaction has not expired
require(expireTime >= block.timestamp, 'Transaction expired');
// Try to insert the sequence ID. Will revert if the sequence id was invalid
tryInsertSequenceId(sequenceId);
require(isSigner(otherSigner), 'Invalid signer');
require(otherSigner != msg.sender, 'Signers cannot be equal');
return otherSigner;
}
/**
* Irrevocably puts contract into safe mode. When in this mode, transactions may only be sent to signing addresses.
*/
function activateSafeMode() external onlySigner {
safeMode = true;
SafeModeActivated(msg.sender);
}
/**
* Gets signer's address using ecrecover
* @param operationHash see Data Formats
* @param signature see Data Formats
* returns address recovered from the signature
*/
function recoverAddressFromSignature(
bytes32 operationHash,
bytes memory signature
) private pure returns (address) {
require(signature.length == 65, 'Invalid signature - wrong length');
// We need to unpack the signature, which is given as an array of 65 bytes (like eth.sign)
bytes32 r;
bytes32 s;
uint8 v;
// solhint-disable-next-line
assembly {
r := mload(add(signature, 32))
s := mload(add(signature, 64))
v := and(mload(add(signature, 65)), 255)
}
if (v < 27) {
v += 27; // Ethereum versions are 27 or 28 as opposed to 0 or 1 which is submitted by some signing libs
}
// protect against signature malleability
// S value must be in the lower half orader
// reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/051d340171a93a3d401aaaea46b4b62fa81e5d7c/contracts/cryptography/ECDSA.sol#L53
require(
uint256(s) <=
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,
"ECDSA: invalid signature 's' value"
);
// note that this returns 0 if the signature is invalid
// Since 0x0 can never be a signer, when the recovered signer address
// is checked against our signer list, that 0x0 will cause an invalid signer failure
return ecrecover(operationHash, v, r, s);
}
/**
* Verify that the sequence id has not been used before and inserts it. Throws if the sequence ID was not accepted.
* We collect a window of up to 10 recent sequence ids, and allow any sequence id that is not in the window and
* greater than the minimum element in the window.
* @param sequenceId to insert into array of stored ids
*/
function tryInsertSequenceId(uint256 sequenceId) private onlySigner {
// Keep a pointer to the lowest value element in the window
uint256 lowestValueIndex = 0;
// fetch recentSequenceIds into memory for function context to avoid unnecessary sloads
uint256[SEQUENCE_ID_WINDOW_SIZE] memory _recentSequenceIds = recentSequenceIds;
for (uint256 i = 0; i < SEQUENCE_ID_WINDOW_SIZE; i++) {
require(_recentSequenceIds[i] != sequenceId, 'Sequence ID already used');
if (_recentSequenceIds[i] < _recentSequenceIds[lowestValueIndex]) {
lowestValueIndex = i;
}
}
// The sequence ID being used is lower than the lowest value in the window
// so we cannot accept it as it may have been used before
require(
sequenceId > _recentSequenceIds[lowestValueIndex],
'Sequence ID below window'
);
// Block sequence IDs which are much higher than the lowest value
// This prevents people blocking the contract by using very large sequence IDs quickly
require(
sequenceId <=
(_recentSequenceIds[lowestValueIndex] + MAX_SEQUENCE_ID_INCREASE),
'Sequence ID above maximum'
);
recentSequenceIds[lowestValueIndex] = sequenceId;
}
/**
* Gets the next available sequence ID for signing when using executeAndConfirm
* returns the sequenceId one higher than the highest currently stored
*/
function getNextSequenceId() public view returns (uint256) {
uint256 highestSequenceId = 0;
for (uint256 i = 0; i < SEQUENCE_ID_WINDOW_SIZE; i++) {
if (recentSequenceIds[i] > highestSequenceId) {
highestSequenceId = recentSequenceIds[i];
}
}
return highestSequenceId + 1;
}
}
File 2 of 4: ERC20Interface.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.7.5;
/**
* Contract that exposes the needed erc20 token functions
*/
abstract contract ERC20Interface {
// Send _value amount of tokens to address _to
function transfer(address _to, uint256 _value)
public
virtual
returns (bool success);
// Get the account balance of another account with address _owner
function balanceOf(address _owner)
public
virtual
view
returns (uint256 balance);
}
File 3 of 4: Forwarder.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.7.5;
import './TransferHelper.sol';
import './ERC20Interface.sol';
/**
* Contract that will forward any incoming Ether to the creator of the contract
*
*/
contract Forwarder {
// Address to which any funds sent to this contract will be forwarded
address public parentAddress;
event ForwarderDeposited(address from, uint256 value, bytes data);
/**
* Initialize the contract, and sets the destination address to that of the creator
*/
function init(address _parentAddress) external onlyUninitialized {
parentAddress = _parentAddress;
uint256 value = address(this).balance;
if (value == 0) {
return;
}
(bool success, ) = parentAddress.call{ value: value }('');
require(success, 'Flush failed');
// NOTE: since we are forwarding on initialization,
// we don't have the context of the original sender.
// We still emit an event about the forwarding but set
// the sender to the forwarder itself
emit ForwarderDeposited(address(this), value, msg.data);
}
/**
* Modifier that will execute internal code block only if the sender is the parent address
*/
modifier onlyParent {
require(msg.sender == parentAddress, 'Only Parent');
_;
}
/**
* Modifier that will execute internal code block only if the contract has not been initialized yet
*/
modifier onlyUninitialized {
require(parentAddress == address(0x0), 'Already initialized');
_;
}
/**
* Default function; Gets called when data is sent but does not match any other function
*/
fallback() external payable {
flush();
}
/**
* Default function; Gets called when Ether is deposited with no data, and forwards it to the parent address
*/
receive() external payable {
flush();
}
/**
* Execute a token transfer of the full balance from the forwarder token to the parent address
* @param tokenContractAddress the address of the erc20 token contract
*/
function flushTokens(address tokenContractAddress) external onlyParent {
ERC20Interface instance = ERC20Interface(tokenContractAddress);
address forwarderAddress = address(this);
uint256 forwarderBalance = instance.balanceOf(forwarderAddress);
if (forwarderBalance == 0) {
return;
}
TransferHelper.safeTransfer(
tokenContractAddress,
parentAddress,
forwarderBalance
);
}
/**
* Flush the entire balance of the contract to the parent address.
*/
function flush() public {
uint256 value = address(this).balance;
if (value == 0) {
return;
}
(bool success, ) = parentAddress.call{ value: value }('');
require(success, 'Flush failed');
emit ForwarderDeposited(msg.sender, value, msg.data);
}
}
File 4 of 4: TransferHelper.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.7.5;
// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeApprove: approve failed'
);
}
function safeTransfer(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeTransfer: transfer failed'
);
}
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::transferFrom: transferFrom failed'
);
}
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
}
Contract ABI
API[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"msgSender","type":"address"},{"indexed":false,"internalType":"address","name":"otherSigner","type":"address"},{"indexed":false,"internalType":"bytes32","name":"operation","type":"bytes32"}],"name":"BatchTransacted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"BatchTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"msgSender","type":"address"}],"name":"SafeModeActivated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"msgSender","type":"address"},{"indexed":false,"internalType":"address","name":"otherSigner","type":"address"},{"indexed":false,"internalType":"bytes32","name":"operation","type":"bytes32"},{"indexed":false,"internalType":"address","name":"toAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Transacted","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"activateSafeMode","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"forwarderAddress","type":"address"},{"internalType":"address","name":"tokenContractAddress","type":"address"}],"name":"flushForwarderTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getNextSequenceId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"allowedSigners","type":"address[]"}],"name":"init","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"}],"name":"isSigner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"safeMode","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"expireTime","type":"uint256"},{"internalType":"uint256","name":"sequenceId","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"sendMultiSig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"},{"internalType":"uint256","name":"expireTime","type":"uint256"},{"internalType":"uint256","name":"sequenceId","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"sendMultiSigBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"address","name":"tokenContractAddress","type":"address"},{"internalType":"uint256","name":"expireTime","type":"uint256"},{"internalType":"uint256","name":"sequenceId","type":"uint256"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"sendMultiSigToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"signers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.