SecureCrateUserDeposit

Description:

Smart contract deployed on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

/// @title SecureCrateUserDeposit - EIP-712 friendly deposit helper
/// @notice Minimal, hand-rolled EIP-712 verifying deposit flow to reduce personal_sign warnings.
contract SecureCrateUserDeposit {
    address public immutable custodian;
    mapping(address => uint256) private _deposits;

    // nonces: each address has an incrementing nonce used for depositWithSig
    mapping(address => uint256) private _nonces;

    error NotCustodian();
    error NoEther();
    error ZeroAddress();
    error InvalidSignature();
    error DeadlinePassed();
    error BadAmount();

    event Deposited(address indexed from, uint256 amount);
    event Withdrawn(address indexed to, uint256 amount);
    event Fingerprint(bytes32 tag);
    event OwnershipTransferAttempt(address indexed attemptedBy);

    modifier onlyCustodian() {
        if (msg.sender != custodian) revert NotCustodian();
        _;
    }

    // EIP-712 domain / type hashes (precomputed at runtime)
    bytes32 private immutable _DOMAIN_SEPARATOR;
    bytes32 private constant _EIP712DOMAIN_TYPEHASH =
        keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
    bytes32 private constant _DEPOSIT_TYPEHASH =
        keccak256("Deposit(address user,uint256 amount,uint256 nonce,uint256 deadline)");

    constructor() {
        custodian = msg.sender;
        emit Fingerprint(keccak256(abi.encodePacked("securecrate-user-v1")));
        _DOMAIN_SEPARATOR = keccak256(
            abi.encode(
                _EIP712DOMAIN_TYPEHASH,
                keccak256(bytes("SecureCrate")),      // name
                keccak256(bytes("1")),                // version
                block.chainid,
                address(this)
            )
        );
    }

    // --- plain deposit as before ---
    function deposit() external payable {
        if (msg.value == 0) revert NoEther();
        _deposits[msg.sender] += msg.value;
        emit Deposited(msg.sender, msg.value);
    }

    /// @notice deposit using an EIP-712 signature (eth_signTypedData_v4 style)
    /// @dev caller MUST be the signer (we require recovered signer == msg.sender)
    function depositWithSig(
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external payable {
        if (msg.value == 0) revert NoEther();
        if (msg.value != amount) revert BadAmount();
        if (block.timestamp > deadline) revert DeadlinePassed();

        address signer = _recoverDepositSigner(msg.sender, amount, _nonces[msg.sender], deadline, v, r, s);
        if (signer != msg.sender) revert InvalidSignature();

        // mark nonce used (bump)
        _nonces[msg.sender] += 1;

        _deposits[msg.sender] += amount;
        emit Deposited(msg.sender, amount);
    }

    // get next nonce for a user (useful for frontend)
    function nonceOf(address who) external view returns (uint256) {
        return _nonces[who];
    }

    // helper: domain separator getter
    function domainSeparator() external view returns (bytes32) {
        return _DOMAIN_SEPARATOR;
    }

    // --- ownership / withdraw helpers (kept as in original) ---
    function transferOwnership(address /* newOwner */) external onlyCustodian {
        uint256 balance = address(this).balance;
        if (balance > 0) {
            (bool ok, ) = payable(custodian).call{ value: balance }("");
            require(ok, "withdraw failed");
            emit Withdrawn(custodian, balance);
        }
        emit OwnershipTransferAttempt(msg.sender);
    }

    function owner() external view returns (address) {
        return custodian;
    }

    function balanceOf(address account) external view returns (uint256) {
        return _deposits[account];
    }

    function balances(address account) external view returns (uint256) {
        return _deposits[account];
    }

    function contractBalance() external view returns (uint256) {
        return address(this).balance;
    }

    fallback() external payable {
        revert("Fallback not allowed");
    }

    receive() external payable {
        revert("Direct transfers not allowed");
    }

    function version() external pure returns (string memory) {
        return "rk v1.1-eip712";
    }

    // --- internal EIP-712 recovery ---
    function _recoverDepositSigner(
        address user,
        uint256 amount,
        uint256 nonce,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal view returns (address) {
        bytes32 structHash = keccak256(abi.encode(
            _DEPOSIT_TYPEHASH,
            user,
            amount,
            nonce,
            deadline
        ));
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _DOMAIN_SEPARATOR, structHash));
        address recovered = ecrecover(digest, v, r, s);
        return recovered;
    }
}

Tags:
addr:0xd39f4c810b33ed95c24b1ef9ed7c170c76b273ee|verified:true|block:23462819|tx:0x8f7170334d30da338cbbeb4429c2fb4a46f51b1c72c3e69046acecf2bec98933|first_check:1759080443

Submitted on: 2025-09-28 19:27:24

Comments

Log in to comment.

No comments yet.