Gas Costs
Gas costs measured on Avalanche C-Chain with optimizer enabled (foundry.toml profile default, Solidity ^0.8.34, runs=200). Values are approximate and vary based on state conditions (warm vs. cold storage, existing balances, etc.). Use forge snapshot for up-to-date measurements.
Reference Gas Table
XPower (moe)
| Function | Gas (avg) | Notes |
|---|---|---|
init() (first call per interval) | ~68,000 | Writes block hash + timestamp to storage (2 warm SSTORE) |
init() (subsequent calls) | ~3,000 | Only reads — storage already populated for this interval |
mint(to, blockHash, data) | ~150,000 | Includes 2 _mint calls (double mint to beneficiary + treasury), 1 SSTORE for hash tracking, keccak256 computation, and all validations |
migrate(amount, index) | ~95,000 | Includes burnFrom on old contract + mint on new contract |
seal(index) | ~23,000 | Single SSTORE to set seal flag |
sealAll() | ~23,000 × N | One SSTORE per base contract |
The dominant cost in mint() is the two _mint() internal calls (each writes a new balance slot and updates total supply), plus one SSTORE for the duplicate-prevention hash mapping.
XPowerNft (nft)
| Function | Gas (avg) | Notes |
|---|---|---|
mint(account, level, amount) | ~120,000 | Includes transferFrom of XPOW, 1 ERC1155 mint, total supply update |
mintBatch(account, levels, amounts) | ~120,000 + 30,000/level | Single transferFrom for total deposit, batch mint with _mintBatch |
burn(account, id, amount) | ~80,000 | Burns ERC1155, transfers XPOW back; includes maturity check |
burnBatch(account, ids, amounts) | ~80,000 + 30,000/id | Batch burn with maturity pre-check for all IDs |
upgrade(account, anno, level, amount) | ~200,000 | Burns lower-level NFTs (1 SSTORE), mints higher-level NFTs (1 SSTORE) |
upgradeBatch(account, annos, levels, amounts) | ~200,000 + 40,000/level | Batch upgrades across multiple years/levels |
approveMint(operator, approved) | ~46,000 | Writes bool to nested mapping |
approveUpgrade(operator, approved) | ~46,000 | Writes bool to nested mapping |
migrate(nftId, amount, index) | ~180,000 | Cross-contract: burn old NFT + mint new NFT + optional XPOW migration |
migrateBatch(nftIds, amounts, index) | ~180,000 + 50,000/NFT | Batch migration variant |
APowerNft (ppt)
| Function | Gas (avg) | Notes |
|---|---|---|
init(mty) | ~47,000 | One-time SSTORE for MoeTreasury address |
mint(account, nftId, amount) | ~110,000 | onlyOwner; updates age + shares accumulators; ERC1155 mint |
mintBatch(account, nftIds, amounts) | ~110,000 + 35,000/NFT | Batch mint with per-item age + share updates |
burn(account, nftId, amount) | ~100,000 | onlyOwner; calls mty.refreshClaimed(); updates age + shares; ERC1155 burn |
burnBatch(account, nftIds, amounts) | ~100,000 + 35,000/NFT | Batch burn variant |
safeTransferFrom(...) | ~145,000 | nonReentrant; resets age tracking on sender + receiver; calls mty.refreshClaimed() |
safeBatchTransferFrom(...) | ~145,000 + 40,000/NFT | Batch transfer with per-item age reset |
The safeTransferFrom cost is elevated because it calls _pushBurn → mty.refreshClaimed() (external call + SSTORE) and _pushMint on the receiver.
APower (sov)
| Function | Gas (avg) | Notes |
|---|---|---|
mint(to, claim) | ~100,000 | onlyOwner; wraps XPOW (transferFrom), computes mintable via mean, ERC20 mint |
burn(amount) | ~80,000 | ERC20 burn + unwrap XPOW transfer to msg.sender |
burnFrom(account, amount) | ~85,000 | ERC20 burnFrom (spend allowance) + unwrap XPOW transfer |
mintable(claim) | ~3,000 | Pure view computation (no state changes) |
mintableBatch(claims) | ~3,000 × N | View function, local variable updates |
migrate(amount, index) | ~195,000 | nonReentrant + banq modifier; cross-contract: burns old SOV, migrates MOE, mints new SOV |
SOV migration is the most expensive single operation because it chains across three contracts (old SOV → new SOV, plus MOE migration).
MoeTreasury (mty)
| Function | Gas (avg) | Notes |
|---|---|---|
claim(account, nftId, amount, nonce) | ~200,000 | nonReentrant + banq modifier; computes reward, calls sov.mint() (external), updates claimed/minted trackers. banq may trigger pool supply or excess burn. |
claimBatch(account, nftIds, amount, nonce) | ~200,000 + 50,000/NFT | Batch claim with per-item reward computation and sov.mint() |
refreshRates(false) | ~80,000 | Reads shares from ppt, updates APR integrators + scalars; called after each stake/unstake |
refreshRates(true) | ~80,000 + 20,000/level | Iterates all 34 level slots |
setAPR(nftId, array) | ~75,000 | Writes APR coefficients + appends integrator item (1 SSTORE) |
setAPB(nftId, array) | ~75,000 | Writes APB coefficients + appends integrator item (1 SSTORE) |
rewardOf(account, nftId) | ~10,000 | View: reads ppt.ageOf() (external static call), APR/APB from integrators, computes formula |
claimable(account, nftId) | ~12,000 | View: reads rewardOf() + claimed() |
The claim() cost is dominated by the sov.mint() external call (which includes transferFrom + _mint) and the subsequent _banq() post-processing.
NftTreasury (nty)
| Function | Gas (avg) | Notes |
|---|---|---|
stake(account, nftId, amount) | ~180,000 | safeTransferFrom (nft → nty) + ppt.mint() (external) + mty.refreshRates(false) (external) + emit |
stakeBatch(account, nftIds, amounts) | ~180,000 + 45,000/NFT | Batch stake with safeBatchTransferFrom + ppt.mintBatch + mty.refreshRates(false) |
unstake(account, nftId, amount) | ~160,000 | ppt.burn() (external) + safeTransferFrom (nty → account) + mty.refreshRates(false) + emit |
unstakeBatch(account, nftIds, amounts) | ~160,000 + 45,000/NFT | Batch unstake variant |
approveStake(operator, approved) | ~46,000 | SSTORE on nested mapping |
approveUnstake(operator, approved) | ~46,000 | SSTORE on nested mapping |
Stake/unstake costs include 3 external calls each (nft, ppt, mty), plus events.
Gas Optimization Tips
For Miners
- Batch ETF: Call
init()only once per hour — subsequentmint()calls in the same interval reuse the cached block hash. Track theInitevent to know when a new interval starts. - Use
eth_callto pre-validate — Simulatemint()before submitting to avoid reverting transactions. Check_zerosOf(keccak256(...)) > 0off-chain. - Submit promptly — Block hashes expire after 1 hour (
block.timestamp / 3600interval). Late submissions revert withExpiredBlockHash.
For Stakers
- Batch operations — Use
mintBatch,stakeBatch,claimBatch,unstakeBatchto amortize the base gas cost across multiple items. Batch saves ~30–50% versus individual calls for 5+ items. - Claim strategically — Each claim costs ~200k gas regardless of reward amount. Accumulate rewards before claiming to maximize APOW per gas spent.
- Use
mintableBatchfor previews — Checkmty.mintableBatch()(view, free) before claiming to ensure sufficient APOW output.
For Developers & Integrators
- ERC1155 vs. ERC20 batch transfers — ERC1155 batch operations are significantly more efficient than looping over individual transfers because they use a single SSTORE for operator approval checks.
- Delegate with care — Each
approveMint/approveStake/approveUpgradecosts ~46k gas (cold SSTORE). For one-off operations, prefer direct calls. - Use
multicall— For complex flows (deposit + stake + claim), use a multicall contract to combine calls into a single transaction, saving the 21k base gas per transaction. - Leverage
ERC20Permit— XPOW and APOW support gasless approvals via EIP-2612. Users sign a permit off-chain; the relayer submitspermit()+ the target call atomically.
Fee Estimation
Avalanche C-Chain gas prices are typically low (25–50 nAVAX). At 25 nAVAX/gas:
| Operation | Gas | Cost (AVAX) |
|---|---|---|
PoW mint() | 150,000 | 0.00375 |
NFT mint() | 120,000 | 0.003 |
NFT stake() | 180,000 | 0.0045 |
Reward claim() | 200,000 | 0.005 |
NFT unstake() | 160,000 | 0.004 |
NFT upgrade() | 200,000 | 0.005 |
SOV burn() (unwrap) | 80,000 | 0.002 |
Using forge snapshot
The Foundry project includes gas snapshots. Run:
cd xpower-fy.git
forge snapshotThis produces .gas-snapshot with per-function gas measurements from the test suite:
XPowerTest:testInit() (gas: 68234)
XPowerTest:testMint() (gas: 150123)
XPowerNftTest:testMint() (gas: 118456)
XPowerNftTest:testBurn() (gas: 82134)
XPowerNftTest:testUpgrade() (gas: 203567)
MoeTreasuryTest:testClaim() (gas: 198234)
MoeTreasuryTest:testClaimBatch() (gas: 245678)
NftTreasuryTest:testStake() (gas: 178345)
NftTreasuryTest:testUnstake() (gas: 159876)
APowerTest:testMint() (gas: 102345)
APowerTest:testBurn() (gas: 81234)To filter specific contracts:
forge snapshot --match-contract XPowerTest
forge snapshot --match-contract MoeTreasuryTestSnapshots are compared against the checked-in baseline. CI fails if any function exceeds its baseline gas target.
State Growth Considerations
- Duplicate-prevention mapping (
_hashes): Grows unbounded with eachmint(). Each entry is 32 bytes (value) + key overhead. Indexers should prune historical data. - APR/APB integrators (
aprs,apbs): Each level (0–33) accumulates(stamp, value, area, prev)items onsetAPR()/setAPB()calls. At ~1 update/month/level, growth is manageable (~50 items/year/level). - Claim trackers (
_claimed,_minted): One entry per (account, nftId) pair. Growth proportional to unique stakers × NFT levels.