Skip to content

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.

PropertyDetail
ContractXPowerNft.sol
Name / SymbolXPower NFTs / XPOWNFT
Token StandardERC-1155 (multi-token)
CreationMinted via mint(account, level, amount) — requires XPOW deposit
DestructionBurned via burn(account, id, amount) — releases XPOW back
TransferabilityFully transferable (ERC-1155 standard)

Lifecycle

XPOW Token ──[deposit]──→ XPowerNft ──[stake]──→ APowerNft
                          ↑                        │
                          └──[redeem]──────────────┘

XPowerNfts exist in three states:

  1. Held in wallet — transferable, can be sold or used as collateral
  2. Staked in NftTreasury — replaced by an APowerNft, earning rewards
  3. 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.

PropertyDetail
ContractAPowerNft.sol
Name / SymbolAPower NFTs / APOWNFT
Token StandardERC-1155 (multi-token)
Minter/BurnerOnly 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 operationsmintBatch, burnBatch, and safeBatchTransferFrom process 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:

nftId=100×year+level

Where:

  • year is the UTC calendar year (e.g., 2026), subject to year>2020
  • level{0,3,6,,99} — must be a multiple of 3 and strictly less than 100

Extraction Functions

FunctionFormulaExample (id=202603)
yearOf(id)id/1002026
levelOf(id)idmod1003
idBy(year, level)100×year+level202603
denominationOf(level)10level1,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 the URI_DATA_ROLE
  • Decade malleability — a year-specific URI can be changed up until year+10 passes; after that decade window, the URI becomes permanent (fixedURI(id) returns true)
  • Contract-level URIcontractURI() returns an OpenSea-compatible collection metadata URI, settable by setContractURI()

The URI for an NFT is resolved as:

uri(id) = perYearURI[yearOf(id)] || defaultURI

This 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:

royalty=salePrice200

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

ActionXPowerNftAPowerNft
MintAny user (with XPOW deposit)NftTreasury only (onlyOwner)
BurnAny holder (after maturity)NftTreasury only (onlyOwner)
StakeAny holder (via NftTreasury)
UnstakeReleased by NftTreasury
UpgradeAny holder (1000:1 ratio)
URI changeURI_DATA_ROLEURI_DATA_ROLE
Royalty changeNFT_ROYAL_ROLENFT_ROYAL_ROLE
MigrationOwner + deadlineOwner + 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:

solidity
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 nonReentrant modifier 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:

RoleScopeFunctions Guarded
DEFAULT_ADMIN_ROLERole managementGrant/revoke all other roles
URI_DATA_ROLEMetadatasetURI(), setURI(year), setContractURI()
NFT_ROYAL_ROLERoyaltiessetRoyal(beneficiary)
SUPERVISOR_ROLESupervisionSupervisor-specific operations
Owner (Ownable)OwnershiptransferOwnership(), 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:

solidity
function init(address mty) external

This 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

XPowerNftAPowerNft
RoleDeposit receiptStaking receipt
CreationUser deposits XPOWNftTreasury stakes XPowerNft
BackingXPOW held in escrowXPowerNft held in NftTreasury
MinterAny user (mint is public)NftTreasury only (onlyOwner)
BurnerAny holder (after maturity)NftTreasury only (onlyOwner)
Age trackingNoYes (_age accumulator)
Maturity lockYes (per-level)No (inherits from backing XPowerNft)
RewardsNone directlyStaking rewards via MoeTreasury
TransferabilityFully transferableTransferable (age resets on transfer)
EIP-29810.5% royalty0.5% royalty
MetadataPer-year via URIMalleablePer-year via URIMalleable

Further Reading