adjustStake.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /**
  2. * autopayout.js
  3. *
  4. * Claim and distribute validator staking rewards for your stakers
  5. *
  6. * https://github.com/Colm3na/polkadot-auto-payout
  7. *
  8. * Author: Mario Pino | @mariopino:matrix.org
  9. */
  10. const BigNumber = require("bignumber.js");
  11. const { ApiPromise, WsProvider } = require("@polkadot/api");
  12. const { types } = require("@joystream/types");
  13. const keyring = require("@polkadot/ui-keyring").default;
  14. keyring.initKeyring({
  15. isDevelopment: false,
  16. });
  17. const fs = require("fs");
  18. const prompts = require("prompts");
  19. const yargs = require("yargs");
  20. const config = require("./config.js");
  21. const argv = yargs
  22. .scriptName("autopayout.js")
  23. .option("dryrun", {
  24. alias: "d",
  25. description: "Only show info",
  26. type: "boolean",
  27. })
  28. .option("stash", {
  29. alias: "s",
  30. description: "Stash account json file path",
  31. type: "string",
  32. })
  33. .option("controller", {
  34. alias: "c",
  35. description: "Controller account json file path",
  36. type: "string",
  37. })
  38. .option("password", {
  39. alias: "p",
  40. description: "Account password, or stdin if this is not set",
  41. type: "string",
  42. })
  43. .option("log", {
  44. alias: "l",
  45. description: "log (append) to autopayout.log file",
  46. type: "boolean",
  47. })
  48. .usage(
  49. "node adjustStake.js -s keystores/stash.json -c keystores/controller.json -p password"
  50. )
  51. .help()
  52. .alias("help", "h")
  53. .version()
  54. .alias("version", "V").argv;
  55. // Exported account json file param
  56. const stashJSON = argv.stash || config.accountJSON;
  57. const controllerJSON = argv.controller || config.controllerJSON;
  58. // Password param
  59. let password = argv.password || config.password;
  60. // Logging to file param
  61. const log = argv.log || config.log;
  62. // Node websocket
  63. const wsProvider = config.nodeWS;
  64. const main = async () => {
  65. console.log(
  66. "\x1b[1m - Source at https://github.com/Colm3na/substrate-auto-payout\x1b[0m"
  67. );
  68. let json = [];
  69. try {
  70. json[0] = fs.readFileSync(stashJSON, { encoding: "utf-8" });
  71. } catch (err) {
  72. console.log(`\x1b[31m\x1b[1mError! Can't open ${stashJSON}\x1b[0m\n`, err);
  73. process.exit(1);
  74. }
  75. try {
  76. json[1] = fs.readFileSync(controllerJSON, { encoding: "utf-8" });
  77. } catch (err) {
  78. console.log(
  79. `\x1b[31m\x1b[1mError! Can't open ${controllerJSON}\x1b[0m\n`,
  80. err
  81. );
  82. process.exit(1);
  83. }
  84. const stash = JSON.parse(json[0]);
  85. const controller = JSON.parse(json[1]);
  86. // Prompt user to enter password
  87. if (!password) {
  88. const response = await prompts({
  89. type: "password",
  90. name: "password",
  91. message: `Enter password for ${address}:`,
  92. });
  93. password = response.password;
  94. }
  95. if (!password) {
  96. console.log(`No pasword. Bye!`);
  97. process.exit(0);
  98. }
  99. // Connect to node
  100. console.log(`\x1b[1m -> Connecting to\x1b[0m`, wsProvider);
  101. const provider = new WsProvider(wsProvider);
  102. const api = await ApiPromise.create({ provider, types });
  103. // Get session progress info
  104. const chainActiveEra = await api.query.staking.activeEra();
  105. const activeEra = JSON.parse(JSON.stringify(chainActiveEra)).index;
  106. console.log(`\x1b[1m -> Active era is ${activeEra}\x1b[0m`);
  107. const formatM = (n) => (n / 1000000).toFixed(3);
  108. const issuance = await api.query.balances.totalIssuance();
  109. const staked = await api.query.staking.erasTotalStake(activeEra);
  110. const quarter = issuance / 4;
  111. const percent = staked / issuance;
  112. console.log(
  113. `\x1b[1m -> Issued: ${formatM(issuance)} M\n\r`,
  114. `\x1b[1m-> Quarter: ${formatM(quarter)} M\n`,
  115. `\x1b[1m-> Staked: ${formatM(staked)} M (${(100 * percent).toFixed(2)}%)`
  116. );
  117. if (argv.dryrun) process.exit(0);
  118. let transactions = [];
  119. let addess = ``;
  120. let signer;
  121. if (percent < 0.25) {
  122. const missing = 1000 * formatM(quarter - staked);
  123. const ledger = await api.query.staking.ledger(controller.address);
  124. const unbonding = ledger.toJSON().unlocking[0].value;
  125. if (unbonding) {
  126. // rebond! signed by controller
  127. const rebond = unbonding > missing ? missing : unbonding;
  128. console.log(`\x1b[1m-> Rebonding ${rebond} K`);
  129. transactions.push(api.tx.staking.rebond(rebond));
  130. signer = keyring.restoreAccount(controller, password);
  131. address = controller.address;
  132. } else {
  133. // bondExtra! signed by stash
  134. console.log(`\x1b[1m-> Bonding ${missing} K`);
  135. transactions.push(api.tx.staking.bondExtra(missing));
  136. signer = keyring.restoreAccount(stash, password);
  137. address = stash.address;
  138. }
  139. } else if (percent > 0.25) {
  140. const spare = 1000 * formatM(staked - quarter);
  141. console.log(`\x1b[1m -> Unbonding ${spare} K`);
  142. // unbond! singed by controller
  143. transactions.push(api.tx.staking.unbond(spare));
  144. signer = keyring.restoreAccount(controller, password);
  145. address = controller.address;
  146. }
  147. if (!transactions.length) process.exit(0);
  148. console.log(`\x1b[1m -> Importing account\x1b[0m`, address);
  149. signer.decodePkcs8(password);
  150. processTransactions(api, signer, address, transactions);
  151. };
  152. const processTransactions = async (api, signer, address, transactions) => {
  153. const nonce = (await api.derive.balances.account(address)).accountNonce;
  154. const hash = await api.tx.utility
  155. .batch(transactions.slice(0, 40))
  156. .signAndSend(signer, { nonce });
  157. console.log(
  158. `\n\x1b[32m\x1b[1mSuccess! \x1b[37mtx hash: ${hash.toString()}\x1b[0m\n`
  159. );
  160. process.exit(0);
  161. };
  162. try {
  163. main();
  164. } catch (error) {
  165. console.error(error);
  166. }