按审计要求, 修改202403活动相关合约
This commit is contained in:
parent
663eea2ac8
commit
cf3bcd96c7
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -3259,7 +3259,7 @@
|
||||
}
|
||||
},
|
||||
"schemaVersion": "3.4.16",
|
||||
"updatedAt": "2024-01-12T05:44:22.587Z",
|
||||
"updatedAt": "2024-02-27T05:13:36.919Z",
|
||||
"networkType": "ethereum",
|
||||
"devdoc": {
|
||||
"kind": "dev",
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,9 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.10;
|
||||
|
||||
import "../core/HasSignature.sol";
|
||||
import "../utils/TimeChecker.sol";
|
||||
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
|
||||
import "../core/HasSignature.sol";
|
||||
|
||||
/**
|
||||
* Contract for activity NFT claim.
|
||||
@ -14,13 +13,14 @@ interface IClaimAbleNFT {
|
||||
) external returns (uint256);
|
||||
}
|
||||
|
||||
contract NFTClaimer is HasSignature, TimeChecker, ReentrancyGuard{
|
||||
contract NFTClaimer is HasSignature, ReentrancyGuard{
|
||||
uint256 private immutable _CACHED_CHAIN_ID;
|
||||
address private immutable _CACHED_THIS;
|
||||
address public immutable nftAddress;
|
||||
address public signer;
|
||||
uint256 public startTime;
|
||||
uint256 public endTime;
|
||||
mapping(address => bool) public tokenSupported;
|
||||
|
||||
mapping(address => uint256) public claimHistory;
|
||||
|
||||
event NFTClaimed(
|
||||
@ -29,20 +29,17 @@ contract NFTClaimer is HasSignature, TimeChecker, ReentrancyGuard{
|
||||
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() {
|
||||
constructor(address _nftAddress) {
|
||||
_CACHED_CHAIN_ID = block.chainid;
|
||||
_CACHED_THIS = address(this);
|
||||
nftAddress = _nftAddress;
|
||||
}
|
||||
|
||||
function updateTokenSupport(address nftToken, bool support) external onlyOwner {
|
||||
tokenSupported[nftToken] = support;
|
||||
emit NFTSupportUpdated(nftToken, support);
|
||||
}
|
||||
|
||||
function updateStartTime(uint256 _startTime) external onlyOwner {
|
||||
startTime = _startTime;
|
||||
@ -57,37 +54,31 @@ contract NFTClaimer is HasSignature, TimeChecker, ReentrancyGuard{
|
||||
|
||||
/**
|
||||
* @dev update signer
|
||||
* @param account new signer address
|
||||
* @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);
|
||||
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 {
|
||||
) external 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);
|
||||
@ -100,13 +91,11 @@ contract NFTClaimer is HasSignature, TimeChecker, ReentrancyGuard{
|
||||
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
|
||||
|
@ -11,8 +11,6 @@ contract HasSignature is Ownable {
|
||||
bytes32 hash,
|
||||
bytes memory signature
|
||||
) public pure {
|
||||
require(signer != address(0), "[BE] invalid signer");
|
||||
require(signature.length == 65, "[BE] invalid signature length");
|
||||
bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(hash);
|
||||
|
||||
address recovered = ECDSA.recover(ethSignedMessageHash, signature);
|
||||
|
@ -2,72 +2,37 @@
|
||||
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";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
contract BEBadgeV2 is AccessControl, ERC721Enumerable, ERC721Burnable {
|
||||
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
||||
contract BEBadgeV2 is Ownable, ERC721Enumerable, ERC721Burnable {
|
||||
string private _baseTokenURI = "";
|
||||
uint256 public immutable supplyLimit;
|
||||
uint256 private _tokenIndex;
|
||||
uint256 public maxBatchSize = 500;
|
||||
mapping(address => bool) public minters;
|
||||
|
||||
// ============ Events ============
|
||||
event MetaAddressUpdated(address indexed metaAddress);
|
||||
event BatchLimitUpdated(uint256 indexed maxBatchSize);
|
||||
event MinterUpdated(address indexed minter, bool support);
|
||||
|
||||
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.
|
||||
* tokenId begin with 1.
|
||||
* @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"
|
||||
);
|
||||
function safeMint(address _to) external onlyMinter returns (uint256){
|
||||
require(_tokenIndex < supplyLimit, "Exceeds the total supply");
|
||||
uint256 tokenId = ++_tokenIndex;
|
||||
_safeMint(to, tokenId);
|
||||
_safeMint(_to, tokenId);
|
||||
return tokenId;
|
||||
}
|
||||
|
||||
@ -76,7 +41,7 @@ contract BEBadgeV2 is AccessControl, ERC721Enumerable, ERC721Burnable {
|
||||
*/
|
||||
function updateBaseURI(
|
||||
string calldata baseTokenURI
|
||||
) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
) external onlyOwner() {
|
||||
_baseTokenURI = baseTokenURI;
|
||||
}
|
||||
|
||||
@ -84,20 +49,6 @@ contract BEBadgeV2 is AccessControl, ERC721Enumerable, ERC721Burnable {
|
||||
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}.
|
||||
@ -120,9 +71,25 @@ contract BEBadgeV2 is AccessControl, ERC721Enumerable, ERC721Burnable {
|
||||
public
|
||||
view
|
||||
virtual
|
||||
override(ERC721, AccessControl, ERC721Enumerable)
|
||||
override(ERC721, ERC721Enumerable)
|
||||
returns (bool)
|
||||
{
|
||||
return super.supportsInterface(interfaceId);
|
||||
}
|
||||
/**
|
||||
* @dev Updates the minters mapping to allow or disallow a specific address to mint tokens.
|
||||
* Only the contract owner can call this function.
|
||||
* @param _address The address to update the minting permission for.
|
||||
* @param _support A boolean indicating whether the address should be allowed to mint tokens or not.
|
||||
*/
|
||||
|
||||
function updateMinters(address _address, bool _support) external onlyOwner {
|
||||
minters[_address] = _support;
|
||||
emit MinterUpdated(_address, _support);
|
||||
}
|
||||
|
||||
modifier onlyMinter() {
|
||||
require(minters[_msgSender()], "Address does not have the minter permission");
|
||||
_;
|
||||
}
|
||||
}
|
||||
|
52
migrations/15_deploy_activity_202403.js
Normal file
52
migrations/15_deploy_activity_202403.js
Normal file
@ -0,0 +1,52 @@
|
||||
const base = require("../scripts/base");
|
||||
const NFTClaimer = artifacts.require("activity/NFTClaimer");
|
||||
const NFT = artifacts.require("tokens/erc721/BEBadgeV2");
|
||||
|
||||
module.exports = async function (deployer, network, accounts) {
|
||||
const config = require(`../config/config_${network}`);
|
||||
let cfgs = base.loadData({ network });
|
||||
|
||||
// await deployer.deploy(NFT, 'BEBadgeV2', 'BEBadgeV2', 500);
|
||||
// const nftInstance = await NFT.deployed();
|
||||
// if (nftInstance) {
|
||||
// console.log("NFT successfully deployed.");
|
||||
// console.log("address: " + nftInstance.address);
|
||||
// }
|
||||
// base.updateArray({
|
||||
// name: "BEBadgeV2",
|
||||
// type: "erc721",
|
||||
// json: "assets/contracts/BEBadgeV2.json",
|
||||
// address: nftInstance.address,
|
||||
// network,
|
||||
// });
|
||||
|
||||
// await deployer.deploy(NFTClaimer);
|
||||
// const claimInstance = await NFTClaimer.deployed();
|
||||
// if (claimInstance) {
|
||||
// console.log("ClaimToken successfully deployed.");
|
||||
// console.log("address: " + claimInstance.address);
|
||||
// }
|
||||
// base.updateArray({
|
||||
// name: "NFTClaimer",
|
||||
// type: "logic",
|
||||
// json: "assets/contracts/NFTClaimer.json",
|
||||
// address: claimInstance.address,
|
||||
// network,
|
||||
// });
|
||||
|
||||
|
||||
const nftAddress = cfgs.find((c) => c.name === "BEBadgeV2").address
|
||||
const nftInstance = await NFT.at(nftAddress);
|
||||
const claimerAddress = cfgs.find((c) => c.name === "NFTClaimer").address
|
||||
const claimInstance = await NFTClaimer.at(claimerAddress);
|
||||
|
||||
await claimInstance.updateTokenSupport(nftInstance.address, true);
|
||||
console.log("updateTokenSupport successfully deployed.");
|
||||
await claimInstance.updateSigner(config.admins.admin);
|
||||
console.log("updateSigner successfully deployed.");
|
||||
await claimInstance.updateStartTime((Date.now() / 1000 | 0) - 3600 * 24);
|
||||
await claimInstance.updateEndTime((Date.now() / 1000 | 0) + 3600 * 24 * 10);
|
||||
console.log("update start time and end time successfully deployed.");
|
||||
await nftInstance.grantRole(await nftInstance.MINTER_ROLE(), claimInstance.address);
|
||||
console.log("grantRole successfully deployed.");
|
||||
};
|
14
out/sepolia_dev.json
Normal file
14
out/sepolia_dev.json
Normal file
@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"name": "BEBadgeV2",
|
||||
"type": "erc721",
|
||||
"json": "assets/contracts/BEBadgeV2.json",
|
||||
"address": "0x09F0dFFA584B1277D7c4E44265a6b5D03303Fc99"
|
||||
},
|
||||
{
|
||||
"name": "NFTClaimer",
|
||||
"type": "logic",
|
||||
"json": "assets/contracts/NFTClaimer.json",
|
||||
"address": "0xe4112A9490765463471a9a73050328153C509B0b"
|
||||
}
|
||||
]
|
@ -20,29 +20,24 @@ contract("NFTClaimer", (accounts) => {
|
||||
|
||||
beforeEach(async () => {
|
||||
nft = await NFT.new("NFT", "NFT", 5);
|
||||
tokenClaimer = await NFTClaimer.new();
|
||||
await tokenClaimer.updateTokenSupport(nft.address, true);
|
||||
tokenClaimer = await NFTClaimer.new(nft.address);
|
||||
await tokenClaimer.updateSigner(singer);
|
||||
await tokenClaimer.updateStartTime(start);
|
||||
await tokenClaimer.updateEndTime(end);
|
||||
});
|
||||
|
||||
it("should cliam token", async () => {
|
||||
const minterRole = await nft.MINTER_ROLE();
|
||||
await nft.grantRole(minterRole, tokenClaimer.address);
|
||||
await nft.updateMinters(tokenClaimer.address, true);
|
||||
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]);
|
||||
[user, token, 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 }
|
||||
@ -51,31 +46,46 @@ contract("NFTClaimer", (accounts) => {
|
||||
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);
|
||||
it("should revert cliam token for no minter permission", async () => {
|
||||
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]);
|
||||
[user, token, chainId, address, saltNonce]);
|
||||
let signature = await web3.eth.sign(signStr, singer);
|
||||
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||
|
||||
const err = `Address does not have the minter permission`
|
||||
await expectRevert(
|
||||
tokenClaimer.claim(
|
||||
saltNonce,
|
||||
signature,
|
||||
{ from: user }
|
||||
),
|
||||
err
|
||||
);
|
||||
|
||||
});
|
||||
it("should revert cliam token for error signature", async () => {
|
||||
await nft.updateMinters(tokenClaimer.address, true);
|
||||
const token = nft.address;
|
||||
const chainId = await web3.eth.getChainId();
|
||||
const address = tokenClaimer.address;
|
||||
const saltNonce = (Math.random() * 1000) | 0;
|
||||
let signStr = web3.utils.soliditySha3.apply(this,
|
||||
[user, token, 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`
|
||||
const err = `NFTClaimer: already claimed.`
|
||||
await expectRevert(
|
||||
tokenClaimer.claim(
|
||||
token,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
{ from: user }
|
||||
@ -88,37 +98,30 @@ contract("NFTClaimer", (accounts) => {
|
||||
});
|
||||
|
||||
it("should revert cliam token for already claimed", async () => {
|
||||
const minterRole = await nft.MINTER_ROLE();
|
||||
await nft.grantRole(minterRole, tokenClaimer.address);
|
||||
await nft.updateMinters(tokenClaimer.address, true);
|
||||
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]);
|
||||
[user, token, 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]);
|
||||
[user, token, 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 }
|
||||
@ -127,24 +130,20 @@ contract("NFTClaimer", (accounts) => {
|
||||
);
|
||||
});
|
||||
it("should revert cliam token for activity not begin", async () => {
|
||||
const minterRole = await nft.MINTER_ROLE();
|
||||
await nft.updateMinters(tokenClaimer.address, true);
|
||||
await tokenClaimer.updateStartTime(Date.now() / 1000 | 0 + 3600);
|
||||
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]);
|
||||
[user, token, chainId, address, saltNonce]);
|
||||
let signature = await web3.eth.sign(signStr, singer);
|
||||
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||
|
||||
const err = `NFTClaimer: not started`
|
||||
await expectRevert(
|
||||
tokenClaimer.claim(
|
||||
token,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
{ from: user }
|
||||
@ -153,24 +152,20 @@ contract("NFTClaimer", (accounts) => {
|
||||
);
|
||||
});
|
||||
it("should revert cliam token for activity already end", async () => {
|
||||
const minterRole = await nft.MINTER_ROLE();
|
||||
await nft.updateMinters(tokenClaimer.address, true);
|
||||
await tokenClaimer.updateEndTime((Date.now() / 1000 | 0) - 3600);
|
||||
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]);
|
||||
[user, token, chainId, address, saltNonce]);
|
||||
let signature = await web3.eth.sign(signStr, singer);
|
||||
signature = signature.replace(/00$/, "1b").replace(/01$/, "1c");
|
||||
|
||||
const err = `NFTClaimer: already ended`
|
||||
await expectRevert(
|
||||
tokenClaimer.claim(
|
||||
token,
|
||||
startTime,
|
||||
saltNonce,
|
||||
signature,
|
||||
{ from: user }
|
||||
|
Loading…
x
Reference in New Issue
Block a user