Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v0.2.17 #41

Merged
merged 12 commits into from
Mar 5, 2022
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,19 @@ sudo apt install docker

### Running the script (to see if everything works correctly)

Running the autostake script manually is then simple:
Running the autostake script manually is then simple.

Note you might need `sudo` depending on your docker install.

```
docker-compose run app npm run autostake
```

Note you might need `sudo` depending on your docker install.
Pass a network name to run the script for a single network at a time.

```
docker-compose run app npm run autostake osmosis
```

### Setting up Cron to make sure the script runs daily

Expand Down
127 changes: 68 additions & 59 deletions scripts/autostake.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import Network from '../src/utils/Network.mjs'
import Operator from '../src/utils/Operator.mjs'
import {filterAsync} from '../src/utils/Helpers.mjs'
import {filterAsync, mapAsync, executeSync} from '../src/utils/Helpers.mjs'

import {
coin
Expand All @@ -23,15 +23,22 @@ class Autostake {
}
}

async run(){
async run(networkName){
const calls = this.getNetworksData().map(data => {
return async () => {
console.log('Checking', data.name)
if(networkName && data.name !== networkName) return

const client = await this.getClient(data)
if(!client) return

if(!client.operator) return console.log('Not an operator')
if(!client.network.authzSupport) return console.log('No Authz support')
if(!client.network.connected) return console.log('Could not connect to REST API')
if(!client.signingClient.connected) return console.log('Could not connect to RPC API')

console.log('Running autostake')
await this.checkBalance(client)

console.log('Finding delegators...')
let delegations
const addresses = await this.getDelegations(client).then(delegations => {
return delegations.map(delegation => {
Expand All @@ -41,58 +48,49 @@ class Autostake {
})
})

const grantedAddresses = await filterAsync(addresses, (address) => {
return this.getGrantValidators(client, address).then(validators => {
return !!validators
console.log("Checking", addresses.length, "delegators for grants...")
const batchSize = 250
const batchDelegations = _.chunk(addresses, batchSize);

let grantedAddresses = await mapAsync(batchDelegations, async (batch, index) => {
if(addresses.length > batchSize) console.log('...batch', index + 1)
return await filterAsync(batch, (address) => {
return this.getGrantValidators(client, address).then(validators => {
return !!validators
})
})
})
grantedAddresses = grantedAddresses.flat()

console.log("Found", grantedAddresses.length, "delegators with valid grants...")
let calls = _.compact(grantedAddresses).map(item => {
return async () => {
await this.autostake(client, item, [client.operator.address])
}
})
await this.executeSync(calls, 1)
await executeSync(calls, 1)
}
})
await this.executeSync(calls, 1)
}

async executeSync(calls, count){
const batchCalls = _.chunk(calls, count);
for (const batchCall of batchCalls) {
await Promise.all(batchCall.map(call => call()))
}
}

getNetworksData(){
let response = fs.readFileSync('src/networks.json');
return JSON.parse(response);
await executeSync(calls, 1)
}

async getClient(data){
const network = await Network(data)
if(!network.connected) return null

const wallet = await DirectSecp256k1HdWallet.fromMnemonic(this.mnemonic, {
prefix: network.prefix
});

const accounts = await wallet.getAccounts()
const botAddress = accounts[0].address
console.log('Your bot address for', data.name, 'is', botAddress)

const client = await network.signingClient(wallet)
if(!client.connected) return null
console.log(data.prettyName, 'bot address is', botAddress)

const client = await network.signingClient(wallet)
client.registry.register("/cosmos.authz.v1beta1.MsgExec", MsgExec)
const operatorData = data.operators.find(el => el.botAddress === botAddress)

if(!operatorData){
return null
}

const operator = Operator(operatorData)
const operatorData = data.operators.find(el => el.botAddress === botAddress)
const operator = operatorData && Operator(operatorData)

return{
network: network,
Expand Down Expand Up @@ -123,7 +121,6 @@ class Autostake {
return client.restClient.getValidatorDelegations(client.operator.address, 1_000)
.then(
(delegations) => {
console.log("Checking", delegations.length, "delegators for grants..")
return delegations
},
(error) => {
Expand All @@ -138,14 +135,12 @@ class Autostake {
.then(
(result) => {
if(result.claimGrant || result.stakeGrant){
console.log(delegatorAddress, "Grants found")
if(result.claimGrant && result.stakeGrant){
const grantValidators = result.stakeGrant.authorization.allow_list.address
if(!grantValidators.includes(client.operator.address)){
console.log(delegatorAddress, "Not autostaking for this validator, skipping")
return
}
console.log(delegatorAddress, "Grants valid")

return grantValidators
}else{
Expand All @@ -163,38 +158,19 @@ class Autostake {
async autostake(client, address, validators){
const totalRewards = await this.totalRewards(client, address, validators)
const perValidatorReward = parseInt(totalRewards / validators.length)
console.log(address, "Total rewards", totalRewards, client.network.denom)
console.log(address, "Autostaking", perValidatorReward, client.network.denom, "per validator")

if(perValidatorReward < client.operator.data.minimumReward){
console.log(address, 'Reward is too low, skipping')
console.log(address, perValidatorReward, client.network.denom, 'reward is too low, skipping')
return
}

console.log(address, "Autostaking", perValidatorReward, client.network.denom, validators.length > 1 ? "per validator" : '')

let messages = validators.map(el => {
return [{
typeUrl: "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward",
value: MsgWithdrawDelegatorReward.encode(MsgWithdrawDelegatorReward.fromPartial({
delegatorAddress: address,
validatorAddress: el
})).finish()
}, {
typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
value: MsgDelegate.encode(MsgDelegate.fromPartial({
delegatorAddress: address,
validatorAddress: el,
amount: coin(perValidatorReward, client.network.denom)
})).finish()
}]
return this.buildRestakeMessage(address, el, perValidatorReward, client.network.denom)
}).flat()

let execMsg = {
typeUrl: "/cosmos.authz.v1beta1.MsgExec",
value: {
grantee: client.operator.botAddress,
msgs: messages
}
}
let execMsg = this.buildExecMessage(client.operator.botAddress, messages)

const memo = 'REStaked by ' + client.operator.moniker
return client.signingClient.signAndBroadcast(client.operator.botAddress, [execMsg], undefined, memo).then((result) => {
Expand All @@ -205,6 +181,33 @@ class Autostake {
})
}

buildExecMessage(botAddress, messages){
return {
typeUrl: "/cosmos.authz.v1beta1.MsgExec",
value: {
grantee: botAddress,
msgs: messages
}
}
}

buildRestakeMessage(address, validatorAddress, amount, denom){
return [{
typeUrl: "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward",
value: MsgWithdrawDelegatorReward.encode(MsgWithdrawDelegatorReward.fromPartial({
delegatorAddress: address,
validatorAddress: validatorAddress
})).finish()
}, {
typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
value: MsgDelegate.encode(MsgDelegate.fromPartial({
delegatorAddress: address,
validatorAddress: validatorAddress,
amount: coin(amount, denom)
})).finish()
}]
}

totalRewards(client, address, validators){
return client.restClient.getRewards(address)
.then(
Expand All @@ -224,7 +227,13 @@ class Autostake {
}
)
}

getNetworksData(){
let response = fs.readFileSync('src/networks.json');
return JSON.parse(response);
}
}

const autostake = new Autostake();
autostake.run()
const networkName = process.argv[2]
autostake.run(networkName)
5 changes: 3 additions & 2 deletions src/components/DelegateForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ class DelegateForm extends React.Component {
this.props.stargateClient.simulate(this.props.address, messages).then(gas => {
const saveTxFeeNum = (this.props.redelegate || this.props.undelegate) ? 0 : 10
const gasPrice = this.props.stargateClient.getFee(gas).amount[0].amount
const amount = (this.props.availableBalance.amount - (gasPrice * saveTxFeeNum))
this.setState({amount: amount / 1_000_000.0})
const amount = (this.props.availableBalance.amount - (gasPrice * saveTxFeeNum)) / 1_000_000.0

this.setState({amount: amount > 0 ? amount : 0})
}, error => {
this.setState({error: error.message})
})
Expand Down
30 changes: 11 additions & 19 deletions src/components/Delegations.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
class Delegations extends React.Component {
constructor(props) {
super(props);
this.state = {operatorGrants: {}, validatorLoading: {}, orderedOperators: [], orderedRegularDelegations: []}
this.state = {operatorGrants: {}, validatorLoading: {}}

this.setError = this.setError.bind(this)
this.setClaimLoading = this.setClaimLoading.bind(this)
Expand Down Expand Up @@ -54,8 +54,6 @@ class Delegations extends React.Component {
}

refresh(){
this.setState({orderedOperators: this.orderedOperators()})
this.setState({orderedRegularDelegations: this.orderedRegularDelegations()})
this.getRewards()
this.refreshInterval()
if(this.props.operators.length){
Expand Down Expand Up @@ -201,22 +199,16 @@ class Delegations extends React.Component {
return this.props.operators.map(operator => operator.botAddress)
}

noDelegations(){
return Object.values(this.props.operators).length < 1 && Object.values(this.props.delegations).length < 1
orderedOperators(){
return _.sortBy(this.props.operators, ({address}) => this.props.delegations[address] ? 0 : 1)
}

orderedOperators(){
const random = _.shuffle(this.props.operators)
const ownerAddress = this.props.network && this.props.network.data.ownerAddress
if(ownerAddress){
return _.sortBy(random, ({address}) => address === ownerAddress ? 0 : 1)
}
return random
regularDelegations(){
return Object.values(_.omit(this.props.delegations, this.operatorAddresses()))
}

orderedRegularDelegations(){
const delegations = Object.values(_.omit(this.props.delegations, this.operatorAddresses()))
return _.shuffle(delegations)
noDelegations(){
return Object.values(this.props.operators).length < 1 && Object.values(this.props.delegations).length < 1
}

totalRewards(validators){
Expand Down Expand Up @@ -454,14 +446,14 @@ class Delegations extends React.Component {
</tr>
</thead>
<tbody>
{this.state.orderedOperators.length > 0 && (
this.state.orderedOperators.map(operator => {
{this.orderedOperators().length > 0 && (
this.orderedOperators().map(operator => {
const delegation = this.props.delegations && this.props.delegations[operator.address]
return this.renderValidator(operator.address, delegation)
})
)}
{this.state.orderedRegularDelegations.length > 0 && (
this.state.orderedRegularDelegations.map(delegation => {
{this.regularDelegations().length > 0 && (
this.regularDelegations().map(delegation => {
return this.renderValidator(delegation.delegation.validator_address, delegation)
})
)}
Expand Down
42 changes: 10 additions & 32 deletions src/components/Validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,15 @@ import {

function Validators(props) {
const [filter, setFilter] = useState()
const [orderedOperatorValidators, setOrderedOperatorValidators] = useState([])
const [orderedRegularValidators, setOrderedRegularValidators] = useState([])

useEffect(() => {
const ownerAddress = props.network && props.network.data.ownerAddress
setOrderedOperatorValidators(orderValidators(operatorValidators(), ownerAddress))
setOrderedRegularValidators(orderValidators(regularValidators()))
}, [props.operators, props.validators])

function orderValidators(validators, ownerAddress){
const random = _.shuffle(validators)
if(ownerAddress){
return _.sortBy(random, ({operator_address}) => operator_address === ownerAddress ? 0 : 1)
}
return random
}

function orderedRegularDelegations(){
const delegations = Object.values(_.omit(this.props.delegations, this.operatorAddresses()))
return _.shuffle(delegations)
function filteredOperators(){
const results = _.pick(props.validators, props.operators.map(el => el.address))
return Object.entries(filteredResults(results))
}

function operatorValidators(){
return _.pick(props.validators, props.operators.map(el => el.address))
}

function regularValidators(){
return _.omit(props.validators, props.operators.map(el => el.address))
function filteredValidators(){
const results = _.omit(props.validators, props.operators.map(el => el.address))
return Object.entries(filteredResults(results))
}

function filterValidators(event){
Expand Down Expand Up @@ -99,26 +80,23 @@ function Validators(props) {
)
}

const filteredOperators = Object.entries(filteredResults(orderedOperatorValidators))
const filteredValidators = Object.entries(filteredResults(orderedRegularValidators))

return (
<>
<input className="form-control mb-3" id="myInput" onKeyUp={filterValidators} type="text" placeholder="Search.." />
{(filteredOperators.length > 0 || filteredValidators.length > 0) &&
{(filteredOperators().length > 0 || filteredValidators().length > 0) &&
<Table className="align-middle">
<tbody>
<tr>
<th>Validator</th>
<th className="text-center">REStake</th>
<th></th>
</tr>
{filteredOperators.map(([validator_address, item], i) => renderItem(item, true))}
{filteredValidators.map(([validator_address, item], i) => renderItem(item))}
{filteredOperators().map(([validator_address, item], i) => renderItem(item, true))}
{filteredValidators().map(([validator_address, item], i) => renderItem(item))}
</tbody>
</Table>
}
{filteredOperators.length < 1 && filteredValidators.length < 1 &&
{filteredOperators().length < 1 && filteredValidators().length < 1 &&
<p>No results found</p>
}
</>
Expand Down
Loading