NFT System Overview
The XPower protocol uses two complementary ERC-1155 multi-token contracts to represent different stages of a user's capital commitment: XPowerNft (the deposit receipt) and APowerNft (the staking receipt). Together they form the backbone of the protocol's tiered staking architecture.
XPowerNft — Deposit Receipt
When a user locks XPOW into the protocol, they receive an XPowerNft — an ERC-1155 token representing the locked XPOW at a specific level and mint year. Each XPowerNft is a claim on the underlying XPOW, redeemable once its maturity date passes.
| Property | Detail |
|---|---|
| Contract | XPowerNft.sol |
| Name / Symbol | XPower NFTs / XPOWNFT |
| Token Standard | ERC-1155 (multi-token) |
| Creation | Minted via mint(account, level, amount) — requires XPOW deposit |
| Destruction | Burned via burn(account, id, amount) — releases XPOW back |
| Transferability | Fully transferable (ERC-1155 standard) |
Lifecycle
XPOW Token ──[deposit]──→ XPowerNft ──[stake]──→ APowerNft
↑ │
└──[redeem]──────────────┘XPowerNfts exist in three states:
- Held in wallet — transferable, can be sold or used as collateral
- Staked in NftTreasury — replaced by an APowerNft, earning rewards
- Burned for redemption — XPOW released after maturity
APowerNft — Staking Receipt
When a user stakes an XPowerNft in the NftTreasury, the deposit receipt is held in escrow and an APowerNft is minted in exchange. The APowerNft tracks the staked position and accumulates age — the time-weighted presence in the staking pool, which determines reward eligibility.
| Property | Detail |
|---|---|
| Contract | APowerNft.sol |
| Name / Symbol | APower NFTs / APOWNFT |
| Token Standard | ERC-1155 (multi-token) |
| Minter/Burner | Only NftTreasury (onlyOwner) |
| Age Tracking | _age[account][nftId] accumulator per position |
APowerNfts cannot be minted or burned directly by users — only the NftTreasury contract holds this privilege. This ensures every APowerNft corresponds one-to-one with a legitimate XPowerNft held in escrow.
On transfer, APowerNfts reset their age accumulator on both sender and receiver sides, preventing double-claiming of rewards accumulated during the holding period.
Why ERC-1155?
Both contracts use the ERC-1155 multi-token standard rather than ERC-721. This choice provides several advantages:
- Gas efficiency — a single contract manages all levels and years, avoiding per-token deployment costs
- Batch operations —
mintBatch,burnBatch, andsafeBatchTransferFromprocess multiple token IDs in a single transaction - Fungibility within level — all NFTs at the same
(year, level)are identical and interchangeable, making them suitable for partial staking/unstaking - Unified metadata — a single
uri()function serves all token IDs, with per-year overrides via URIMalleable
NFT ID Encoding
Each NFT is identified by a single uint256 that encodes both the mint year and tier level:
Where:
is the UTC calendar year (e.g., 2026), subject to — must be a multiple of 3 and strictly less than 100
Extraction Functions
| Function | Formula | Example (id=202603) |
|---|---|---|
yearOf(id) | 2026 | |
levelOf(id) | 3 | |
idBy(year, level) | 202603 | |
denominationOf(level) | 1,000 XPOW |
This encoding naturally groups NFTs by year (0–99 IDs per year) and ensures that a new year automatically introduces a new set of token IDs without collision.
Metadata and IPFS
Both contracts inherit URIMalleable, a metadata management layer that supports:
- Default URI — the fallback
uri()for all token IDs, set at construction - Per-year overrides — a separate URI can be set for each mint year via
setURI(newuri, year), called by theURI_DATA_ROLE - Decade malleability — a year-specific URI can be changed up until
passes; after that decade window, the URI becomes permanent ( fixedURI(id)returnstrue) - Contract-level URI —
contractURI()returns an OpenSea-compatible collection metadata URI, settable bysetContractURI()
The URI for an NFT is resolved as:
uri(id) = perYearURI[yearOf(id)] || defaultURIThis design enables IPFS-based metadata hosting where the metadata for a given year can be updated for 10 years before freezing permanently — sufficient time to settle on final artwork, attributes, and descriptions.
EIP-2981 Royalties
Both NFT contracts implement EIP-2981 (NFT Royalty Standard) with a flat 0.5% royalty:
The royaltyInfo(tokenId, salePrice) function returns the beneficiary address and royalty amount. The beneficiary is configurable via setRoyal(beneficiary) by the NFT_ROYAL_ROLE.
This 0.5% rate applies uniformly across all token IDs and is designed to be protocol-sustainable rather than extractive, covering operational costs without discouraging secondary market activity.
Contract Ownership and Access Control
| Action | XPowerNft | APowerNft |
|---|---|---|
| Mint | Any user (with XPOW deposit) | NftTreasury only (onlyOwner) |
| Burn | Any holder (after maturity) | NftTreasury only (onlyOwner) |
| Stake | Any holder (via NftTreasury) | — |
| Unstake | Released by NftTreasury | — |
| Upgrade | Any holder (1000:1 ratio) | — |
| URI change | URI_DATA_ROLE | URI_DATA_ROLE |
| Royalty change | NFT_ROYAL_ROLE | NFT_ROYAL_ROLE |
| Migration | Owner + deadline | Owner + deadline |
The NftTreasury contract acts as the sole authorized minter and burner of APowerNfts. This ownership model guarantees that every APowerNft in circulation is backed by a corresponding XPowerNft held in the treasury's escrow balance. Users interact with the NftTreasury via its stake/unstake methods — they never call APowerNft mint/burn directly.
Transferability and Secondary Markets
Both XPowerNft and APowerNft are fully transferable via the standard ERC-1155 safeTransferFrom and safeBatchTransferFrom functions. This enables:
- Wallet-to-wallet transfers — standard ERC-1155 behavior
- Marketplace listings — compatible with any ERC-1155 marketplace (OpenSea, Rarible, etc.)
- Collateral usage — NFTs can be deposited into lending protocols or used as loan collateral
Transfer and Age Reset (APowerNft)
When an APowerNft is transferred, the contract resets age accumulators for both sender and receiver:
function safeTransferFrom(address account, address to, uint256 nftId, uint256 amount, bytes memory data)
public override nonReentrant
{
_pushBurn(account, nftId, amount); // age decrement for sender
_pushMint(to, nftId, amount); // age reset for receiver
super.safeTransferFrom(account, to, nftId, amount, data);
}The _pushBurn function proportionally reduces the sender's age, updating the MoeTreasury's refreshClaimed to prevent the sender from claiming rewards they no longer hold. The _pushMint initializes the receiver's age with a fresh timestamp. This ensures that:
- The sender retains rewards only for the staked duration they held the position
- The receiver starts with age=0 (their holding period begins at transfer time)
- The
nonReentrantmodifier prevents reentrancy attacks during the multi-step process
XPowerNft Transfers
XPowerNft transfers use the standard ERC-1155 path without any age bookkeeping (age only applies to APowerNft). A transferred XPowerNft carries its maturity date with it — the new owner gains the same redemption rights as the original minter, with the same lock period applying.
Supervisor and Role-Based Access
Both contracts inherit a supervised role system that segments administrative privileges. This is not a single-owner model but a role-based architecture where different roles control different aspects of the contract:
| Role | Scope | Functions Guarded |
|---|---|---|
DEFAULT_ADMIN_ROLE | Role management | Grant/revoke all other roles |
URI_DATA_ROLE | Metadata | setURI(), setURI(year), setContractURI() |
NFT_ROYAL_ROLE | Royalties | setRoyal(beneficiary) |
SUPERVISOR_ROLE | Supervision | Supervisor-specific operations |
| Owner (Ownable) | Ownership | transferOwnership(), renounceOwnership() |
APowerNft additionally uses the onlyOwner modifier on mint/burn — in this context, the owner is the NftTreasury contract. The NftTreasury itself does not expose mint/burn to end users; it only calls APowerNft internally during stake/unstake.
Contract Initialization (APowerNft)
APowerNft requires a post-deployment initialization step:
function init(address mty) externalThis links the APowerNft to the MoeTreasury contract, which it uses during transfers for refreshClaimed() calls. The init function can only be called once — subsequent calls revert with AlreadyInitialized. This two-phase initialization pattern (constructor + init) is used because the MoeTreasury may not be deployed at the time the APowerNft constructor executes.
Summary: Token Comparison
| XPowerNft | APowerNft | |
|---|---|---|
| Role | Deposit receipt | Staking receipt |
| Creation | User deposits XPOW | NftTreasury stakes XPowerNft |
| Backing | XPOW held in escrow | XPowerNft held in NftTreasury |
| Minter | Any user (mint is public) | NftTreasury only (onlyOwner) |
| Burner | Any holder (after maturity) | NftTreasury only (onlyOwner) |
| Age tracking | No | Yes (_age accumulator) |
| Maturity lock | Yes (per-level) | No (inherits from backing XPowerNft) |
| Rewards | None directly | Staking rewards via MoeTreasury |
| Transferability | Fully transferable | Transferable (age resets on transfer) |
| EIP-2981 | 0.5% royalty | 0.5% royalty |
| Metadata | Per-year via URIMalleable | Per-year via URIMalleable |
Further Reading
- Levels and Denominations — how tier levels map to XPOW values
- Minting and Upgrading — deposit mechanics and 1000:1 consolidation
- Redemption Maturity — lock periods and when XPOW can be reclaimed