Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
"
},
"contracts/CFHelpersStatev0.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import "@openzeppelin/contracts/access/Ownable.sol";
interface ICFHelpersVaultFactory {
function createVault(bytes32 clientId) external returns (address);
}
contract CFHelpersStatev0 {
// Error codes
error UnauthorizedUser();
error UserExists();
error AddressExists();
error KeyExists();
error InvalidInvite();
error InviteReplay();
error InviteExpired();
error InviteExists();
error InvalidPreimage();
error InactiveUser();
error InvalidUser();
error InvalidPosition();
error MinAdmins();
error PolicyReplay();
error UnauthorizedCaller();
error InvalidPolicy();
error InvalidHelper();
error InvalidPolicyType();
error InvalidClient();
// Structs
struct User {
address addr;
string position;
bool isAdmin;
bool active;
}
struct Policy {
bool active;
string title;
string policyType;
address[] signers;
uint256 threshold;
Transaction transactionDetails;
Transfer transferDetails;
bool isTransaction;
}
struct Transaction {
address wrapper;
string func;
}
struct Transfer {
uint256 maxAmount;
address token;
address[] recipients;
}
struct Invite {
bool activated;
string userId;
bool isAdmin;
string userPosition;
uint256 expireHeight;
}
// Mappings
mapping(string => address) public helperContracts;
mapping(address => bool) public isCofundAdmin;
mapping(string => bool) public isSupportedType;
mapping(bytes32 => mapping(bytes32 => Invite)) public invites;
mapping(bytes32 => mapping(string => Policy)) public policies;
mapping(bytes32 => mapping(string => User)) public users;
mapping(address => mapping(bytes32 => string)) public usersByAddress;
mapping(bytes32 => uint256) public activeAdmins;
mapping(bytes32 => mapping(address => mapping(string => bool))) private clientContractAuthBool;
mapping(bytes32 => address) public clientVaults;
// Events
event UserInviteAdded(bytes32 indexed clientId, bytes32 inviteHash, string userId, string position);
event UserInviteCompleted(bytes32 indexed clientId, bytes32 inviteHash, address userAddress);
event UserRemoved(bytes32 indexed clientId, string userId, string removedUserId);
event UserRotated(bytes32 indexed clientId, string userId, bytes newKey);
event PolicyActivated(bytes32 indexed clientId, string policyId);
event PolicyDeactivated(bytes32 indexed clientId, string policyId);
event DebugSetClientContractAuthBool(bytes32 clientId, address contractAddr, string authId, bool value);
event NewClientCreated(bytes32 indexed clientId, bytes32 inviteHash, string adminId);
constructor(address initialAdmin) {
// Initialize helper contracts
isCofundAdmin[initialAdmin] = true;
helperContracts["signatures"] = address(0);
isSupportedType["Contractor_Stipend"] = true;
isSupportedType["Crypto_Onramp"] = true;
isSupportedType["Business_Invoice"] = true;
isSupportedType["Operational_Expense"] = true;
isSupportedType["Treasury_Management"] = true;
// Manually create initial admin user (userId = "0")
bytes32 clientId = 0x16cbd0716887fd9259f39d403e19eb3436e3bdf3c17a37035cf0f8f0d7851e0b;
users[clientId]["0"] = User({
addr: initialAdmin,
position: "admin",
isAdmin: true,
active: true
});
usersByAddress[initialAdmin][clientId] = "0";
activeAdmins[clientId] = 1;
}
function setHelperContract(string memory contractType, address contractAddress) external {
// Check that caller is a Cofund Admin
if (!isCofundAdmin[msg.sender]) revert UnauthorizedCaller();
// Update helper contracts mapping
helperContracts[contractType] = contractAddress;
}
function setSupportedType(string memory policyType) external {
// Check that caller is a Cofund Admin
if (!isCofundAdmin[msg.sender]) revert UnauthorizedCaller();
// Update supported type
isSupportedType[policyType] = true;
}
function setClientContractAuthBool(bytes32 clientId, address contractAddr, string memory authId, bool value) external {
// Replay protection: only allow setting if not already set
if (clientContractAuthBool[clientId][contractAddr][authId]) revert KeyExists();
emit DebugSetClientContractAuthBool(clientId, contractAddr, authId, value);
clientContractAuthBool[clientId][contractAddr][authId] = value;
}
function getClientContractAuthBool(bytes32 clientId, address contractAddr, string memory authId) external view returns (bool) {
return clientContractAuthBool[clientId][contractAddr][authId];
}
// User Functions
function addUserInvite(
bytes32 clientId,
bytes32 inviteHash,
string memory callerId,
string memory newUserId,
bool isAdmin,
string memory newUserPosition
) external returns (bool) {
// Checks that caller is the active "users" contract
if (msg.sender != helperContracts["users"]) revert UnauthorizedCaller();
// Fetch calling user
User storage caller = users[clientId][callerId];
// Check that caller is active
if (!caller.active) revert InactiveUser();
// Check that caller address matches tx.origin
if (caller.addr != tx.origin) revert UnauthorizedUser();
// Check that invite does not exist
if (invites[clientId][inviteHash].expireHeight != 0) revert InviteExists();
// Check that new user does not exist
if (users[clientId][newUserId].addr != address(0)) revert UserExists();
// Add invite, expires in 1 day (7160 blocks)
invites[clientId][inviteHash] = Invite({
activated: false,
userId: newUserId,
isAdmin: isAdmin,
userPosition: newUserPosition,
expireHeight: block.number + 7160
});
// Emit event
emit UserInviteAdded(clientId, inviteHash, newUserId, newUserPosition);
return true;
}
function completeUserInvite(
bytes32 clientId,
bytes32 inviteHash,
uint256 invitePreimageId
) external returns (bool) {
Invite storage invite = invites[clientId][inviteHash];
if (invite.expireHeight == 0) revert InvalidInvite();
if (invite.activated) revert InviteReplay();
if (block.number > invite.expireHeight) revert InviteExpired();
// Verify preimage hash
bytes32 computedHash = keccak256(abi.encodePacked(
keccak256(abi.encodePacked(invitePreimageId)),
keccak256(abi.encodePacked(clientId))
));
if (computedHash != inviteHash) revert InvalidPreimage();
// Add new user
users[clientId][invite.userId] = User({
addr: tx.origin,
position: invite.userPosition,
isAdmin: invite.isAdmin,
active: true
});
usersByAddress[tx.origin][clientId] = invite.userId;
if (invite.isAdmin) {
activeAdmins[clientId]++;
}
invite.activated = true;
emit UserInviteCompleted(clientId, inviteHash, tx.origin);
return true;
}
function removeUser(
bytes32 clientId,
string memory userId,
string memory removedUserId,
uint256 adminSignedDataOptLength
) external returns (bool) {
// Check that caller is the active "users" contract
if (msg.sender != helperContracts["users"]) revert UnauthorizedCaller();
// Fetch calling user
User storage removedUser = users[clientId][removedUserId];
// Update user to inactive
users[clientId][removedUserId] = User({
addr: removedUser.addr,
isAdmin: removedUser.isAdmin,
position: removedUser.position,
active: false
});
// Emit event
emit UserRemoved(clientId, userId, removedUserId);
return true;
}
// Policy Functions
// Activate Policy
function activatePolicy(
bytes32 clientId,
string memory callerId,
string memory policyId,
string memory policyType,
string memory title,
address[] memory signers,
uint256 threshold,
Transaction memory transactionDetails,
Transfer memory transferDetails,
bool isTransaction
) external returns (bool) {
// Check that caller is the active "policies" contract
if (msg.sender != helperContracts["policies"]) revert UnauthorizedCaller();
// Fetch User
User storage caller = users[clientId][callerId];
// Check that user is active
if (!caller.active) revert InactiveUser();
// Check that policy does not exist
if (policies[clientId][policyId].active) revert InvalidPolicy();
// Check that policy type is supported
if (!isSupportedType[policyType]) revert InvalidPolicyType();
// Update policy
policies[clientId][policyId] = Policy({
active: true,
title: title,
policyType: policyType,
signers: signers,
threshold: threshold,
transactionDetails: transactionDetails,
transferDetails: transferDetails,
isTransaction: isTransaction
});
emit PolicyActivated(clientId, policyId);
return true;
}
// Deactivate Policy
function deactivatePolicy(
bytes32 clientId,
string memory callerId,
string memory policyId
) external returns (bool) {
if (msg.sender != helperContracts["policies"]) revert UnauthorizedCaller();
User storage caller = users[clientId][callerId];
if (!caller.active) revert InactiveUser();
Policy storage policy = policies[clientId][policyId];
if (!policy.active) revert InvalidPolicy();
policy.active = false;
emit PolicyDeactivated(clientId, policyId);
return true;
}
// Activate new client
function newClient(
bytes32 clientId,
bytes32 inviteHash,
string memory adminId
) external returns (bool) {
// Check that caller is a Cofund Admin
if (!isCofundAdmin[msg.sender]) revert UnauthorizedCaller();
// Check that invite doesn't exist
if (invites[clientId][inviteHash].expireHeight != 0) revert InviteExists();
// Create new invite for first admin
invites[clientId][inviteHash] = Invite({
activated: false,
userId: adminId,
isAdmin: true,
userPosition: "admin",
expireHeight: block.number + 7160 // 1 day in blocks
});
// Create new vault for client
address vaultFactory = helperContracts["vaults"];
if (vaultFactory == address(0)) revert InvalidHelper();
address vault = ICFHelpersVaultFactory(vaultFactory).createVault(clientId);
clientVaults[clientId] = vault;
emit NewClientCreated(clientId, inviteHash, adminId);
return true;
}
// View Functions
function getUser(bytes32 clientId, string memory userId) external view returns (User memory) {
return users[clientId][userId];
}
function getPolicy(bytes32 clientId, string memory policyId) external view returns (Policy memory) {
return policies[clientId][policyId];
}
function getActiveAdmins(bytes32 clientId) external view returns (uint256) {
return activeAdmins[clientId];
}
function getInvite(bytes32 clientId, bytes32 inviteHash) external view returns (Invite memory) {
return invites[clientId][inviteHash];
}
function getUserIdByAddress(address userAddress, bytes32 clientId) external view returns (string memory) {
return usersByAddress[userAddress][clientId];
}
}"
}
},
"settings": {
"viaIR": true,
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
}
}
}}
Submitted on: 2025-09-22 16:02:09
Comments
Log in to comment.
No comments yet.