⚙️Smart Contract
Our smart contract is ERC721 compatible and has a couple of additional function to batch send prizes, here is a breakdown of our callable functions:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {ERC721, ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol";
contract SparkwaveLogicV2 is
Multicall,
Ownable2Step,
ReentrancyGuard,
EIP712,
ERC721URIStorage
{
using Counters for Counters.Counter;
using ECDSA for bytes32;
struct MintVoucher {
string uuid;
address to;
string uri;
bool soulbound;
}
struct Fees {
uint256 disperseFungible;
uint256 disperseNFT;
}
uint256 private _mintFee;
mapping(address => Fees) private addressFees;
mapping(uint256 => bool) public isSoulbound;
Counters.Counter private _tokenIdCounter;
mapping(uint256 => string) private _tokenURIs;
mapping(bytes32 => bool) public minted;
event Minted(address indexed to, string uri);
constructor()
EIP712("Sparkwave", "2")
ERC721("SparkwaveV2", "SPARKWAVE-2")
{}
function setMintFee(uint256 fee) external onlyOwner {
_mintFee = fee;
}
function setCustomerFees(
address sender,
Fees calldata fees
) external onlyOwner {
addressFees[sender] = fees;
}
function typeHash() internal pure returns (bytes32) {
return
keccak256(
"MintVoucher(string uuid,address to,string uri,bool soulbound)"
);
}
function _hash(
MintVoucher calldata voucher
) internal view returns (bytes32) {
return
_hashTypedDataV4(
keccak256(
abi.encode(
typeHash(),
keccak256(bytes(voucher.uuid)),
voucher.to,
keccak256(bytes(voucher.uri)),
voucher.soulbound
)
)
);
}
/**
* @notice Mint through voucher + signedMessage
*
*/
function mint(
MintVoucher calldata voucher,
bytes calldata signedMessage
) external payable nonReentrant {
// Ensure not minted
address signer = _hash(voucher).recover(signedMessage);
require(signer == owner(), "INVALID_SIGNER");
// If the receiver is `0x0`, send it to the sender.
address to = voucher.to == address(0) ? _msgSender() : voucher.to;
// fee
payable(owner()).transfer(_mintFee);
// check minted
bytes32 uuid = keccak256(abi.encode(voucher.uuid));
require(!minted[uuid], "DUPLICATE_USE");
minted[uuid] = true;
uint256 tokenId = _tokenIdCounter.current();
_mint(to, tokenId);
_setTokenURI(tokenId, voucher.uri);
isSoulbound[tokenId] = voucher.soulbound;
_tokenIdCounter.increment();
emit Minted(voucher.to, voucher.uri);
}
function _transfer(
address from,
address to,
uint256 tokenId
) internal override {
require(!isSoulbound[tokenId], "SOULBOUND");
super._transfer(from, to, tokenId);
}
function disperseNative(
address[] calldata recipients,
uint256[] calldata values
) external payable nonReentrant {
uint256 leftover = msg.value;
uint256 totalSent;
for (uint256 i = 0; i < recipients.length; i++) {
leftover -= values[i];
totalSent += values[i];
payable(recipients[i]).transfer(values[i]);
}
// fee
payable(owner()).transfer(
(totalSent * addressFees[_msgSender()].disperseFungible) / 1e5
);
if (leftover > 0) {
payable(_msgSender()).transfer(leftover);
}
}
function disperseERC20(
IERC20 token,
address[] calldata recipients,
uint256[] calldata values
) external nonReentrant {
uint256 totalSent;
for (uint256 i = 0; i < recipients.length; i++) {
token.transferFrom(_msgSender(), recipients[i], values[i]);
totalSent += values[i];
}
// fee
token.transferFrom(
_msgSender(),
owner(),
(totalSent * addressFees[_msgSender()].disperseFungible) / 1e5
);
}
function disperseERC721(
IERC721 nftContract,
address[] calldata recipients,
uint256[] calldata ids
) external nonReentrant {
require(
nftContract.isApprovedForAll(_msgSender(), address(this)),
"MISSING_APPROVAL"
);
for (uint256 i = 0; i < recipients.length; i++) {
nftContract.safeTransferFrom(_msgSender(), recipients[i], ids[i]);
}
// fee
payable(owner()).transfer(
addressFees[_msgSender()].disperseNFT * recipients.length
);
}
}
Last updated