Skip to content

Events and Indexing

Overview

XPower emits events across all 6 core contracts. Events are critical for tracking state changes: mints, burns, transfers, stakes, unstakes, claims, and parameter updates. All token-standard events (Transfer, Approval, TransferSingle, TransferBatch, ApprovalForAll) are emitted by the inherited OpenZeppelin contracts.

Key Events by Contract

XPower (moe)

EventParametersIndexedDescription
Initbytes32 blockHash, uint256 timestampblockHashBlock hash cached for the current 1-hour interval
Transferaddress from, address to, uint256 valuefrom, toERC20 transfer (inherited)
Approvaladdress owner, address spender, uint256 valueowner, spenderERC20 approval (inherited)
Sealuint256 indexMOE immigration sealed for base index
SealAllAll MOE immigration sealed

XPowerNft (nft)

EventParametersIndexedDescription
ApproveMintingaddress account, address operator, bool approvedaccount, operatorMinting delegation updated
ApproveUpgradingaddress account, address operator, bool approvedaccount, operatorUpgrade delegation updated
TransferSingleaddress operator, address from, address to, uint256 id, uint256 valueoperator, from, toERC1155 single transfer (inherited)
TransferBatchaddress operator, address from, address to, uint256[] ids, uint256[] valuesoperator, from, toERC1155 batch transfer (inherited)
ApprovalForAlladdress account, address operator, bool approvedaccount, operatorERC1155 approval (inherited)
URIstring value, uint256 ididMetadata URI updated (inherited)
Sealuint256 indexNFT immigration sealed
SealAllAll NFT immigration sealed
Migratableuint256 timestampNFT emigration opened with effective timestamp
SetRoyaladdress beneficiaryRoyalty beneficiary changed
ApproveMigrateaddress account, address operator, bool approvedaccount, operatorMigration delegation updated

APowerNft (ppt)

EventParametersIndexedDescription
Initaddress mtyMoeTreasury address set (one-time)

Inherits all ERC1155 events (TransferSingle, TransferBatch, ApprovalForAll, URI) plus migration and royalty events from NftBase.

APower (sov)

EventParametersIndexedDescription
Transferaddress from, address to, uint256 valuefrom, toERC20 transfer (inherited)
Approvaladdress owner, address spender, uint256 valueowner, spenderERC20 approval (inherited)

MoeTreasury (mty)

EventParametersIndexedDescription
Claimaddress account, uint256 nftId, uint256 amountAPOW reward claimed for single NFT
ClaimBatchaddress account, uint256[] nftIds, uint256[] amountsAPOW rewards claimed for multiple NFTs
SetAPRuint256 nftId, uint256[] arrayAPR polynomial for an NFT level updated
SetAPBuint256 nftId, uint256[] arrayAPB polynomial for an NFT level updated
RefreshRatesbool allLevelsAPR scalars recalculated based on shares
Initaddress poolLending pool address set (inherited from Banq)

NftTreasury (nty)

EventParametersIndexedDescription
Stakeaddress account, uint256 nftId, uint256 amountSingle NFT staked
StakeBatchaddress account, uint256[] nftIds, uint256[] amountsMultiple NFTs staked
Unstakeaddress account, uint256 nftId, uint256 amountSingle NFT unstaked
UnstakeBatchaddress account, uint256[] nftIds, uint256[] amountsMultiple NFTs unstaked
ApproveStakingaddress account, address operator, bool approvedaccount, operatorStaking delegation updated
ApproveUnstakingaddress account, address operator, bool approvedaccount, operatorUnstaking delegation updated

Event-Driven Architecture

Tracking Minted Supply

Monitor Transfer events from XPower (moe) where from == address(0):

solidity
// Each PoW mint produces TWO Transfers from address(0):
// Transfer(address(0), beneficiary, amount)
// Transfer(address(0), treasuryOwner, amount)

The total supply increase per mint() is 2 * (2^zeros - 1) * 1e18.

Tracking Staking Activity

Stake event on NftTreasury:
  └─ TransferSingle: nft from account → nty (XPowerNft deposited)
  └─ TransferSingle: ppt from address(0) → account (APowerNft minted)
  └─ RefreshRates: false (APR scalars recalculated)

Unstake event on NftTreasury:
  └─ TransferSingle: ppt from account → address(0) (APowerNft burned)
  └─ TransferSingle: nft from nty → account (XPowerNft returned)
  └─ RefreshRates: false (APR scalars recalculated)

Tracking Reward Claims

Claim event on MoeTreasury:
  └─ Transfer: sov from address(0) → account (APower minted)
  └─ Transfer: moe from treasury → sov contract (XPOW wrapped)

Indexing Strategies

Option 1: The Graph (Subgraph)

Schema outline:

graphql
type XPowerMint @entity {
  id: ID!
  beneficiary: Bytes!
  amount: BigInt!
  blockHash: Bytes!
  interval: BigInt!
  transaction: Bytes!
  blockNumber: BigInt!
  timestamp: BigInt!
}

type Stake @entity {
  id: ID!
  account: Bytes!
  nftId: BigInt!
  amount: BigInt!
  timestamp: BigInt!
}

type Claim @entity {
  id: ID!
  account: Bytes!
  nftId: BigInt!
  amount: BigInt!
  mintedAPOW: BigInt!
  timestamp: BigInt!
}

Event handlers:

yaml
# subgraph.yaml (excerpt)
dataSources:
  - kind: ethereum/contract
    name: XPower
    network: avalanche
    source:
      address: "0xa2Ecb675Ad2A2E199c98F5206C5C3fFE5a8D5753"
      abi: XPower
    mapping:
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer

  - kind: ethereum/contract
    name: NftTreasury
    network: avalanche
    source:
      address: "0xF113c3fb6b557d84B5A96E19b7918ba8fc21a741"
      abi: NftTreasury
    mapping:
      eventHandlers:
        - event: Stake(address,uint256,uint256)
          handler: handleStake
        - event: Unstake(address,uint256,uint256)
          handler: handleUnstake

Option 2: Dune Analytics

Query XPower mints (from Transfer events where from = 0x0):

sql
SELECT
  evt_block_time,
  evt_tx_hash,
  "to" AS beneficiary,
  value / 1e18 AS amount_xpow
FROM erc20_avalanche_c.evt_Transfer
WHERE contract_address = 0xa2Ecb675Ad2A2E199c98F5206C5C3fFE5a8D5753
  AND "from" = 0x0000000000000000000000000000000000000000
ORDER BY evt_block_time DESC
LIMIT 100;

Query stake events:

sql
SELECT
  evt_block_time,
  evt_tx_hash,
  account,
  nftId,
  amount / 1e18 AS amount
FROM nft_treasury_avalanche_c.Stake
ORDER BY evt_block_time DESC;

Option 3: Custom Indexer

Using ethers.js event filters with block range batching:

typescript
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider(RPC_URL);
const mty = new ethers.Contract(MTY_ADDRESS, mtyABI, provider);

const BATCH_SIZE = 2000n; // blocks per query

async function indexClaims(fromBlock: bigint, toBlock: bigint) {
  for (let block = fromBlock; block < toBlock; block += BATCH_SIZE) {
    const endBlock = block + BATCH_SIZE - 1n < toBlock ? block + BATCH_SIZE - 1n : toBlock;
    const filter = mty.filters.Claim();
    const events = await mty.queryFilter(filter, block, endBlock);
    for (const event of events) {
      const { account, nftId, amount } = event.args;
      // process: store in database
      console.log({ account, nftId: nftId.toString(), amount: amount.toString(), tx: event.transactionHash });
    }
  }
}

Block Range Batching Best Practices

  • Avalanche C-Chain: Recommended batch size is 2,000–5,000 blocks per query. The chain has ~2s block time (~43,200 blocks/day).
  • Rate limiting: Public RPC endpoints may throttle. Use a delay of 200–500ms between batches.
  • Concurrent queries: Run queries for different contracts in parallel since they are independent.
  • Checkpointing: Store lastIndexedBlock persistently and resume from there on restart.
typescript
async function indexWithCheckpoint(
  contract: ethers.Contract,
  eventFilter: ethers.EventFilter,
  fromBlock: bigint,
  toBlock: bigint,
  getCheckpoint: () => Promise<bigint>,
  setCheckpoint: (block: bigint) => Promise<void>
) {
  let current = await getCheckpoint();
  if (current < fromBlock) current = fromBlock;

  while (current < toBlock) {
    const end = current + BATCH_SIZE < toBlock ? current + BATCH_SIZE : toBlock;
    const events = await contract.queryFilter(eventFilter, current, end);
    for (const event of events) {
      // process event
    }
    current = end + 1n;
    await setCheckpoint(current);
  }
}

Event Topic Signatures

For raw log filtering:

EventTopic (keccak256)
Init(bytes32,uint256) (XPower)0xc1dd22814b5e76a3e53de884431909a44dc43d0a1e7f4c61cc436987b47b2c83
Claim(address,uint256,uint256)0x5566dbe175d7364c1fbbb585b4b86543b15e2f452a7add83fca4b9b893e4dfc5
ClaimBatch(address,uint256[],uint256[])0x14819e25def18df36ee84e1b86d7206b1cabaa9f57098ef74f0e83e5a648cf0f
Stake(address,uint256,uint256)0x70dbb5fa6cf95c4b3c0ed42016dd260f329f8ff8b16a820fbc88202fe410cefa
Unstake(address,uint256,uint256)0xe7fe8d8b54deb85547e1cc803e1a85275f2a6162a68026015e2b36c3f626997a
StakeBatch(address,uint256[],uint256[])0x2d920b12edc44b31303e15352e3ea530149c675aabf144329d87596647c9d7cf
UnstakeBatch(address,uint256[],uint256[])0xbd0efb5fca19ce3794cad7e6ca2a32eaf8a85e4efd9e78bb7d400f5b0c5b6f46
ApproveStaking(address,address,bool)0x24d653d53f4a25a7f5af297a775bb38338205e39b06a29445eb4e1ca1a4ba3e3
ApproveUnstaking(address,address,bool)0x8fcf7cb09dc916d34d5862100988a50566410d1c25ca238661db43dbed257fb5
ApproveMinting(address,address,bool)0xd11b26b9c3e719cf8d93ed6d6ccd82e5ec1a1bd82f8c8e69163221b8dc909079
ApproveUpgrading(address,address,bool)0xa20c8a1ed79fd4a20a3cab04f3c2d0e352f730edb1e16ea3288438ff7313fdb7
SetAPR(uint256,uint256[])0xffd6f0739d2c09957769abfd54b3dc271098a4538f73b8b2a0f9b1e29f55da8e
SetAPB(uint256,uint256[])0xa373dc54beafc1e4e85c2d61465a854b9a9c20eae8e6c38bcdae5936439642c0
RefreshRates(bool)0x223815674cebd405654f76ca26d72785ce98838138a2cf823afb449e18ba55bb

To compute topic signatures at runtime:

typescript
const topicHash = ethers.id("Claim(address,uint256,uint256)");