Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save procorbin/4c0b2f6966b7b61194367321d361fad4 to your computer and use it in GitHub Desktop.

Select an option

Save procorbin/4c0b2f6966b7b61194367321d361fad4 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.34+commit.80d5c536.js&optimize=undefined&runs=200&gist=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
/**
* ShibaBlastarDistributor
*
* Allows users to claim ShibaBlastar tokens using a server-signed voucher.
* The server (Symfony) signs the claim off-chain; the user broadcasts the
* transaction and pays gas fees themselves.
*/
contract ShibaBlastarDistributor is Ownable {
IERC20 public immutable token;
// Address that holds the ShibaBlastar tokens and has approved this contract
address public tokenHolder;
// Nonces used to prevent replay attacks
mapping(uint256 => bool) private _usedNonces;
event TokensClaimed(address indexed recipient, uint256 amount, uint256 nonce);
event TokenHolderUpdated(address indexed previousHolder, address indexed newHolder);
constructor(address tokenAddress, address _tokenHolder) Ownable(msg.sender) {
token = IERC20(tokenAddress);
tokenHolder = _tokenHolder;
}
/**
* Claim tokens using a server-signed voucher.
*
* The server signs off-chain:
* keccak256(abi.encodePacked(recipientAddress, amount, nonce))
*
* @param amount Amount of tokens to claim (in wei, 18 decimals)
* @param nonce Unique identifier for this claim (from server)
* @param signature ECDSA signature produced by the server (owner)
*/
function claimTokens(uint256 amount, uint256 nonce, bytes memory signature) external {
require(!_usedNonces[nonce], "Nonce already used");
require(amount > 0, "Amount must be greater than zero");
bytes32 message = keccak256(abi.encodePacked(msg.sender, amount, nonce));
bytes32 ethSignedMessage = MessageHashUtils.toEthSignedMessageHash(message);
address signer = ECDSA.recover(ethSignedMessage, signature);
require(signer == owner(), "Invalid signature");
_usedNonces[nonce] = true;
require(
token.transferFrom(tokenHolder, msg.sender, amount),
"Token transfer failed"
);
emit TokensClaimed(msg.sender, amount, nonce);
}
/**
* Check if a nonce has already been used.
*/
function isNonceUsed(uint256 nonce) external view returns (bool) {
return _usedNonces[nonce];
}
/**
* Update the token holder address (onlyOwner).
*/
function setTokenHolder(address _tokenHolder) external onlyOwner {
emit TokenHolderUpdated(tokenHolder, _tokenHolder);
tokenHolder = _tokenHolder;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment