ElataPoints is a non-transferable ERC20Votes token used for governance weight and rewards. XP can be issued via:
- Direct award by operator (
award) - Off-chain signature (
updateBySig) - Merkle-based per-epoch claims (
claimXP)
This document describes the Merkle distribution flow, delta semantics, operator roles, JSON format, security, and runbooks.
- Each distribution (epoch) encodes incremental XP to mint for that epoch.
- Decreases/decay are handled via operator
revoke(address, amount); do not use negative Merkle values.
currentDistributionId: incremental idmerkleRoots[id]: Merkle root for epochdistributionDataHash[id]: keccak256 of canonical JSON (full file)hasClaimed(id,user): true after user claims
- Verify
merkleRoots[id] != 0andamount > 0and not already claimed - Leaf =
keccak256(abi.encodePacked(user, amount)) MerkleProof.verify(proof, root, leaf)with pair sorting semantics- Mark claimed, mint
amount, self-delegate if none, emitXPClaimedandXPAwarded
{
"distributionId": 7,
"merkleRoot": "0x…",
"dataHash": "0x…",
"claims": {
"0xabc...": { "amount": "100000000000000000000", "proof": ["0x..","0x.."] }
}
}- Addresses are lowercased keys
amountis a decimal string in weiproofis leaf→root order, hex0x-prefixeddataHash = keccak256(utf8Bytes(full minified JSON))
scripts/xp/xp-generate-merkle.ts: Build tree{ sortPairs: true }, aggregate duplicates, drop zeros, output canonical JSON anddataHashscripts/xp/xp-publish-root.ts: Validate JSONdataHash, callsetMerkleRoot(root,dataHash)using Viem
- Loads
/xp-distribution.json - Validates on-chain vs file (id, root, dataHash)
- If eligible, calls
claimXP(id, amount, proof)via wagmi/viem - Refreshes
balanceOfand updates tier
XP_OPERATOR_ROLEcanaward,revoke,setMerkleRoot- Rotation:
- Admin
grantRole(XP_OPERATOR_ROLE, new) - Use new key to publish next root
- Admin
revokeRole(XP_OPERATOR_ROLE, old)
- Admin
- Claims are O(log n) hashing; state writes are minimal
- Events monitored:
MerkleRootUpdated,XPClaimed,XPAwarded - If compromised root is published, revoke XP from affected addresses and rotate operator
- Consider adding
pauseClaimsin a future update
- Foundry: success/failure cases, role gating, rotation
- Node: generator determinism, proof round-trip, duplicate aggregation
- Manual E2E: testnet publish, UI claim, block mismatched JSON
- Prepare allocations (JSON/CSV) with
{ address, amountWei } - Generate canonical file:
pnpm ts-node scripts/xp/xp-generate-merkle.ts \
--in scripts/xp/data/allocs.json \
--out ./output/xp-distribution.json \
--id <N>- Publish root:
pnpm ts-node scripts/xp/xp-publish-root.ts \
--rpc $RPC --key $OPERATOR_PK --contract $XP_ADDR \
--json ./output/xp-distribution.json- Users claim via the frontend; UI validates proofs and enables claim
- Users do not publish roots; operators do
- Users influence their XP inputs by participating in protocol activities (apps launched, staking, governance)
- Off-chain pipeline aggregates activity into allocations; if disputes, operators can include corrections in next epoch or revoke