Opt-out payment grant
opt-out-payment-grant · conditional-settlement · 1496 B runtime · funds-movement
scores
- usefulness: 78
- safety: 75
- liveness: 60
- authenticity: 95
- extensibility: 70
- bytecodeDiscipline: 92
- practical: 75
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();
}
}