add contracts for nft and nft claim activity
This commit is contained in:
parent
b656d3ae3b
commit
a8ab7b4e3a
26048
build/contracts/BEBadgeV2.json
Normal file
26048
build/contracts/BEBadgeV2.json
Normal file
File diff suppressed because one or more lines are too long
2985
build/contracts/IClaimAbleNFT.json
Normal file
2985
build/contracts/IClaimAbleNFT.json
Normal file
File diff suppressed because it is too large
Load Diff
16070
build/contracts/NFTClaimer.json
Normal file
16070
build/contracts/NFTClaimer.json
Normal file
File diff suppressed because one or more lines are too long
116
contracts/activity/NFTClaimer.sol
Normal file
116
contracts/activity/NFTClaimer.sol
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.10;
|
||||||
|
|
||||||
|
import "../core/HasSignature.sol";
|
||||||
|
import "../utils/TimeChecker.sol";
|
||||||
|
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contract for activity NFT claim.
|
||||||
|
*/
|
||||||
|
interface IClaimAbleNFT {
|
||||||
|
function safeMint(
|
||||||
|
address to
|
||||||
|
) external returns (uint256);
|
||||||
|
}
|
||||||
|
|
||||||
|
contract NFTClaimer is HasSignature, TimeChecker, ReentrancyGuard{
|
||||||
|
uint256 private immutable _CACHED_CHAIN_ID;
|
||||||
|
address private immutable _CACHED_THIS;
|
||||||
|
address public signer;
|
||||||
|
uint256 public startTime;
|
||||||
|
uint256 public endTime;
|
||||||
|
mapping(address => bool) public tokenSupported;
|
||||||
|
mapping(address => uint256) public claimHistory;
|
||||||
|
|
||||||
|
event NFTClaimed(
|
||||||
|
address indexed nftAddress,
|
||||||
|
address indexed to,
|
||||||
|
uint256 tokenId,
|
||||||
|
uint256 nonce
|
||||||
|
);
|
||||||
|
event NFTSupportUpdated(address indexed nftAddress, bool support);
|
||||||
|
event SignerUpdated(address indexed signer);
|
||||||
|
event StartTimeUpdated(uint256 indexed startTime);
|
||||||
|
event EndTimeUpdated(uint256 indexed endTime);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
_CACHED_CHAIN_ID = block.chainid;
|
||||||
|
_CACHED_THIS = address(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTokenSupport(address nftToken, bool support) external onlyOwner {
|
||||||
|
tokenSupported[nftToken] = support;
|
||||||
|
emit NFTSupportUpdated(nftToken, support);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStartTime(uint256 _startTime) external onlyOwner {
|
||||||
|
startTime = _startTime;
|
||||||
|
emit StartTimeUpdated(_startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateEndTime(uint256 _endTime) external onlyOwner {
|
||||||
|
endTime = _endTime;
|
||||||
|
emit EndTimeUpdated(_endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev update signer
|
||||||
|
* @param account new signer address
|
||||||
|
*/
|
||||||
|
function updateSigner(address account) external onlyOwner {
|
||||||
|
require(account != address(0), "NFTClaimer: address can not be zero");
|
||||||
|
signer = account;
|
||||||
|
emit SignerUpdated(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev claim NFT
|
||||||
|
* Get whitelist signature from a third-party service, then call this method to claim NFT
|
||||||
|
* @param nftAddress NFT address
|
||||||
|
* @param signTime sign time
|
||||||
|
* @param saltNonce nonce
|
||||||
|
* @param signature signature
|
||||||
|
*/
|
||||||
|
function claim(
|
||||||
|
address nftAddress,
|
||||||
|
uint256 signTime,
|
||||||
|
uint256 saltNonce,
|
||||||
|
bytes calldata signature
|
||||||
|
) external signatureValid(signature) timeValid(signTime) nonReentrant {
|
||||||
|
require(block.timestamp >= startTime, "NFTClaimer: not started");
|
||||||
|
require(block.timestamp <= endTime, "NFTClaimer: already ended");
|
||||||
|
require(tokenSupported[nftAddress], "NFTClaimer: unsupported NFT");
|
||||||
|
address to = _msgSender();
|
||||||
|
require(claimHistory[to] == 0, "NFTClaimer: already claimed");
|
||||||
|
bytes32 criteriaMessageHash = getMessageHash(
|
||||||
|
to,
|
||||||
|
nftAddress,
|
||||||
|
signTime,
|
||||||
|
saltNonce
|
||||||
|
);
|
||||||
|
checkSigner(signer, criteriaMessageHash, signature);
|
||||||
|
uint256 tokenId = IClaimAbleNFT(nftAddress).safeMint(to);
|
||||||
|
claimHistory[to] = tokenId;
|
||||||
|
_useSignature(signature);
|
||||||
|
emit NFTClaimed(nftAddress, to, tokenId, saltNonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessageHash(
|
||||||
|
address _to,
|
||||||
|
address _address,
|
||||||
|
uint256 _signTime,
|
||||||
|
uint256 _saltNonce
|
||||||
|
) public view returns (bytes32) {
|
||||||
|
bytes memory encoded = abi.encodePacked(
|
||||||
|
_to,
|
||||||
|
_address,
|
||||||
|
_signTime,
|
||||||
|
_CACHED_CHAIN_ID,
|
||||||
|
_CACHED_THIS,
|
||||||
|
_saltNonce
|
||||||
|
);
|
||||||
|
return keccak256(encoded);
|
||||||
|
}
|
||||||
|
}
|
128
contracts/tokens/erc721/BEBadgeV2.sol
Normal file
128
contracts/tokens/erc721/BEBadgeV2.sol
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.10;
|
||||||
|
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
|
||||||
|
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
|
||||||
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
||||||
|
|
||||||
|
contract BEBadgeV2 is AccessControl, ERC721Enumerable, ERC721Burnable {
|
||||||
|
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
||||||
|
string private _baseTokenURI = "";
|
||||||
|
uint256 public immutable supplyLimit;
|
||||||
|
uint256 private _tokenIndex;
|
||||||
|
uint256 public maxBatchSize = 500;
|
||||||
|
|
||||||
|
// ============ Events ============
|
||||||
|
event MetaAddressUpdated(address indexed metaAddress);
|
||||||
|
event BatchLimitUpdated(uint256 indexed maxBatchSize);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
string memory _name,
|
||||||
|
string memory _symbol,
|
||||||
|
uint256 _supplyLimt
|
||||||
|
) ERC721(_name, _symbol) {
|
||||||
|
_setRoleAdmin(MINTER_ROLE, DEFAULT_ADMIN_ROLE);
|
||||||
|
|
||||||
|
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
|
||||||
|
_setupRole(MINTER_ROLE, msg.sender);
|
||||||
|
supplyLimit = _supplyLimt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Batch mint tokens and transfer to specified address.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
* - Caller must have `MINTER_ROLE`.
|
||||||
|
* - The total supply limit should not be exceeded if supplyLimit is greater than zero.
|
||||||
|
* - The number of tokenIds offered for minting should not exceed maxBatchSize.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function batchMint(
|
||||||
|
address to,
|
||||||
|
uint256 count
|
||||||
|
) external onlyRole(MINTER_ROLE) returns (uint256[] memory) {
|
||||||
|
require(count > 0, "count is too small");
|
||||||
|
require(count <= maxBatchSize, "Exceeds the maximum batch size");
|
||||||
|
require(
|
||||||
|
(supplyLimit == 0) || (totalSupply() + count <= supplyLimit),
|
||||||
|
"Exceeds the total supply"
|
||||||
|
);
|
||||||
|
uint256[] memory tokenIds = new uint256[](count);
|
||||||
|
for (uint256 i = 0; i < count; i++) {
|
||||||
|
_tokenIndex += 1;
|
||||||
|
_safeMint(to, _tokenIndex);
|
||||||
|
tokenIds[i] = _tokenIndex;
|
||||||
|
}
|
||||||
|
return tokenIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Safely mints a new token and assigns it to the specified address.
|
||||||
|
* Only the account with the MINTER_ROLE can call this function.
|
||||||
|
*
|
||||||
|
* @param to The address to which the newly minted token will be assigned.
|
||||||
|
*/
|
||||||
|
function safeMint(address to) external onlyRole(MINTER_ROLE) returns (uint256){
|
||||||
|
require(
|
||||||
|
(supplyLimit == 0) || (totalSupply() < supplyLimit),
|
||||||
|
"Exceeds the total supply"
|
||||||
|
);
|
||||||
|
uint256 tokenId = ++_tokenIndex;
|
||||||
|
_safeMint(to, tokenId);
|
||||||
|
return tokenId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Set token URI
|
||||||
|
*/
|
||||||
|
function updateBaseURI(
|
||||||
|
string calldata baseTokenURI
|
||||||
|
) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||||
|
_baseTokenURI = baseTokenURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _baseURI() internal view virtual override returns (string memory) {
|
||||||
|
return _baseTokenURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Updates the maximum batch size for a batch operation.
|
||||||
|
* @param valNew The new maximum batch size.
|
||||||
|
* Requirements:
|
||||||
|
* - The caller must have the DEFAULT_ADMIN_ROLE.
|
||||||
|
* - The new batch size must be greater than 0.
|
||||||
|
*/
|
||||||
|
function updateBatchLimit(
|
||||||
|
uint256 valNew
|
||||||
|
) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||||
|
require(valNew > 0, "Batch size is too short");
|
||||||
|
maxBatchSize = valNew;
|
||||||
|
emit BatchLimitUpdated(valNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev See {IERC165-_beforeTokenTransfer}.
|
||||||
|
*/
|
||||||
|
function _beforeTokenTransfer(
|
||||||
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 firstTokenId,
|
||||||
|
uint256 batchSize
|
||||||
|
) internal virtual override(ERC721, ERC721Enumerable) {
|
||||||
|
super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev See {IERC165-supportsInterface}.
|
||||||
|
*/
|
||||||
|
function supportsInterface(
|
||||||
|
bytes4 interfaceId
|
||||||
|
)
|
||||||
|
public
|
||||||
|
view
|
||||||
|
virtual
|
||||||
|
override(ERC721, AccessControl, ERC721Enumerable)
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
return super.supportsInterface(interfaceId);
|
||||||
|
}
|
||||||
|
}
|
@ -3,34 +3,34 @@ pragma solidity 0.8.10;
|
|||||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||||
|
|
||||||
contract TimeChecker is Ownable {
|
contract TimeChecker is Ownable {
|
||||||
uint256 private _duration;
|
uint256 public duration;
|
||||||
uint256 private minDuration;
|
uint256 public minDuration;
|
||||||
|
|
||||||
|
event DurationUpdated(uint256 indexed duration);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
_duration = 1 days;
|
duration = 1 days;
|
||||||
minDuration = 30 minutes;
|
minDuration = 30 minutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Check if the time is valid
|
||||||
|
*/
|
||||||
modifier timeValid(uint256 time) {
|
modifier timeValid(uint256 time) {
|
||||||
require(
|
require(
|
||||||
time + _duration >= block.timestamp,
|
time + duration >= block.timestamp,
|
||||||
"expired, please send another transaction with new signature"
|
"expired, please send another transaction with new signature"
|
||||||
);
|
);
|
||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Returns the max duration for function called by user
|
|
||||||
*/
|
|
||||||
function getDuration() external view returns (uint256 duration) {
|
|
||||||
return _duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Change duration value
|
* @dev Change duration value
|
||||||
*/
|
*/
|
||||||
function updateDuation(uint256 valNew) external onlyOwner {
|
function updateDuation(uint256 valNew) external onlyOwner {
|
||||||
require(valNew > minDuration, "duration too short");
|
require(valNew > minDuration, "duration too short");
|
||||||
_duration = valNew;
|
duration = valNew;
|
||||||
|
emit DurationUpdated(valNew);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
109
test/badgev2.test.js
Normal file
109
test/badgev2.test.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
const BEBadge = artifacts.require("BEBadgeV2");
|
||||||
|
const {
|
||||||
|
BN,
|
||||||
|
constants,
|
||||||
|
expectEvent,
|
||||||
|
expectRevert,
|
||||||
|
} = require("@openzeppelin/test-helpers");
|
||||||
|
|
||||||
|
contract("BEBadgeV2", (accounts) => {
|
||||||
|
let badge;
|
||||||
|
const owner = accounts[0];
|
||||||
|
const user = accounts[1];
|
||||||
|
const executor = accounts[2];
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
badge = await BEBadge.new("CRYPTO ELITE'S HERO", "HERO", 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should batch mint two badges", async () => {
|
||||||
|
await badge.batchMint(user, 2, { from: owner });
|
||||||
|
const balanceOfUser = await badge.balanceOf(user);
|
||||||
|
assert.equal(balanceOfUser, 2, "Incorrect user balance");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should revert batch minting two badges with not mint role", async () => {
|
||||||
|
const mintRole = await badge.MINTER_ROLE();
|
||||||
|
const err = `AccessControl: account ${(user+'').toLowerCase()} is missing role ${mintRole}`
|
||||||
|
await expectRevert(
|
||||||
|
badge.batchMint(user, 2, { from: user }),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should revert batch minting badges with not enough supply", async () => {
|
||||||
|
const err = `Exceeds the total supply`
|
||||||
|
await expectRevert(
|
||||||
|
badge.batchMint(user, 4, { from: owner }),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should mint one badge", async () => {
|
||||||
|
await badge.safeMint(user, { from: owner });
|
||||||
|
const balanceOfUser = await badge.balanceOf(user);
|
||||||
|
assert.equal(balanceOfUser, 1, "Incorrect user balance");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should revert minting one badge with not mint role", async () => {
|
||||||
|
const mintRole = await badge.MINTER_ROLE();
|
||||||
|
const err = `AccessControl: account ${(user+'').toLowerCase()} is missing role ${mintRole}`
|
||||||
|
await expectRevert(
|
||||||
|
badge.safeMint(user, { from: user }),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should burn one badge", async () => {
|
||||||
|
await badge.safeMint(user, { from: owner });
|
||||||
|
const tokenId = await badge.tokenOfOwnerByIndex(user, 0);
|
||||||
|
await badge.burn(tokenId, { from: user });
|
||||||
|
const balanceOfUser = await badge.balanceOf(user);
|
||||||
|
assert.equal(balanceOfUser, 0, "Incorrect user balance");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should burn one badge with approval", async () => {
|
||||||
|
await badge.safeMint(user, { from: owner });
|
||||||
|
const tokenId = await badge.tokenOfOwnerByIndex(user, 0);
|
||||||
|
const balanceOfUserTmp = await badge.balanceOf(user);
|
||||||
|
assert.equal(balanceOfUserTmp, 1, "Incorrect user balance");
|
||||||
|
await badge.approve(executor, tokenId, { from: user });
|
||||||
|
await badge.burn(tokenId, { from: executor });
|
||||||
|
const balanceOfUser = await badge.balanceOf(user);
|
||||||
|
assert.equal(balanceOfUser, 0, "Incorrect user balance");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should revert burning one badge", async () => {
|
||||||
|
const err = `ERC721: invalid token ID.`
|
||||||
|
await expectRevert(
|
||||||
|
badge.burn(1, { from: user }),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should revert burning one badge without approval", async () => {
|
||||||
|
const err = `ERC721: caller is not token owner or approved.`
|
||||||
|
await badge.safeMint(user, { from: owner });
|
||||||
|
const tokenId = await badge.tokenOfOwnerByIndex(user, 0);
|
||||||
|
await expectRevert(
|
||||||
|
badge.burn(tokenId, { from: executor }),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should receive event for RoleGranted and RoleRevoked", async () => {
|
||||||
|
const role = await badge.MINTER_ROLE();
|
||||||
|
const receipt = await badge.grantRole(role, user, { from: owner });
|
||||||
|
expectEvent(receipt, "RoleGranted", {
|
||||||
|
account: user,
|
||||||
|
role: role,
|
||||||
|
sender: owner,
|
||||||
|
});
|
||||||
|
const receipt2 = await badge.revokeRole(role, user, { from: owner });
|
||||||
|
expectEvent(receipt2, "RoleRevoked", {
|
||||||
|
account: user,
|
||||||
|
role: role,
|
||||||
|
sender: owner,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
125
test/nftclaimer.test.js
Normal file
125
test/nftclaimer.test.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
const NFTClaimer = artifacts.require("NFTClaimer");
|
||||||
|
const NFT = artifacts.require("BEBadgeV2");
|
||||||
|
const {
|
||||||
|
BN,
|
||||||
|
constants,
|
||||||
|
expectEvent,
|
||||||
|
expectRevert,
|
||||||
|
} = require("@openzeppelin/test-helpers");
|
||||||
|
|
||||||
|
|
||||||
|
contract("NFTClaimer", (accounts) => {
|
||||||
|
let tokenClaimer;
|
||||||
|
let nft;
|
||||||
|
const owner = accounts[0];
|
||||||
|
const user = accounts[1];
|
||||||
|
const singer = accounts[2];
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
nft = await NFT.new("NFT", "NFT", 5);
|
||||||
|
tokenClaimer = await NFTClaimer.new();
|
||||||
|
await tokenClaimer.updateTokenSupport(nft.address, true);
|
||||||
|
await tokenClaimer.updateSigner(singer);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should cliam token", async () => {
|
||||||
|
const minterRole = await nft.MINTER_ROLE();
|
||||||
|
await nft.grantRole(minterRole, tokenClaimer.address);
|
||||||
|
const token = nft.address;
|
||||||
|
const chainId = await web3.eth.getChainId();
|
||||||
|
const address = tokenClaimer.address;
|
||||||
|
const startTime = Date.now() / 1000 | 0;
|
||||||
|
const saltNonce = (Math.random() * 1000) | 0;
|
||||||
|
let signStr = web3.utils.soliditySha3.apply(this,
|
||||||
|
[user, token, startTime, chainId, address, saltNonce]);
|
||||||
|
let signature = await web3.eth.sign(signStr, singer);
|
||||||
|
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||||
|
|
||||||
|
const receipt = await tokenClaimer.claim(
|
||||||
|
token,
|
||||||
|
startTime,
|
||||||
|
saltNonce,
|
||||||
|
signature,
|
||||||
|
{ from: user }
|
||||||
|
);
|
||||||
|
|
||||||
|
const balanceOfUser = await nft.balanceOf(user);
|
||||||
|
assert.equal(balanceOfUser, 1, "Incorrect user balance");
|
||||||
|
});
|
||||||
|
it("should revert cliam token for error signature", async () => {
|
||||||
|
const minterRole = await nft.MINTER_ROLE();
|
||||||
|
await nft.grantRole(minterRole, tokenClaimer.address);
|
||||||
|
const token = nft.address;
|
||||||
|
const chainId = await web3.eth.getChainId();
|
||||||
|
const address = tokenClaimer.address;
|
||||||
|
const startTime = Date.now() / 1000 | 0;
|
||||||
|
const saltNonce = (Math.random() * 1000) | 0;
|
||||||
|
let signStr = web3.utils.soliditySha3.apply(this,
|
||||||
|
[user, token, startTime, chainId, address, saltNonce]);
|
||||||
|
let signature = await web3.eth.sign(signStr, singer);
|
||||||
|
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||||
|
|
||||||
|
const receipt = await tokenClaimer.claim(
|
||||||
|
token,
|
||||||
|
startTime,
|
||||||
|
saltNonce,
|
||||||
|
signature,
|
||||||
|
{ from: user }
|
||||||
|
);
|
||||||
|
const err = `[BE] signature used. please send another transaction with new signature`
|
||||||
|
await expectRevert(
|
||||||
|
tokenClaimer.claim(
|
||||||
|
token,
|
||||||
|
startTime,
|
||||||
|
saltNonce,
|
||||||
|
signature,
|
||||||
|
{ from: user }
|
||||||
|
),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
|
||||||
|
const balanceOfUser = await nft.balanceOf(user);
|
||||||
|
assert.equal(balanceOfUser, 1, "Incorrect user balance");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should revert cliam token for already claimed", async () => {
|
||||||
|
const minterRole = await nft.MINTER_ROLE();
|
||||||
|
await nft.grantRole(minterRole, tokenClaimer.address);
|
||||||
|
const token = nft.address;
|
||||||
|
const chainId = await web3.eth.getChainId();
|
||||||
|
const address = tokenClaimer.address;
|
||||||
|
const startTime = Date.now() / 1000 | 0;
|
||||||
|
const saltNonce = (Math.random() * 1000) | 0;
|
||||||
|
let signStr = web3.utils.soliditySha3.apply(this,
|
||||||
|
[user, token, startTime, chainId, address, saltNonce]);
|
||||||
|
let signature = await web3.eth.sign(signStr, singer);
|
||||||
|
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||||
|
|
||||||
|
const receipt = await tokenClaimer.claim(
|
||||||
|
token,
|
||||||
|
startTime,
|
||||||
|
saltNonce,
|
||||||
|
signature,
|
||||||
|
{ from: user }
|
||||||
|
);
|
||||||
|
|
||||||
|
const startTime2 = Date.now() / 1000 | 0;
|
||||||
|
const saltNonce2 = (Math.random() * 1000) | 0;
|
||||||
|
let signStr2 = web3.utils.soliditySha3.apply(this,
|
||||||
|
[user, token, startTime2, chainId, address, saltNonce2]);
|
||||||
|
let signature2 = await web3.eth.sign(signStr2, singer);
|
||||||
|
signature2 = signature2.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||||
|
const err = `NFTClaimer: already claimed`
|
||||||
|
await expectRevert(
|
||||||
|
tokenClaimer.claim(
|
||||||
|
token,
|
||||||
|
startTime2,
|
||||||
|
saltNonce2,
|
||||||
|
signature2,
|
||||||
|
{ from: user }
|
||||||
|
),
|
||||||
|
err
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user