Posts

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

avatar of @spknetwork
25
@spknetwork
·
0 views
·
7 min read

▶️ 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 <em> (1 + r / 365);
 
  const interest = amount - p;
 
  return parseInt(interest </em> 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:



About the SPK Network:


▶️ 3Speak