Created
May 24, 2023 22:20
-
-
Save ChristianOConnor/fe7d339c3b221fa636784e53ab65acf3 to your computer and use it in GitHub Desktop.
Contract to mint Random Classifier NFT with authorized wallet signature
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: UNLICENSED | |
pragma solidity ^0.8.18; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; | |
import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol"; | |
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | |
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; | |
import "@openzeppelin/contracts/utils/Counters.sol"; | |
contract RandomReachDebug2 is ERC721URIStorage, Ownable, RrpRequesterV0, EIP712 { | |
using Counters for Counters.Counter; | |
bytes32 private constant _REQUEST_RANDOM_NFT_TYPEHASH = keccak256("Request(address minter,uint256 nonce,uint256 deadline)"); | |
event RequestedRandom(bytes32 indexed requestId); | |
event MintedRandomNFT(bytes32 indexed requestId, uint256 response); | |
event MintCostChanged(uint256 newCost); | |
event Withdrawn(address indexed to, uint256 amount); | |
event Verified(address indexed signer, uint256 nonce, address from, bytes32 sigR, bytes32 sigS, uint8 sigV, address recovered); | |
address public airnode; | |
bytes32 public endpointIdUint256; | |
address public sponsorWallet; | |
address public authorizedAccount; | |
uint256 public mintCost = 0.01 ether; | |
uint256 public constant MAX_MINTS_PER_ADDRESS = 3; | |
Counters.Counter private _tokenIdTracker; | |
mapping(address => Counters.Counter) private _nonces; | |
mapping(uint256 => Classifier) public tokenIdToClassifier; | |
mapping(bytes32 => bool) public awaitingFulfillment; | |
mapping(bytes32 => address) public requestToMinter; | |
mapping(address => uint256) public minterToMintCount; | |
enum Classifier {FIRST, SECOND, THIRD} | |
string public firstUri; | |
string public secondUri; | |
string public thirdUri; | |
string private constant ERR_INVALID_SIGNER = "INVALID_SIGNER"; | |
string private constant ERR_REQUEST_ID_UNKNOWN = "Request ID not known"; | |
string private constant ERR_MINT_COST_NOT_MET = "Minting cost not met"; | |
string private constant ERR_MINT_LIMIT_REACHED = "Mint limit reached"; | |
string private constant ERR_VERIFICATION_FAILED = "Verification failed"; | |
constructor(string memory name, string memory symbol, address _airnodeRrp) | |
RrpRequesterV0(_airnodeRrp) | |
ERC721(name, symbol) | |
EIP712(name, "1") {} | |
function setParameters( | |
address _airnode, | |
bytes32 _endpointIdUint256, | |
address _sponsorWallet, | |
address _authorizedAccount | |
) external onlyOwner() { | |
airnode = _airnode; | |
endpointIdUint256 = _endpointIdUint256; | |
sponsorWallet = _sponsorWallet; | |
authorizedAccount = _authorizedAccount; | |
} | |
function setURIs( | |
string calldata _firstUri, | |
string calldata _secondUri, | |
string calldata _thirdUri | |
) external onlyOwner() { | |
firstUri = _firstUri; | |
secondUri = _secondUri; | |
thirdUri = _thirdUri; | |
} | |
function setMintCost(uint256 _newCost) public onlyOwner() { | |
mintCost = _newCost; | |
emit MintCostChanged(_newCost); | |
} | |
function requestRandomNFT( | |
address minter, | |
uint256 nonce, | |
uint256 deadline, | |
uint8 v, | |
bytes32 r, | |
bytes32 s | |
) external payable { | |
require(block.timestamp <= deadline, "Request has expired"); | |
bytes32 structHash = keccak256( | |
abi.encode(_REQUEST_RANDOM_NFT_TYPEHASH, minter, nonce, deadline) | |
); | |
bytes32 hash = _hashTypedDataV4(structHash); | |
address signer = ECDSA.recover(hash, v, r, s); | |
require(signer == authorizedAccount, "Invalid signature"); | |
bytes32 requestId = airnodeRrp.makeFullRequest( | |
airnode, | |
endpointIdUint256, | |
address(this), | |
sponsorWallet, | |
address(this), | |
this.fulfill.selector, | |
"" | |
); | |
awaitingFulfillment[requestId] = true; | |
requestToMinter[requestId] = msg.sender; | |
emit RequestedRandom(requestId); | |
} | |
function fulfill(bytes32 requestId, bytes calldata data) | |
external | |
onlyAirnodeRrp | |
{ | |
require(awaitingFulfillment[requestId], ERR_REQUEST_ID_UNKNOWN); | |
uint256 newId = _tokenIdTracker.current(); | |
_tokenIdTracker.increment(); | |
uint256 randomUint256 = abi.decode(data, (uint256)); | |
Classifier classifier = Classifier(randomUint256 % 3); | |
tokenIdToClassifier[newId] = classifier; | |
_safeMint(requestToMinter[requestId], newId); | |
minterToMintCount[requestToMinter[requestId]]++; | |
awaitingFulfillment[requestId] = false; | |
if (classifier == Classifier.FIRST) { | |
_setTokenURI(newId, firstUri); | |
} else if (classifier == Classifier.SECOND) { | |
_setTokenURI(newId, secondUri); | |
} else if (classifier == Classifier.THIRD) { | |
_setTokenURI(newId, thirdUri); | |
} | |
emit MintedRandomNFT(requestId, randomUint256); | |
} | |
function nonces(address minter) public view returns (uint256) { | |
return _nonces[minter].current(); | |
} | |
function _useNonce(address minter) internal virtual returns (uint256 current) { | |
Counters.Counter storage nonce = _nonces[minter]; | |
current = nonce.current(); | |
nonce.increment(); | |
} | |
function withdraw() external onlyOwner() { | |
uint balance = address(this).balance; | |
payable(owner()).transfer(balance); | |
emit Withdrawn(owner(), balance); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment