Earn SPK by Delegating LARYNX | Share your Feedback and Review the Code

in SPK Network2 years ago

▶️ Watch on 3Speak


We've been humming along for a few months now, it's been mostly smooth but of course there have been a couple of hiccoughs. There have been more than 50 accounts running node services which provides decentralization as well as capital to collateralize the ad-hoc multi-signature decentralized exchange. The goal of the SPK network is to facilitate decentralized storage which means we have a few stops on the way.

Proposing software version 1.1.0

Here we are, for the first time, issuing some SPK tokens. The idea is to trickle out tokens to our users for governance voting so when the time comes to scale, it's not us making all the decisions. Initially we're setting our APY at a very low number so our first movers advantage will be more in the decision making and less in token accumulation.

Proposed Rates:

.1% APR to node operators
.03% APR to Delegators to node operators(split between the delegator and the delegatee, 0.015% each)
.01% APR to Non-Delegators

This rate is calculated daily, and is non-compounding, based on Locked, or Powered LARYNX only.

The incentive is for our users to provide infrastructure, or to select their infrastructure providers. The low amount for the delegation makes it more profitable to provide services than to game the delegation system. The lowest amount goes toward those interested enough to stake into the ecosystem, but not much else.

Interest

Those familiar with the HBD interest algorithms will find some nostalgic methods here. When an account sends SPK, powers up or delegates Larynx, or claims earnings will have their interest calculated first. The periods are based on whole days (28800 blocks). This keeps compute cycles low and makes scaling easier. The down-side is front ends will have to calculate balances.

Code Review

I welcome and strongly encourage code review. I'll explain some of the biggest pieces below.

Interest Calc

const simpleInterest = (p, t, r) => { 
  const amount = p * (1 + r / 365);
  const interest = amount - p;
  return parseInt(interest * t);
};

p => principal
t => time in days
r => rate (0.01, 0.0015, 0.001)

SPK Earnings Calc

const reward_spk = (acc, bn) => {
    return new Promise((res, rej) => {
        const Pblock = getPathNum(["spkb", acc]);
        const Pstats = getPathObj(["stats"]);
        const Ppow = getPathNum(["pow", acc]);
        const Pgranted = getPathNum(["granted", acc, "t"]);
        const Pgranting = getPathNum(["granting", acc, "t"]);
        const Pgov = getPathNum(["gov", acc]);
        const Pspk = getPathNum(['spk', acc])
        const Pspkt = getPathNum(['spk', 't'])
        Promise.all([Pblock, Pstats, Ppow, Pgranted, Pgranting, Pgov, Pspk, Pspkt]).then(
            (mem) => {
                var block = mem[0],
                    diff = bn - block,
                    stats = mem[1],
                    pow = mem[2],
                    granted = mem[3],
                    granting = mem[4],
                    gov = mem[5],
                    spk = mem[6],
                    spkt = mem[7],
                    r = 0, a = 0, b = 0, c = 0, t = 0
                if (!block){
                    store.batch(
                      [{ type: "put", path: ["spkb", acc], data: bn}],
                      [res, rej, 0]
                    );
                } else if(diff < 28800){ //min claim period
                    res(r)
                } else {
                    t = parseInt(diff/28800)
                    a = simpleInterest(gov, t, stats.spk_rate_lgov)
                    b = simpleInterest(pow, t, stats.spk_rate_lpow);
                    c = simpleInterest(
                      (granted + granting),
                      t,
                      stats.spk_rate_ldel
                    );
                    const i = a + b + c
                    if(i){
                        store.batch(
                          [{type: "put", path: ["spk", acc], data: spk + i}, 
               {type: "put", path: ["spk", "t"], data: spkt + i}, 
               {type: "put", path: ["spkb", acc], data: bn - (diff % 28800)
                           }],
                          [res, rej, i]
                        );
                    } else {
                        res(0)
                    }
                }

            }
        );
    })
}

Here the different balances are accessed in memory interest is calculated, and the SPK balance and total SPK balance are adusted. The Interest is calculated for whole days stored as block numbers in ['spkb']

SPK Send

exports.spk_send = (json, from, active, pc) => {
    let Pinterest = reward_spk(from, json.block_num),
        Pinterest2 = reward_spk(json.to, json.block_num);
    Promise.all([Pinterest, Pinterest2])
        .then(interest => {
            let fbalp = getPathNum(["spk", from]),
                tbp = getPathNum(["spk", json.to]); //to balance promise
            Promise.all([fbalp, tbp])
                .then((bals) => {
                    let fbal = bals[0],
                        tbal = bals[1],
                        ops = [];
                    send = parseInt(json.amount);
                    if (
                        json.to &&
                        typeof json.to == "string" &&
                        send > 0 &&
                        fbal >= send &&
                        active &&
                        json.to != from
                    ) {
                        //balance checks
                        ops.push({
                            type: "put",
                            path: ["spk", from],
                            data: parseInt(fbal - send),
                        });
                        ops.push({
                            type: "put",
                            path: ["spk", json.to],
                            data: parseInt(tbal + send),
                        });
                        let msg = `@${from}| Sent @${json.to} ${parseFloat(
                            parseInt(json.amount) / 1000
                        ).toFixed(3)} SPK`;
                        if (config.hookurl || config.status)
                            postToDiscord(msg, `${json.block_num}:${json.transaction_id}`);
                        ops.push({
                            type: "put",
                            path: ["feed", `${json.block_num}:${json.transaction_id}`],
                            data: msg,
                        });
                    } else {
                        ops.push({
                            type: "put",
                            path: ["feed", `${json.block_num}:${json.transaction_id}`],
                            data: `@${from}| Invalid spk send operation`,
                        });
                    }
                    if (process.env.npm_lifecycle_event == "test") pc[2] = ops;
                    store.batch(ops, pc);
                })
                .catch((e) => {
                    console.log(e);
                });
        })
};

Here you can see the interest is calculated and rewarded before the send operation occurs. In the future this will also happen for all smart contracts that rely on SPK balance or changes to locked Larynx balances.

One concern here, if the approach to voting is space sensitive, changing SPK balances will require vote weights to be returned to the average. If votes are stored in the system the vote can be recalculated. I'm interested in hearing about clever ways to track votes with out keeping a whole accounting of them in memory.

Power Up and Delegate

exports.power_up = (json, from, active, pc) => {
    reward_spk(from, json.block_num).then(interest => {
        var amount = parseInt(json.amount),
            lpp = getPathNum(["balances", from]),
            tpowp = getPathNum(["pow", "t"]),
            powp = getPathNum(["pow", from]);

        Promise.all([lpp, tpowp, powp])
            .then((bals) => {
                let lb = bals[0],
                    tpow = bals[1],
                    pow = bals[2],
                    lbal = typeof lb != "number" ? 0 : lb,
                    pbal = typeof pow != "number" ? 0 : pow,
                    ops = [];
                if (amount <= lbal && active) {
                    ops.push({
                        type: "put",
                        path: ["balances", from],
                        data: lbal - amount,
                    });
                    ops.push({
                        type: "put",
                        path: ["pow", from],
                        data: pbal + amount,
                    });
                    ops.push({
                        type: "put",
                        path: ["pow", "t"],
                        data: tpow + amount,
                    });
                    const msg = `@${from}| Powered ${parseFloat(
                        json.amount / 1000
                    ).toFixed(3)} ${config.TOKEN}`;
                    if (config.hookurl || config.status)
                        postToDiscord(msg, `${json.block_num}:${json.transaction_id}`);
                    ops.push({
                        type: "put",
                        path: ["feed", `${json.block_num}:${json.transaction_id}`],
                        data: msg,
                    });
                } else {
                    ops.push({
                        type: "put",
                        path: ["feed", `${json.block_num}:${json.transaction_id}`],
                        data: `@${from}| Invalid power up`,
                    });
                }
                store.batch(ops, pc);
            })
            .catch((e) => {
                console.log(e);
            });
    })
}

exports.power_grant = (json, from, active, pc) => {
    var amount = parseInt(json.amount),
        to = json.to,
        Pgranting_from_total = getPathNum(["granting", from, "t"]),
        Pgranting_to_from = getPathNum(["granting", from, to]),
        Pgranted_to_from = getPathNum(["granted", to, from]),
        Pgranted_to_total = getPathNum(["granted", to, "t"]),
        Ppower = getPathNum(["pow", from]),
        Pup_from = getPathObj(["up", from]),
        Pdown_from = getPathObj(["down", from]),
        Pup_to = getPathObj(["up", to]),
        Pdown_to = getPathObj(["down", to]),
        Pgov = getPathNum(['gov', to])
        Pinterest = reward_spk(from, json.block_num), //interest calc before balance changes.
        Pinterest2 = reward_spk(json.to, json.block_num);
    Promise.all([
        Ppower,
        Pgranted_to_from,
        Pgranted_to_total,
        Pgranting_to_from,
        Pgranting_from_total,
        Pup_from,
        Pup_to,
        Pdown_from,
        Pdown_to,
        Pgov,
        Pinterest,
        Pinterest2
    ])
        .then((mem) => {
            let from_power = mem[0],
                granted_to_from = mem[1],
                granted_to_total = mem[2],
                granting_to_from = mem[3],
                granting_from_total = mem[4],
                up_from = mem[5],
                up_to = mem[6],
                down_from = mem[7],
                down_to = mem[8],
                ops = [];
            if (amount < from_power && amount >= 0 && active && mem[9]) { //mem[9] checks for gov balance in to account. 
                if (amount > granted_to_from) {
                    let more = amount - granted_to_from;
                    if (up_from.max) {
                        up_from.max -= more;
                    }
                    if (down_from.max) {
                        down_from.max -= more;
                    }
                    if (up_to.max) {
                        up_to.max += more;
                    }
                    if (down_to.max) {
                        down_to.max += more;
                    }
                    ops.push({
                        type: "put",
                        path: ["granting", from, "t"],
                        data: granting_from_total + more,
                    });
                    ops.push({
                        type: "put",
                        path: ["granting", from, to],
                        data: granting_to_from + more,
                    });
                    ops.push({
                        type: "put",
                        path: ["granted", to, from],
                        data: granted_to_from + more,
                    });
                    ops.push({
                        type: "put",
                        path: ["granted", to, "t"],
                        data: granted_to_total + more,
                    });
                    ops.push({
                        type: "put",
                        path: ["pow", from],
                        data: from_power - more,
                    }); //weeks wait? chron ops? no because of the power growth at vote
                    ops.push({
                        type: "put",
                        path: ["up", from],
                        data: up_from,
                    });
                    ops.push({
                        type: "put",
                        path: ["down", from],
                        data: down_from,
                    });
                    ops.push({ type: "put", path: ["up", to], data: up_to });
                    ops.push({
                        type: "put",
                        path: ["down", to],
                        data: down_to,
                    });
                    const msg = `@${from}| Has granted ${parseFloat(
                        amount / 1000
                    ).toFixed(3)} to ${to}`;
                    if (config.hookurl || config.status)
                        postToDiscord(
                            msg,
                            `${json.block_num}:${json.transaction_id}`
                        );
                    ops.push({
                        type: "put",
                        path: [
                            "feed",
                            `${json.block_num}:${json.transaction_id}`,
                        ],
                        data: msg,
                    });
                } else if (amount < granted_to_from) {
                    let less = granted_to_from - amount;
                    if (up_from.max) {
                        up_from.max += less;
                    }
                    if (down_from.max) {
                        down_from.max += less;
                    }
                    if (up_to.max) {
                        up_to.max -= less;
                    }
                    if (down_to.max) {
                        down_to.max -= less;
                    }
                    ops.push({
                        type: "put",
                        path: ["granting", from, "t"],
                        data: granting_from_total - less,
                    });
                    ops.push({
                        type: "put",
                        path: ["granting", from, to],
                        data: granting_to_from - less,
                    });
                    ops.push({
                        type: "put",
                        path: ["granted", to, from],
                        data: granted_to_from - less,
                    });
                    ops.push({
                        type: "put",
                        path: ["granted", to, "t"],
                        data: granted_to_total - less,
                    });
                    ops.push({
                        type: "put",
                        path: ["pow", from],
                        data: from_power + less,
                    });
                    ops.push({
                        type: "put",
                        path: ["up", from],
                        data: up_from,
                    });
                    ops.push({
                        type: "put",
                        path: ["down", from],
                        data: down_from,
                    });
                    ops.push({ type: "put", path: ["up", to], data: up_to });
                    ops.push({
                        type: "put",
                        path: ["down", to],
                        data: down_to,
                    });
                    const msg = `@${from}| Has granted ${parseFloat(
                        amount / 1000
                    ).toFixed(3)} to ${to}`;
                    if (config.hookurl || config.status)
                        postToDiscord(
                            msg,
                            `${json.block_num}:${json.transaction_id}`
                        );
                    ops.push({
                        type: "put",
                        path: [
                            "feed",
                            `${json.block_num}:${json.transaction_id}`,
                        ],
                        data: msg,
                    });
                } else {
                    const msg = `@${from}| Has already granted ${parseFloat(
                        amount / 1000
                    ).toFixed(3)} to ${to}`;
                    if (config.hookurl || config.status)
                        postToDiscord(
                            msg,
                            `${json.block_num}:${json.transaction_id}`
                        );
                    ops.push({
                        type: "put",
                        path: [
                            "feed",
                            `${json.block_num}:${json.transaction_id}`,
                        ],
                        data: msg,
                    });
                }
            } else {
                const msg = `@${from}| Invalid delegation`;
                if (config.hookurl || config.status)
                    postToDiscord(
                        msg,
                        `${json.block_num}:${json.transaction_id}`
                    );
                ops.push({
                    type: "put",
                    path: ["feed", `${json.block_num}:${json.transaction_id}`],
                    data: msg,
                });
            }
            store.batch(ops, pc);
        })
        .catch((e) => {
            console.log(e);
        });
}

The only thing new here to note is delegation are only allowed to accounts with a gov balance, which only node operating accounts can have. Removing or lowering a delegation will also calculate the SPK balance before the change. As far as I can figure there is no way to "double spend" Larynx for rewards... please check me on this, it's important.

API

Stated previously front-ends will have to calculate SPK balances based on the same information, which means a little extra API is needed. This will need to be coupled with the interest rate stats and head block number.

Thank You

Thank you to the community of node runners and that help me and each other run and improve this and other Hive software. I appreciate your feedback here and on our Discord Server


Vote for our Witness:




▶️ 3Speak

Sort:  

"The only thing new here to note is delegation are only allowed to accounts with a gov balance, which only node operating accounts can have."

I'm a little confused here... In order to delegate to a node operator you have to be running a node yourself?

No, the account you delegate to will need to provide a service(which is currently only DEX service).

Ok that's what I thought 😅 I guess the wording was tripping me up, thanks!

I had the same doubt, at least I clarify it better.

Tenia la misma duda, al menos lo aclaro mejor.

All I can say is the sooner we can put these SPK tokens to work the better!

Posted Using LeoFinance Beta

I understand, but I'm a bit confused, I would like to delegate for the APR provided, I should mention that I'm a bit new to this.

I want to support the project looks very interesting, especially the APR....

I would like the tokens to be in circulation so I would like to support the project.

Posted Using LeoFinance Beta

This is the best news of the day for me! SPK has the potential to be in Top 100 if they manage to pull off everything. At this moment, I think what we need the most is faster development and more users engaging with content.
!PIZZA
!LUV

Posted Using LeoFinance Beta

PIZZA!

PIZZA Holders sent $PIZZA tips in this post's comments:
@vimukthi(1/5) tipped @spknetwork (x1)

Please vote for pizza.witness!

This is a great idea and enables me to put my tokens to use because atm I have no intention on operating a node so I don't have any purpose for the tokens.

Posted Using LeoFinance Beta

It would be great to be able to earn off these tokens. I am not running a node so I can't do much with my tokens.

Posted Using LeoFinance Beta

please in one sentence.

What is it? How much, how long lockup APR and so on.

It's good to be able to delegate them so they won't just sit idle :)

Posted using LeoFinance Mobile

What's the process from here till a regular user will be able to power up and delegate his/her LARYNX tokens?

Posted Using LeoFinance Beta

const amount = p * (1 + r / 365);

What happens in leap years? Slightly increased interest?

Posted Using LeoFinance Beta

The daily return is always exactly the same.

Yes, but it's 1 more day in a leap year. Which means return parseInt(interest * t); will return a little more when t is the equivalent of 366 days. Right?

It will never be calculated to 366 tho. If you earn 10 cents today, you'll earn 10 cents any day. The interest also doesn't compound in this case.

Works for me. One extra day of interest in leap years. I believe it's the same in Hive.


The rewards earned on this comment will go directly to the people( @threespeak ) sharing the post on Twitter as long as they are registered with @poshtoken. Sign up at https://hiveposh.com.