Opt-out payment grant

opt-out-payment-grant · conditional-settlement · 1496 B runtime · funds-movement

scores

source

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

contract OptOutGrant {
    struct Grant {
        uint256 amount;
        address recipient;
        bool active;
    }

    mapping(bytes32 => Grant) public grants;

    event GrantCreated(address indexed donor, address indexed recipient, uint256 deadline, uint256 amount);
    event GrantRejected(address indexed donor, address indexed recipient, uint256 deadline, uint256 amount);
    event GrantClaimed(address indexed donor, address indexed recipient, uint256 deadline, uint256 amount);

    error InvalidDeadline();
    error InvalidAmount();
    error InvalidRecipient();
    error GrantExists();
    error GrantInactive();
    error NotRecipient();
    error NotParty();
    error BeforeDeadline();
    error AfterDeadline();
    error TransferFailed();

    function createGrant(address recipient, uint256 deadline) external payable {
        if (deadline <= block.timestamp) revert InvalidDeadline();
        if (msg.value == 0) revert InvalidAmount();
        if (recipient == address(0)) revert InvalidRecipient();
        bytes32 key = _key(msg.sender, recipient, deadline);
        Grant storage g = grants[key];
        if (g.active) revert GrantExists();

        g.amount = msg.value;
        g.recipient = recipient;
        g.active = true;

        emit GrantCreated(msg.sender, recipient, deadline, msg.value);
    }

    function reject(address donor, address recipient, uint256 deadline) external {
        bytes32 key = _key(donor, recipient, deadline);
        Grant storage g = grants[key];
        if (!g.active) revert GrantInactive();
        if (msg.sender != recipient) revert NotRecipient();
        if (block.timestamp >= deadline) revert AfterDeadline();

        uint256 amount = g.amount;
        g.active = false;

        emit GrantRejected(donor, recipient, deadline, amount);
        _send(donor, amount);
    }

    function claim(address donor, address recipient, uint256 deadline) external {
        bytes32 key = _key(donor, recipient, deadline);
        Grant storage g = grants[key];
        if (!g.active) revert GrantInactive();
        if (msg.sender != donor && msg.sender != recipient) revert NotParty();
        if (block.timestamp < deadline) revert BeforeDeadline();

        uint256 amount = g.amount;
        g.active = false;

        emit GrantClaimed(donor, recipient, deadline, amount);
        _send(recipient, amount);
    }

    function _key(address donor, address recipient, uint256 deadline) private pure returns (bytes32) {
        return keccak256(abi.encodePacked(donor, recipient, deadline));
    }

    function _send(address to, uint256 amount) private {
        (bool ok, ) = payable(to).call{value: amount}("");
        if (!ok) revert TransferFailed();
    }
}