Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Instant rewards smart contract #171

Merged
merged 7 commits into from
Aug 26, 2024
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract InstantRewards is Ownable, Pausable, ReentrancyGuard {
/* An ECDSA signature. */
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}

struct ClaimData {
address to;
Signature signature;
uint256 sigExpiration;
andresaiello marked this conversation as resolved.
Show resolved Hide resolved
bytes32 taskId;
uint256 amount;
}

mapping(address => mapping(bytes32 => bool)) public taskCompletedByUser;

address public signerAddress;

event Claimed(address indexed to, bytes32 indexed taskId, uint256 amount);

error InvalidSigner();
error SignatureExpired();
error InvalidAddress();
error TaskAlreadyClaimed();
error TransferFailed();

constructor(address signerAddress_, address owner) Ownable() {
if (signerAddress_ == address(0)) revert InvalidAddress();
transferOwnership(owner);
signerAddress = signerAddress_;
}

function _verify(ClaimData memory claimData) private view {
bytes32 payloadHash = _calculateHash(claimData);

bytes32 messageHash = ECDSA.toEthSignedMessageHash(payloadHash);

address messageSigner = ECDSA.recover(
messageHash,
claimData.signature.v,
claimData.signature.r,
claimData.signature.s
);

if (signerAddress != messageSigner) revert InvalidSigner();
if (block.timestamp > claimData.sigExpiration) revert SignatureExpired();
}
Dismissed Show dismissed Hide dismissed
andresaiello marked this conversation as resolved.
Show resolved Hide resolved

// Function to compute the hash of the data and tasks for a token
function _calculateHash(ClaimData memory claimData) private pure returns (bytes32) {
bytes memory encodedData = abi.encode(
claimData.to,
claimData.sigExpiration,
claimData.taskId,
claimData.amount
);

return keccak256(encodedData);
}

function claim(ClaimData memory claimData) external whenNotPaused nonReentrant {
claimData.to = msg.sender;
_verify(claimData);

if (taskCompletedByUser[claimData.to][claimData.taskId]) revert TaskAlreadyClaimed();

taskCompletedByUser[claimData.to][claimData.taskId] = true;

(bool success, ) = claimData.to.call{value: claimData.amount}("");
if (!success) revert TransferFailed();

emit Claimed(claimData.to, claimData.taskId, claimData.amount);
}
Dismissed Show dismissed Hide dismissed
andresaiello marked this conversation as resolved.
Show resolved Hide resolved

function setSignerAddress(address signerAddress_) external onlyOwner {
if (signerAddress_ == address(0)) revert InvalidAddress();
signerAddress = signerAddress_;
}

function withdraw(address wallet, uint256 amount) external onlyOwner {
if (wallet == address(0)) revert InvalidAddress();
if (amount > address(this).balance) revert TransferFailed();
payable(wallet).transfer(amount);
}

receive() external payable {}
}
Loading