Created
September 29, 2017 08:48
-
-
Save teo-nyx/3e2a3fba7fdda7b681600e2a67dff220 to your computer and use it in GitHub Desktop.
Estimate Steemit curation reward before voting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="//cdn.steemjs.com/lib/latest/steem.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script> | |
<script> | |
//steem.config.set('websocket', 'wss://steemd.steemit.com/'); | |
// Usage example prints to console | |
var expected_value_sp = 100; // blessed is he who believes | |
getPredictedVotePayout('teo-nyx', 'teo-nyx/estimate-curation-reward-before-voting', expected_value_sp) | |
.then(console.log, console.error); | |
// Returns approximate curation reward (in SP) if given user would upvote given post | |
// with 100% voting strength and his current voting power. | |
// `post_expected_payout` is a guess how much SP the post should eventually be worth | |
// at the time of reward payout. If not specified, current post payout (after voting) | |
// is used in calculation. | |
function getPredictedVotePayout(user, post, post_expected_payout, dynamic_global_properties, reward_fund) { | |
var promises = []; | |
// Load user from API unless loaded already | |
if (typeof user === 'string') { | |
promises.push(steem.api.getAccounts([user]).then(function(account) { | |
if (!account[0]) { | |
throw "User does not exist: "+user; | |
} | |
user = account[0]; | |
})); | |
} | |
// Load post from API unless loaded already | |
if (typeof post === 'string') { | |
var parts = post.split('/', 2); | |
promises.push(steem.api.getContent(parts[0], parts[1]).then(function(content) { | |
if (!content || !content.id) { | |
throw "Post does not exist: @" + post; | |
} | |
post = content; | |
})); | |
} | |
// Load dynamic global properties | |
if (!dynamic_global_properties) { | |
promises.push(steem.api.getDynamicGlobalProperties().then(function(result) { | |
dynamic_global_properties = result; | |
})); | |
} | |
// Load reward fund data | |
if (!post_expected_payout && !reward_fund) { | |
promises.push(steem.api.getRewardFund("post").then(function(result) { | |
reward_fund = result; | |
})); | |
} | |
// Wait for data from API, then do the magic | |
return Promise.all(promises).then(function() { | |
console.log(user, post, dynamic_global_properties, reward_fund); | |
// Vesting shares user can grant with a maximum allowed vote. | |
// Vesting shares determine how much reward the post receives as a whole. | |
var vote_shares = getVoteShares(getEffectiveVestingShares(user), user.voting_power); | |
// Total vesting shares post received so far | |
var post_shares = parseInt(post.net_rshares, 10); | |
// Calculate total post weight before and after the vote. | |
// Post weight is the sum of weights of all its votes. | |
// Vote weights are used to distribute post curation rewards between voters. | |
// In ideal world `post_weight_before_vote` would be equal to `post.total_vote_weight`, | |
// but since Math.sqrt() slightly differs from approx_sqrt() used by Steem, | |
// it's better to calculate both using JS. | |
var post_weight_before_vote = calculateWeight(post_shares); | |
var post_weight_after_vote = calculateWeight(post_shares + vote_shares); | |
// Calculate vote weight, including the penalty for early voting. | |
var vote_weight = post_weight_after_vote - post_weight_before_vote; | |
vote_weight *= calculatePenalty(+moment(post.created), +moment(dynamic_global_properties.time)); | |
// Total post payout | |
if (!post_expected_payout) { | |
post_expected_payout = (post_shares + vote_shares) | |
* parseFloat(reward_fund.reward_balance.replace(' STEEM', '')) | |
/ parseInt(reward_fund.recent_claims, 10); | |
} | |
// For old posts, substruct what that is paid already | |
post_expected_payout -= parseFloat(post.total_payout_value.replace(' SBD', '')); | |
// Calculate curation payout of a vote | |
return Math.max(0, post_expected_payout) * 0.25 * vote_weight / post_weight_after_vote; | |
}, function(e) { | |
console.log('Unable to get data from API.'); | |
console.log(arguments); | |
throw e; | |
}); | |
// | |
// Helper functions | |
// | |
function getVoteWeight(post_net_shares, vote_shares, post_time, vote_time) { | |
// Calculate vote weight. They are used to distribute reward | |
// between all voters of a post. | |
var vote_weight = calculateWeight(post_net_shares + vote_shares) - calculateWeight(post_net_shares); | |
// Adjust post weight according to time-penalty. | |
// If no time specified, assume no penalty. | |
if (post_time && vote_time) { | |
vote_weight *= calculatePenalty(post_time, vote_time); | |
} | |
} | |
// Calculate weight of a post from its reward shares. | |
// Since HF19 this is a simple square root. | |
// (Well, not really simple; for exact function used see `approx_sqrt` | |
// in libraries/chain/util/reward.cpp in steem source) | |
function calculateWeight(shares) { | |
return Math.sqrt(shares); | |
} | |
// Linear voting weight penalty during first 30 minutes after publication. | |
// Inputs are JS timestamps, returns a float between 0 and 1. | |
function calculatePenalty(post_created, current_time) { | |
return Math.min(1, Math.max(0, | |
(current_time - post_created) / (30 * 60 * 1000) | |
)); | |
} | |
// | |
// getVoteShares() returns number of reward shares account will grant to content | |
// with a single vote. | |
// | |
// Some background: | |
// | |
// *Vesting shares* are distributed between users in proportion to their SP balance. | |
// `account.vesting_shares` returned from API represents shares dustributed this way. | |
// Shares can be delegated to other accounts and received from them. | |
// voter_effective_shares = vesting_shares + received_vesting_shares - delegated_vesting_shares | |
// | |
// When users vote for content, they temporarily give up part of vesting shares | |
// from their account to the content via the vote. | |
// `account.voting_power` returned from API (number 0-10000, meaning 0-100%) | |
// represents part of their maximum vesting shares still under their control, | |
// i.e. not given up to posts. It regenerates over time. This is `voter_voting_power`. | |
// | |
// Users are able to control how strong they want a particular vote to be. | |
// This is `vote_strength`: number between 1 and 10000, meaning 0-100%. | |
// | |
function getVoteShares(voter_effective_shares, voter_voting_power, vote_strength) { | |
// Assume full-powered vote if not specified | |
voter_voting_power = voter_voting_power || 10000; | |
vote_strength = vote_strength || 10000; | |
return Math.floor(voter_effective_shares * getUsedSharesRatio(voter_voting_power, vote_strength)); | |
// `getUsedSharesRatio()` calculates how many vesting shares account | |
// gives up with a vote, returning a float between 0 and 1. | |
function getUsedSharesRatio(voter_voting_power, vote_strength) { | |
// Start with voter's current remaining voting power. It is an integer 0-10000. | |
// Apply user-specified strength of a vote (0-100%). | |
voter_voting_power *= vote_strength / 10000; | |
// `used_power` (number between 0 and 10000) represens percent of vesting shares | |
// that belong to voter's account - shares he uses in the given vote. | |
// It is currently allowed to use 2% of remaining voting power per vote, | |
// plus a fixed small amount that makes sure `used_power` is always >=1. | |
var used_power = voter_voting_power * 2 / 100; | |
used_power += 98 / 100; | |
return used_power / 10000; | |
} | |
} | |
// Returns effective vesting of user, taking into account | |
// delegated and received vesting shares. | |
// This value is in microVESTS, as used in post.net_rshares and fund.recent_claims | |
function getEffectiveVestingShares(account) { | |
var parts = "vesting_shares received_vesting_shares delegated_vesting_shares".split(' ').map(function(k) { | |
return parseFloat(account[k].replace(" VESTS", "")) * 1000000; | |
}); | |
return parts[0] + parts[1] - parts[2]; | |
} | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment