// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SecureMultiWallet {
event FundsDeposited(address indexed depositor, uint amount, uint newBalance);
event TransactionSubmitted(
address indexed initiator,
uint indexed txID,
address indexed target,
uint amount,
bytes payload
);
event TransactionConfirmed(address indexed approver, uint indexed txID);
event ConfirmationRevoked(address indexed approver, uint indexed txID);
event TransactionExecuted(address indexed executor, uint indexed txID);
address[] public authorizedUsers;
mapping(address => bool) public isAuthorized;
uint public requiredApprovals;
struct PendingTransaction {
address target;
uint amount;
bytes payload;
bool hasBeenExecuted;
uint approvalCount;
}
// mapping from tx ID => approver => bool
mapping(uint => mapping(address => bool)) public hasConfirmed;
PendingTransaction[] public pendingTransactions;
modifier onlyAuthorized() {
require(isAuthorized[msg.sender], "Unauthorized");
_;
}
modifier transactionExists(uint _txID) {
require(_txID < pendingTransactions.length, "Transaction not found");
_;
}
modifier notYetExecuted(uint _txID) {
require(!pendingTransactions[_txID].hasBeenExecuted, "Transaction already executed");
_;
}
modifier notYetConfirmed(uint _txID) {
require(!hasConfirmed[_txID][msg.sender], "Transaction already approved");
_;
}
constructor(address[] memory _users, uint _requiredApprovals) {
require(_users.length > 0, "Users required");
require(
_requiredApprovals > 0 &&
_requiredApprovals <= _users.length,
"Invalid approval count"
);
for (uint i = 0; i < _users.length; i++) {
address user = _users[i];
require(user != address(0), "Invalid user");
require(!isAuthorized[user], "Duplicate user");
isAuthorized[user] = true;
authorizedUsers.push(user);
}
requiredApprovals = _requiredApprovals;
}
receive() external payable {
emit FundsDeposited(msg.sender, msg.value, address(this).balance);
}
function addTransaction(
address _target,
uint _amount,
bytes memory _payload
) public onlyAuthorized {
uint txID = pendingTransactions.length;
pendingTransactions.push(
PendingTransaction({
target: _target,
amount: _amount,
payload: _payload,
hasBeenExecuted: false,
approvalCount: 0
})
);
emit TransactionSubmitted(msg.sender, txID, _target, _amount, _payload);
}
function approveTransaction(
uint _txID
) public onlyAuthorized transactionExists(_txID) notYetExecuted(_txID) notYetConfirmed(_txID) {
PendingTransaction storage pendingTx = pendingTransactions[_txID];
pendingTx.approvalCount += 1;
hasConfirmed[_txID][msg.sender] = true;
emit TransactionConfirmed(msg.sender, _txID);
}
function runTransaction(
uint _txID
) public onlyAuthorized transactionExists(_txID) notYetExecuted(_txID) {
PendingTransaction storage pendingTx = pendingTransactions[_txID];
require(
pendingTx.approvalCount >= requiredApprovals,
"Insufficient approvals"
);
pendingTx.hasBeenExecuted = true;
(bool success, ) = pendingTx.target.call{value: pendingTx.amount}(
pendingTx.payload
);
require(success, "Transaction execution failed");
emit TransactionExecuted(msg.sender, _txID);
}
function retractApproval(
uint _txID
) public onlyAuthorized transactionExists(_txID) notYetExecuted(_txID) {
PendingTransaction storage pendingTx = pendingTransactions[_txID];
require(hasConfirmed[_txID][msg.sender], "No prior approval found");
pendingTx.approvalCount -= 1;
hasConfirmed[_txID][msg.sender] = false;
emit ConfirmationRevoked(msg.sender, _txID);
}
function listUsers() public view returns (address[] memory) {
return authorizedUsers;
}
function countTransactions() public view returns (uint) {
return pendingTransactions.length;
}
function fetchTransaction(
uint _txID
)
public
view
returns (
address target,
uint amount,
bytes memory payload,
bool hasBeenExecuted,
uint approvalCount
)
{
PendingTransaction storage pendingTx = pendingTransactions[_txID];
return (
pendingTx.target,
pendingTx.amount,
pendingTx.payload,
pendingTx.hasBeenExecuted,
pendingTx.approvalCount
);
}
}
{
"compilationTarget": {
"src/MultiSig.sol": "SecureMultiWallet"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [],
"viaIR": true
}
[{"inputs":[{"internalType":"address[]","name":"_users","type":"address[]"},{"internalType":"uint256","name":"_requiredApprovals","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"approver","type":"address"},{"indexed":true,"internalType":"uint256","name":"txID","type":"uint256"}],"name":"ConfirmationRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newBalance","type":"uint256"}],"name":"FundsDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"approver","type":"address"},{"indexed":true,"internalType":"uint256","name":"txID","type":"uint256"}],"name":"TransactionConfirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"executor","type":"address"},{"indexed":true,"internalType":"uint256","name":"txID","type":"uint256"}],"name":"TransactionExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"initiator","type":"address"},{"indexed":true,"internalType":"uint256","name":"txID","type":"uint256"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"payload","type":"bytes"}],"name":"TransactionSubmitted","type":"event"},{"inputs":[{"internalType":"address","name":"_target","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bytes","name":"_payload","type":"bytes"}],"name":"addTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_txID","type":"uint256"}],"name":"approveTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"authorizedUsers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"countTransactions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_txID","type":"uint256"}],"name":"fetchTransaction","outputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"bool","name":"hasBeenExecuted","type":"bool"},{"internalType":"uint256","name":"approvalCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"hasConfirmed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isAuthorized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"listUsers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"pendingTransactions","outputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"payload","type":"bytes"},{"internalType":"bool","name":"hasBeenExecuted","type":"bool"},{"internalType":"uint256","name":"approvalCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"requiredApprovals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_txID","type":"uint256"}],"name":"retractApproval","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_txID","type":"uint256"}],"name":"runTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]