{"status":"full","files":[{"name":"immutable-references.json","path":"/contracts/full_match/7120/0xc9D7a61D7C2F428F6A055916488041fD00532110/immutable-references.json","content":"{\"15\":[{\"length\":32,\"start\":307},{\"length\":32,\"start\":1372}]}"},{"name":"metadata.json","path":"/contracts/full_match/7120/0xc9D7a61D7C2F428F6A055916488041fD00532110/metadata.json","content":"{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_owners\",\"type\":\"address[]\"},{\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"AddedOwner\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"threshold\",\"type\":\"uint256\"}],\"name\":\"ChangedThreshold\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"name\":\"ExecutionFailure\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"name\":\"ExecutionSuccess\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"RemovedOwner\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"name\":\"addOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"dataHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"signatures\",\"type\":\"bytes\"}],\"name\":\"checkSignatures\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"operation\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"signatures\",\"type\":\"bytes\"}],\"name\":\"execTransaction\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getOwners\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"operation\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_nonce\",\"type\":\"uint256\"}],\"name\":\"getTransactionHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isOwner\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"owners\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"name\":\"removeOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"threshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"author\":\"Sentrix Labs\",\"details\":\"Inspired by Gnosis Safe v1.4.1 but trimmed to the      execute-from-N-of-M-owners core. No modules, no guards,      no fallback handlers - those are out of scope for canonical      treasury use. Owners sign tx hashes off-chain (EIP-712 typed      hashing); on-chain `execTransaction` verifies threshold + nonce.\",\"kind\":\"dev\",\"methods\":{\"execTransaction(address,uint256,bytes,uint256,bytes)\":{\"params\":{\"data\":\"Calldata.\",\"operation\":\"0 = call, 1 = delegatecall.\",\"signatures\":\"Concatenated 65-byte ECDSA signatures, sorted by signer address ascending.\",\"to\":\"Target address.\",\"value\":\"Native value (wei) to send.\"}}},\"title\":\"SentrixSafe\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"execTransaction(address,uint256,bytes,uint256,bytes)\":{\"notice\":\"Execute a transaction with `threshold` signatures.\"}},\"notice\":\"Minimal multi-signature wallet for treasury management.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/SentrixSafe.sol\":\"SentrixSafe\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/=lib/openzeppelin-contracts/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"contracts/SentrixSafe.sol\":{\"keccak256\":\"0xd4a949530bfe5b8502a05a078c41d8c6fbe777951a0f33dab9d63264755f42f4\",\"license\":\"BUSL-1.1\",\"urls\":[\"bzz-raw://1acd7a8282a3048236e86cbb4c276f38f42d8172a10e4a6512ef177853273695\",\"dweb:/ipfs/QmSV217Ra56QRLXJA1RKuxzN8f5pUYTwTe288cKydkrve7\"]}},\"version\":1}"},{"name":"SentrixSafe.sol","path":"/contracts/full_match/7120/0xc9D7a61D7C2F428F6A055916488041fD00532110/sources/contracts/SentrixSafe.sol","content":"// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.24;\n\n/// @title SentrixSafe\n/// @author Sentrix Labs\n/// @notice Minimal multi-signature wallet for treasury management.\n/// @dev Inspired by Gnosis Safe v1.4.1 but trimmed to the\n///      execute-from-N-of-M-owners core. No modules, no guards,\n///      no fallback handlers - those are out of scope for canonical\n///      treasury use. Owners sign tx hashes off-chain (EIP-712 typed\n///      hashing); on-chain `execTransaction` verifies threshold + nonce.\ncontract SentrixSafe {\n    // ── Storage ──────────────────────────────────────────────\n    address[] public owners;\n    mapping(address => bool) public isOwner;\n    uint256 public threshold;\n    uint256 public nonce;\n\n    // EIP-712 domain separator\n    bytes32 public immutable DOMAIN_SEPARATOR;\n    bytes32 private constant TX_TYPEHASH = keccak256(\n        \"SafeTx(address to,uint256 value,bytes data,uint256 operation,uint256 nonce)\"\n    );\n\n    // ── Events ───────────────────────────────────────────────\n    event ExecutionSuccess(bytes32 indexed txHash, uint256 nonce);\n    event ExecutionFailure(bytes32 indexed txHash, uint256 nonce);\n    event AddedOwner(address indexed owner);\n    event RemovedOwner(address indexed owner);\n    event ChangedThreshold(uint256 threshold);\n\n    // ── Constructor ──────────────────────────────────────────\n    constructor(address[] memory _owners, uint256 _threshold) {\n        require(_owners.length > 0, \"Safe: no owners\");\n        require(_threshold > 0 && _threshold <= _owners.length, \"Safe: invalid threshold\");\n\n        for (uint256 i = 0; i < _owners.length; i++) {\n            address owner = _owners[i];\n            require(owner != address(0) && owner != address(this), \"Safe: invalid owner\");\n            require(!isOwner[owner], \"Safe: duplicate owner\");\n            isOwner[owner] = true;\n            owners.push(owner);\n            emit AddedOwner(owner);\n        }\n        threshold = _threshold;\n        emit ChangedThreshold(_threshold);\n\n        DOMAIN_SEPARATOR = keccak256(\n            abi.encode(\n                keccak256(\"EIP712Domain(uint256 chainId,address verifyingContract)\"),\n                block.chainid,\n                address(this)\n            )\n        );\n    }\n\n    // ── Core: execute with N-of-M signatures ─────────────────\n    /// @notice Execute a transaction with `threshold` signatures.\n    /// @param to Target address.\n    /// @param value Native value (wei) to send.\n    /// @param data Calldata.\n    /// @param operation 0 = call, 1 = delegatecall.\n    /// @param signatures Concatenated 65-byte ECDSA signatures, sorted by signer address ascending.\n    function execTransaction(\n        address to,\n        uint256 value,\n        bytes calldata data,\n        uint256 operation,\n        bytes calldata signatures\n    ) external returns (bool success) {\n        bytes32 txHash = getTransactionHash(to, value, data, operation, nonce);\n        checkSignatures(txHash, signatures);\n        nonce++;\n\n        if (operation == 1) {\n            assembly {\n                let dataPtr := add(data.offset, 0)\n                success := delegatecall(gas(), to, dataPtr, data.length, 0, 0)\n            }\n        } else {\n            (success, ) = to.call{value: value}(data);\n        }\n\n        if (success) {\n            emit ExecutionSuccess(txHash, nonce - 1);\n        } else {\n            emit ExecutionFailure(txHash, nonce - 1);\n        }\n    }\n\n    // ── Signature verification ───────────────────────────────\n    function checkSignatures(bytes32 dataHash, bytes calldata signatures) public view {\n        uint256 _threshold = threshold;\n        require(signatures.length >= _threshold * 65, \"Safe: signatures too short\");\n\n        address lastOwner = address(0);\n        for (uint256 i = 0; i < _threshold; i++) {\n            address currentOwner = recoverSigner(dataHash, signatures, i);\n            require(currentOwner > lastOwner, \"Safe: signatures not sorted\");\n            require(isOwner[currentOwner], \"Safe: signer not owner\");\n            lastOwner = currentOwner;\n        }\n    }\n\n    function recoverSigner(bytes32 dataHash, bytes calldata signatures, uint256 pos) internal pure returns (address) {\n        uint8 v;\n        bytes32 r;\n        bytes32 s;\n        assembly {\n            let sigPtr := add(signatures.offset, mul(pos, 65))\n            r := calldataload(sigPtr)\n            s := calldataload(add(sigPtr, 32))\n            v := byte(0, calldataload(add(sigPtr, 64)))\n        }\n        return ecrecover(dataHash, v, r, s);\n    }\n\n    function getTransactionHash(\n        address to,\n        uint256 value,\n        bytes calldata data,\n        uint256 operation,\n        uint256 _nonce\n    ) public view returns (bytes32) {\n        bytes32 structHash = keccak256(abi.encode(\n            TX_TYPEHASH,\n            to,\n            value,\n            keccak256(data),\n            operation,\n            _nonce\n        ));\n        return keccak256(abi.encodePacked(\"\\x19\\x01\", DOMAIN_SEPARATOR, structHash));\n    }\n\n    // ── Receive native SRX ──────────────────────────────────\n    receive() external payable {}\n\n    // ── Owner management (self-call only via execTransaction) ─\n    function addOwner(address owner, uint256 _threshold) external {\n        require(msg.sender == address(this), \"Safe: self-call only\");\n        require(owner != address(0) && !isOwner[owner], \"Safe: invalid or existing owner\");\n        isOwner[owner] = true;\n        owners.push(owner);\n        emit AddedOwner(owner);\n        if (_threshold != threshold) {\n            require(_threshold > 0 && _threshold <= owners.length, \"Safe: invalid threshold\");\n            threshold = _threshold;\n            emit ChangedThreshold(_threshold);\n        }\n    }\n\n    function removeOwner(address owner, uint256 _threshold) external {\n        require(msg.sender == address(this), \"Safe: self-call only\");\n        require(isOwner[owner], \"Safe: not owner\");\n        require(owners.length - 1 >= _threshold, \"Safe: threshold too high\");\n\n        isOwner[owner] = false;\n        for (uint256 i = 0; i < owners.length; i++) {\n            if (owners[i] == owner) {\n                owners[i] = owners[owners.length - 1];\n                owners.pop();\n                break;\n            }\n        }\n        emit RemovedOwner(owner);\n\n        if (_threshold != threshold) {\n            require(_threshold > 0, \"Safe: invalid threshold\");\n            threshold = _threshold;\n            emit ChangedThreshold(_threshold);\n        }\n    }\n\n    function getOwners() external view returns (address[] memory) {\n        return owners;\n    }\n}\n"}]}