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)
| Event | Parameters | Indexed | Description |
|---|---|---|---|
Init | bytes32 blockHash, uint256 timestamp | blockHash | Block hash cached for the current 1-hour interval |
Transfer | address from, address to, uint256 value | from, to | ERC20 transfer (inherited) |
Approval | address owner, address spender, uint256 value | owner, spender | ERC20 approval (inherited) |
Seal | uint256 index | — | MOE immigration sealed for base index |
SealAll | — | — | All MOE immigration sealed |
XPowerNft (nft)
| Event | Parameters | Indexed | Description |
|---|---|---|---|
ApproveMinting | address account, address operator, bool approved | account, operator | Minting delegation updated |
ApproveUpgrading | address account, address operator, bool approved | account, operator | Upgrade delegation updated |
TransferSingle | address operator, address from, address to, uint256 id, uint256 value | operator, from, to | ERC1155 single transfer (inherited) |
TransferBatch | address operator, address from, address to, uint256[] ids, uint256[] values | operator, from, to | ERC1155 batch transfer (inherited) |
ApprovalForAll | address account, address operator, bool approved | account, operator | ERC1155 approval (inherited) |
URI | string value, uint256 id | id | Metadata URI updated (inherited) |
Seal | uint256 index | — | NFT immigration sealed |
SealAll | — | — | All NFT immigration sealed |
Migratable | uint256 timestamp | — | NFT emigration opened with effective timestamp |
SetRoyal | address beneficiary | — | Royalty beneficiary changed |
ApproveMigrate | address account, address operator, bool approved | account, operator | Migration delegation updated |
APowerNft (ppt)
| Event | Parameters | Indexed | Description |
|---|---|---|---|
Init | address mty | — | MoeTreasury address set (one-time) |
Inherits all ERC1155 events (TransferSingle, TransferBatch, ApprovalForAll, URI) plus migration and royalty events from NftBase.
APower (sov)
| Event | Parameters | Indexed | Description |
|---|---|---|---|
Transfer | address from, address to, uint256 value | from, to | ERC20 transfer (inherited) |
Approval | address owner, address spender, uint256 value | owner, spender | ERC20 approval (inherited) |
MoeTreasury (mty)
| Event | Parameters | Indexed | Description |
|---|---|---|---|
Claim | address account, uint256 nftId, uint256 amount | — | APOW reward claimed for single NFT |
ClaimBatch | address account, uint256[] nftIds, uint256[] amounts | — | APOW rewards claimed for multiple NFTs |
SetAPR | uint256 nftId, uint256[] array | — | APR polynomial for an NFT level updated |
SetAPB | uint256 nftId, uint256[] array | — | APB polynomial for an NFT level updated |
RefreshRates | bool allLevels | — | APR scalars recalculated based on shares |
Init | address pool | — | Lending pool address set (inherited from Banq) |
NftTreasury (nty)
| Event | Parameters | Indexed | Description |
|---|---|---|---|
Stake | address account, uint256 nftId, uint256 amount | — | Single NFT staked |
StakeBatch | address account, uint256[] nftIds, uint256[] amounts | — | Multiple NFTs staked |
Unstake | address account, uint256 nftId, uint256 amount | — | Single NFT unstaked |
UnstakeBatch | address account, uint256[] nftIds, uint256[] amounts | — | Multiple NFTs unstaked |
ApproveStaking | address account, address operator, bool approved | account, operator | Staking delegation updated |
ApproveUnstaking | address account, address operator, bool approved | account, operator | Unstaking delegation updated |
Event-Driven Architecture
Tracking Minted Supply
Monitor Transfer events from XPower (moe) where from == address(0):
// 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:
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:
# 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: handleUnstakeOption 2: Dune Analytics
Query XPower mints (from Transfer events where from = 0x0):
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:
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:
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
lastIndexedBlockpersistently and resume from there on restart.
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:
| Event | Topic (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:
const topicHash = ethers.id("Claim(address,uint256,uint256)");