/** * autopayout.js * * Claim and distribute validator staking rewards for your stakers * * 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 { types } = require("@joystream/types"); 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("dryrun", { alias: "d", description: "Only show info", type: "boolean", }) .option("stash", { alias: "s", description: "Stash account json file path", type: "string", }) .option("controller", { alias: "c", description: "Controller 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 adjustStake.js -s keystores/stash.json -c keystores/controller.json -p password" ) .help() .alias("help", "h") .version() .alias("version", "V").argv; // Exported account json file param const stashJSON = argv.stash || config.accountJSON; const controllerJSON = argv.controller || config.controllerJSON; // 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( "\x1b[1m - Source at https://github.com/Colm3na/substrate-auto-payout\x1b[0m" ); let json = []; try { json[0] = fs.readFileSync(stashJSON, { encoding: "utf-8" }); } catch (err) { console.log(`\x1b[31m\x1b[1mError! Can't open ${stashJSON}\x1b[0m\n`, err); process.exit(1); } try { json[1] = fs.readFileSync(controllerJSON, { encoding: "utf-8" }); } catch (err) { console.log( `\x1b[31m\x1b[1mError! Can't open ${controllerJSON}\x1b[0m\n`, err ); process.exit(1); } const stash = JSON.parse(json[0]); const controller = JSON.parse(json[1]); // 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(`No pasword. Bye!`); process.exit(0); } // Connect to node console.log(`\x1b[1m -> Connecting to\x1b[0m`, wsProvider); const provider = new WsProvider(wsProvider); const api = await ApiPromise.create({ provider, types }); // 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`); const formatM = (n) => (n / 1000000).toFixed(3); const issuance = await api.query.balances.totalIssuance(); const staked = await api.query.staking.erasTotalStake(activeEra); const quarter = issuance / 4; const percent = staked / issuance; console.log( `\x1b[1m -> Issued: ${formatM(issuance)} M\n\r`, `\x1b[1m-> Quarter: ${formatM(quarter)} M\n`, `\x1b[1m-> Staked: ${formatM(staked)} M (${(100 * percent).toFixed(2)}%)` ); if (argv.dryrun) process.exit(0); let transactions = []; let addess = ``; let signer; if (percent < 0.25) { const missing = 1000 * formatM(quarter - staked); const ledger = await api.query.staking.ledger(controller.address); const unbonding = ledger.toJSON().unlocking[0].value; if (unbonding) { // rebond! signed by controller const rebond = unbonding > missing ? missing : unbonding; console.log(`\x1b[1m-> Rebonding ${rebond} K`); transactions.push(api.tx.staking.rebond(rebond)); signer = keyring.restoreAccount(controller, password); address = controller.address; } else { // bondExtra! signed by stash console.log(`\x1b[1m-> Bonding ${missing} K`); transactions.push(api.tx.staking.bondExtra(missing)); signer = keyring.restoreAccount(stash, password); address = stash.address; } } else if (percent > 0.25) { const spare = 1000 * formatM(staked - quarter); console.log(`\x1b[1m -> Unbonding ${spare} K`); // unbond! singed by controller transactions.push(api.tx.staking.unbond(spare)); signer = keyring.restoreAccount(controller, password); address = controller.address; } if (!transactions.length) process.exit(0); console.log(`\x1b[1m -> Importing account\x1b[0m`, address); signer.decodePkcs8(password); processTransactions(api, signer, address, transactions); }; const processTransactions = async (api, signer, address, transactions) => { const nonce = (await api.derive.balances.account(address)).accountNonce; const hash = await api.tx.utility .batch(transactions.slice(0, 40)) .signAndSend(signer, { nonce }); console.log( `\n\x1b[32m\x1b[1mSuccess! \x1b[37mtx hash: ${hash.toString()}\x1b[0m\n` ); process.exit(0); }; try { main(); } catch (error) { console.error(error); }