Last active
July 24, 2020 12:51
-
-
Save m-kus/a070a0ea75b02d3d24809857fbf3dcda to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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