// contracts/Achievement.sol// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.0/contracts/access/Ownable.sol";
import"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.0/contracts/token/ERC721/ERC721.sol";
import"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.0/contracts/utils/cryptography/EIP712.sol";
import"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.0/contracts/utils/Counters.sol";
import { ECDSA } from"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.0/contracts/utils/cryptography/ECDSA.sol";
import {
AccessDenied,
EIP712Signature,
InvalidEAS,
InvalidLength,
InvalidSignature,
NotFound,
NO_EXPIRATION_TIME
} from"./Common.sol";
import { TrustedForwarderManager } from"./TrustedForwarderManager.sol";
/**
* @dev A struct representing the arguments of the claiming request.
*/structClaimingRequestData {
address recipient; // The recipient of the claiming.uint256 identifier; // The identifier of the NFT.bytes data; // Custom claiming data.
}
/**
* @dev A struct representing the full arguments of the claiming request.
*/structClaimingRequest {
ClaimingRequestData data; // The arguments of the claiming request.
EIP712Signature signature; // The EIP712 signature data.uint64 deadline; // The deadline of the signature/request.
}
/**
* @title Achievement
* @author Dingning-aspecta
* @notice This contract aims to provide an ownable ERC721 that supports ERC2771
* @dev Only ClaimingRequest that signed by the owner will be executed successfully
*/contractAchievementisERC721, EIP712, Ownable, TrustedForwarderManager{
usingCountersforCounters.Counter;
Counters.Counter private _tokenIds;
errorDeadlineExpired();
errorUsedSignature();
// Emitted when a new NFT is claimed to help off-chain services to identify the NFT.eventClaimToken(uint256indexed tokenId, uint256indexed identifier);
// Replay protection signatures.mapping(bytes signature =>bool used) private _signatures;
// Mapping from user address to claimed tokens.mapping(address=>uint256[]) private _userTokens;
stringpublic baseURI;
// Claiming cost, in weiuint256public claimCost;
/**
* @notice Contract constructor.
*
* @param initialBaseURI initial base URI of tokens.
*
* @param trustedForwarder initial address of trusted forwarder.
*/constructor(stringmemory initialBaseURI, address trustedForwarder) ERC721("Aspecta Builders Achievement", "ASP") EIP712("Aspecta Builder Achievement Regularly", "0.1") Ownable() {
baseURI = initialBaseURI;
claimCost =0.00ether;
_setTrustedForwarder(trustedForwarder);
}
/**
* @dev Only the owner can set new trusted forwarder.
*
* @param _trustedForwarder The arguments of the new trusted forwarder.
*/functionsetTrustedForwarder(address _trustedForwarder) publiconlyOwner{
_setTrustedForwarder(_trustedForwarder);
}
/**
* @dev Only owner can set new base URI.
*
* @param _newBaseURI The arguments of the new base URI.
*/functionsetBaseURI(stringmemory _newBaseURI) publiconlyOwner{
baseURI = _newBaseURI;
}
/**
* @dev Returns the tokens claimed by the user.
*
* @param user The address of the user.
*/functionuserTokens(address user) publicviewonlyOwnerreturns (uint256[] memory) {
return _userTokens[user];
}
/**
* @dev Only owner can set new claim cost.
*
* @param _newClaimCost The arguments of the new claim cost.
*/functionsetClaimCost(uint256 _newClaimCost) publiconlyOwner{
claimCost = _newClaimCost;
}
/**
* @dev Withdraw contract balance to owner.
*/functionwithdraw() publiconlyOwner{
payable(owner()).transfer(address(this).balance);
}
/**
* @dev Returns the base URI used in the contract.
*/function_baseURI() internalviewoverridereturns (stringmemory) {
return baseURI;
}
/**
* @notice Claim NFT.
*
* @dev ClaimingRequest that must be signed by the owner can be executed successfully.
*
* @param request The arguments of the claiming request.
*/functionclaim(ClaimingRequest calldata request)
publicpayablereturns (uint256)
{
require(msg.value>= claimCost, "Claiming cost is not enough");
_verifyClaiming(request);
uint256 newItemId = _tokenIds.current();
_mint(request.data.recipient, newItemId);
_userTokens[request.data.recipient].push(newItemId);
_tokenIds.increment();
// Emit an event to let off-chain services know that the token has been claimed.emit ClaimToken(newItemId, request.data.identifier);
return newItemId;
}
/**
* @dev Returns the domain separator used in the encoding of the signatures for attest, and revoke.
*/functiongetDomainSeparator() externalviewreturns (bytes32) {
return _domainSeparatorV4();
}
/**
* @dev Verifies claiming request.
*
* @param request The arguments of the claiming request.
*/function_verifyClaiming(ClaimingRequest memory request) internal{
if (request.deadline != NO_EXPIRATION_TIME && request.deadline <= _time()) {
revert DeadlineExpired();
}
ClaimingRequestData memory data = request.data;
EIP712Signature memory signature = request.signature;
_verifyUnusedSignature(signature);
bytes32 digest = _hashTypedDataV4(
keccak256(
abi.encode(
data.recipient,
data.identifier,
keccak256(data.data),
request.deadline
)
)
);
if (ECDSA.recover(digest, signature.v, signature.r, signature.s) != owner()) {
revert InvalidSignature();
}
}
/**
* @dev Ensures that the provided EIP712 signature wasn't already used.
*
* @param signature The EIP712 signature data.
*/function_verifyUnusedSignature(EIP712Signature memory signature) internal{
bytesmemory packedSignature =abi.encodePacked(signature.v, signature.r, signature.s);
if (_signatures[packedSignature]) {
revert UsedSignature();
}
_signatures[packedSignature] =true;
}
/**
* @dev Returns the current's block timestamp. This method is overridden during tests and used to simulate the
* current block time.
*/function_time() internalviewvirtualreturns (uint64) {
returnuint64(block.timestamp);
}
}
Contract Source Code
File 2 of 21: Address.sol
Contract Source Code
File 3 of 21: Common.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;// A representation of an empty/uninitialized UID.bytes32constant EMPTY_UID =0;
// A zero expiration represents an non-expiring attestation.uint64constant NO_EXPIRATION_TIME =0;
errorAccessDenied();
errorInvalidEAS();
errorInvalidLength();
errorInvalidSignature();
errorNotFound();
/**
* @dev A struct representing EIP712 signature data.
*/structEIP712Signature {
uint8 v; // The recovery ID.bytes32 r; // The x-coordinate of the nonce R.bytes32 s; // The signature data.
}
/**
* @dev A struct representing a single attestation.
*/structAttestation {
bytes32 uid; // A unique identifier of the attestation.bytes32 schema; // The unique identifier of the schema.uint64 time; // The time when the attestation was created (Unix timestamp).uint64 expirationTime; // The time when the attestation expires (Unix timestamp).uint64 revocationTime; // The time when the attestation was revoked (Unix timestamp).bytes32 refUID; // The UID of the related attestation.address recipient; // The recipient of the attestation.address attester; // The attester/sender of the attestation.bool revocable; // Whether the attestation is revocable.bytes data; // Custom attestation data.
}
Contract Source Code
File 4 of 21: Context.sol
Contract Source Code
File 5 of 21: Counters.sol
Contract Source Code
File 6 of 21: ECDSA.sol
Contract Source Code
File 7 of 21: EIP712.sol
Contract Source Code
File 8 of 21: ERC165.sol
Contract Source Code
File 9 of 21: ERC721.sol
Contract Source Code
File 10 of 21: IERC165.sol
Contract Source Code
File 11 of 21: IERC5267.sol
Contract Source Code
File 12 of 21: IERC721.sol
Contract Source Code
File 13 of 21: IERC721Metadata.sol
Contract Source Code
File 14 of 21: IERC721Receiver.sol
Contract Source Code
File 15 of 21: Math.sol
Contract Source Code
File 16 of 21: Ownable.sol
Contract Source Code
File 17 of 21: ShortStrings.sol
Contract Source Code
File 18 of 21: SignedMath.sol
Contract Source Code
File 19 of 21: StorageSlot.sol
Contract Source Code
File 20 of 21: Strings.sol
Contract Source Code
File 21 of 21: TrustedForwarderManager.sol
// SPDX-License-Identifier: MIT// solhint-disable no-inline-assemblypragmasolidity >=0.6.9;/**
* @title TrustedForwarderManager
*
* @notice This contract provides the function of receiving relayed transactions for functions that do not access msg.sender and msg.data.
*/abstractcontractTrustedForwarderManager{
/*
* Forwarder singleton we accept calls from
*/addressprivate _trustedForwarder;
/**
* :warning: **Warning** :warning: The Forwarder can have a full control over your Recipient. Only trust verified Forwarder.
* @notice Method is not a required method to allow Recipients to trust multiple Forwarders. Not recommended yet.
* @return forwarder The address of the Forwarder contract that is being used.
*/functiongetTrustedForwarder() publicvirtualviewreturns (address forwarder){
return _trustedForwarder;
}
function_setTrustedForwarder(address _forwarder) internal{
_trustedForwarder = _forwarder;
}
functionisTrustedForwarder(address forwarder) publicvirtualviewreturns(bool) {
return forwarder == _trustedForwarder;
}
}