PowerPass
Inherits: Initializable, ERC721Upgradeable, ERC721URIStorageUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, IPowerPass
Title: PowerPass - ShareX Ecosystem Premium Access NFT
Author: ShareX Team
Upgradeable ERC721 contract with time-based whitelist sales rounds PowerPass implements sophisticated whitelist-based sales with two rounds:
- Priority Round: Exclusive access for MAGNA/NOVA stakers
- General Round: Access for all ShareX Keys stakers Features:
- Gas-optimized struct packing and unchecked arithmetic
- Snapshot-based whitelist eligibility tied to ShareXKeysStaking
- Admin-configurable sales rounds and pricing
- Upgradeable proxy pattern for future enhancements
- Comprehensive validation and error handling
State Variables
MAX_SUPPLY
Maximum supply of PowerPass NFTs
uint256 public constant MAX_SUPPLY = 5000
BPS_DENOMINATOR
Basis points denominator for discount calculations
uint16 private constant BPS_DENOMINATOR = 10_000
NOVA_DISCOUNT_BPS
Nova discount expressed in basis points (80%)
uint16 private constant NOVA_DISCOUNT_BPS = 8_000
MAX_NOVA_DISCOUNT_PER_ADDRESS
Maximum number of discounted NFTs per address for Nova holders
uint64 private constant MAX_NOVA_DISCOUNT_PER_ADDRESS = 10
_paymentToken
ERC20 payment token (e.g., USDT on BSC)
IERC20 private _paymentToken
MINTER_ROLE
Optional direct mint role for operational tooling
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE")
_currentRoundId
Current active round ID (starts from 1, 0 = no round started)
uint64 private _currentRoundId
_roundConfigs
Round configurations: roundId => RoundConfig
mapping(uint64 roundId => PowerPassRoundConfig config) private _roundConfigs
_roundWhitelists
Round whitelists: roundId => user => WhitelistInfo
mapping(uint64 roundId => mapping(address user => PowerPassWhitelistInfo whitelist)) private
_roundWhitelists
_custodialWhitelists
Custodial whitelist allocations for ShareX wallet flow (independent of rounds)
mapping(address account => PowerPassCustodialWhitelistInfo whitelist) private
_custodialWhitelists
_baseTokenURI
Base URI for token metadata
string private _baseTokenURI
_discountCodes
Active discount code hashes
mapping(bytes32 codeHash => bool isActive) private _discountCodes
_totalSupply
Current token supply
uint256 private _totalSupply
_currentTokenId
Current token ID counter
uint256 private _currentTokenId
_web3MintedCount
Web3 channel minted count (via public mint function)
uint256 private _web3MintedCount
_custodialMintedCount
Custodial/Web2 channel minted count (via MINTER_ROLE)
uint256 private _custodialMintedCount
_discountCodeUsageCount
Discount code usage count: codeHash => number of NFTs minted using this code
mapping(bytes32 codeHash => uint256 usageCount) private _discountCodeUsageCount
__gap
Storage gap for upgrade safety
uint256[30] private __gap
Functions
validAddress
Modifier to validate non-zero addresses
modifier validAddress(address addr) ;
validAmount
Modifier to validate non-zero amounts
modifier validAmount(uint256 amount) ;
initialize
Initialize the PowerPass contract
Replaces constructor for upgradeable pattern
function initialize(address _admin, string calldata baseURI_)
external
initializer
validAddress(_admin);
Parameters
| Name | Type | Description |
|---|---|---|
_admin | address | Address to grant admin role to |
baseURI_ | string | Base URI for token metadata |
initializeWithToken
Initialize with admin, baseURI and payment token
function initializeWithToken(address _admin, string calldata baseURI_, address paymentToken_)
external
initializer
validAddress(_admin)
validAddress(paymentToken_);
Parameters
| Name | Type | Description |
|---|---|---|
_admin | address | Address to grant admin role to |
baseURI_ | string | Base URI for token metadata |
paymentToken_ | address | ERC20 token used for payments (e.g., USDT) |
startNewRound
Start a new sales round with configuration and supply limit
Admin function to create a new round; increments roundId automatically
function startNewRound(SalesConfig calldata config, uint256 supplyLimit)
external
override
onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
config | SalesConfig | Sales configuration including timestamps and pricing |
supplyLimit | uint256 | Maximum NFTs for this round (0 = no limit, still capped by MAX_SUPPLY) Requirements: - Caller must have DEFAULT_ADMIN_ROLE - Priority round must end before or when general round starts - All timestamps must be future timestamps (> block.timestamp) - Standard price must be greater than 0 Emits: PowerPassNewRoundStarted event |
setPaymentToken
Set ERC20 payment token address (e.g., USDT on BSC)
Admin function; price is interpreted in this token's decimals
function setPaymentToken(address token) external override onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
token | address | ERC20 token address used for payments |
setDiscountCodeDiscount
Set discount code discount amount for current round
Updates discountCodeDiscount in current round config
function setDiscountCodeDiscount(uint256 discountAmount)
external
override
onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
discountAmount | uint256 | New discount amount per NFT |
addDiscountCodes
Register discount code hashes that can be redeemed on-chain
function addDiscountCodes(bytes32[] calldata codeHashes)
external
override
onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
codeHashes | bytes32[] | Array of keccak256 hashes of discount codes |
removeDiscountCodes
Remove discount code hashes
function removeDiscountCodes(bytes32[] calldata codeHashes)
external
override
onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
codeHashes | bytes32[] | Array of keccak256 hashes of discount codes to remove |
isDiscountCodeValid
Check if discount code hash is currently active
function isDiscountCodeValid(bytes32 codeHash) external view override returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
codeHash | bytes32 | keccak256 hash of the off-chain discount code |
getDiscountCodeUsageCount
Get the usage count for a specific discount code
Only counts usage when discount code is actually applied (excludes Nova discount cases)
function getDiscountCodeUsageCount(bytes32 codeHash) external view override returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
codeHash | bytes32 | keccak256 hash of the discount code |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | usageCount Number of NFTs minted using this discount code |
mint
Mint PowerPass NFTs during active sales rounds
Gas-optimized minting with comprehensive validation
function mint(uint256 amount, string calldata discountCode)
external
payable
override
nonReentrant
validAmount(amount);
Parameters
| Name | Type | Description |
|---|---|---|
amount | uint256 | Number of NFTs to mint |
discountCode | string | Optional discount code string; ignored when empty or Nova discount applies Validation includes: - Active sales round verification (priority or general) - Whitelist allocation and remaining balance checks - Payment in ERC20 payment token (e.g., USDT) equals applicable discounted price * amount - Maximum supply limit enforcement Requirements: - Active sales round (priority or general) must be ongoing - Amount must not exceed remaining allocation for current round - Payment must be provided via ERC20 approve + transferFrom - User must have whitelist allocation for current round Emits: PowerPassBatchMinted event |
_calculateTotalPrice
Calculate total price with Nova and discount code discounts
function _calculateTotalPrice(
PowerPassRoundConfig storage roundConfig,
uint256 amount,
uint256 novaEligible,
string calldata discountCode
) private returns (uint256 totalPrice);
Parameters
| Name | Type | Description |
|---|---|---|
roundConfig | PowerPassRoundConfig | Round configuration containing prices |
amount | uint256 | Total amount to mint |
novaEligible | uint256 | Amount eligible for Nova discount |
discountCode | string | Discount code string |
Returns
| Name | Type | Description |
|---|---|---|
totalPrice | uint256 | Final price after discounts |
batchSetWhitelists
Batch set whitelist allocations for multiple users
Admin function for efficient whitelist management. Preserves already minted counts and clamps new allocations to be >= minted so far for each round.
function batchSetWhitelists(
address[] calldata accounts,
uint256[] calldata priorityAmounts,
uint256[] calldata generalAmounts
) external override onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
accounts | address[] | Array of addresses to set allocations for |
priorityAmounts | uint256[] | Array of priority allocations corresponding to accounts |
generalAmounts | uint256[] | Array of total (priority + general) allocations corresponding to accounts Requirements: - Caller must have DEFAULT_ADMIN_ROLE - All arrays must have equal length - No account can be zero address - Never reduces effective allocation below already minted amounts - Gas-optimized with unchecked arithmetic for large batches Emits: PowerPassBatchWhitelistSet event |
batchSetNovaDiscounts
Batch set Nova discount allocations for multiple users
Admin function to configure 80% pricing eligibility per address
function batchSetNovaDiscounts(
address[] calldata accounts,
uint256[] calldata discountAllocations
) external override onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
accounts | address[] | Array of addresses to configure |
discountAllocations | uint256[] | Array of discount allocations corresponding to accounts |
batchSetCustodialWhitelists
Batch set custodial mint allocations for ShareX wallet users
Separate whitelist for web2 custodial flow; amounts represent direct mint allowance
function batchSetCustodialWhitelists(
address[] calldata accounts,
uint256[] calldata allocations
) external override onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
accounts | address[] | Custodial wallet addresses (ShareX wallet addresses or end-user custodial addrs) |
allocations | uint256[] | Total NFTs each address may receive via custodial mint |
setBaseURI
Set the base URI for token metadata
Admin function to configure NFT metadata URIs
function setBaseURI(string calldata baseURI) external override onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
baseURI | string | The new base URI for all tokens Requirements: - Caller must have DEFAULT_ADMIN_ROLE - BaseURI should point to valid metadata endpoint Emits: PowerPassBaseURIUpdated event |
withdrawFunds
Withdraw payment tokens to admin
Admin function to withdraw accumulated ERC20 (USDT) from sales Requirements:
- Caller must have DEFAULT_ADMIN_ROLE
- Contract must have token balance > 0 Emits: PowerPassFundsWithdrawn event
function withdrawFunds() external override onlyRole(DEFAULT_ADMIN_ROLE);
paymentToken
Get ERC20 payment token address
function paymentToken() external view override returns (address);
mint
Direct mint by operators with MINTER_ROLE
function mint(address to, string calldata tokenURI_)
external
onlyRole(MINTER_ROLE)
returns (uint256 tokenId);
mint
Direct mint by operators with MINTER_ROLE using automatic token URI formatting
function mint(address to) external onlyRole(MINTER_ROLE) returns (uint256 tokenId);
burn
Burn a token (legacy compatibility)
function burn(uint256 tokenId) external;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | Token ID to burn |
setTokenURI
Set token URI for a specific token (admin only)
function setTokenURI(uint256 tokenId, string calldata newTokenURI)
external
onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | Token ID to set URI for |
newTokenURI | string | New URI for the token |
setCurrentTokenId
Set the next token ID counter (admin only)
Use this to skip token ID 0 and start from 1, or resume from a specific ID
function setCurrentTokenId(uint256 newTokenId) external onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
newTokenId | uint256 | The next token ID to be minted |
currentTokenId
Get the current token ID counter
function currentTokenId() external view returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The next token ID that will be minted |
getMintStatus
Get current mint status for a user
View function providing complete minting information and round status
function getMintStatus(address account)
external
view
override
returns (MintStatus memory status);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | The address to query mint status for |
Returns
| Name | Type | Description |
|---|---|---|
status | MintStatus | Complete mint status including allocations, current price, and round status The MintStatus contains: - priorityRemaining: NFTs remaining in priority allocation - generalRemaining: NFTs remaining in general allocation - standardPrice: Current standard price per NFT based on active round - isPriorityActive: Whether priority round is currently active - isGeneralActive: Whether general round is currently active |
getWhitelistInfo
Get whitelist allocations for an account
View function for checking total allocations across both rounds
function getWhitelistInfo(address account)
external
view
override
returns (WhitelistInfo memory info);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | The address to query allocations for |
Returns
| Name | Type | Description |
|---|---|---|
info | WhitelistInfo | Complete whitelist information including allocations and minted amounts The WhitelistInfo contains: - priorityAllocation: Total NFTs allocated for priority round - generalAllocation: Total NFTs allocated for general round - priorityMinted: Already minted in priority round - generalMinted: Already minted in general round |
getCustodialWhitelistInfo
Get custodial whitelist allocation info for a ShareX wallet address
function getCustodialWhitelistInfo(address account)
external
view
override
returns (CustodialWhitelistInfo memory info);
getSalesConfig
Get current sales configuration (from current round)
View function for checking sales round parameters and pricing
function getSalesConfig() external view override returns (SalesConfig memory config);
Returns
| Name | Type | Description |
|---|---|---|
config | SalesConfig | Complete sales configuration including timestamps and pricing The SalesConfig contains: - priorityStartTime: Priority round start timestamp (UTC+8) - priorityEndTime: Priority round end timestamp (UTC+8) - generalStartTime: General round start timestamp (UTC+8) - generalEndTime: General round end timestamp (UTC+8) - standardPrice: Price per NFT in wei |
getCurrentRoundId
Get current round ID
function getCurrentRoundId() external view override returns (uint64);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint64 | roundId Current active round ID (0 = no round started) |
getCurrentRoundInfo
Get current round information including supply limit and minted count
function getCurrentRoundInfo() external view override returns (RoundInfo memory info);
Returns
| Name | Type | Description |
|---|---|---|
info | RoundInfo | RoundInfo struct with roundId, supplyLimit, mintedCount, remaining |
getRoundConfig
Get configuration for a specific round
function getRoundConfig(uint64 roundId)
external
view
override
returns (SalesConfig memory config, uint256 supplyLimit, uint256 mintedCount);
Parameters
| Name | Type | Description |
|---|---|---|
roundId | uint64 | Round ID to query |
Returns
| Name | Type | Description |
|---|---|---|
config | SalesConfig | Sales configuration for the round |
supplyLimit | uint256 | Maximum NFTs for the round |
mintedCount | uint256 | NFTs already minted in the round |
getWhitelistInfoForRound
Get whitelist info for a user in a specific round
function getWhitelistInfoForRound(uint64 roundId, address account)
external
view
override
returns (WhitelistInfo memory info);
Parameters
| Name | Type | Description |
|---|---|---|
roundId | uint64 | Round ID to query |
account | address | User address to query |
Returns
| Name | Type | Description |
|---|---|---|
info | WhitelistInfo | WhitelistInfo for the user in the specified round |
totalSupply
Get total supply of minted PowerPass NFTs
View function for supply tracking and analytics
function totalSupply() external view override returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | totalSupply Current total supply of minted NFTs |
getChannelMintedCounts
Get minted counts by channel (Web3 vs Custodial/Web2)
function getChannelMintedCounts()
external
view
returns (uint256 web3Count, uint256 custodialCount);
Returns
| Name | Type | Description |
|---|---|---|
web3Count | uint256 | Number of NFTs minted via Web3 public mint |
custodialCount | uint256 | Number of NFTs minted via custodial/Web2 channel |
maxSupply
Get maximum supply limit for PowerPass NFTs
View function for supply cap information
function maxSupply() external pure override returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | maxSupply Maximum number of NFTs that can ever be minted |
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721Upgradeable, ERC721URIStorageUpgradeable, AccessControlUpgradeable, IERC165)
returns (bool);
tokenURI
function tokenURI(uint256 tokenId)
public
view
override(ERC721Upgradeable, ERC721URIStorageUpgradeable)
returns (string memory);
_update
Transfers tokenId from its current owner to to, or alternatively mints (or burns) if
the current owner
(or to) is the zero address. Returns the owner of the tokenId before the update.
The auth argument is optional. If the value passed is non 0, then this function will check
that
auth is either the owner of the token, or approved to operate on the token (by the owner).
Emits a {Transfer} event.
NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
function _update(address to, uint256 tokenId, address auth)
internal
override(ERC721Upgradeable)
returns (address);
_increaseBalance
Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override. NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that a uint256 would ever overflow from increments when these increments are bounded to uint128 values. WARNING: Increasing an account's balance using this function tends to be paired with an override of the {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership remain consistent with one another.
function _increaseBalance(address account, uint128 value) internal override(ERC721Upgradeable);
_baseURI
Base URI for tokens
function _baseURI() internal view override returns (string memory);
_validateAndUpdateAllocation
Validate and update allocation for minting
function _validateAndUpdateAllocation(
PowerPassWhitelistInfo storage userWhitelist,
uint256 amount,
bool isPriorityActive
) private returns (uint256 novaEligible);
Parameters
| Name | Type | Description |
|---|---|---|
userWhitelist | PowerPassWhitelistInfo | |
amount | uint256 | Amount to mint |
isPriorityActive | bool | Whether priority round is active |
_mintTokens
Mint tokens with gas-optimized loop and proper token URI formatting
function _mintTokens(uint256 amount) private;
Parameters
| Name | Type | Description |
|---|---|---|
amount | uint256 | Amount of tokens to mint |
_formatTokenURI
Format token URI properly as {tokenId}.json
function _formatTokenURI(uint256 tokenId) private pure returns (string memory);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | Token ID to format |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | Formatted token URI relative path |
_toString
Convert uint256 to string (gas-optimized)
function _toString(uint256 value) private pure returns (string memory);
Parameters
| Name | Type | Description |
|---|---|---|
value | uint256 | Number to convert |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | String representation of the number |
_checkRoundActive
Check if priority or general round is active for given config
function _checkRoundActive(PowerPassRoundConfig storage config)
private
view
returns (bool isPriorityActive, bool isGeneralActive);
Parameters
| Name | Type | Description |
|---|---|---|
config | PowerPassRoundConfig | Round configuration to check |
Returns
| Name | Type | Description |
|---|---|---|
isPriorityActive | bool | Whether priority round is active |
isGeneralActive | bool | Whether general round is active |
_priorityRemaining
function _priorityRemaining(PowerPassWhitelistInfo storage info)
private
view
returns (uint256);
_generalRemaining
function _generalRemaining(PowerPassWhitelistInfo storage info) private view returns (uint256);
_novaDiscountRemainingRaw
function _novaDiscountRemainingRaw(PowerPassWhitelistInfo storage info)
private
view
returns (uint256);
_novaDiscountRemaining
function _novaDiscountRemaining(PowerPassWhitelistInfo storage info)
private
view
returns (uint256);
_calculateNovaDiscountPrice
function _calculateNovaDiscountPrice(uint256 standardPrice) private pure returns (uint256);
_calculatePriorityAllocation
Calculate priority round allocation based on MAGNA/NOVA stakes
function _calculatePriorityAllocation(uint256 magnaStaked, uint256 novaStaked)
private
pure
returns (uint64 allocation);
Parameters
| Name | Type | Description |
|---|---|---|
magnaStaked | uint256 | Amount of MAGNA tokens staked |
novaStaked | uint256 | Amount of NOVA tokens staked |
Returns
| Name | Type | Description |
|---|---|---|
allocation | uint64 | Priority round allocation (max 10 NFTs) |
_calculateGeneralAllocation
Calculate general round allocation based on all stakes
function _calculateGeneralAllocation(
uint256 essentiaStaked,
uint256 magnaStaked,
uint256 novaStaked
) private pure returns (uint64 allocation);
Parameters
| Name | Type | Description |
|---|---|---|
essentiaStaked | uint256 | Amount of ESSENTIA tokens staked |
magnaStaked | uint256 | Amount of MAGNA tokens staked |
novaStaked | uint256 | Amount of NOVA tokens staked |
Returns
| Name | Type | Description |
|---|---|---|
allocation | uint64 | General round allocation (max 10 NFTs) |