Skip to content

Instantly share code, notes, and snippets.

@teo-nyx
Created September 29, 2017 08:48
Show Gist options
  • Save teo-nyx/3e2a3fba7fdda7b681600e2a67dff220 to your computer and use it in GitHub Desktop.
Save teo-nyx/3e2a3fba7fdda7b681600e2a67dff220 to your computer and use it in GitHub Desktop.
Estimate Steemit curation reward before voting
<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