Skip to content

Instantly share code, notes, and snippets.

@rishipr
Last active November 23, 2022 13:11
Show Gist options
  • Save rishipr/a416b5c408357ff860b4ed41f946983e to your computer and use it in GitHub Desktop.
Save rishipr/a416b5c408357ff860b4ed41f946983e to your computer and use it in GitHub Desktop.
Rebase Awards Tracker for Wonderland $TIME Staking (MEMO) πŸ§™β€β™‚οΈ
'''
🐸 : Created by @0xRishi
πŸ§™β€β™‚οΈ : Generates CSV of MEMO (staked TIME) balances every 8 hours since first MEMO transaction + overlays all non-rebase MEMO transactions
πŸ• : Used to create wonderland-apy.vercel.app (historical implied APY chart)
(🎩, 🎩) : How to
1) Input your AVAX wallet address into `personal_avax_addr`, your Snowtrace API key into `snowtrace_key`, and your Moralis API key into `moralis_key`
2) Run with `python3`
πŸ‡ : Sample output
date timestamp blocknumber type delta_memo implied_apy balance_at_block_memo
XXXX-XX-XX XX:XX:XX XXXXXXXXXX XXXXXXX transaction 0.34454045 0.34454045
XXXX-XX-XX XX:XX:XX XXXXXXXXXX XXXXXXX rebase 0.0020437560000000077 64,947% 0.346584206
XXXX-XX-XX XX:XX:XX XXXXXXXXXX XXXXXXX rebase 0.0020560809999999874 64,988% 0.348640287
XXXX-XX-XX XX:XX:XX XXXXXXXXXX XXXXXXX rebase 0.002074844999999992 66,334% 0.350715132
XXXX-XX-XX XX:XX:XX XXXXXXXXXX XXXXXXX rebase 0.0020969470000000157 68,373% 0.352812079
XXXX-XX-XX XX:XX:XX XXXXXXXXXX XXXXXXX rebase 0.002105852999999991 67,612% 0.354917932
🐼 :
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 AUTHOR 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.
'''
# Packages
import requests
from datetime import datetime, date
from pprint import pprint
import time
import math
import csv
# Config variables
memo_addr = '0x136acd46c134e8269052c62a67042d6bdedde3c9' # Don't edit
personal_avax_addr = 'ENTER_AVAX_ADDRESS_HERE' # Input
snowtrace_key = 'ENTER_SNOWTRACE_API_KEY_HERE' # Input - get from snowtrace.io
moralis_key = 'ENTER_MORALIS_API_KEY_HERE' # Input - get from moralis.io
# Support functions
def get_ts_from_block(blocknum):
url = f"https://api.snowtrace.io/api?module=block&action=getblockreward&blockno={blocknum}&apikey={snowtrace_key}"
r = requests.get(url).json()['result']['timeStamp']
return int(r)
def get_block_from_ts(ts):
url = f"https://api.snowtrace.io/api?module=block&action=getblocknobytime&timestamp={ts}&closest=before&apikey={snowtrace_key}"
r = requests.get(url).json()['result']
return int(r)
def convert_ts_to_human_date(ts):
return datetime.utcfromtimestamp(
ts).strftime('%Y-%m-%d %H:%M:%S')
def get_memo_transactions_from_addr(avax_addr):
url = f"https://api.snowtrace.io/api?module=account&action=tokentx&address={avax_addr}&sort=asc&apikey={snowtrace_key}&contractaddress={memo_addr}"
results = requests.get(url).json()['result']
results = [r for r in results if r['contractAddress']
== memo_addr]
final_res = []
num_results = len(results)
i = 0
for r in results:
i += 1
try:
print(
f"Fetched data for {i} of {num_results} non-rebase transactions")
item = {
'date': convert_ts_to_human_date(int(r['timeStamp'])),
'timestamp': int(r['timeStamp']),
'blocknumber': r['blockNumber'],
'type': 'transaction',
'delta_memo': '',
'implied_apy': '',
'balance_at_block_memo': get_memo_balance_from_block(r['blockNumber'])
}
final_res.append(item)
except:
continue
return final_res
def get_memo_balance_from_block(blocknum):
try:
base_url = f'https://deep-index.moralis.io/api/v2/{personal_avax_addr}/erc20?chain=avalanche&to_block={blocknum}'
results = requests.get(base_url, headers={
"x-api-key": moralis_key}).json()
results = [r for r in results if r['token_address']
== memo_addr] # only get MEMO balance
balance = float(results[0]['balance']) / 1000000000
return balance
except:
return 0
# Script start
def main():
print(
f"\nGetting all non-rebase MEMO transactions for address {personal_avax_addr}...")
memo_transactions = get_memo_transactions_from_addr(
personal_avax_addr)
initial_block = memo_transactions[0]['blocknumber']
initial_timestamp = get_ts_from_block(initial_block)
current_timestamp = int(time.time())
current_block = get_block_from_ts(current_timestamp)
starting_memo_balance = get_memo_balance_from_block(initial_block)
current_memo_balance = get_memo_balance_from_block(current_block)
print(
f"\n\nStarting MEMO Balance: {starting_memo_balance} - As of {convert_ts_to_human_date(initial_timestamp)} UTC")
print(
f"Current MEMO Balance: {current_memo_balance} - As of {convert_ts_to_human_date(current_timestamp)} UTC\n\n")
snapshots_to_parse = math.ceil(
(current_timestamp - initial_timestamp) / 28800) # 8 hours = 28,800 seconds
timestamp = initial_timestamp
output_data = []
print(f"{snapshots_to_parse} snapshots (8 hrs each) to parse between {convert_ts_to_human_date(initial_timestamp)} UTC and {convert_ts_to_human_date(current_timestamp)} UTC\n")
# Get 8 hour snapshots (proxy for rebases)
print("----- MEMO 8 HOUR BALANCE SNAPSHOTS -----")
i = 1
while timestamp <= current_timestamp:
human_date = convert_ts_to_human_date(timestamp)
block_number = get_block_from_ts(timestamp)
balance = get_memo_balance_from_block(block_number)
print(
f"Snapshot {i} of {snapshots_to_parse} - {human_date} ({timestamp}): {balance} MEMO")
item = {
'date': human_date,
'timestamp': timestamp,
'blocknumber': block_number,
# first record is the first stake transaction, after which snapshots are taken
'type': 'rebase' if i > 1 else 'transaction',
'delta_memo': '',
'implied_apy': '',
'balance_at_block_memo': balance
}
output_data.append(item)
# add 8 hours to timestamp (8 hours = 28,800 seconds)
timestamp += 28800
i += 1
time.sleep(0.4) # avoid rate limiting
# Merge & sort rebase and transaction lists
print("\nMerging & sorting rebase + non-rebase transactions...")
output_data = output_data + \
[x for x in memo_transactions[1:len(
memo_transactions)] if x not in output_data] # de-dupe first transaction as already captured in snapshot loop
output_data = sorted(
output_data, key=lambda x: x['timestamp']) # make sure results are sorted by time stamp (from latest to most recent)
# Calculate deltas / implied APYs of each rebase
print("\nCalculating deltas / implied APYs of each rebase...")
output_data[0]['delta_memo'] = output_data[0]['balance_at_block_memo']
last_index = len(output_data) - 1
for i in range(len(output_data)):
if i == last_index:
break
delta = output_data[i+1]['balance_at_block_memo'] - \
output_data[i]['balance_at_block_memo']
output_data[i+1]['delta_memo'] = delta
if output_data[i+1]['type'] == 'rebase':
output_data[i +
1]['implied_apy'] = f"{format(int(math.pow(1+(delta/output_data[i]['balance_at_block_memo']), 1095) * 100), ',')}%" if output_data[i]['balance_at_block_memo'] > 0 else ""
# Remove any zero'd snapshots
output_data = [x for x in output_data if x['delta_memo'] > 0]
# Write output to CSV
print("\nOutputting results to CSV")
keys = output_data[0].keys()
date_ran = date.today().strftime("%b-%d-%Y")
file_name = f'time-wonderland-{date_ran}-analysis-{personal_avax_addr}.csv'
try:
with open(file_name, 'w', newline='') as output_file:
dict_writer = csv.DictWriter(output_file, keys)
dict_writer.writeheader()
dict_writer.writerows(output_data)
print(f"\nData saved to CSV - {file_name}\n")
except:
print("\nFailed to save data to CSV\n")
if __name__ == "__main__":
main()
@ChristosCh00
Copy link

ChristosCh00 commented Jan 1, 2022

Hey @rishipr I noticed that sometimes -depending on when the intial transaction happened- the last rebase was not being accounted for.

the reason is that you are checking for the balance every 8 hours from when the stacking started, but in reality the first rebase happens sooner than 8hrs. For example if you stake at 22:00 the first rebase will happen at 00:00.

I had a go with it and you can grab it from here: https://controlc.com/3e09f555 [UPDATED]

There is a chance you might need to change the hours[] i've introduced. The next rebase counter at wonderland.money shows that rebases are happening at 00:00, 08:00, 16:00 but my timezone is GMT+2 so you might need to change hours to ['22:00','06:00,''14:00'], but TBH I find it to be more likely to be a bug of the website front end.

Thanks for doing this for the community and for the easy to read code!

I HAVE 0 KNOWLEDGE OF BLOCKCHAIN TRANSACTIONS ETC AND IT'S THE FIRST TIME EVER I AM TOUCHING PYTHON SO APOLOGIES IF GOT SOMETHING WRONG OR THE CODE SUCKS!!!

@catob
Copy link

catob commented Jan 6, 2022

This is amazing, thank you so much for creating this. I know that the moralis API has some price data, but is there any way of getting the historical prices and add them here?

@raid5
Copy link

raid5 commented Jan 13, 2022

This script is great! I've been using it for a couple weeks now to track my rebase rewards. I'm curious what happens if someone is to wrap their MEMO into wMEMO. I don't believe this script will work correctly with wMEMO, so I am not sure how to correctly track rebase rewards after wrapping.

@RogerRoger101
Copy link

Ok before I comment please let me say, great code but....

Zapper.fi will show you the rebases as how many $TIME tokens you have acquired.

(the below numbers are not actually but used for an example)
2022-06-24 17_09_24-Zapper - Your Home to Web3

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