Created
May 26, 2023 00:26
-
-
Save ChristianOConnor/1a06c5f06b4f88eec188775469a3dab2 to your computer and use it in GitHub Desktop.
Fifth iteration of RandomReachDebug which mints from an array of 3 Classifiers and relies on an authorized private key.
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 RandomReachDebug5 is ERC721URIStorage, Ownable, RrpRequesterV0, EIP712 { | |
using Counters for Counters.Counter; | |
struct RandomRequest { | |
address minter; | |
uint256 nonce; | |
uint256 deadline; | |
} | |
bytes32 private constant _REQUEST_RANDOM_NFT_TYPEHASH = keccak256("RandomRequest(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"); | |
require(nonce == _nonces[minter].current(), "Nonce does not match expected value"); | |
bytes32 structHash = keccak256( | |
abi.encode( | |
_REQUEST_RANDOM_NFT_TYPEHASH, | |
keccak256( | |
abi.encode( | |
minter, // Your struct data | |
nonce, // Your struct data | |
deadline // Your struct data | |
) | |
) | |
) | |
); | |
bytes32 hash = _hashTypedDataV4(structHash); | |
address signer = ECDSA.recover(hash, v, r, s); | |
emit Verified(signer, nonce, minter, r, s, v, signer); | |
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; | |
_nonces[minter].increment(); | |
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 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