Skip to content

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)

FunctionGas (avg)Notes
init() (first call per interval)~68,000Writes block hash + timestamp to storage (2 warm SSTORE)
init() (subsequent calls)~3,000Only reads — storage already populated for this interval
mint(to, blockHash, data)~150,000Includes 2 _mint calls (double mint to beneficiary + treasury), 1 SSTORE for hash tracking, keccak256 computation, and all validations
migrate(amount, index)~95,000Includes burnFrom on old contract + mint on new contract
seal(index)~23,000Single SSTORE to set seal flag
sealAll()~23,000 × NOne 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)

FunctionGas (avg)Notes
mint(account, level, amount)~120,000Includes transferFrom of XPOW, 1 ERC1155 mint, total supply update
mintBatch(account, levels, amounts)~120,000 + 30,000/levelSingle transferFrom for total deposit, batch mint with _mintBatch
burn(account, id, amount)~80,000Burns ERC1155, transfers XPOW back; includes maturity check
burnBatch(account, ids, amounts)~80,000 + 30,000/idBatch burn with maturity pre-check for all IDs
upgrade(account, anno, level, amount)~200,000Burns lower-level NFTs (1 SSTORE), mints higher-level NFTs (1 SSTORE)
upgradeBatch(account, annos, levels, amounts)~200,000 + 40,000/levelBatch upgrades across multiple years/levels
approveMint(operator, approved)~46,000Writes bool to nested mapping
approveUpgrade(operator, approved)~46,000Writes bool to nested mapping
migrate(nftId, amount, index)~180,000Cross-contract: burn old NFT + mint new NFT + optional XPOW migration
migrateBatch(nftIds, amounts, index)~180,000 + 50,000/NFTBatch migration variant

APowerNft (ppt)

FunctionGas (avg)Notes
init(mty)~47,000One-time SSTORE for MoeTreasury address
mint(account, nftId, amount)~110,000onlyOwner; updates age + shares accumulators; ERC1155 mint
mintBatch(account, nftIds, amounts)~110,000 + 35,000/NFTBatch mint with per-item age + share updates
burn(account, nftId, amount)~100,000onlyOwner; calls mty.refreshClaimed(); updates age + shares; ERC1155 burn
burnBatch(account, nftIds, amounts)~100,000 + 35,000/NFTBatch burn variant
safeTransferFrom(...)~145,000nonReentrant; resets age tracking on sender + receiver; calls mty.refreshClaimed()
safeBatchTransferFrom(...)~145,000 + 40,000/NFTBatch transfer with per-item age reset

The safeTransferFrom cost is elevated because it calls _pushBurnmty.refreshClaimed() (external call + SSTORE) and _pushMint on the receiver.

APower (sov)

FunctionGas (avg)Notes
mint(to, claim)~100,000onlyOwner; wraps XPOW (transferFrom), computes mintable via mean, ERC20 mint
burn(amount)~80,000ERC20 burn + unwrap XPOW transfer to msg.sender
burnFrom(account, amount)~85,000ERC20 burnFrom (spend allowance) + unwrap XPOW transfer
mintable(claim)~3,000Pure view computation (no state changes)
mintableBatch(claims)~3,000 × NView function, local variable updates
migrate(amount, index)~195,000nonReentrant + 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)

FunctionGas (avg)Notes
claim(account, nftId, amount, nonce)~200,000nonReentrant + 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/NFTBatch claim with per-item reward computation and sov.mint()
refreshRates(false)~80,000Reads shares from ppt, updates APR integrators + scalars; called after each stake/unstake
refreshRates(true)~80,000 + 20,000/levelIterates all 34 level slots
setAPR(nftId, array)~75,000Writes APR coefficients + appends integrator item (1 SSTORE)
setAPB(nftId, array)~75,000Writes APB coefficients + appends integrator item (1 SSTORE)
rewardOf(account, nftId)~10,000View: reads ppt.ageOf() (external static call), APR/APB from integrators, computes formula
claimable(account, nftId)~12,000View: 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)

FunctionGas (avg)Notes
stake(account, nftId, amount)~180,000safeTransferFrom (nft → nty) + ppt.mint() (external) + mty.refreshRates(false) (external) + emit
stakeBatch(account, nftIds, amounts)~180,000 + 45,000/NFTBatch stake with safeBatchTransferFrom + ppt.mintBatch + mty.refreshRates(false)
unstake(account, nftId, amount)~160,000ppt.burn() (external) + safeTransferFrom (nty → account) + mty.refreshRates(false) + emit
unstakeBatch(account, nftIds, amounts)~160,000 + 45,000/NFTBatch unstake variant
approveStake(operator, approved)~46,000SSTORE on nested mapping
approveUnstake(operator, approved)~46,000SSTORE on nested mapping

Stake/unstake costs include 3 external calls each (nft, ppt, mty), plus events.

Gas Optimization Tips

For Miners

  1. Batch ETF: Call init() only once per hour — subsequent mint() calls in the same interval reuse the cached block hash. Track the Init event to know when a new interval starts.
  2. Use eth_call to pre-validate — Simulate mint() before submitting to avoid reverting transactions. Check _zerosOf(keccak256(...)) > 0 off-chain.
  3. Submit promptly — Block hashes expire after 1 hour (block.timestamp / 3600 interval). Late submissions revert with ExpiredBlockHash.

For Stakers

  1. Batch operations — Use mintBatch, stakeBatch, claimBatch, unstakeBatch to amortize the base gas cost across multiple items. Batch saves ~30–50% versus individual calls for 5+ items.
  2. Claim strategically — Each claim costs ~200k gas regardless of reward amount. Accumulate rewards before claiming to maximize APOW per gas spent.
  3. Use mintableBatch for previews — Check mty.mintableBatch() (view, free) before claiming to ensure sufficient APOW output.

For Developers & Integrators

  1. 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.
  2. Delegate with care — Each approveMint/approveStake/approveUpgrade costs ~46k gas (cold SSTORE). For one-off operations, prefer direct calls.
  3. 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.
  4. Leverage ERC20Permit — XPOW and APOW support gasless approvals via EIP-2612. Users sign a permit off-chain; the relayer submits permit() + the target call atomically.

Fee Estimation

Avalanche C-Chain gas prices are typically low (25–50 nAVAX). At 25 nAVAX/gas:

OperationGasCost (AVAX)
PoW mint()150,0000.00375
NFT mint()120,0000.003
NFT stake()180,0000.0045
Reward claim()200,0000.005
NFT unstake()160,0000.004
NFT upgrade()200,0000.005
SOV burn() (unwrap)80,0000.002

Using forge snapshot

The Foundry project includes gas snapshots. Run:

bash
cd xpower-fy.git
forge snapshot

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

bash
forge snapshot --match-contract XPowerTest
forge snapshot --match-contract MoeTreasuryTest

Snapshots 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 each mint(). 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 on setAPR()/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.