// SPDX-License-Identifier: MIT pragma solidity 0.8.28; import "./lib/forge-std/src/interfaces/IERC20.sol"; // TODO: test deploying and test functions contract knlWallet { struct Transaction { address destination; uint256 value; bool executed; address tokenAddress; } address[] public owners; uint256 public required; uint256 public transactionCount; mapping(uint256 => Transaction) public transactions; mapping(uint256 => mapping(address => bool)) public confirmations; constructor(address[] memory _owners, uint256 _confirmations) { require(_owners.length > 0, "need to have owners"); require(_confirmations > 0, "need to have a number of confirmation"); require( _confirmations <= _owners.length, "need to have more or equals owners than confirmation" ); owners = _owners; required = _confirmations; } modifier isOwner() { for (uint256 i = 0; i < owners.length; i++) { if (owners[i] == msg.sender) { _; return; } } revert("need to be owner"); } receive() external payable { } function isConfirmed(uint256 transactionId) public view returns (bool) { return getConfirmationsCount(transactionId) >= required; } function getConfirmationsCount(uint256 transactionId) public view returns (uint256) { uint256 count; for (uint256 i = 0; i < owners.length; i++) { if (confirmations[transactionId][owners[i]]) { count++; } } return count; } function addTransaction( address destination, uint256 value, address tokenAddress ) public isOwner returns (uint256) { transactions[transactionCount] = Transaction(destination, value, false, tokenAddress); transactionCount += 1; return transactionCount - 1; } function confirmTransaction(uint256 transactionId) public isOwner { confirmations[transactionId][msg.sender] = true; if (isConfirmed(transactionId)) { executeTransaction(transactionId); } } function submitTransaction( address payable dest, uint256 value, address tokenAddress ) external isOwner { uint256 id = addTransaction(dest, value, tokenAddress); confirmTransaction(id); } function executeTransaction(uint256 transactionId) public isOwner { require( isConfirmed(transactionId), "transaction has not been confirmed" ); Transaction storage _tx = transactions[transactionId]; if (_tx.tokenAddress == address(0)) { // Handle Ether transfer (bool success,) = _tx.destination.call{ value: _tx.value }(""); require(success); } else { // Handle ERC-20 token transfer IERC20 token = IERC20(_tx.tokenAddress); require( token.transfer(_tx.destination, _tx.value), "Failed to transfer ERC20 tokens" ); } _tx.executed = true; } }