Skip to content

Instantly share code, notes, and snippets.

@lucasfernandes
Created January 5, 2025 15:44
Show Gist options
  • Save lucasfernandes/e8c6c7b9a83c5c7ef8d68c75c31c7ea6 to your computer and use it in GitHub Desktop.
Save lucasfernandes/e8c6c7b9a83c5c7ef8d68c75c31c7ea6 to your computer and use it in GitHub Desktop.
Vesting Debug
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.28;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IVesting} from "./interfaces/IVesting.sol";
import {ISamuraiTiers} from "./interfaces/ISamuraiTiers.sol";
import {UD60x18, ud, convert} from "@prb/math/src/UD60x18.sol";
import {BokkyPooBahsDateTimeLibrary} from "@BokkyPooBahsDateTimeLibrary/contracts/BokkyPooBahsDateTimeLibrary.sol";
import {IPoints} from "./interfaces/IPoints.sol";
contract DeployedVesting is Ownable, Pausable, ReentrancyGuard {
using SafeERC20 for ERC20;
uint256 public immutable tgeReleasePercent;
uint256 public immutable pointsPerToken;
address public immutable token;
address public immutable points;
IVesting.VestingType public immutable vestingType;
uint256 public refundPeriod = 48 hours;
uint256 public totalPurchased;
uint256 public totalClaimed;
uint256 public totalPoints;
uint256 public totalPointsClaimed;
uint256 public totalToRefund;
IVesting.Periods public periods;
address[] public walletsToRefund;
mapping(address wallet => uint256 purchased) public purchases;
mapping(address wallet => bool tgeClaimed) public hasClaimedTGE;
mapping(address wallet => uint256 tokens) public tokensClaimed;
mapping(address wallet => uint256 timestamp) public lastClaimTimestamps;
mapping(address wallet => bool askedRefund) public askedRefund;
mapping(address wallet => uint256 claimed) public pointsClaimed;
/**
* @notice Sets the initial configuration for the IDO contract.
* @dev Reverts if the token address is invalid, total purchased is zero, TGE release percent is zero or vesting type is invalid.
* @param _token IDO token address.
* @param _points Samurai Points token address.
* @param _tgeReleasePercent TGE release percent.
* @param _pointsPerToken amount of points per token purchased
* @param _vestingType Type of vesting schedule.
* @param _periods Struct containing initial periods configuration: registration start, participation start/end, TGE vesting at.
* @param _wallets List of wallets addresses.
* @param _tokensPurchased List of tokens purchased by wallets.
*/
constructor(
address _token,
address _points,
uint256 _tgeReleasePercent,
uint256 _pointsPerToken,
IVesting.VestingType _vestingType,
IVesting.Periods memory _periods,
address[] memory _wallets,
uint256[] memory _tokensPurchased
) Ownable(msg.sender) {
require(_token != address(0), IVesting.IVesting__Unauthorized("Invalid address"));
require(_points != address(0), IVesting.IVesting__Unauthorized("Invalid address"));
require(uint256(_vestingType) < 3, IVesting.IVesting__Invalid("Invalid vesting type"));
token = _token;
points = _points;
tgeReleasePercent = _tgeReleasePercent; // this can be zero
pointsPerToken = _pointsPerToken;
vestingType = _vestingType;
_setPeriods(_periods);
_setPurchases(_wallets, _tokensPurchased);
}
/**
* @dev Enforces various conditions for claiming unlockable tokens:
* - Must be past the vesting period.
* - User must have allocated tokens.
* - User must not have claimed tokens in the current claim period.
* - Must be within the claim period since the user's last claim.
* This modifier is used on the `claim` function.
*/
modifier canClaim() {
IVesting.Periods memory periodsCopy = periods;
require(block.timestamp >= periodsCopy.vestingAt, IVesting.IVesting__Unauthorized("Not in vesting phase"));
require(purchases[msg.sender] > 0, IVesting.IVesting__Unauthorized("No tokens available"));
require(block.timestamp > lastClaimTimestamps[msg.sender], IVesting.IVesting__Unauthorized("Not allowed"));
_;
}
/**
* @notice Allows users to deposit IDO tokens based on amount raised in participation and token price.
* @dev This function is used to fill IDO tokens in the contract.
* emit IDOTokenFilled(msg.sender, amount) Emitted when IDO tokens are deposited.
*/
function fillIDOToken(uint256 amount) external whenNotPaused nonReentrant {
require(
ERC20(token).balanceOf(address(this)) + amount <= totalPurchased,
IVesting.IVesting__Unauthorized("Unable to receive more IDO tokens")
);
ERC20(token).safeTransferFrom(msg.sender, address(this), amount);
emit IVesting.TokensFilled(msg.sender, amount);
}
/**
* @notice Allows users to claim their allocated IDO tokens after the TGE vesting period.
* @dev This function can only be called after the TGE vesting period and if the contract is not paused.
* It calculates the claimable amount based on the user's tier and participation.
* emit Claimed(msg.sender, amount) Emitted when a user successfully claims their IDO tokens.
*/
function claim() external canClaim whenNotPaused nonReentrant {
uint256 claimable = previewClaimableTokens(msg.sender);
require(claimable > 0, IVesting.IVesting__Unauthorized("There is no vested tokens available to claim"));
require(
totalClaimed + claimable <= totalPurchased,
IVesting.IVesting__Unauthorized("Exceeds total amount purchased")
);
if (!hasClaimedTGE[msg.sender]) hasClaimedTGE[msg.sender] = true;
// Update claimed tokens by user
tokensClaimed[msg.sender] += claimable;
// Update overal claimed tokens
totalClaimed += claimable;
// Update last claim timestamp based on the release type
lastClaimTimestamps[msg.sender] = block.timestamp;
emit IVesting.Claimed(msg.sender, claimable);
ERC20(token).safeTransfer(msg.sender, claimable);
}
function claimPoints() external canClaim whenNotPaused nonReentrant {
uint256 pointsToClaim = previewClaimablePoints(msg.sender);
require(pointsToClaim > 0, IVesting.IVesting__Unauthorized("Nothing to claim"));
pointsClaimed[msg.sender] = pointsToClaim;
totalPointsClaimed += pointsToClaim;
emit IVesting.PointsClaimed(msg.sender, pointsToClaim);
IPoints(points).mint(msg.sender, pointsToClaim);
}
/**
* @notice Allows the contract owner to withdraw remaining IDO tokens of a specific wallet.
* This feature is designed to help participators with problems in it's wallets.
* @dev This function can only be called by the contract owner and is protected against reentrancy.
* emit Claimed(wallet, balance) Emitted when remaining IDO tokens are withdrawn.
*/
function emergencyWithdrawByWallet(address wallet) external onlyOwner whenNotPaused nonReentrant {
require(block.timestamp > vestingEndsAt(), IVesting.IVesting__Unauthorized("Vesting is ongoing"));
uint256 allocation = purchases[wallet];
require(allocation > 0, IVesting.IVesting__Unauthorized("Wallet has no allocation"));
tokensClaimed[wallet] = allocation;
uint256 claimable = previewClaimableTokens(wallet);
emit IVesting.Claimed(wallet, claimable);
ERC20(token).safeTransfer(owner(), claimable);
}
/**
* @notice Allows the contract owner to withdraw remaining IDO tokens.
* This feature is designed to help with problems and edge cases.
* @dev This function can only be called by the contract owner and is protected against reentrancy.
* It reverts if there are no tokens to withdraw.
* emit RemainingTokensWithdrawal(balance) Emitted when remaining IDO tokens are withdrawn.
*/
function emergencyWithdraw() external onlyOwner nonReentrant {
uint256 balance = ERC20(token).balanceOf(address(this));
require(balance > 0, IVesting.IVesting__Unauthorized("Nothing to withdraw"));
ERC20(token).safeTransfer(owner(), balance);
emit IVesting.RemainingTokensWithdrawal(balance);
}
/**
* @notice Allows the contract owner to withdraw project tokens to be refundable.
* @dev This function can only be called by the contract owner and is protected against reentrancy.
* It reverts if there are no refundable tokens to withdraw.
* emit RefundsWidrawal(balance) Emitted when refundable tokens are withdrawn.
*/
function withdrawRefunds() external onlyOwner nonReentrant {
uint256 totalToRefundCopy = totalToRefund;
require(totalToRefundCopy > 0, IVesting.IVesting__Unauthorized("Nothing to withdraw"));
totalToRefund = 0;
emit IVesting.RefundsWidrawal(msg.sender, totalToRefundCopy);
ERC20(token).safeTransfer(owner(), totalToRefundCopy);
}
/**
* @notice Allows the contract owner to update the refund period.
* @dev This function can only be called by the contract owner and is protected against reentrancy.
* It reverts if _refundPeriod is equal or lower than refundPeriod.
* emit RefundsWidrawal(balance) Emitted when refundable tokens are withdrawn.
*/
function setRefundPeriod(uint256 _refundPeriod) external onlyOwner nonReentrant {
require(_refundPeriod > refundPeriod, IVesting.IVesting__Invalid("New period must be greater than current"));
refundPeriod = _refundPeriod;
emit IVesting.RefundPeriodSet(_refundPeriod);
}
/**
* @notice Allows a wallet to ask for a refund.
* @dev This function can only be called by the contract owner and is protected against reentrancy.
* It reverts if refunding period has passed.
* It reverts if wallet claimed tge.
* It reverts if wallet claimed points.
* emit NeedRefund(balance).
*/
function askForRefund() external whenNotPaused nonReentrant {
require(block.timestamp <= periods.vestingAt + refundPeriod, IVesting.IVesting__Unauthorized("Not refundable"));
require(!hasClaimedTGE[msg.sender], IVesting.IVesting__Unauthorized("Not refundable"));
require(pointsClaimed[msg.sender] == 0, IVesting.IVesting__Unauthorized("Not refundable"));
askedRefund[msg.sender] = true;
walletsToRefund.push(msg.sender);
totalToRefund += purchases[msg.sender];
emit IVesting.NeedRefund(walletsToRefund);
}
/**
* @dev Pauses all token transfers.
* Can only be called by the contract owner.
*/
function pause() public onlyOwner {
_pause();
}
/**
* @dev Unpauses token transfers.
* Can only be called by the contract owner.
*/
function unpause() public onlyOwner {
_unpause();
}
/**
* @notice Calculates the amount of IDO tokens allocated to a wallet at TGE (Token Generation Event).
* @dev This function is a view function and does not modify contract state.
* @param wallet The address of the user wallet for whom to calculate TGE tokens.
* @return tgeAmount The calculated amount of IDO tokens allocated to the wallet at TGE.
*/
function previewTGETokens(address wallet) public view returns (uint256) {
return _calculateTGETokens(wallet).intoUint256();
}
/**
* @notice Previews the total amount of vested tokens.
* @dev Calculates the total amount of vested tokens based on the current block timestamp.
*
* @return vestedTokens The total amount of vested tokens.
*/
function previewVestedTokens() public view returns (uint256) {
return _calculateVestedTokens().intoUint256();
}
/**
* @notice Previews the claimable tokens for a given wallet.
* @dev Calculates the total amount of vested tokens for the specified wallet.
*
* @param wallet The wallet address to calculate claimable tokens for.
* @return claimableTokens The total amount of claimable tokens for the wallet.
*/
function previewClaimableTokens(address wallet) public view returns (uint256) {
return _calculateVestedByWallet(wallet).intoUint256();
}
/**
* @notice Previews the claimable points for a given wallet.
* @dev Calculates the total amount of Samurai Points tokens for the specified wallet.
* - Wallets must had purchased IDO tokens
* - Wallets to be refunded cannot get any Samurai Point
* - Points are claimed only once after vesting starts
*
* @param wallet The wallet address to calculate claimable tokens for.
* @return claimablePoints The total amount of claimable points for the wallet.
*/
function previewClaimablePoints(address wallet) public view returns (uint256) {
uint256 purchased = purchases[wallet];
if (purchased == 0) return 0; // wallet has no purchases
if (askedRefund[wallet]) return 0; // wallets that asked for refund cannot get any points
if (pointsClaimed[wallet] > 0) return 0; // wallet already claimed
return ud(purchased).mul(ud(pointsPerToken)).intoUint256();
}
/**
* @notice Calculates the cliff end timestamp for the IDO.
* @dev This function calculates the cliff end timestamp by adding the cliff duration to the vesting start time.
* @return The timestamp representing the end of the cliff period (uint256).
*/
function cliffEndsAt() public view returns (uint256) {
IVesting.Periods memory periodsCopy = periods;
return BokkyPooBahsDateTimeLibrary.addMonths(periodsCopy.vestingAt, periodsCopy.cliff);
}
/**
* @notice Returns the timestamp when vesting ends.
* @dev Calculates the vesting end timestamp by adding the vesting duration to the cliff end timestamp.
*
* @return The timestamp when vesting ends.
*/
function vestingEndsAt() public view returns (uint256) {
return BokkyPooBahsDateTimeLibrary.addMonths(cliffEndsAt(), periods.vestingDuration);
}
function getWalletsToRefund() public view returns (address[] memory) {
return walletsToRefund;
}
/**
* @notice Calculates the amount of tokens allocated for TGE distribution.
* @dev This function calculates the amount of tokens a participant receives during the TGE (Token Generation Event).
* @param wallet The participant's wallet address (address).
* @return The allocated TGE token amount (UD60x18).
*/
function _calculateTGETokens(address wallet) private view returns (UD60x18) {
if (tgeReleasePercent == 0) return convert(0);
return ud(purchases[wallet]).mul(ud(tgeReleasePercent));
}
/**
* @notice Allows the contract set the different time periods for the IDO process.
* @dev This function can only be called by the contract constructor function.
* @param _periods Struct containing configuration for the IDO periods:
* - vestingDuration: Time when participation ends.
* - vestingAt: Time when vesting starts.
* - cliff: Cliff period after TGE before vesting starts.
*/
function _setPeriods(IVesting.Periods memory _periods) private nonReentrant {
require(_periods.vestingDuration > 0, IVesting.IVesting__Invalid("Invalid vestingDuration"));
require(_periods.vestingAt > 0, IVesting.IVesting__Invalid("Invalid vestingAt"));
require(_periods.cliff > 0, IVesting.IVesting__Invalid("Invalid cliff"));
periods = _periods;
emit IVesting.PeriodsSet(_periods);
}
/**
* @notice Alows contract to set the list of wallets and it's purchases to be vested based on contract strategy
* @param wallets list of wallets
* @param tokensPurchased list of amounts of tokens purchased
* @dev This function also sets totalPurchased and totalPoints
*/
function _setPurchases(address[] memory wallets, uint256[] memory tokensPurchased) private nonReentrant {
require(wallets.length > 0, "wallets cannot be empty");
require(
wallets.length == tokensPurchased.length,
IVesting.IVesting__Unauthorized("wallets and tokensPurchased should have same size length")
);
uint256 _totalPurchased;
for (uint256 i = 0; i < wallets.length; i++) {
require(wallets[i] != address(0), IVesting.IVesting__Invalid("Invalid address"));
require(tokensPurchased[i] > 0, IVesting.IVesting__Invalid("Invalid amount permitted"));
address wallet = wallets[i];
purchases[wallet] = tokensPurchased[i];
_totalPurchased += tokensPurchased[i];
}
totalPurchased = _totalPurchased; // Total amount of IDO tokens purchased
totalPoints = ud(_totalPurchased).mul(ud(pointsPerToken)).intoUint256();
emit IVesting.PurchasesSet(wallets, tokensPurchased);
}
/**
* @notice Calculates the amount of tokens allocated for TGE distribution.
* @dev This function calculates the amount of tokens vested based on the vesting duration.
* @return The total amount of vested tokens (UD60x18).
*/
function _calculateVestedTokens() private view returns (UD60x18) {
UD60x18 zero = convert(0);
IVesting.Periods memory periodsCopy = periods;
if (block.timestamp < periodsCopy.vestingAt) return zero; // Vesting not started
/// IN VESTING PERIOD ======================================================
UD60x18 maxOfTokens = ud(totalPurchased);
// tgeReleasePercent can be equal or higher than 0
// use ud because percent is already in 18 decimals
UD60x18 tgeAmount = tgeReleasePercent > 0 ? maxOfTokens.mul(ud(tgeReleasePercent)) : zero;
UD60x18 maxOfTokensForVesting = maxOfTokens.sub(tgeAmount);
uint256 _cliffEndsAt = cliffEndsAt();
/// IN CLIFF PERIOD ========================================================
/// Only TGE is unlocked before cliff ends =================================
if (block.timestamp <= _cliffEndsAt) return tgeAmount;
if (vestingType == IVesting.VestingType.CliffVesting) {
/// CLIFF VESTING =====================================================
return maxOfTokens;
} else {
uint256 _vestingEndsAt = vestingEndsAt();
/// Vest only TGE amount at first second of vesting period =============
if (block.timestamp == periodsCopy.vestingAt) return tgeAmount;
/// All tokens were vested =============================================
if (block.timestamp > _vestingEndsAt) return maxOfTokens;
UD60x18 vestedAmount;
if (vestingType == IVesting.VestingType.LinearVesting) {
/// LINEAR VESTING =================================================
UD60x18 duration = convert(_getDiffByPeriodType(_cliffEndsAt, _vestingEndsAt, IVesting.PeriodType.Days));
UD60x18 elapsedTime = convert(block.timestamp - _cliffEndsAt);
UD60x18 tokensPerSec = maxOfTokensForVesting.div(duration);
vestedAmount = tokensPerSec.mul(elapsedTime).add(tgeAmount);
} else if (vestingType == IVesting.VestingType.PeriodicVesting) {
/// PERIODC VESTING ================================================
UD60x18 totalMonths =
convert(_getDiffByPeriodType(_cliffEndsAt, _vestingEndsAt, IVesting.PeriodType.Months));
UD60x18 elapsedMonths =
convert(_getDiffByPeriodType(_cliffEndsAt, block.timestamp, IVesting.PeriodType.Months));
UD60x18 tokensPerMonth = maxOfTokensForVesting.div(totalMonths);
UD60x18 vested = tokensPerMonth.mul(elapsedMonths);
vestedAmount = vested.add(tgeAmount);
}
return vestedAmount;
}
}
/**
* @notice Calculates the amount of tokens available for a specific wallet.
* @dev This function calculates the amount of tokens a participant can claim at the moment.
* @param wallet The participant's wallet address (address).
* @return The available tokens amount (UD60x18).
*/
function _calculateVestedByWallet(address wallet) private view returns (UD60x18) {
UD60x18 zero = convert(0);
IVesting.Periods memory periodsCopy = periods;
if (block.timestamp < periodsCopy.vestingAt) return zero; // Vesting not started
if (purchases[wallet] == 0) return zero; // Wallet has no purchases
if (askedRefund[wallet]) return zero;
UD60x18 max = ud(purchases[wallet]);
UD60x18 claimed = ud(tokensClaimed[wallet]);
/// User already claimed all tokens vested
if (claimed == max) return zero;
uint256 _cliffEndsAt = cliffEndsAt();
bool isTgeClaimed = hasClaimedTGE[wallet];
/// Only TGE is vested during cliff period
if (block.timestamp <= _cliffEndsAt) return isTgeClaimed ? zero : _calculateTGETokens(wallet);
UD60x18 balance = max.sub(claimed);
/// All tokens were vested -> return all balance remaining
if (block.timestamp > vestingEndsAt()) return balance;
/// CALCS ================================================
/// CLIFF VESTING
if (vestingType == IVesting.VestingType.CliffVesting) return balance;
/// LINEAR VESTING & PERIODIC VESTING
UD60x18 total = ud(totalPurchased);
UD60x18 vested = _calculateVestedTokens();
UD60x18 totalVestedPercentage = vested.mul(convert(100)).div(total);
UD60x18 walletSharePercentage = max.mul(convert(100)).div(total);
UD60x18 walletVestedPercentage = walletSharePercentage.mul(totalVestedPercentage).div(convert(100));
UD60x18 walletVested = total.mul(walletVestedPercentage).div(convert(100));
UD60x18 walletClaimable = walletVested.sub(claimed);
return walletClaimable;
}
/**
* @dev Calculates the time difference between two timestamps based on a given period type.
*
* @param start The start timestamp.
* @param end The end timestamp.
* @param periodType The period type (days or months).
* @return The calculated time difference in seconds (for days) or months.
*/
function _getDiffByPeriodType(uint256 start, uint256 end, IVesting.PeriodType periodType)
private
pure
returns (uint256)
{
if (periodType == IVesting.PeriodType.Days) {
// return number of days times seconds per day
// eg: 2 * 86400 = number of days in secondss
return BokkyPooBahsDateTimeLibrary.diffDays(start, end) * 86_400;
}
// return number of months
// eg 2 or 8
return BokkyPooBahsDateTimeLibrary.diffMonths(start, end);
}
}
// SPDX-License-Identifier: UNLINCENSED
pragma solidity 0.8.28;
import {Test} from "forge-std/Test.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {DeployedVesting} from "../../../src/DeployedVesting.sol";
import {DeployVesting} from "../../../script/DeployVesting.s.sol";
import {ERC20Mock} from "../../../src/mocks/ERC20Mock.sol";
import {IVesting} from "../../../src/interfaces/IVesting.sol";
import {UD60x18, ud, convert} from "@prb/math/src/UD60x18.sol";
import {BokkyPooBahsDateTimeLibrary} from "@BokkyPooBahsDateTimeLibrary/contracts/BokkyPooBahsDateTimeLibrary.sol";
import {console} from "forge-std/console.sol";
contract xxxTest is Test {
uint256 fork;
string public RPC_URL;
DeployedVesting vesting;
function setUp() public virtual {
RPC_URL = vm.envString("BASE_RPC_URL");
fork = vm.createFork(RPC_URL);
vm.selectFork(fork);
vesting = DeployedVesting(0xCFe4E1e1dDB2c5AcaF57Af30271FA2996Bc1aF9F);
}
function testBasic() public view {
address wallet = 0x614550C16857b11a66548d65aA45805659814c93;
(uint256 vestingDuration, uint256 vestingAt, uint256 cliff) = vesting.periods();
if (block.timestamp < vestingAt) console.log("Vesting not started"); // Vesting not started
if (vesting.purchases(wallet) == 0) console.log("Wallet has no purchases"); // Wallet has no purchases
if (vesting.askedRefund(wallet)) console.log("Asked refund");
UD60x18 max = ud(vesting.purchases(wallet));
UD60x18 claimed = ud(vesting.tokensClaimed(wallet));
/// User already claimed all tokens vested
if (claimed == max) console.log("Claimed everything");
uint256 _cliffEndsAt = vesting.cliffEndsAt();
bool isTgeClaimed = vesting.hasClaimedTGE(wallet);
/// Only TGE is vested during cliff period
if (block.timestamp <= _cliffEndsAt) {
if (isTgeClaimed) console.log("claimed tge - zero");
else console.log("tge tokens - ", vesting.previewTGETokens(wallet));
}
UD60x18 balance = max.sub(claimed);
/// All tokens were vested -> return all balance remaining
if (block.timestamp > vesting.vestingEndsAt()) console.log("code return balance - ", balance.intoUint256());
/// CALCS ================================================
/// CLIFF VESTING
if (vesting.vestingType() == IVesting.VestingType.CliffVesting) {
console.log("code return balance - ", balance.intoUint256());
}
console.log("WILL BEGIN CALCS");
/// LINEAR VESTING & PERIODIC VESTING
UD60x18 total = ud(vesting.totalPurchased());
console.log("total", total.intoUint256());
uint256 vested = vesting.previewVestedTokens();
console.log("vested", vested);
UD60x18 totalVestedPercentage = ud(vested).mul(convert(100)).div(total);
console.log("totalVestedPercentage", totalVestedPercentage.intoUint256());
UD60x18 walletSharePercentage = max.mul(convert(100)).div(total);
console.log("walletSharePercentage", walletSharePercentage.intoUint256());
UD60x18 walletVestedPercentage = walletSharePercentage.mul(totalVestedPercentage).div(convert(100));
console.log("walletVestedPercentage", walletVestedPercentage.intoUint256());
UD60x18 walletVested = total.mul(walletVestedPercentage).div(convert(100));
console.log("walletVested", walletVested.intoUint256());
console.log("claimed", claimed.intoUint256());
// UD60x18 walletClaimable = walletVested.sub(claimed);
// console.log("walletClaimable", walletClaimable.intoUint256());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment