Silence Receipt

silence-receipt · receipt-attestation · 2314 B runtime · funds-movement

scores

source

pragma solidity ^0.8.13;

contract SilenceReceipt {
    event Committed(bytes32 indexed commitmentHash, address indexed deliverer, uint64 nonce, uint32 deadline);
    event Disputed(bytes32 indexed commitmentHash, address indexed disputer, bytes32 reasonHash);
    event ReceiptMinted(bytes32 indexed commitmentHash, address indexed deliverer, uint256 tokenId);

    struct Commitment {
        uint64 nonce;
        uint32 deadline;
        bool disputed;
    }

    mapping(bytes32 => Commitment) private _commitments;
    mapping(uint256 => address) public ownerOf;
    mapping(address => uint256) private _balances;

    function commit(bytes32 deliveryHash, uint32 deadline, uint64 nonce) external returns (bytes32 commitmentHash) {
        require(deadline > block.number, 'deadline must be in future');
        commitmentHash = keccak256(abi.encodePacked(msg.sender, deliveryHash, nonce));
        require(_commitments[commitmentHash].deadline == 0, 'commitment already exists');
        _commitments[commitmentHash] = Commitment(nonce, deadline, false);
        emit Committed(commitmentHash, msg.sender, nonce, deadline);
    }

    function dispute(bytes32 commitmentHash, bytes32 reasonHash) external payable {
        require(msg.value >= 1 wei, 'dispute bond too low');
        Commitment storage c = _commitments[commitmentHash];
        require(c.deadline != 0, 'commitment not found');
        require(block.number <= c.deadline, 'deadline passed');
        require(!c.disputed, 'already disputed');
        c.disputed = true;
        emit Disputed(commitmentHash, msg.sender, reasonHash);
    }

    function claim(bytes32 commitmentHash, bytes32 deliveryHash, uint64 nonce) external {
        Commitment storage c = _commitments[commitmentHash];
        require(c.deadline != 0, 'commitment not found');
        require(block.number > c.deadline, 'deadline not passed');
        require(!c.disputed, 'commitment disputed');
        require(c.nonce == nonce, 'nonce mismatch');
        bytes32 expected = keccak256(abi.encodePacked(msg.sender, deliveryHash, nonce));
        require(expected == commitmentHash, 'not the deliverer');
        uint256 tokenId = uint256(commitmentHash);
        require(ownerOf[tokenId] == address(0), 'receipt already minted');
        ownerOf[tokenId] = msg.sender;
        _balances[msg.sender]++;
        emit ReceiptMinted(commitmentHash, msg.sender, tokenId);
    }

    function balanceOf(address account) external view returns (uint256) {
        require(account != address(0), 'zero address');
        return _balances[account];
    }
}