-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgoodvoter.js
More file actions
195 lines (179 loc) · 5.8 KB
/
Copy pathgoodvoter.js
File metadata and controls
195 lines (179 loc) · 5.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Simple Hive Trail Following Bot (good_voter)
// This follows blocks and votes on content that another user has voted on at a specified weight
// If your voting power is below your target, it will subtract more weight off of the vote
// (it does this in an attempt to let voting power slowly regenerate)
// Configuration: set voters to follow and vote weight to give in config.js
const hive = require('@hiveio/hive-js');
const config = require('./config');
// how long to wait in miliseconds before next run
// (set to 3 seconds)
const blockInterval = 3000;
const username = process.env.HIVE_USERNAME;
const postingWif = process.env.HIVE_WIF;
let lastProcessedBlock = false;
hive.api.setOptions({
url: "https://api.hive.blog",
retry: false,
useAppbaseApi: true,
});
// wrap necessary steem api calls in promises
function getProperties() {
return new Promise((resolve, reject) => {
hive.api.getDynamicGlobalProperties(function(err, result) {
if(!err) {
resolve(result);
}
else {
console.log(err);
reject(err);
}
});
});
}
function getBlock(blockNum) {
return new Promise((resolve, reject) => {
hive.api.getBlock(blockNum, function(err, result) {
if(!err) {
resolve(result);
}
else {
console.log(err);
reject(err);
}
});
});
}
function broadcastVote(author, permlink, weight) {
return new Promise((resolve, reject) => {
hive.broadcast.vote(
postingWif,
username, // Voter
author, // Author
permlink,
weight, // Weight (10000 = 100%)
function(err, result) {
if(!err) {
resolve(result);
}
else {
console.log(err);
reject(err);
}
}
);
});
}
// takes in intended vote weight
// calculates current voting power
// returns an adjusted weight (to try to keep voting power near target)
async function adjustWeight(originalWeight) {
return new Promise((resolve, reject) => {
hive.api.getAccounts([username], function(err, result) {
if(!err) {
const secondsago = (new Date - new Date(result[0].last_vote_time + 'Z')) / 1000;
let vpow = result[0].voting_power + (10000 * secondsago / 432000);
vpow = Math.min(vpow / 100, 100).toFixed(2);
console.log(`${username}'s voting power: ${vpow}%`);
// if voting power is below the target, subtract the difference in percent from weight
// (probably not 100% accurate)
// goal is to let voting power recover by not casting regular vote weight
if(vpow < config.targetMana) {
let difference = config.targetMana - vpow;
difference = difference * 100;
resolve(originalWeight - difference);
}
else {
resolve(originalWeight);
}
}
else {
console.log(err);
reject(err);
}
});
});
}
// parse head block from global properties
async function getHeadBlockNumber(properties) {
return properties.head_block_number;
}
// process all blocks since last update
// returns vote operations from all followed voters in each block
async function processNextBlocks(headBlock) {
let allTransactions = [];
let result = [];
if(!lastProcessedBlock)
lastProcessedBlock = headBlock;
// check if reported head block is less than the last processed one
// (for nodes that do caching and report old data)
// (prevents double votes)
if(lastProcessedBlock >= headBlock)
{
console.log(`Node reported head block of ${headBlock} but I already processed ${lastProcessedBlock}`);
return result;
}
// get all transactions in blocks since last update
for(let i = lastProcessedBlock + 1; i <= headBlock; i++) {
let block = await getBlock(i);
allTransactions.push(block.transactions);
console.log('Processing block: ', i);
}
allTransactions.map(transactionsInBlock => {
transactionsInBlock.map(transaction => {
transaction.operations.map(operation => {
// check all voting operations in each transaction
if(operation[0] == 'vote') {
if(config.followed.voters.includes(operation[1].voter)) {
result.push(operation);
}
}
});
});
});
lastProcessedBlock = headBlock;
return result;
}
// make votes based on list of vote operations
async function makeVotes(voteOperations) {
voteOperations.map(operation => {
const voter = operation[1].voter;
const author = operation[1].author;
const permlink = operation[1].permlink;
const weight = operation[1].weight;
// find intended weight
// example: if we're following voter at 50% (0.5),
// the intended weight before voting power adjustment
// would be 50% of the voters original vote weight
const intendedWeight = config.voteWeight[operation[1].voter] * weight;
// do not follow downvotes
if(weight > 0) {
console.log(`Voting on ${author}'s content: ${permlink}`);
console.log(`Based on finding: ${voter}'s vote`);
console.log(`Voter's vote weight: ${weight / 100}%`);
console.log(`Intended vote weight: ${intendedWeight / 100}%`);
adjustWeight(intendedWeight).then(adjustedWeight => {
console.log(`Adjusted voting weight: ${Math.floor(adjustedWeight) / 100}%`);
broadcastVote(author, permlink, Math.floor(adjustedWeight));
});
}
});
}
// loops every blockInterval
function eventLoop() {
getProperties()
.then(getHeadBlockNumber)
.then(processNextBlocks)
.then(makeVotes);
}
function main() {
console.log('Good voter. Follows votes of specified authors.');
if(!username || !postingWif) {
console.log('Error: Please set HIVE_USERNAME and HIVE_WIF environemnt variables.');
return;
}
const mainLoop = setInterval(function () {
// handle updates here
eventLoop();
}, blockInterval);
}
main();