Draft Proposal - Adjust $POW Distribution Mechanism to Favor Long-Term Stakeholders

Requesting comments, additions, and feedback on proposal about the future of the outstanding $POW:


The current mechanism of the team increasing $POW rewards as we edge closer to the finalization of the $POW reward cutoff date favors newcomers entering the market as opposed to long-term stakeholders who have been staked since the beginning.


To combat the above, I propose that we do not continue to increase $POW emissions at dates pre-determined by the team. This would mean we would not fully deplete the committed $POW rewards by the cutoff date.

I’m proposing (a) that the current reward rate should be maintained until the end of the $POW distribution window, and (b) that the total time staked of each MetaHero should been factored into the final $POW distribution amounts.


If we were to employ this method, it would not only appropriate $POW to long term believers, but it would also discourage people from de-staking early for risk of resetting their clocks and losing out on future $POW rewards.

Game theory wise, this may also have a net effect of having individuals burn more MPs to ensure they get their pro-rata share of $POW rewards.


  1. Snapshot at cutoff for $POW distribution.
  2. Snapshot the remainder of $POW remaining for distribution, let’s call it remainingPow
  3. Let’s presume timeStaked is the seconds since epoch timestamp of each MH’s time staked. Summate all timeStaked as a cumulative total, timeStakedTotal , as well as per owner wallet in a mapped array, timeStakedTotalByOwner[wallet]
  4. Each person’s contribution is the amount of time staked as a percentage of total time, so iterate over the array timeStakedTotalByOwner and calculate distributionPercentageToOwner = timeStakedTotalByOwner / timeStakedTotal , to get a percentage of each individual’s contributed time staked.
  5. To calculate distribution amount to the wallet, multiply distributionPercentageToOwner * remainingPow

So this will adjust the formula to distribute more $POW proportionally to longer-term stakers until the first phase of staking rewards is depleted?

Correct, the goal is to formulate a weighted distribution to favor those who have been staking their MetaHeroes the longest.

One caveat/wrench in the idea is that it would only work if a user does not de-stake, which would presumably reset their timer. This would discourage sales but may create a mass liquidation event at time of $POW disbursement.

For those of you interested in the actual implementation level details of this, I started to mock up the necessary code for actually calculating this on chain. It’s not complete, but it’s a good starting point.

const fs = require('fs');
const Web3 = require('web3');
const BigNumber = require('bignumber.js');

const contracts = [
        name: 'core',
        address: '0xFb10b1717C92e9cc2d634080c3c337808408D9E1',
        abi: './storage/metahero-core-abi.json',
        max_id: 84
        name: 'hero',
        address: '0x6dc6001535e15b9def7b0f6a20a2111dfa9454e2',
        abi: './storage/metahero-abi.json',
        max_id: 6614

(async() => {

    let headerFound = false;
    let totalBalance = new BigNumber(0);

    let results = [];
    let ownerMap = {};

    // add headers
    results.push(['id', 'contract_address', 'owner']);

    // initialize with infura
    const provider = new Web3.providers.HttpProvider('ENTER_YOUR_INFURA_PROVIDER_URL_HERE');
    const web3 = new Web3(provider);

    for (const data of contracts) {
        ownerMap[data.address] = {};

        let contractAbi = fs.readFileSync(data.abi, 'utf8');
        contractAbi = JSON.parse(contractAbi);

        const contractAddress = data.address;
        const contract = new web3.eth.Contract(contractAbi, contractAddress);

        // TODO: do we need to disregard certain wallets?
        // updao treasury:  0x19c30ad5ea4f7f9f36a8662b5fa2cbc09e55fded

        // NOTE: staking wallet is 0x6ce31a42058f5496005b39272c21c576941dbfe9
        // started with 195,665,261.89 $POW on 2021-12-05 20:24:30

        // to calculate rewards... call `calculateRewards()` and/or `calculateRewardsByAccount()` on
        // https://etherscan.io/address/0x6ce31a42058f5496005b39272c21c576941dbfe9#readContract
        // for each of the 6614 metahero identities (total derived from https://etherscan.io/address/0x6dc6001535e15b9def7b0f6a20a2111dfa9454e2)

        let i = 1;
        const max_id = data.max_id + 1;
        for (i; i < max_id; i++) {
            try {

                const tokenId = new BigNumber(i);

                console.log(`${data.name} - Checking id ${i}`);
                const result = await contract.methods.ownerOf(tokenId).call();

                // store balance
                results.push([i, data.address, result.toString()]);

                if (typeof ownerMap[data.address][result] === 'undefined') {
                    ownerMap[data.address][result] = [];


            } catch (e) {
                if (e.message === 'Returned error: execution reverted: ERC721: owner query for nonexistent token') {
                    // console.error('Owner query for nonexistent token');
                    results.push([i, data.address, '']);
                } else {
                    // retry
                    console.error(`${data.name} - ERROR: ${e.message}`, e);
                    i = i - 1;

    // store results
    for (const key in results) {
        results[key] = results[key].join(',');

    console.log('Saving ./storage/metahero-owners.csv');
    fs.writeFileSync('./storage/metahero-owners.csv', results.join('\n'));

    const ownerMapFinal = [];
    for (const contract_address in ownerMap) {
        for (const owner in ownerMap[contract_address]) {
            const ids = ownerMap[contract_address][owner].join(',');
            ownerMapFinal.push('"' + owner + '","' + contract_address + '","' + ids + '"');

    console.log('Saving ./storage/metahero-owners-map.csv');
    fs.writeFileSync('./storage/metahero-owners-map.csv', ownerMapFinal.join('\n'));


function trim (s, c) {
  if (c === "]") c = "\\]";
  if (c === "^") c = "\\^";
  if (c === "\\") c = "\\\\";
  return s.replace(new RegExp(
    "^[" + c + "]+|[" + c + "]+$", "g"
  ), "");

Please correct me if I am wrong but POW distribution would be a decision for UPDAO alone isn’t it?

1 Like

At this point I believe it would simply be PV that decides. I would support this proposal if it was do-able, I just don’t see how the PVFD has authority over $POW distribution. We could suggest this to GFunk and see what he thinks, but I don’t think we’d have the control to actually put this in place ourselves.

I don’t think we should factor time into pow distribution because this would not incentivize new people to stake near the end. Additionally, as @LukasNFT noted, this should be an UPDAO decision (although this voting mechanic is not in place).