From 1d0ba63c440b510eaff15f892e6b99932616a0d2 Mon Sep 17 00:00:00 2001 From: Yun Date: Mon, 24 Jun 2019 18:14:23 +0900 Subject: [PATCH] initial commit --- .gitignore | 83 +++++++++++++++++++ dist/index.d.ts | 1 + dist/index.js | 151 +++++++++++++++++++++++++++++++++++ dist/index.js.map | 1 + dist/utils/rest.d.ts | 36 +++++++++ dist/utils/rest.js | 51 ++++++++++++ dist/utils/rest.js.map | 1 + package.json | 22 ++++++ src/index.ts | 176 +++++++++++++++++++++++++++++++++++++++++ src/utils/rest.ts | 83 +++++++++++++++++++ tsconfig.json | 29 +++++++ 11 files changed, 634 insertions(+) create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/index.js.map create mode 100644 dist/utils/rest.d.ts create mode 100644 dist/utils/rest.js create mode 100644 dist/utils/rest.js.map create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 src/utils/rest.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index ad46b30..327c0fc 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,86 @@ typings/ # next.js build output .next + +node_modules/ +.node_modules/ +built/* +tests/cases/rwc/* +tests/cases/test262/* +tests/cases/perf/* +!tests/cases/webharness/compilerToString.js +test-args.txt +~*.docx +\#*\# +.\#* +tests/baselines/local/* +tests/baselines/local.old/* +tests/services/baselines/local/* +tests/baselines/prototyping/local/* +tests/baselines/rwc/* +tests/baselines/test262/* +tests/baselines/reference/projectOutput/* +tests/baselines/local/projectOutput/* +tests/baselines/reference/testresults.tap +tests/services/baselines/prototyping/local/* +tests/services/browser/typescriptServices.js +src/harness/*.js +src/compiler/diagnosticInformationMap.generated.ts +src/compiler/diagnosticMessages.generated.json +src/parser/diagnosticInformationMap.generated.ts +src/parser/diagnosticMessages.generated.json +rwc-report.html +*.swp +build.json +*.actual +tests/webTestServer.js +tests/webTestServer.js.map +tests/webhost/*.d.ts +tests/webhost/webtsc.js +tests/cases/**/*.js +tests/cases/**/*.js.map +*.config +scripts/debug.bat +scripts/run.bat +scripts/word2md.js +scripts/buildProtocol.js +scripts/ior.js +scripts/authors.js +scripts/configurePrerelease.js +scripts/open-user-pr.js +scripts/open-cherry-pick-pr.js +scripts/processDiagnosticMessages.d.ts +scripts/processDiagnosticMessages.js +scripts/produceLKG.js +scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js +scripts/generateLocalizedDiagnosticMessages.js +scripts/*.js.map +scripts/typings/ +coverage/ +internal/ +**/.DS_Store +.settings +**/.vs +**/.vscode +!**/.vscode/tasks.json +!tests/cases/projects/projectOption/**/node_modules +!tests/cases/projects/NodeModulesSearch/**/* +!tests/baselines/reference/project/nodeModules*/**/* +.idea +yarn.lock +yarn-error.log +.parallelperf.* +tests/cases/user/*/package-lock.json +tests/cases/user/*/node_modules/ +tests/cases/user/*/**/*.js +tests/cases/user/*/**/*.js.map +tests/cases/user/*/**/*.d.ts +!tests/cases/user/zone.js/ +!tests/cases/user/bignumber.js/ +!tests/cases/user/discord.js/ +tests/baselines/reference/dt +.failed-tests +TEST-results.xml +package-lock.json + +unsignedTx.json \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..eac709c --- /dev/null +++ b/dist/index.js @@ -0,0 +1,151 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core = require("@terra-money/core"); +const rest_1 = require("./utils/rest"); +const big_js_1 = require("big.js"); +const fs = require("fs"); +const rest = new rest_1.default("http://127.0.0.1:1317"); +const outputFile = "./unsignedTx.json"; +const foundationAddress = "terra1dp0taj85ruc299rkdvzp4z5pfg6z6swaed74e6"; +const goliathValAddress = "terravaloper163phlen6dn7sp9khhjar2gqqx6kga0ly8d7h9g"; +const marineValAddress = "terravaloper1d3hatwcsvkktgwp3elglw9glca0h42yg6xy4lp"; +const ghostValAddress = "terravaloper1rgu3qmm6rllfxlrfk94pgxa0jm37902dynqehm"; +const wraithValAddress = "terravaloper1eutun6vh83lmyq0wmyf9vgghvurze2xanl9sq6"; +async function loadFoundationRewards() { + const promises = []; + promises.push(rest.loadDelegatorRewards(foundationAddress)); + promises.push(rest.loadValidatorRewards(goliathValAddress)); + promises.push(rest.loadValidatorRewards(marineValAddress)); + promises.push(rest.loadValidatorRewards(ghostValAddress)); + promises.push(rest.loadValidatorRewards(wraithValAddress)); + const rewardMap = {}; + await Promise.all(promises) + .then(res => { + for (let i in res) { + const rewards = res[i]; + if (rewards && rewards.length > 0) { + for (let j in rewards) { + const denom = rewards[j].denom; + const amount = rewards[j].amount.split('.')[0]; + if (rewardMap[denom]) { + rewardMap[denom] = big_js_1.default(rewardMap[denom]).plus(amount).toString(); + } + else { + rewardMap[denom] = amount; + } + } + } + } + }); + const totalRewards = []; + for (let denom in rewardMap) { + totalRewards.push({ + denom: denom, + amount: rewardMap[denom] + }); + } + return totalRewards; +} +const validatorBonusRate = 0.2; +function computeValidatorsRewardRatio(rewardRatioMap, validators) { + let totalBondedToken = big_js_1.default(0); + for (let i in validators) { + totalBondedToken = totalBondedToken.plus(validators[i].tokens); + } + for (let i in validators) { + const validator = validators[i]; + const address = core.convertValAddressToAccAddress(validator.operator_address); + rewardRatioMap[address] = big_js_1.default(validator.tokens).div(totalBondedToken).mul(validatorBonusRate).toString(); + } + return; +} +async function computeDelegatorRewardRatio(rewardRatioMap, validators) { + let totalBondedToken = big_js_1.default(0); + for (let i in validators) { + totalBondedToken = totalBondedToken.plus(validators[i].tokens); + } + for (let i in validators) { + const validator = validators[i]; + const delegations = await rest.loadDelegations(validator.operator_address); + if (!delegations) + continue; + for (let j in delegations) { + const delegation = delegations[j]; + const tokens = big_js_1.default(validator.tokens).mul(delegations[j].shares).div(validator.delegator_shares); + const ratio = big_js_1.default(tokens).div(totalBondedToken).mul(1 - validatorBonusRate).toString(); + if (rewardRatioMap[delegation.delegator_address]) { + rewardRatioMap[delegation.delegator_address] + = big_js_1.default(rewardRatioMap[delegation.delegator_address]) + .plus(ratio).toString(); + } + else { + rewardRatioMap[delegation.delegator_address] = ratio; + } + } + } + return; +} +async function main() { + const foundationRewards = await loadFoundationRewards(); + console.debug("Foundation Rewards:", foundationRewards); + const validators = await rest.loadValidators(); + if (!validators) { + console.error("no validator found"); + return process.exit(-1); + } + const rewardRatioMap = {}; + computeValidatorsRewardRatio(rewardRatioMap, validators); + console.info("Validator Bonus Rewards: ", rewardRatioMap); + await computeDelegatorRewardRatio(rewardRatioMap, validators); + console.info("Total Rewards: ", rewardRatioMap); + // Rotate reward ratio and build msg input + let totalRatio = big_js_1.default(0); + const outputs = []; + for (let addr in rewardRatioMap) { + const ratio = rewardRatioMap[addr]; + totalRatio = totalRatio.plus(ratio); + const coins = []; + for (let i in foundationRewards) { + coins.push({ + denom: foundationRewards[i].denom, + amount: big_js_1.default(foundationRewards[i].amount).mul(ratio).toFixed(0) + }); + } + outputs.push({ + address: addr, + coins: coins + }); + } + if (totalRatio.gt(1)) { + console.error(`Total Reward Ratio(${totalRatio}) is bigger than 1`); + return process.exit(-1); + } + const inputs = []; + const coins = []; + for (let i in foundationRewards) { + coins.push({ + denom: foundationRewards[i].denom, + amount: big_js_1.default(foundationRewards[i].amount).toFixed(0) + }); + } + inputs.push({ + address: foundationAddress, + coins: coins + }); + const multiSendMsg = core.buildMultiSend(inputs, outputs); + const unSingedTx = core.buildStdTx([multiSendMsg], { gas: "1000000", amount: [{ + denom: "ukrw", + amount: "1000000" + }] }, "reward distribution"); + fs.writeFile(outputFile, JSON.stringify(unSingedTx, null, 4), function (err) { + if (err) { + console.error("Writing Failed", err); + } + else { + console.info("Writing Succeed", `Please check ${outputFile}`); + } + }); + return; +} +main(); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..d1dc7d8 --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"src/","sources":["index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/utils/rest.d.ts b/dist/utils/rest.d.ts new file mode 100644 index 0000000..d731d0c --- /dev/null +++ b/dist/utils/rest.d.ts @@ -0,0 +1,36 @@ +declare class RestInterface { + lcdAddress: string; + constructor(lcdAddress: string); + loadValidators(): Promise>; + loadValidatorRewards(valAddress: string): Promise>; + loadDelegations(valAddress: string): Promise>; + loadDelegatorRewards(delAddress: string): Promise>; +} +export interface Validator { + operator_address: string; + consensus_pubkey: string; + jailed: boolean; + status: number; + tokens: string; + delegator_shares: string; + description: [Object]; + unbonding_height: string; + unbonding_time: string; + commission: [Object]; + min_self_delegation: string; +} +export interface Delegation { + delegator_address: string; + validator_address: string; + shares: string; +} +export interface Coin { + denom: string; + amount: string; +} +export interface ValidatorRewardsInfo { + operator_address: string; + self_bond_rewards: [Coin]; + val_commission: [Coin]; +} +export default RestInterface; diff --git a/dist/utils/rest.js b/dist/utils/rest.js new file mode 100644 index 0000000..ed623e9 --- /dev/null +++ b/dist/utils/rest.js @@ -0,0 +1,51 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const axios_1 = require("axios"); +class RestInterface { + constructor(lcdAddress) { + this.lcdAddress = lcdAddress; + } + async loadValidators() { + const queryValidatorsURL = `${this.lcdAddress}/staking/validators`; + try { + const res = await axios_1.default.get(queryValidatorsURL); + return res.data ? res.data : []; + } + catch (error) { + console.error("[LoadValidators]", error); + } + } + async loadValidatorRewards(valAddress) { + const queryValidatorRewardsInfoURL = `${this.lcdAddress}/distribution/validators/${valAddress}`; + try { + const res = await axios_1.default.get(queryValidatorRewardsInfoURL); + // foundation validators have 100% commission, so ignore self_bond_rewards + return res.data ? res.data.val_commission : []; + } + catch (error) { + console.error("[LoadValidatorRewardsInfoURL]", error); + } + } + async loadDelegations(valAddress) { + const queryDelegatorsURL = `${this.lcdAddress}/staking/validators/${valAddress}/delegations`; + try { + const res = await axios_1.default.get(queryDelegatorsURL); + return res.data ? res.data : []; + } + catch (error) { + console.error("[LoadDelegations]", error); + } + } + async loadDelegatorRewards(delAddress) { + const queryDelegatorRewardsURL = `${this.lcdAddress}/distribution/delegators/${delAddress}/rewards`; + try { + const res = await axios_1.default.get(queryDelegatorRewardsURL); + return res.data ? res.data : []; + } + catch (error) { + console.error("[LoadDelegatorRewards]", error); + } + } +} +exports.default = RestInterface; +//# sourceMappingURL=rest.js.map \ No newline at end of file diff --git a/dist/utils/rest.js.map b/dist/utils/rest.js.map new file mode 100644 index 0000000..1e2d731 --- /dev/null +++ b/dist/utils/rest.js.map @@ -0,0 +1 @@ +{"version":3,"file":"rest.js","sourceRoot":"src/","sources":["utils/rest.ts"],"names":[],"mappings":";;AAAA,iCAA4C;AAE5C,MAAM,aAAa;IAGjB,YAAY,UAAkB;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,MAAM,kBAAkB,GAAG,GAAG,IAAI,CAAC,UAAU,qBAAqB,CAAA;QAClE,IAAI;YACF,MAAM,GAAG,GAAG,MAAM,eAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;YAC/C,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;SAChC;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;SAC1C;IACH,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,UAAkB;QAC3C,MAAM,4BAA4B,GAAG,GAAG,IAAI,CAAC,UAAU,4BAA4B,UAAU,EAAE,CAAA;QAC/F,IAAI;YACF,MAAM,GAAG,GAAG,MAAM,eAAK,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAA;YACzD,0EAA0E;YAC1E,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAA;SAC/C;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;SACvD;IACH,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,UAAkB;QACtC,MAAM,kBAAkB,GAAG,GAAG,IAAI,CAAC,UAAU,uBAAuB,UAAU,cAAc,CAAA;QAC5F,IAAI;YACF,MAAM,GAAG,GAAG,MAAM,eAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;YAC/C,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;SAChC;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;SAC3C;IACH,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,UAAkB;QAC3C,MAAM,wBAAwB,GAAG,GAAG,IAAI,CAAC,UAAU,4BAA4B,UAAU,UAAU,CAAA;QACnG,IAAI;YACF,MAAM,GAAG,GAAG,MAAM,eAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAA;YACrD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;SAChC;QAAC,OAAO,KAAK,EAAE;YACd,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;SAChD;IACH,CAAC;CACF;AAiCD,kBAAe,aAAa,CAAA"} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..b776084 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "reward-distributor", + "version": "1.0.0", + "description": "terra foundation reward distributor to validators & delegators", + "main": "dist/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc --declaration", + "start": "node ./dist/index.js" + }, + "author": "YunSuk Yeo ", + "license": "ISC", + "dependencies": { + "@terra-money/core": "^1.0.2", + "@types/axios": "^0.14.0", + "@types/big.js": "^4.0.5", + "@types/loglevel": "^1.5.4", + "axios": "^0.19.0", + "big.js": "^5.2.2", + "loglevel": "^1.6.3" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d201087 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,176 @@ +import * as core from "@terra-money/core" +import {default as RestInterface, Coin, Validator, ValidatorRewardsInfo} from "./utils/rest" +import bn from "big.js" +import * as fs from "fs" + + +const rest = new RestInterface("http://127.0.0.1:1317") +const outputFile = "./unsignedTx.json" + +const foundationAddress = "terra1dp0taj85ruc299rkdvzp4z5pfg6z6swaed74e6" +const goliathValAddress = "terravaloper163phlen6dn7sp9khhjar2gqqx6kga0ly8d7h9g" +const marineValAddress = "terravaloper1d3hatwcsvkktgwp3elglw9glca0h42yg6xy4lp" +const ghostValAddress = "terravaloper1rgu3qmm6rllfxlrfk94pgxa0jm37902dynqehm" +const wraithValAddress = "terravaloper1eutun6vh83lmyq0wmyf9vgghvurze2xanl9sq6" + +async function loadFoundationRewards(): Promise> { + const promises: Array>> = [] + promises.push(rest.loadDelegatorRewards(foundationAddress)) + promises.push(rest.loadValidatorRewards(goliathValAddress)) + promises.push(rest.loadValidatorRewards(marineValAddress)) + promises.push(rest.loadValidatorRewards(ghostValAddress)) + promises.push(rest.loadValidatorRewards(wraithValAddress)) + + const rewardMap = {} + await Promise.all(promises) + .then(res => { + for (let i in res) { + const rewards = res[i] + + if (rewards && rewards.length > 0) { + for (let j in rewards) { + const denom = rewards[j].denom + const amount = rewards[j].amount.split('.')[0] + if (rewardMap[denom]) { + rewardMap[denom] = bn(rewardMap[denom]).plus(amount).toString() + } else { + rewardMap[denom] = amount + } + } + } + } + }) + + const totalRewards: Array = [] + for (let denom in rewardMap) { + totalRewards.push({ + denom: denom, + amount: rewardMap[denom] + }) + } + + return totalRewards +} + +const validatorBonusRate = 0.2 +function computeValidatorsRewardRatio(rewardRatioMap: object, validators: Array) { + let totalBondedToken = bn(0) + for (let i in validators) { + totalBondedToken = totalBondedToken.plus(validators[i].tokens) + } + + for (let i in validators) { + const validator = validators[i] + const address = core.convertValAddressToAccAddress(validator.operator_address) + rewardRatioMap[address] = bn(validator.tokens).div(totalBondedToken).mul(validatorBonusRate).toString() + } + + return +} + +async function computeDelegatorRewardRatio(rewardRatioMap: object, validators: Array): Promise { + let totalBondedToken = bn(0) + for (let i in validators) { + totalBondedToken = totalBondedToken.plus(validators[i].tokens) + } + + for (let i in validators) { + const validator = validators[i] + const delegations = await rest.loadDelegations(validator.operator_address) + + if (!delegations) continue + + for (let j in delegations) { + const delegation = delegations[j] + const tokens = bn(validator.tokens).mul(delegations[j].shares).div(validator.delegator_shares) + const ratio = bn(tokens).div(totalBondedToken).mul(1 - validatorBonusRate).toString() + + if (rewardRatioMap[delegation.delegator_address]) { + rewardRatioMap[delegation.delegator_address] + = bn(rewardRatioMap[delegation.delegator_address]) + .plus(ratio).toString() + } else { + rewardRatioMap[delegation.delegator_address] = ratio + } + } + } + + return +} + +async function main() { + + const foundationRewards = await loadFoundationRewards() + console.debug("Foundation Rewards:", foundationRewards) + + const validators = await rest.loadValidators() + if (!validators) { + console.error("no validator found") + return process.exit(-1) + } + + const rewardRatioMap = {} + computeValidatorsRewardRatio(rewardRatioMap, validators) + console.info("Validator Bonus Rewards: ", rewardRatioMap) + + await computeDelegatorRewardRatio(rewardRatioMap, validators) + console.info("Total Rewards: ", rewardRatioMap) + + // Rotate reward ratio and build msg input + let totalRatio = bn(0) + const outputs: Array = [] + for (let addr in rewardRatioMap) { + const ratio = rewardRatioMap[addr] + totalRatio = totalRatio.plus(ratio) + + const coins: Array = [] + for (let i in foundationRewards) { + coins.push({ + denom: foundationRewards[i].denom, + amount: bn(foundationRewards[i].amount).mul(ratio).toFixed(0) + }) + } + + outputs.push({ + address: addr, + coins: coins + }) + } + + if (totalRatio.gt(1)) { + console.error(`Total Reward Ratio(${totalRatio}) is bigger than 1`) + return process.exit(-1) + } + + const inputs: Array = [] + const coins: Array = [] + for (let i in foundationRewards) { + coins.push({ + denom: foundationRewards[i].denom, + amount: bn(foundationRewards[i].amount).toFixed(0) + }) + } + + inputs.push({ + address: foundationAddress, + coins: coins + }) + + const multiSendMsg = core.buildMultiSend(inputs, outputs) + const unSingedTx = core.buildStdTx([multiSendMsg], {gas: "1000000", amount: [{ + denom: "ukrw", + amount: "1000000" + }]}, "reward distribution") + + fs.writeFile(outputFile, JSON.stringify(unSingedTx, null, 4), function(err) { + if (err) { + console.error("Writing Failed", err); + } else { + console.info("Writing Succeed", `Please check ${outputFile}`) + } + }) + + return +} + +main() \ No newline at end of file diff --git a/src/utils/rest.ts b/src/utils/rest.ts new file mode 100644 index 0000000..ee57c03 --- /dev/null +++ b/src/utils/rest.ts @@ -0,0 +1,83 @@ +import axios, { AxiosResponse } from "axios" + +class RestInterface { + lcdAddress: string + + constructor(lcdAddress: string) { + this.lcdAddress = lcdAddress + } + + async loadValidators(): Promise> { + const queryValidatorsURL = `${this.lcdAddress}/staking/validators` + try { + const res = await axios.get(queryValidatorsURL) + return res.data ? res.data : [] + } catch (error) { + console.error("[LoadValidators]", error); + } + } + + async loadValidatorRewards(valAddress: string): Promise> { + const queryValidatorRewardsInfoURL = `${this.lcdAddress}/distribution/validators/${valAddress}` + try { + const res = await axios.get(queryValidatorRewardsInfoURL) + // foundation validators have 100% commission, so ignore self_bond_rewards + return res.data ? res.data.val_commission : [] + } catch (error) { + console.error("[LoadValidatorRewardsInfoURL]", error); + } + } + + async loadDelegations(valAddress: string): Promise> { + const queryDelegatorsURL = `${this.lcdAddress}/staking/validators/${valAddress}/delegations` + try { + const res = await axios.get(queryDelegatorsURL) + return res.data ? res.data : [] + } catch (error) { + console.error("[LoadDelegations]", error); + } + } + + async loadDelegatorRewards(delAddress: string): Promise> { + const queryDelegatorRewardsURL = `${this.lcdAddress}/distribution/delegators/${delAddress}/rewards` + try { + const res = await axios.get(queryDelegatorRewardsURL) + return res.data ? res.data : [] + } catch (error) { + console.error("[LoadDelegatorRewards]", error); + } + } +} + +export interface Validator { + operator_address: string + consensus_pubkey: string + jailed: boolean + status: number + tokens: string + delegator_shares: string + description: [Object] + unbonding_height: string + unbonding_time: string + commission: [Object] + min_self_delegation: string +} + +export interface Delegation { + delegator_address: string + validator_address: string + shares: string +} + +export interface Coin { + denom: string + amount: string +} + +export interface ValidatorRewardsInfo { + operator_address:string + self_bond_rewards:[Coin] + val_commission:[Coin] +} + +export default RestInterface diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2a4f39d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "strict": false, + "allowJs": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "moduleResolution": "node", + "skipLibCheck": true, + "strictNullChecks": true, + "sourceMap": true, + "baseUrl": "src", + "sourceRoot": "src", + "outDir": "dist", + "typeRoots": [ + "./src/typings", + "./node_modules/@types" + ] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +}