-
Notifications
You must be signed in to change notification settings - Fork 7
/
autopayout-validators.js
153 lines (134 loc) · 5.05 KB
/
autopayout-validators.js
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
/**
* autopayout-validators.js
*
* Claim and distribute validator staking rewards for your stakers
*
* Accepts several validators in config.js file
*
* https://github.com/Colm3na/polkadot-auto-payout
*
* Author: Mario Pino | @mariopino:matrix.org
*/
const BigNumber = require('bignumber.js');
const { ApiPromise, WsProvider } = require('@polkadot/api');
const keyring = require('@polkadot/ui-keyring').default;
keyring.initKeyring({
isDevelopment: false,
});
const fs = require('fs');
const prompts = require('prompts');
const yargs = require('yargs');
const config = require('./config.js');
const argv = yargs
.scriptName("autopayout.js")
.option('account', {
alias: 'a',
description: 'Account json file path',
type: 'string',
})
.option('password', {
alias: 'p',
description: 'Account password, or stdin if this is not set',
type: 'string',
})
.option('log', {
alias: 'l',
description: 'log (append) to autopayout.log file',
type: 'boolean',
})
.usage("node autopayout.js -c keystores/account.json -p password -v validator_stash_address")
.help()
.alias('help', 'h')
.version()
.alias('version', 'V')
.argv;
// Exported account json file param
const accountJSON = argv.account || config.accountJSON;
// Password param
let password = argv.password || config.password;
// Logging to file param
const log = argv.log || config.log;
// Node websocket
const wsProvider = config.nodeWS;
const main = async () => {
console.log("\n\x1b[45m\x1b[1m Substrate auto payout \x1b[0m\n");
console.log("\x1b[1m - Check source at https://github.com/Colm3na/substrate-auto-payout\x1b[0m");
console.log("\x1b[32m\x1b[1m - Made with love from ColmenaLabs_SVQ https://colmenalabs.org/\x1b[0m\n");
let raw;
try {
raw = fs.readFileSync(accountJSON, { encoding: 'utf-8' });
} catch(err) {
console.log(`\x1b[31m\x1b[1mError! Can't open ${accountJSON}\x1b[0m\n`);
process.exit(1);
}
const account = JSON.parse(raw);
const address = account.address;
// Prompt user to enter password
if (!password) {
const response = await prompts({
type: 'password',
name: 'password',
message: `Enter password for ${address}:`
});
password = response.password;
}
if (password) {
console.log(`\x1b[1m -> Importing account\x1b[0m`, address);
const signer = keyring.restoreAccount(account, password);
signer.decodePkcs8(password);
// Connect to node
console.log(`\x1b[1m -> Connecting to\x1b[0m`, wsProvider);
const provider = new WsProvider(wsProvider);
const api = await ApiPromise.create({ provider });
// Check account balance
const accountBalance = await api.derive.balances.all(address);
const availableBalance = accountBalance.availableBalance;
if (availableBalance.eq(0)) {
console.log(`\x1b[31m\x1b[1mError! Account ${address} doesn't have available funds\x1b[0m\n`);
process.exit(1);
}
console.log(`\x1b[1m -> Account ${address} available balance is ${availableBalance.toHuman()}\x1b[0m`);
// Get session progress info
const chainActiveEra = await api.query.staking.activeEra();
const activeEra = JSON.parse(JSON.stringify(chainActiveEra)).index;
console.log(`\x1b[1m -> Active era is ${activeEra}\x1b[0m`);
let transactions = [];
for (let index = 0; index < config.validators.length; index++) {
const validator = config.validators[index];
let unclaimedRewards = [];
let era = activeEra - 84;
const stakingInfo = await api.derive.staking.account(validator);
const claimedRewards = stakingInfo.stakingLedger.claimedRewards;
console.log(`\x1b[1m -> Claimed eras for validator ${validator}: ${JSON.stringify(claimedRewards)}\x1b[0m`);
for (era; era < activeEra; era++) {
const eraPoints = await api.query.staking.erasRewardPoints(era);
const eraValidators = Object.keys(eraPoints.individual.toHuman());
if (eraValidators.includes(validator) && !claimedRewards.includes(era)) {
transactions.push(api.tx.staking.payoutStakers(validator, era));
unclaimedRewards.push(era);
}
}
console.log(`\x1b[1m -> Unclaimed eras for validator ${validator}: ${JSON.stringify(unclaimedRewards)}\x1b[0m`);
}
if (transactions.length > 0) {
// Claim rewards tx
const nonce = (await api.derive.balances.account(address)).accountNonce
const hash = await api.tx.utility.batch(transactions).signAndSend(signer, { nonce });
console.log(`\n\x1b[32m\x1b[1mSuccess! \x1b[37mCheck tx in PolkaScan: https://polkascan.io/kusama/transaction/${hash.toString()}\x1b[0m\n`);
if (log) {
fs.appendFileSync(`autopayout.log`, `${new Date()} - Claimed rewards, transaction hash is ${hash.toString()}\n`);
}
} else {
console.log(`\n\x1b[33m\x1b[1mWarning! There's no unclaimed rewards, exiting!\x1b[0m\n`);
if (log) {
fs.appendFileSync(`autopayout.log`, `${new Date()} - There's no unclaimed rewards\n`);
}
}
process.exit(0);
}
}
try {
main();
} catch (error) {
console.error(error);
}