Skip to content

Instantly share code, notes, and snippets.

@Erumite
Created April 9, 2021 20:28
Show Gist options
  • Save Erumite/98471f62e97d1f76981b01f2985ede25 to your computer and use it in GitHub Desktop.
Save Erumite/98471f62e97d1f76981b01f2985ede25 to your computer and use it in GitHub Desktop.
#! /bin/bash
# -------- Config --------
# WALLET_NAME should be the name of the wallet (chain-maind keys list)
WALLET_NAME="Default"
# If WALLET_ADDRESS is blank, it will try to pull your address. (chain-maind keys show ${WALLET_NAME} --address)
# This is my address, so replace it with your own. Feel free to send me a murder of CROs.
# NOTE: All transactions with my address will fail since you don't have my keys, of course.
WALLET_ADDRESS="cro1ghlngrfrvjjf7r2h6ffpwaqcs630qxm509p2el"
# VALIDATOR_IGNORE_REGEX is a Regular Expression that will ignore specific validators for re-delegating but
# will still withdraw rewards. This is useful if you accidentally staked to a high percentage validator and don't
# care to unstake/redelegate the CRO being held by them. These addresses will be listed in RED on the summary page.
VALIDATOR_IGNORE_REGEX="(crocncl1ewgpxu7ec2kdeu9cyze0xaufnwkkqkpwq7rmst|crocncl1h6350mf68lg52ll44t7whcnffhf3qcycxrqfu7)"
# GAS can be left at auto to detect required gas when performing mult-withdraw or staking operations.
# GAS_LIMIT is used when combining withdrawal + staking since it can't be auto-detected. Excess gas fees are returned.
GAS="auto"
GAS_LIMIT=350000
# FEE_CLAIM and FEE_DELEGATE are fees to offer to the network when claiming rewards or delegating respectively.
# Values are in basecro (CRO/100000000)
# If the fee is insufficient, it asks to continue with the value suggested in the network response.
# I typically see below 6000 basecro fees per multi-withdraw and <4000 for delegation.
# This is considerably cheaper than 10000-20000basecro per withdrawal and delegation.
# If using combined withdraw+restake, these can't be 0 since it can't auto-detect.
FEE_CLAIM=5800
FEE_DELEGATE=3900
# Combine the above for a rough idea of the combined fees. This can be set manually as well.
FEE_COMBINED=$((${FEE_CLAIM} + ${FEE_DELEGATE}))
# If YES is --yes it will skip chain-maind confirmations when collecting rewards and redelegating.
#YES="--yes"
YES=""
# Optional Config
CHAIN="--chain-id crypto-org-chain-mainnet-1"
NODE="" # Use local full node.
NODE="--node https://mainnet.crypto.org:26657" # Use CDC's mainnet node (slower)
# -------- Shouldn't need editing below here. --------
# Some colors because it helps me parse things visually.
RED=$(tput setaf 1)
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
LIME_YELLOW=$(tput setaf 190)
POWDER_BLUE=$(tput setaf 153)
BLUE=$(tput setaf 4)
MAGENTA=$(tput setaf 5)
CYAN=$(tput setaf 6)
WHITE=$(tput setaf 7)
BOLD=$(tput bold)
NORMAL=$(tput sgr0)
UNDERLINE=$(tput smul)
# If the wallet address isn't configured, set it based on Wallet Name
[[ -z $WALLET_ADDRESS ]] && WALLET_ADDRESS=$($(which chain-maind) keys show ${WALLET_NAME} --address)
# Convert basecro to cro. Accepts raw numbers as well as "###basecro" "###,basecro" and "### basecro"
function basecro_to_cro() {
basecro=${*//\"/}
basecro=${basecro%% *}
basecro=${basecro%%,*}
echo ${basecro%basecro} | awk '{printf "%8f", $1/100000000}'
}
# Read current balance (Unstaked)
json=$(chain-maind ${CHAIN} ${NODE} query bank balances ${WALLET_ADDRESS} --output=json)
WALLET_BALANCE=$(echo "${json}"|jq -r ".balances[]|[.amount,.denom]|@tsv")
# Find Delegations to individual validators and do some math to get a total.
json=$(chain-maind ${CHAIN} ${NODE} query staking delegations ${WALLET_ADDRESS} --output json)
DELEGATIONS=$(echo "${json}" | \
jq -r '.delegation_responses[]|[.delegation.validator_address,.delegation.shares,.balance.amount,.balance.denom]|@csv')
DELEGATIONS=${DELEGATIONS//\"/}
DELEGATION_TOTAL=$(echo "${DELEGATIONS}" | awk -F, '{t+=$2};END{printf "%d",t}')
# Get available rewards from each of the delegators and count up the total.
# Also generate a new total by combining withdrawable rewards with current balance.
# This is used to generate NEW_STAKE, which is what will be re-staked to the network.
unset rewards; declare -A rewards
unset REWARD_TOTAL; REWARD_TOTAL=0
for d in ${DELEGATIONS}; do
v=${d%%,*}
r="$(chain-maind ${CHAIN} ${NODE} query distribution rewards ${WALLET_ADDRESS} ${v} --output=json |jq -r '.rewards[]|[.amount,.denom]|@tsv')"
rewards[${v}]=$r
done
REWARD_TOTAL=$(echo "${rewards[@]}" | awk '{ for (i=1; i<=NF; i++) t+=$i};END{printf "%d", t}')
NEW_TOTAL=$((${REWARD_TOTAL} + ${WALLET_BALANCE//basecro}))
NEW_STAKE=$(((${REWARD_TOTAL} + ${WALLET_BALANCE//basecro}) - 1000000))
# Print off some information about the current wallet.
echo -e "\n${BOLD}${UNDERLINE}${YELLOW}${WALLET_NAME}${NORMAL} (${UNDERLINE}${WALLET_ADDRESS}${NORMAL})"; \
echo -en "${GREEN}Bal: $(basecro_to_cro ${WALLET_BALANCE}) CRO${NORMAL}\t" ;\
echo -en "${LIME_YELLOW}Stk: ${BOLD}$(basecro_to_cro ${DELEGATION_TOTAL}) CRO${NORMAL}\t" ;\
echo -e "${CYAN}Rew: ${BOLD}$(basecro_to_cro ${REWARD_TOTAL})${NORMAL}\n"
# Find the smallest non-ignored validator which we'll delegate to later.
DELEGATION_TARGET=$(echo "${DELEGATIONS}"|sed -r "/${VALIDATOR_IGNORE_REGEX}/d"|sort -t, -k3n|head -n1|cut -d, -f1)
# Print off individual delegators, amount staked, and available rewards.
{ echo "${BOLD}${CYAN}Validator${NORMAL}@Staked Amount@${BOLD}Rewards${NORMAL}"
for d in ${DELEGATIONS} ; do
validator=${d%%,*}
[[ ${validator} =~ ${VALIDATOR_IGNORE_REGEX} ]] && validator="${BOLD}${RED}${validator}${NORMAL}" || validator="${BOLD}${GREEN}${validator}${NORMAL}"
staked=$(basecro_to_cro $(echo "${d}" | awk -F, '{printf "%d",$2}'))
reward="${BOLD}$(basecro_to_cro ${rewards[${d%%,*}]})${NORMAL}"
[[ ${d%%,*} == ${DELEGATION_TARGET} ]] && reward="${reward} ${BOLD}${POWDER_BLUE}<+> Delegation Target${NORMAL}"
echo ${validator}@${staked}@${reward}
done | sort -k2n -t@ ; } |column -t -s@
# -------- Claim & Restake 1-TX --------
# Print out some helpful info about what we'll be doing.
echo -e "\n\n${BOLD}${UNDERLINE}${CYAN}Claim all transactions and restake immediately?${NORMAL}"
echo -e "${BOLD}New Total: $(basecro_to_cro ${NEW_TOTAL})"
echo -e "${BOLD}New Stake: $(basecro_to_cro ${NEW_STAKE})"
echo -e "${BOLD}Stake To : ${DELEGATION_TARGET}\n"
# Set up some variables and confirm. Will continue to separeate withdraw/stake prompts if declined.
done=0 fee=$((${FEE_CLAIM}+${FEE_DELEGATE})) gas=${GAS_LIMIT}
read -p "Continue to withdraw and redelegate? [Y/n]: "
[[ ! $REPLY =~ ^[Yy] ]] && done=1
# This is hacky since there's no real supported method of combining these that I know of.
# Essentially generate the message for staking based on the NEW_STAKE value and extract the relevant message.
# Then, generate a message for withdrawing all rewards, then append the message from the redelegation.
# Also, manually set the fee/gas limits in the json and save the transaction to a file.
# Finally, sign the transaction (password prompt) and broadcast the transaction to the network.
while [[ ${done} == 0 ]] ; do
json=$(chain-maind ${CHAIN} ${NODE} ${YES} tx staking delegate ${DELEGATION_TARGET} ${NEW_STAKE}basecro --from ${WALLET_ADDRESS} --generate-only)
delegation=$(echo "${json}" | jq '.body.messages[0]')
json=$(chain-maind ${CHAIN} ${NODE} ${YES} tx distribution withdraw-all-rewards --from ${WALLET_ADDRESS} --generate-only)
append_slot=$(echo "${json}"| jq ".body.messages|length")
tx=$(echo "${json}" | jq ".body.messages[${append_slot}]|=${delegation}|.auth_info.fee.amount[0]|={\"denom\":\"basecro\",\"amount\":\"${fee}\"}|.auth_info.fee.gas_limit|=${gas}")
tmpfile='/tmp/cro_restake.tx'
echo "${tx}" > ${tmpfile}
tx=$(chain-maind ${CHAIN} ${NODE} tx sign ${tmpfile} --from ${WALLET_ADDRESS})
echo "${tx}" > ${tmpfile}.signed
chain-maind ${CHAIN} ${NODE} tx broadcast ${tmpfile}.signed
rm ${tmpfile} ${tmpfile}.signed
echo -e "${BOLD}${GREEN}Done broadcasting transaction. If you have a failure message, increase gas limit or fees.${NORMAL}"
exit 1
done
# (If the combined withdraw/restake action wasn't taken, then offer to do them individually)
# ------- Withdraw all delegations --------
# Withdraw all delegations in one transaction.
# If fee is empty, it will keep retrying with the value suggested in the response.
# This can take several attempts, so I tend to just set the fee to something slightly
# above the usual value I land on when doing the manual method.
# Gas can generally be left as auto in the config and it will auto-detect required.
echo
done=0 fee=${FEE_CLAIM} gas=${GAS}
read -p "Withdraw all delegations now? [Y/n]: "
[[ ! $REPLY =~ ^[Yy] ]] && done=1
while [[ ${done} == 0 ]] ; do
response=$(chain-maind ${CHAIN} ${NODE} ${YES} tx distribution withdraw-all-rewards --from ${WALLET_ADDRESS} --gas ${gas} --fees ${fee}basecro)
if [[ ${response} =~ "insufficient fees" ]] ; then
fee=$(echo "${response}" | jq -r '.raw_log' | grep -oP "(?<=required: )[0-9]*(?=basecro)")
read -p "[Failed] : Retry with ${fee} fee? : "
[[ $REPLY =~ ^[Yy] ]] && done=0 || done=1
echo
else
done=1
fi
done
# ---- Redelegate balance of Wallet, keeping .01 CRO for later ----
# Print off some info about the new balance and stake to be delegated.
json=$(chain-maind ${CHAIN} ${NODE} query bank balances ${WALLET_ADDRESS} --output=json)
WALLET_BALANCE=$(echo "${json}"|jq -r ".balances[]|[.amount,.denom]|@tsv")
NEW_STAKE=$((${WALLET_BALANCE//basecro/} - 1000000))
echo -e "\n${BOLD}${UNDERLINE}${YELLOW}${WALLET_NAME}${NORMAL} [${UNDERLINE}${WALLET_ADDRESS}${NORMAL}]"; \
echo -en "${GREEN}Bal: ${BOLD}$(basecro_to_cro ${WALLET_BALANCE}) CRO${NORMAL}\t" ;\
echo -en "${LIME_YELLOW}Stk: ${BOLD}$(basecro_to_cro ${DELEGATION_TOTAL}) CRO${NORMAL}\t" ; \
echo -e "${RED}NewStk: ${BOLD}$(basecro_to_cro ${NEW_STAKE}) CRO${NORMAL}\n"
# Confirm if we want to continue or not.
read -p "Redelegate ${BOLD}$(basecro_to_cro ${NEW_STAKE})${NORMAL} to ${UNDERLINE}${DELEGATION_TARGET}${NORMAL}? [Y/n]: "
[[ ! $REPLY =~ ^[Yy] ]] && exit 0
# Pretty much the same as the claim function, though fees tend to be lower.
done=0 fee=${FEE_DELEGATE} gas=${GAS}
while [[ ${done} == 0 ]] ; do
response=$(chain-maind ${CHAIN} ${NODE} ${YES} tx staking delegate ${DELEGATION_TARGET} ${NEW_STAKE}basecro --from ${WALLET_ADDRESS} --gas ${gas} --fees ${fee}basecro)
if [[ ${response} =~ "insufficient fees" ]] ; then
fee=$(echo "${response}" | jq -r '.raw_log' | grep -oP "(?<=required: )[0-9]*(?=basecro)")
read -p "[Failed] : Retry with ${fee} fee? : "
[[ $REPLY =~ ^[Yy] ]] && done=0 || done=1
echo
else
done=1
fi
done
exit 0
@Erumite
Copy link
Author

Erumite commented Apr 9, 2021

Older version for reference.

@jlavos
Copy link

jlavos commented Apr 16, 2021

Greate script, thank you!!

Trying to run as a schedule but it keeps asking me for the passphrase. Is there a way to pass the passphrase to chain-maind or not to have to type it ever?

@Erumite
Copy link
Author

Erumite commented Apr 16, 2021

It's possible with the --keyring-backend flag shown here: https://crypto.org/docs/wallets/cli.html#the-keyring-keyring-backend-option
I usually run this on the linux subsystem for windows and it asks for a password every time, though if I try on Mac, it seems to store my password in the keyring and doesn't require a password.

It's supposed to work the same on Linux, but I couldn't get libsecret to work. :/ The other option is to use --keyring-backend test but I don't like the fact that it stores the keys in an unencrypted file, though if it's a risk you're willing to take, that's an option.

@jlavos
Copy link

jlavos commented Apr 19, 2021

Yes, I also use WSL and the test backend was the only way I found for it to work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment