Skip to content

Instantly share code, notes, and snippets.

@ChristianOConnor
Created May 24, 2023 22:20
Show Gist options
  • Save ChristianOConnor/fe7d339c3b221fa636784e53ab65acf3 to your computer and use it in GitHub Desktop.
Save ChristianOConnor/fe7d339c3b221fa636784e53ab65acf3 to your computer and use it in GitHub Desktop.
Contract to mint Random Classifier NFT with authorized wallet signature
// 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