Skip to content

Instantly share code, notes, and snippets.

@m-kus
Last active July 24, 2020 12:51
Show Gist options
  • Save m-kus/a070a0ea75b02d3d24809857fbf3dcda to your computer and use it in GitHub Desktop.
Save m-kus/a070a0ea75b02d3d24809857fbf3dcda to your computer and use it in GitHub Desktop.
# Useful Python 2.x script for Tezos bakers to calculate payouts for their delegators
# This is a highly edited payout calculation script which aims to be a bit friendlier while giving more information.
# Original credit goes to /u/8x1_ from https://reddit.com/r/tezos/comments/98jwb4/paypy_there_is_no_more_excuse_for_not_paying/
# Released under the MIT License - See bottom of file for license information
import urllib
import json
import random
import sys
####################################################################
# Edit the values below to customize the script for your own needs #
####################################################################
baker_address = 'tz1VmiY38m3y95HqQLjMwqnMS7sdMfGomzKi' # the address of the baker
baker_alias = 'baker' # alias of the baker wallet
hot_wallet_address = '' # if payouts are made from a non-baker address, enter it here (could be either tz1 or KT1)
wallet_alias = '' # alias of the hot wallet
default_fee_percent = 9.5 # default delegation service fee
special_addresses = ['KT1...', 'KT1...'] # special accounts that get a different fee, set to '' if none
special_fee_percent = 0 # delegation service fee for special accounts
tx_fee = 0 # transaction fee on payouts
precision = 6 # Tezos supports up to 6 decimal places of precision
minimum_delegation_threshold = 500 # required amount of tz delegated to baker to qualify for payouts
minimum_payout_threshold = 0 # delegator's gross reward must be at least this high for payout to occur
#######################################################
# You shouldn't need to edit anything below this line #
#######################################################
# API URLs
api_url_head = 'https://api.tzkt.io/v1/head' # info about current status
api_url_rewards = 'http://api.tzkt.io/v1/rewards/split/' # info about rewards at specific cycle
max_delegators = 1000 # should be not less than the number of delegators
# get current cycle info
cycle = 247
# get rewards data
response = urllib.urlopen('{}{}/{}?limit={}'.format(api_url_rewards, baker_address, cycle, max_delegators))
data = json.loads(response.read())
print ''
total_delegators = int(data['numDelegators'])
if total_delegators == 0:
print 'No non-baker delegators for cycle {}.'.format(cycle)
paid_delegators = 0
total_staking_balance = long(data['stakingBalance'])
baker_balance = total_staking_balance
total_rewards = long(data['ownBlockRewards']) + \
long(data['extraBlockRewards']) + \
long(data['endorsementRewards']) + \
long(data['ownBlockFees']) + \
long(data['extraBlockFees']) + \
long(data['futureBlockRewards']) + \
long(data['futureEndorsementRewards']) + \
long(data['doubleBakingRewards']) - \
long(data['doubleBakingLostDeposits']) - \
long(data['doubleBakingLostFees']) - \
long(data['doubleBakingLostRewards']) + \
long(data['doubleEndorsingRewards']) - \
long(data['doubleEndorsingLostDeposits']) - \
long(data['doubleEndorsingLostFees']) - \
long(data['doubleEndorsingLostRewards']) + \
long(data['revelationRewards']) - \
long(data['revelationLostRewards']) - \
long(data['revelationLostFees'])
# make sure there's actually something to pay out
if total_rewards <= 0:
print 'WARNING: Total rewards this cycle is {}, so there\'s nothing to pay out. :('.format(total_rewards)
sys.exit()
total_payouts_gross = 0
total_payouts = 0
total_fees = 0
net_earnings = total_rewards
# calculate and print out payment commands
for del_balance in data['delegators']:
delegator_address = del_balance['address']
bal = int(del_balance['balance'])
# skip to the next if we encounter a 0 balance entry
if bal == 0:
continue
# don't include your hot wallet when calculating payouts (in case your hot wallet is a KT1 address delegated to yourself)
if delegator_address == hot_wallet_address:
continue
# don't make payouts to accounts which have delegated below the threshold
if (bal < minimum_delegation_threshold * 1000000):
continue
baker_balance -= bal
fee_percent = default_fee_percent
# handle any special addresses
for address in special_addresses:
if delegator_address == address:
fee_percent = special_fee_percent
break
# calculate gross payout amount
payout_gross = (float(bal) / total_staking_balance) * total_rewards
total_payouts_gross += payout_gross
# subtract fee
payout = (payout_gross * (100 - fee_percent)) / 100
total_fees += payout_gross - payout
net_earnings -= payout
# convert from mutez (0.000001 XTZ) to XTZ
payout = round(payout / 1000000, precision)
# display the payout command to pay this delegator, filtering out any too-small payouts
if (payout >= minimum_payout_threshold):
total_payouts += payout
paid_delegators += 1
payout_string = '{0:.6f}'.format(payout) # force tiny values to show all digits
if wallet_alias:
payout_alias = wallet_alias
else:
payout_alias = baker_alias
print './tezos-client transfer {} from {} to {}'.format(payout_string, payout_alias, delegator_address)
# print some information about all payouts made for this cyle
if total_payouts > 0:
result_txt = '\nTotal payouts made: {} to {} delegator'.format(total_payouts, paid_delegators)
if paid_delegators > 1:
result_txt +='s\n' # pluralize it!
print result_txt
# display the command to transfer total payout amount to the hot wallet
if hot_wallet_address:
print './tezos-client transfer {} from {} to {}'.format(total_payouts, baker_alias, wallet_alias)
# convert the amounts to a human readable format
total_rewards = round(float(total_rewards) / 1000000, precision)
net_earnings = round(float(net_earnings) / 1000000, precision)
share_of_gross = round(net_earnings / total_rewards * 100, 2)
total_fees = round(float(total_fees) / 1000000, precision)
total_staking_balance = round(float(total_staking_balance) / 1000000, precision)
baker_balance = round(float(baker_balance) / 1000000, precision)
baker_percentage = round(baker_balance / total_staking_balance * 100, 2)
# print out stats for this cycle's payouts
print ''
print '===================================================='
print 'Stats for cycle {}'.format(cycle)
print 'Total staked balance: {}'.format(total_staking_balance)
print 'Baker staked balance: {} ({}% of total)'.format(baker_balance, baker_percentage)
print 'Total (gross) earnings: {0:.6f}'.format(total_rewards)
if total_payouts > 0:
net_earnings_txt = '{0:.6f}'.format(net_earnings)
print 'Baker\'s (net) earnings: {} ({}% of gross) (that is, {} + {} as fees charged)'.format(net_earnings_txt, share_of_gross, net_earnings - total_fees, total_fees)
###############################################################################
# MIT License #
###############################################################################
# Copyright 2018 u/8x1_ and BakeTzForMe
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
###############################################################################
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment