├── datas
└── axie_list.yaml
├── img
├── qr.PNG
├── axies.PNG
├── infos.PNG
├── Pyaxie.png
├── all_mmr.PNG
├── all_rank.PNG
├── transfer.PNG
├── all_axies.PNG
└── axies
│ ├── egg.png
│ └── you.png
├── requirements.txt
├── install_ubuntu.sh
├── pyaxie_utils.py
├── secret.yaml
├── README.md
├── slp_abi.json
├── pyaxie-bot.py
└── pyaxie.py
/datas/axie_list.yaml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/img/qr.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/qr.PNG
--------------------------------------------------------------------------------
/img/axies.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/axies.PNG
--------------------------------------------------------------------------------
/img/infos.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/infos.PNG
--------------------------------------------------------------------------------
/img/Pyaxie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/Pyaxie.png
--------------------------------------------------------------------------------
/img/all_mmr.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/all_mmr.PNG
--------------------------------------------------------------------------------
/img/all_rank.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/all_rank.PNG
--------------------------------------------------------------------------------
/img/transfer.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/transfer.PNG
--------------------------------------------------------------------------------
/img/all_axies.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/all_axies.PNG
--------------------------------------------------------------------------------
/img/axies/egg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/axies/egg.png
--------------------------------------------------------------------------------
/img/axies/you.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shraknard/pyaxie-bot/HEAD/img/axies/you.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.22.0
2 | qrcode==7.1
3 | web3==5.21.0
4 | mnemonic==0.20
5 | discord.py==1.7.3
6 | eth_account==0.5.4
7 | discord==1.7.3
8 | Pillow==8.3.2
9 | PyYAML==5.4.1
10 | quickchart==0.0.1
11 |
--------------------------------------------------------------------------------
/install_ubuntu.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "Installing the requirements ... "
3 |
4 | appending source list required for the application
5 | sudo echo "deb http://archive.ubuntu.com/ubuntu bionic main universe" >> /etc/apt/sources.list
6 | sudo echo "deb http://archive.ubuntu.com/ubuntu bionic-security main universe" >> /etc/apt/sources.list
7 | sudo echo "deb http://archive.ubuntu.com/ubuntu bionic-updates main universe" >> /etc/apt/sources.list
8 |
9 | echo "Updating source lists"
10 | sudo apt update
11 |
12 | echo "Installing python"
13 | sudo apt install -y python3-pip
14 | sudo apt-get install -y libcairo2-dev libgirepository1.0-dev
15 |
16 | echo "Updating requirements file"
17 | pw=$(pwd | tr -d '\r')
18 |
19 | cd $pw
20 | pip3 freeze > requirements.txt
21 |
22 | echo "Installing application requirements"
23 | pip3 install -r requirements.txt
24 | pip3 install discord
25 | pip3 install web3
26 |
27 | echo "Giving user permissions to the bot directory"
28 | sudo chown -R $name:$name $pw
29 |
30 | nohup python3 ./pyaxie-bot.py &
31 |
--------------------------------------------------------------------------------
/pyaxie_utils.py:
--------------------------------------------------------------------------------
1 | # uncompyle6 version 3.7.4
2 | # Python bytecode 3.8 (3413)
3 | # Decompiled from: Python 3.8.10 (default, Jun 2 2021, 10:49:15)
4 | # [GCC 9.4.0]
5 | # Embedded file name: /home/vi/pyaxie/pyaxie_utils.py
6 | # Compiled at: 2021-08-31 23:29:51
7 | # Size of source mod 2**32: 1305 bytes
8 | from mnemonic import Mnemonic
9 | from PIL import Image
10 | import string, random
11 |
12 | def gen_pass_phrase():
13 | mnemo = Mnemonic('english')
14 | return mnemo.generate(strength=128)
15 |
16 |
17 | def gen_password(n=0):
18 | """
19 | Generate a random password
20 | :param n:
21 | :return:
22 | """
23 | n = 20 if n <= 10 else n
24 | chars = string.ascii_lowercase + string.ascii_uppercase + string.digits + string.punctuation.replace(';', '')
25 | res = ''
26 | for i in range(n):
27 | res += random.choice(chars)
28 | else:
29 | return res
30 |
31 |
32 | def merge_images(path1, path2, path3, name):
33 | """
34 | Merge 3 axies pictures into one image in line
35 | :param path1: Path of the 1st axie image (usually './img/axies/AXIE-ID.jpg')
36 | :param path2: Path of the 2nd axie image
37 | :param path3: Path of the 3rd axie image
38 | :param name: Name of the scholar
39 | :return: Path of the freshly created image
40 | """
41 | img = Image.open(path1).convert('RGBA')
42 | img2 = Image.open(path2).convert('RGBA')
43 | img3 = Image.open(path3).convert('RGBA')
44 | dst = Image.new('RGB', (img.width * 3, img.height)).convert('RGBA')
45 | final = './img/axies/' + name + '.png'
46 | dst.paste(img, (0, 0))
47 | dst.paste(img2, (img.width, 0))
48 | dst.paste(img3, (img.width * 2, 0))
49 | dst.save(final)
50 | return final
51 | # okay decompiling pyaxie_utils.cpython-38.pyc
52 |
--------------------------------------------------------------------------------
/secret.yaml:
--------------------------------------------------------------------------------
1 | appName: "secret"
2 |
3 | discord_token: "THE DISCORD TOKEN OF YOUR BOT. Example : 0djzH879ODc2NzAw.ZDj876.HKHV9DHiZercwTQXyBCZKos5"
4 |
5 | url_api: "API for MMR, rank, infos, etc... Most of the functions will not work if you don't set it. You have to find it by yourself as it is forbidden to make it public in in Axie's ToS."
6 |
7 | paths:
8 | axie_list_path: "datas/axie_list.yaml"
9 | account_log_path: "datas/account_log.yaml"
10 | slp_track_path: "datas/slp_track.yaml"
11 |
12 | personal:
13 | ronin_address: "Manager ronin address. Make sure you replace the 'ronin:' part by '0x' exemple : 0x0000000000000000000000000000000001"
14 | private_key: "Manager private key without the '0x' example: alcsdfsdqhl65f5zu8iop4h8fakez4zh8e7a8sd1az48F8ds4gsd6g7qsdqsdz"
15 | discord_id: 12346269824881831
16 |
17 | scholars:
18 | EXAMPLE:
19 | ronin_address: "Scholar account ronin address. Remove 'ronin:' with '0x' Example : 0x0000000000000000000000000000000002"
20 | private_key: "Scholar account private key. Remove the '0x' part. Example : plqsdqhl65df5zu8iop48fakez4zh8ec7a8sd148F8ds4gsd6g78ezr8978f6745po3"
21 | discord_id: 123456789452669
22 | personal_ronin: "Scholar personnal ronin (for payout) example : 0x000000000000000000000000000000000000000002"
23 | payout_percentage: 0.6
24 | payout_description: "Value between 0 and 1. Represents the percentage the scholar will receive. The rest is sent to the academy."
25 | tips: "Delete the EXAMPLE before starting and copy/paste scholar1 block for each of your scholars"
26 | scholar1:
27 | ronin_address: "0x0000000000000000000000000000000002"
28 | private_key: "plqsdqhl65df5zu8iop48fakez4zh8ec7a8sd148F8ds4gsd6g78ezr8978f6745po3"
29 | discord_id: 123456789452669
30 | personal_ronin: "0x00000000000000000000000000000000003"
31 | payout_percentage: 0.6
32 | scholar2:
33 | ronin_address: "0x00000000000000000000000000000000004"
34 | private_key: "plqsdqhl65f5zu8iop48fakez4zh8e7a8sd1s48F8cds4gsd6g78ezr8978f6745po3"
35 | discord_id: 123456786971378652
36 | personal_ronin: "0x00000000000000000000000000000000005"
37 | payout_percentage: 0.6
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pyaxie bot
2 |
3 |
4 |
5 | ## THIS BOT IS NOT UPDATED / SUPPORTED ANYMORE
6 |
7 | ## Description
8 |
9 | A python discord bot to help you manage your **Axie Infinity** scholarships.
10 | This is based on my other Axie project : [Pyaxie](https://github.com/vmercadi/pyaxie) which is a python lib to interact with Axie world.
11 |
12 | If you don't have the API URL for Axies informations, some of the functionalities will not work.
13 | You will have to find this by yourself and then add it in secret.yaml as it is forbidden to make it public in Axie ToS.
14 |
15 | If you encounter a problem or have a question, you can join the discord server : https://discord.gg/gqaSv2PZbF
16 |
17 | ## [Follow this step-by-step guide to install and use the bot](https://github.com/vmercadi/pyaxie-bot/wiki)
18 |
19 | As I'm adding a bit every day, you'll have to update the project as much as you can (infos in the discord).
20 | Here is a command to easily update : `mv secret.yaml asecret.yaml; git pull; mv asecret.yaml secret.yaml`
21 | With this command, you'll not have to write the config in secret.yaml again
22 |
23 | ## BOT commands
24 |
25 | "**Commands for everybody :**"
26 |
27 | "`$infos` = Send all the infos about your account "
28 | "`$qr` = Send your QR code "
29 | "`$mmr` = Send your current MMR "
30 | "`$rank` = Send your current rank"
31 | "`$axies` = Send the list of axies of your account"
32 | "`$axies 506011891353903121` = Send axies list of given discord ID"
33 | "`$profile` = Send the link of your Axie Infinity profile"
34 | "`$all_profile` = Send a link of every Axie account in the scholarship"
35 | "`$self_payout` = To claim and payout for yourself. Send to the personal address you gave to your manager."
36 |
37 | "**Commands for manager :**"
38 |
39 | "`$claim 506011891353903121` = Claim for the given discord ID"
40 | "`$all_claim` = Claim for all the scholars"
41 | "`$all_mmr` = Send scholar list sorted by MMR "
42 | "`$all_rank` = Send scholar list sorted by rank "
43 | "`$payout` = Send the available SLP to manager and scholars "
44 | "`$payout me` = Send all scholarship SLP directly to manager account with no split"
45 | "`$transfer 0xfrom_address 0xto_address amount` = Transfer amount SLP from from_address to to_address"
46 | "`$breed_infos` = How much does it cost to breed now. You can also specify a breed lvl (0-6)"
47 | "`$breed_cost 123456` = How much did you spent breeding an axie. (take AXS/SLP prices from time of breed)"
48 | "`$account_balance ronin_address` = Balance of specified account"
49 | "`$all_account_balance` = Balance of all the accounts in the scholarship"
50 | "`$all_address` = Get all the addresses in the scholarship"
51 |
52 | ## Donation
53 |
54 | Thanks to [ZracheSs](https://github.com/ZracheSs-xyZ) for his Payout and QR script code.
55 | And thanks to Andy for his help with debug !
56 |
57 | **My ronin address :** ronin:f561bb92d33e4feaae617a182264a4c7d7272948
58 | **My ethereum address :** 0x60900b1740E9e164137Db9b982d9681A2E74446c
59 |
60 | **ZracheSs ronin address :** ronin:a04d88fbd1cf579a83741ad6cd51748a4e2e2b6a
61 | **ZracheSs ethereum address :** 0x3C133521589daFa7213E5c98C74464a9577bEE01
62 |
63 | ## Examples
64 |
65 | 
66 | 
67 | 
68 | 
69 | 
70 | 
71 |
72 |
73 |
--------------------------------------------------------------------------------
/slp_abi.json:
--------------------------------------------------------------------------------
1 | [{ "inputs": [{ "internalType": "uint256", "name": "_dailyPotionLimit", "type": "uint256" }], "payable": false , "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "_oldAdmin", "type": "address" }, { "indexed": true, "internalType": "address", "name": "_newAdmin", "type": "address" }], "name": "AdminChanged", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "_oldAdmin", "type": "address" }], "name": "AdminRemoved", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "_owner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "_spender", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "_owner", "type": "address" }, { "indexed": true, "internalType": "uint256", "name": "_amount", "type": "uint256" }], "name": "BreedingPotionCheckpoint", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "_minter", "type": "address" }], "name": "MinterAdded", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "_minter", "type": "address" }], "name": "MinterRemoved", "type": "event" }, { "anonymous": false, "inputs": [], "name": "Paused", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "_spender", "type": "address" }], "name": "SpenderUnwhitelisted", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "_spender", "type": "address" }], "name": "SpenderWhitelisted", "type": "event" }, { "anonymous": false, "inputs": [{ "indexed": true, "internalType": "address", "name": "_from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "_to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "Transfer", "type": "event" }, { "anonymous": false, "inputs": [], "name": "Unpaused", "type": "event" }, { "constant": false, "inputs": [{ "internalType": "address[]", "name": "_addedMinters", "type": "address[]" }], "name": "addMinters", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "admin", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [{ "internalType": "address", "name": "_owner", "type": "address" }, { "internalType": "address", "name": "_spender", "type": "address" }], "name": "allowance", "outputs": [{ "internalType": "uint256", "name": "_value", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address", "name": "_spender", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "approve", "outputs": [{ "internalType": "bool", "name": "_success", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "contract IERC20Receiver", "name": "_spender", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }, { "internalType": "bytes", "name": "_data", "type": "bytes" }], "name": "approveAndCall", "outputs": [{ "internalType": "bool", "name": "_success", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "contract IERC20PayableReceiver", "name": "_spender", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }, { "internalType": "bytes", "name": "_data", "type": "bytes" }], "name": "approveAndCallPayable", "outputs": [{ "internalType": "bool", "name": "_success", "type": "bool" }], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": true, "inputs": [{ "internalType": "address", "name": "", "type": "address" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "burn", "outputs": [{ "internalType": "bool", "name": "_success", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address", "name": "_from", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "burnFrom", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "cappedSupply", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address", "name": "_newAdmin", "type": "address" }], "name": "changeAdmin", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address", "name": "_owner", "type": "address" }, { "internalType": "uint256", "name": "_amount", "type": "uint256" }, { "internalType": "uint256", "name": "_createdAt", "type": "uint256" }, { "internalType": "bytes", "name": "_signature", "type": "bytes" }], "name": "checkpoint", "outputs": [{ "internalType": "uint256", "name": "_balance", "type": "uint256" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "uint256", "name": "_amount", "type": "uint256" }, { "internalType": "uint256", "name": "_createdAt", "type": "uint256" }, { "internalType": "bytes", "name": "_signature", "type": "bytes" }, { "internalType": "contract IERC20PayableReceiver", "name": "_spender", "type": "address" }, { "internalType": "uint256", "name": "_approvedAmount", "type": "uint256" }, { "internalType": "bytes", "name": "_data", "type": "bytes" }], "name": "checkpointAndCall", "outputs": [{ "internalType": "uint256", "name": "_balance", "type": "uint256" }], "payable": true, "stateMutability": "payable", "type": "function" }, { "constant": true, "inputs": [], "name": "dailyPotionLimit", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [{ "internalType": "address", "name": "_owner", "type": "address" }], "name": "getCheckpoint", "outputs": [{ "internalType": "uint256", "name": "_amount", "type": "uint256" }, { "internalType": "uint256", "name": "_createdAt", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [{ "internalType": "address", "name": "_addr", "type": "address" }], "name": "isMinter", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address", "name": "_to", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "mint", "outputs": [{ "internalType": "bool", "name": "_success", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [{ "internalType": "address", "name": "", "type": "address" }], "name": "minter", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "name": "minters", "outputs": [{ "internalType": "address", "name": "", "type": "address" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "name", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "pause", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "paused", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [], "name": "removeAdmin", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address[]", "name": "_removedMinters", "type": "address[]" }], "name": "removeMinters", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "uint256", "name": "_dailyPotionLimit", "type": "uint256" }], "name": "setDailyPotionLimit", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address", "name": "_to", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "transfer", "outputs": [{ "internalType": "bool", "name": "_success", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address", "name": "_from", "type": "address" }, { "internalType": "address", "name": "_to", "type": "address" }, { "internalType": "uint256", "name": "_value", "type": "uint256" }], "name": "transferFrom", "outputs": [{ "internalType": "bool", "name": "_success", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [], "name": "unpause", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address", "name": "_spender", "type": "address" }], "name": "unwhitelist", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": false, "inputs": [{ "internalType": "address", "name": "_spender", "type": "address" }], "name": "whitelist", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [{ "internalType": "address", "name": "", "type": "address" }], "name": "whitelisted", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "payable": false, "stateMutability": "view", "type": "function" }]
--------------------------------------------------------------------------------
/pyaxie-bot.py:
--------------------------------------------------------------------------------
1 | import discord
2 | import yaml
3 | import os
4 | import sys
5 | import time
6 | import json
7 | import requests
8 |
9 | from web3 import Web3
10 | from datetime import datetime
11 | from pyaxie import pyaxie
12 | from datetime import timedelta
13 | from pprint import pprint
14 |
15 | now = datetime.now()
16 | client = discord.Client(intents=discord.Intents.all())
17 | current_time = now.strftime("%d/%m/%Y %H:%M:%S")
18 |
19 |
20 | def create_info_message(pyax):
21 | """
22 | Create a message for the SLP infos (bot command : $wen)
23 | :param pyax: a pyaxie object with scholar informations
24 | :return: The response with the infos
25 | """
26 | balance = pyax.get_claimed_slp()
27 | last_claim = pyax.get_last_claim()
28 |
29 | try:
30 | if datetime.utcnow() + timedelta(days=-14) < datetime.fromtimestamp(last_claim):
31 | wait = datetime.fromtimestamp(last_claim) - (datetime.utcnow() + timedelta(days=-14))
32 | mod = wait.seconds
33 | hour = str(int(mod // 3600))
34 | mod %= 3600
35 | m = str(int(mod // 60))
36 | mod %= 60
37 | s = str(int(mod))
38 | response = "❌ **NOT ABLE TO CLAIM** : **" + str(wait.days) + " day(s) " + hour + " hour(s) " + m + " min " + s + " sec\n**"
39 | else:
40 | response = "✅ **ABLE TO CLAIM**\n"
41 |
42 | perc = 1 if pyax.ronin_address == config['personal']['ronin_address'] else pyax.payout_percentage
43 |
44 | unclaimed = pyax.get_unclaimed_slp()
45 | total = int(balance + unclaimed)
46 | response += "\nBalance : **" + str(balance) + " SLP**" + \
47 | "\nUnclaimed : **" + str(unclaimed) + " SLP**" + \
48 | "\nAfter we split, you'll have : **" + str(int(total * perc)) + " SLP** or **" + str(int((total * pyax.get_price('slp') * perc))) + "$**" + \
49 | "\nApproximate daily ratio : **" + str(pyax.get_daily_slp()) + " SLP**\n---"
50 | except ValueError as e:
51 | return "Error creating message : " + str(e)
52 | return response
53 |
54 |
55 | def get_account_from_id(id):
56 | """
57 | Get a pyaxie object depending on config
58 | :param id: discord_id
59 | :return: Scholar account or Manager account or None
60 | """
61 | scholar = None
62 | id = int(id)
63 | if id == config['personal']['discord_id']:
64 | scholar = pyaxie(config['personal']['ronin_address'], config['personal']['private_key'])
65 | else:
66 | for sch in config['scholars']:
67 | if config['scholars'][sch]['discord_id'] == id:
68 | scholar = pyaxie(config['scholars'][sch]['ronin_address'], config['scholars'][sch]['private_key'])
69 | scholar.name = str(client.get_user(config['scholars'][sch]['discord_id'])).split('#', -1)[0]
70 | break
71 | return scholar
72 |
73 |
74 | def get_account_from_ronin(ronin_address):
75 | """
76 | Get a pyaxie object depending on config
77 | :param id: ronin address
78 | :return: Scholar account or Manager account or None
79 | """
80 | scholar = None
81 | if ronin_address == config['personal']['ronin_address']:
82 | scholar = pyaxie(config['personal']['ronin_address'], config['personal']['private_key'])
83 | else:
84 | for sch in config['scholars']:
85 | if config['scholars'][sch]['ronin_address'] == ronin_address:
86 | scholar = pyaxie(config['scholars'][sch]['ronin_address'], config['scholars'][sch]['private_key'])
87 | scholar.name = str(client.get_user(config['scholars'][sch]['discord_id'])).split('#', -1)[0]
88 | break
89 | return scholar
90 |
91 |
92 | def log(message="", end="\n"):
93 | pprint(message + end)
94 | f = open('pyaxie.log', "a")
95 | og = sys.stdout
96 | print(message, end=end, flush=True)
97 | sys.stdout = f
98 | print(message, end=end)
99 | f.flush()
100 | f.close()
101 | sys.stdout = og
102 |
103 |
104 | @client.event
105 | async def on_ready():
106 | print('\nWe are logged in as {0.user}'.format(client))
107 |
108 |
109 | @client.event
110 | async def on_message(message):
111 | if message.author == client.user or len(message.content) < 2 or message.content[0] != '$':
112 | return
113 |
114 | with open("secret.yaml", "r") as file:
115 | config = yaml.safe_load(file)
116 |
117 | scholar = get_account_from_id(message.author.id)
118 | if scholar is None:
119 | print("\nNon scholar tried to use the bot : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
120 | return await message.channel.send("You are not part of the scholarship. Check with your manager to be added to the bot.")
121 |
122 | ##############################
123 | # Send the list of commands #
124 | ##############################
125 | if message.content == "$help":
126 | await message.channel.send("\n\n**Commands for everybody :**\n" +
127 | "\n`$infos` = Send all the infos about your account " +
128 | "\n`$qr` = Send your QR code " +
129 | "\n`$mmr` = Send your current MMR " +
130 | "\n`$rank` = Send your current rank" +
131 | "\n`$axies` = Send the list of axies of your account" +
132 | "\n`$axies 506011891353903121` = Send axies list of given discord ID" +
133 | "\n`$profile` = Send the link of your Axie Infinity profile" +
134 | "\n`$all_profile` = Send a link of every Axie account in the scholarship" +
135 | "\n`$self_payout` = To claim and payout for yourself. Send to the personal address you gave to your manager." +
136 |
137 | "\n\n**Commands for manager :**\n" +
138 | "\n`$claim 506011891353903121` = Claim for the given discord ID (Manager only) " +
139 | "\n`$all_claim` = Claim for all the scholars (Manager only) " +
140 | "\n`$all_mmr` = Send scholar list sorted by MMR " +
141 | "\n`$all_rank` = Send scholar list sorted by rank " +
142 | "\n`$payout` = Send the available SLP to manager and scholars " +
143 | "\n`$payout me` = Send all scholarship SLP directly to manager account with no split" +
144 | "\n`$transfer 0xfrom_address 0xto_address amount` = Transfer amount SLP from from_address to to_address" +
145 | "\n`$breed_infos` = How much does it cost to breed now. You can also specify a breed lvl (0-6)" +
146 | "\n`$breed_cost 123456` = How much did you spent breeding an axie. (take AXS/SLP prices from time of breed)" +
147 | "\n`$account_balance ronin_address` = Balance of specified account" +
148 | "\n`$all_account_balance` = Balance of all the accounts in the scholarship" +
149 | "\n`$all_address` = Get all the addresses in the scholarship")
150 |
151 |
152 | return
153 |
154 | ##############################
155 | # Send a QR code #
156 | ##############################
157 | if message.content == "$qr":
158 | print("\nGet QR code for : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
159 | try:
160 | qr_path = scholar.get_qr_code()
161 | await message.author.send("\nHello " + message.author.name + " ! 😃 \nHere is your new QR Code to login : ")
162 | await message.author.send(file=discord.File(qr_path))
163 | os.remove(qr_path)
164 | except ValueError as e:
165 | await message.channel.send("Error getting QR code : " + str(e))
166 | return
167 |
168 | ##############################
169 | # Get MMR of author #
170 | ##############################
171 | if message.content == "$mmr":
172 | if config['url_api'] == '':
173 | return await message.channel.send("No api_url set in secret.yaml. You have to FIND and add it by yourself as it is private and I can't make it public.")
174 |
175 | print("\nGet MMR for : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
176 | try:
177 | await message.channel.send("\nHello " + message.author.name + " !\n"
178 | "Your MMR is : {} 🥇".format(scholar.get_rank_mmr()['mmr']))
179 | except ValueError as e:
180 | await message.channel.send("Error getting MMR : " + str(e))
181 | return
182 |
183 | ##############################
184 | # Get rank of author #
185 | ##############################
186 | if message.content == "$rank":
187 | if config['url_api'] == '':
188 | return await message.channel.send("No api_url set in secret.yaml. You have to FIND and add it by yourself as it is private and I can't make it public.")
189 |
190 | print("\nGet rank for : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
191 | try:
192 | await message.channel.send("\nHello " + message.author.name + " !\n"
193 | "Your rank is : {} 🎖️".format(scholar.get_rank_mmr()['rank']))
194 | except ValueError as e:
195 | await message.channel.send("Error getting rank : " + str(e))
196 | return
197 |
198 | ##################################
199 | # Get all infos about the author #
200 | ##################################
201 | if "$infos" in message.content:
202 | if config['url_api'] == '':
203 | return await message.channel.send("No api_url set in secret.yaml. You have to FIND and add it by yourself as it is private and I can't make it public.")
204 | if "$infos " in message.content:
205 | try:
206 | id = message.content.split(" ")[1]
207 | if id.isnumeric():
208 | scholar = get_account_from_id(id)
209 | else:
210 | return await message.channel.send("Error in ID. Example: $infos 496061891353903121")
211 | except ValueError as e:
212 | await message.channel.send("Error : " + str(e))
213 | return e
214 |
215 | if scholar is None:
216 | return await message.channel.send("Error: No scholar found with this ID")
217 |
218 | rank_mmr = scholar.get_rank_mmr()
219 | print("\nGet infos for : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
220 | try:
221 | imgline = scholar.get_axies_imageline()
222 | await message.channel.send("\nHere are the infos for **" + scholar.name + "** account [**" + scholar.ronin_address + "**] \n" +
223 | "NB of axies : **" + str(scholar.get_number_of_axies()) + "**\n---\n" +
224 | "Claim status : {}\n".format(create_info_message(scholar).replace("\n", "", 1)) +
225 | "MMR : **{}** 🥇\n".format(rank_mmr['mmr']) +
226 | "Rank : **{}** 🎖️".format(rank_mmr['rank']), file=discord.File(imgline))
227 | except ValueError as e:
228 | await message.channel.send("Error getting infos : " + str(e))
229 | return
230 |
231 | ###################################
232 | # Get MMR/rank for all scholars #
233 | ###################################
234 | if "$all_mmr" in message.content or "$all_rank" in message.content:
235 | if config['url_api'] == '':
236 | return await message.channel.send("No api_url set in secret.yaml. You have to FIND and add it by yourself as it is private and I can't make it public.")
237 | if message.author.id != config['personal']['discord_id']:
238 | return await message.channel.send("This command is only available for manager")
239 |
240 | print("\nGet all MMR/RANK, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
241 | await message.channel.send("🥇 Getting MMR/RANK for all scholars, this can take some time...")
242 | try:
243 | rm = message.content.split('_')[1]
244 | rm = rm.split(' ')[0] if ' ' in rm else rm
245 | l = list()
246 |
247 | for account in config['scholars']:
248 | rank_mmr = scholar.get_rank_mmr(config['scholars'][account]['ronin_address'])
249 | rank_mmr['name'] = str(client.get_user(config['scholars'][account]['discord_id'])).split('#', -1)[0]
250 | l.append(rank_mmr)
251 |
252 | mmrs = reversed(sorted(l, key=lambda k: k[rm]))
253 | nb = message.content.split(' ')[1] if ' ' in message.content else 99999
254 | if nb != 99999 and nb.isdigit():
255 | nb = int(nb)
256 |
257 | msg = ""
258 | i = 1
259 | for m in mmrs:
260 | if i > nb:
261 | break
262 | msg += "\nTop [**{}**] : **{}** | {}".format(i, m[rm], m['name'])
263 | if i % 20 == 0:
264 | await message.channel.send(msg)
265 | msg = ""
266 | i += 1
267 |
268 | except ValueError as e:
269 | return await message.channel.send("Error getting MMRs : " + str(e))
270 | return await message.channel.send(msg)
271 |
272 |
273 | ##################################
274 | # Claim for the current scholars #
275 | ##################################
276 | if "$claim" in message.content:
277 | if config['url_api'] == '':
278 | return await message.channel.send("No api_url set in secret.yaml. You have to FIND and add it by yourself as it is private and I can't make it public.")
279 |
280 | print("\nClaim, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
281 | try:
282 | if message.content == "$claim":
283 | return await message.channel.send("You have to specify the discord ID of the scholar you want to claim for.\n" +
284 | "Example: `$claim 506011891353903121`")
285 |
286 | to_id = message.content.split(' ')[1]
287 | scholar = get_account_from_id(to_id)
288 | if scholar is None:
289 | return await message.channel.send("The Discord ID you specified is not in the scholarship.\n")
290 |
291 | amount = scholar.get_unclaimed_slp()
292 | if amount > 0:
293 | await message.channel.send("{} SLP claimed for {} !\nTransaction hash of the claim : {} ".format(amount, scholar.name, str(scholar.claim_slp())))
294 | else:
295 | await message.channel.send("No SLP to claim for {} at this moment !\n".format(message.author.name))
296 |
297 | except ValueError as e:
298 | await message.channel.send("Error while claiming : " + str(e))
299 | return
300 |
301 | ##############################
302 | # Claim for all scholars #
303 | ##############################
304 | if message.content == "$all_claim":
305 | print("\nAll claim, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
306 | if message.author.id != config['personal']['discord_id']:
307 | return await message.channel.send("This command is only available for manager")
308 |
309 | await message.channel.send("\nClaiming for all scholars... This can take some time.\n")
310 |
311 | try:
312 | l = list()
313 | for account in config['scholars']:
314 | scholar = pyaxie(config['scholars'][account]['ronin_address'], config['scholars'][account]['private_key'])
315 | amount = scholar.get_unclaimed_slp()
316 | if datetime.utcnow() + timedelta(days=-14) < datetime.fromtimestamp(scholar.get_last_claim()) or amount < 0:
317 | l.append("**No SLP to claim for {} at this moment** \n".format(scholar.name))
318 | else:
319 | l.append("**{} SLP claimed for {} !** Transaction hash : {} \n".format(amount, scholar.name, str(scholar.claim_slp())))
320 | except ValueError as e:
321 | return await message.channel.send("Error getting QR code : " + str(e))
322 | return await message.channel.send("--------\n".join(l))
323 |
324 | ##############################
325 | # Payout for all scholars #
326 | ##############################
327 | if "payout" in message.content:
328 | print("\nPayout, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
329 | # Self payout for scholars
330 | if "$self_payout" in message.content:
331 | await message.channel.send("\nSelf payout for **" + message.author.name + "**. Please wait...\n")
332 | if not ' ' in message.content:
333 | to_address = scholar.personal_ronin
334 | else:
335 | to_address = message.content.split(' ')[1].replace('ronin:', '0x')
336 | if not Web3.isAddress(to_address):
337 | return await message.channel.send("\nError in the address, make sure you try to send to the right one.\n")
338 |
339 | if to_address == scholar.ronin_address:
340 | return await message.channel.send("Your from_address and to_address are the same.")
341 |
342 | tx = scholar.payout()
343 | claimed = scholar.get_claimed_slp()
344 | msg = "Sent **{} SLP**\nFrom : **{}**\nTo : **{}**\nTransaction : \n".format(claimed * (1 - scholar.payout_percentage), scholar.ronin_address, config['personal']['ronin_address'], tx[0])
345 | msg += "-----\nSent **{} SLP**\nFrom : **{}**\nTo : **{}**\nTansaction : \n".format(claimed * scholar.payout_percentage, scholar.ronin_address, to_address, tx[1])
346 | return await message.channel.send(msg)
347 |
348 | # Payout for all scholars
349 | if message.content == "$payout":
350 | await message.channel.send("\nPayout for all scholar ! This can take some time.\n")
351 | if message.author.id != config['personal']['discord_id']:
352 | return await message.channel.send("This command is only available for manager")
353 | try:
354 | for account in config['scholars']:
355 | scholar = pyaxie(config['scholars'][account]['ronin_address'], config['scholars'][account]['private_key'])
356 | unclaimed = scholar.get_unclaimed_slp()
357 | claimed = scholar.get_claimed_slp()
358 |
359 | if datetime.utcnow() + timedelta(days=-14) < datetime.fromtimestamp(scholar.get_last_claim()) or unclaimed <= 0:
360 | await message.channel.send("**No SLP to claim for {} at this moment** \n".format(scholar.name))
361 |
362 | if claimed <= 0:
363 | await message.channel.send("**No SLP to send for {} account.**\n".format(scholar.name))
364 | elif "me" in message.content:
365 | tx = scholar.transfer_slp(config['personal']['ronin_address'], claimed)
366 | await message.channel.send("**All the {} SLP are sent to you !**\n Transaction : \n".format(claimed, str(tx)))
367 | else:
368 | res = scholar.payout()
369 | msg = "Sent **{} SLP**\nFrom : **{}**\nTo : **{}**\nTransaction : \n".format(claimed * (1 - scholar.payout_percentage), scholar.ronin_address, config['personal']['ronin_address'], res[0])
370 | msg += "-----\nSent **{} SLP**\nFrom : **{}**\nTo : **{}**\nTransaction : \n".format(claimed * scholar.payout_percentage, scholar.ronin_address, scholar.personal_ronin, res[1])
371 | await message.channel.send(msg)
372 |
373 | await message.channel.send("\n-------------\n")
374 | await message.channel.send("\n\n--- END OF PAYOUT ---")
375 | except ValueError as e:
376 | await message.channel.send("Error while paying out : " + str(e))
377 | return
378 |
379 | ##############################################
380 | # Transfer SLP from an account to another #
381 | ##############################################
382 | if "$transfer" in message.content and " " in message.content:
383 | print("\nTransfer, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
384 | if message.author.id != config['personal']['discord_id']:
385 | return await message.channel.send("This command is only available for manager")
386 |
387 | cmd = message.content.split(' ')
388 | if ("0x" not in cmd[1] and not cmd[1].isnumeric()) or ("0x" not in cmd[2] and not cmd[2].isnumeric()) or not cmd[3].isnumeric():
389 | return await message.channel.send("Error in the command. Should look like this : $transfer 0xfrom_address 0xto_address 100")
390 |
391 | try:
392 | scholar = get_account_from_id(cmd[1]) if cmd[0] == "$transfer_id" else get_account_from_ronin(cmd[1])
393 | scholar2 = get_account_from_id(cmd[2]) if cmd[0] == "$transfer_id" else get_account_from_ronin(cmd[2])
394 | if scholar is None:
395 | return await message.channel.send("The from address or discord ID that you specified is not in the scholarship.")
396 |
397 | if scholar2 is None and cmd[0] != "$transfer_id":
398 | ronin_address = cmd[2]
399 | else:
400 | ronin_address = scholar2.ronin_address
401 |
402 | try:
403 | tx = scholar.transfer_slp(ronin_address, int(cmd[3]))
404 | except ValueError as e:
405 | return e
406 | await message.channel.send("Sent **{} SLP**\nFrom : ** {}\n**To : ** {}** \nTransaction : \n".format(cmd[3], cmd[1], ronin_address, tx))
407 | except ValueError as e:
408 | await message.channel.send("Error while transfering SLP : " + str(e))
409 | return
410 |
411 | ##############################################
412 | # Get list of axie of the account #
413 | ##############################################
414 | if "$axies" in message.content:
415 | print("\nAxie list, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
416 | if message.content == "$axies":
417 | scholar = get_account_from_id(message.author.id)
418 | elif "$axies " in message.content:
419 | try:
420 | id = message.content.split(" ")[1]
421 | if id.isnumeric():
422 | scholar = get_account_from_id(id)
423 | else:
424 | return await message.channel.send("Error in ID. Example: $axies 496061891353903121")
425 | except ValueError as e:
426 | await message.channel.send("Error : " + str(e))
427 | return e
428 |
429 | if scholar is None:
430 | return await message.channel.send("Error: No scholar found with this ID")
431 |
432 | try:
433 | axies = scholar.get_axie_list()
434 | await message.channel.send("\nHere is the axie list for " + scholar.name + " account :\n")
435 | for axie in axies:
436 | await message.channel.send(scholar.axie_link(int(axie['id'])))
437 | await message.channel.send(file=discord.File(scholar.download_axie_image(int(axie['id']))))
438 | except ValueError as e:
439 | await message.channel.send("Error while getting axies : " + str(e))
440 | return
441 | elif "$all_axies" in message.content:
442 | if "$all_axies " in message.content:
443 | axie_class = message.content.split(' ')[1]
444 | if axie_class.lower() in ["reptile", "plant", "dusk", "aquatic", "bird", "dawn", "beast", "bug"]:
445 | axies = scholar.get_all_axie_class(axie_class)
446 | else:
447 | return await message.channel.send(axie_class + " is not a class. Class list : Reptile, Plant, Dusk, Aquatic, Bird, Dawn, Beast, Bug ")
448 |
449 | print("\nListing of all " + axie_class + " axies in the scholarship, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
450 | await message.channel.send("Getting list of all the " + axie_class + " axies in the scholarship ! This can take some time.\n")
451 | for axie in axies:
452 | await message.channel.send("\n" + scholar.axie_link(int(axie['id'])) + "\n")
453 | await message.channel.send(file=discord.File(scholar.download_axie_image(int(axie['id']))))
454 | await message.channel.send("\n----------- END OF AXIES LIST ----------")
455 | else:
456 | print("\nListing of all axies in the scholarship, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
457 | await message.channel.send("Getting list of all the axies in the scholarship ! This can take some time.\n")
458 | try:
459 | axies = scholar.get_all_axie_list()
460 | for axie in axies:
461 | await message.channel.send("\n" + scholar.axie_link(int(axie['id'])) + "\n")
462 | await message.channel.send(file=discord.File(scholar.download_axie_image(int(axie['id']))))
463 | await message.channel.send("\n----------- END OF AXIES LIST ----------")
464 | except ValueError as e:
465 | await message.channel.send("Error while getting axies : " + str(e))
466 | return
467 |
468 | #################################################
469 | # Get a graph for burned/minted SLPs #
470 | #################################################
471 | if message.content == "$graph":
472 | print("\nGraph, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
473 | try:
474 | path = scholar.get_mint_burn_graph()
475 | discord.File(path, filename=path)
476 | embed = discord.Embed(title="SLP data", description=" \n Usefull SLP data like prices and minted vs burned SLP.", color=0xf5f542)
477 | total = json.loads(requests.get("token/0xa8754b9fa15fc18bb59458815510e40a12cd2014").content)
478 |
479 | embed.add_field(name="Total SLP supply", value=total, inline=False)
480 | embed.add_field(name="SLP/USD", value=scholar.get_price("usd"), inline=True)
481 | embed.add_field(name="SLP/EUR", value=scholar.get_price("eur"), inline=True)
482 | embed.add_field(name="SLP/PHP", value=scholar.get_price("php"), inline=True)
483 | embed.set_image(url="attachment://" + path)
484 | await message.channel.send(embed)
485 | except ValueError as e:
486 | await message.channel.send("Error while getting burned/minted SLP graph : " + str(e))
487 | return
488 |
489 | ################################################
490 | # Breed prices infos #
491 | ################################################
492 | if "$breed_infos" in message.content:
493 | print("\nBreed_infos, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
494 | if message.author.id != config['personal']['discord_id']:
495 | return await message.channel.send("This command is only available for manager")
496 | costs = scholar.get_breed_cost()
497 | if " " in message.content:
498 | nb = message.content.split(" ")[1]
499 | if not nb.isdigit():
500 | return await message.channel.send("Error breed level should be between **0** and 6. Example : `$breed_cost 2`")
501 |
502 | nb = int(nb)
503 | if nb > 0:
504 | msg = "Breeding cost infos for breed level **{}**. **AXS** : **${}** -|- **SLP** : **${}**\n\n".format(nb, scholar.get_price('axs'),scholar.get_price('slp'))
505 | msg += "Breed price : **${}**\nAverage price from breed **0** to **{}** : **${}**\n".format(costs[nb]['price'], nb, costs[nb]['average_price'])
506 | msg += "Total price for **{}** breed : **${}**".format(nb, costs[nb]['total_breed_price'])
507 | return await message.channel.send(msg)
508 |
509 | else:
510 | msg = list()
511 | nb = 0
512 | for c in costs:
513 | msg.append("**Breed level {}**\nBreed price : **${}**\nAverage price from breed **0** to **{}** : **${}**\nTotal price for **{}** breed : **${}**".format(
514 | nb, costs[c]['price'], nb, costs[c]['average_price'], nb, costs[c]['total_breed_price']))
515 | nb += 1
516 | return await message.channel.send("Breeding cost for all breed levels :\n\n" + '\n-----\n'.join(msg))
517 |
518 | ################################################
519 | # GEt the price an axie cost to breed #
520 | ################################################
521 | if "$breed_cost " in message.content:
522 | print("\nBreed_costs, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
523 | if message.author.id != config['personal']['discord_id']:
524 | return await message.channel.send("This command is only available for manager")
525 | if " " in message.content:
526 | id = message.content.split(" ")[1]
527 | if not id.isdigit():
528 | return await message.channel.send("Error in axie ID. Exampe: `$breed_costs 123456`")
529 | id = int(id)
530 | costs = scholar.get_axie_total_breed_cost(id)
531 |
532 | m = "You spent **${}** while breeding axie number **{}**.\nThe average cost for 1 breed is : **${}**.\n\n".format(costs['total_breed_cost'], id, costs['average_breed_cost'])
533 | m += "**CHILDREN**\n-------------\n"
534 | nb = 0
535 | for a in costs['details']:
536 | m += "Child : https://marketplace.axieinfinity.com/axie/{}. Cost **${}**.\n".format(str(a['axie_id']), str(a['breed_cost']))
537 | m += "SLP cost : **$" + str(a['slp_cost']) + "** | SLP price was : **$" + str(a['slp_price']) + "**\n"
538 | m += "AXS cost : **$" + str(a['axs_cost']) + "** | AXS price was : **$" + str(a['axs_price']) + "**\n-----\n"
539 | nb += 1
540 | return await message.channel.send(m)
541 |
542 | ################################################
543 | # Get account balance #
544 | ################################################
545 | if "account_balance" in message.content:
546 | if message.author.id != config['personal']['discord_id']:
547 | return await message.channel.send("This command is only available for manager")
548 | print("\nAccount balance, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
549 | await message.channel.send("\nGetting account balance. This cna take some time.\n")
550 | if message.content == '$account_balance':
551 | datas = scholar.get_account_balances(config['personal']['ronin_address'])
552 | msg = "Balances for account **{}**\n".format(datas['ronin_address'])
553 | msg += "WETH : **{}** | AXS : **{}** | SLP : **{}** | Axies : **{}**\n".format(datas['WETH'], datas['AXS'], datas['SLP'], datas['axies'])
554 | return await message.channel.send(msg)
555 | elif "$account_balance " in message.content:
556 | if not ' ' in message.content:
557 | ronin_address = config['personal']['ronin_address']
558 | else:
559 | ronin_address = message.content.split(' ')[1].replace('ronin:', '0x')
560 | if not Web3.isAddress(ronin_address):
561 | return await message.channel.send("\nError in the address.\n")
562 |
563 | datas = scholar.get_account_balances(ronin_address)
564 | msg = "Balances for account **{}**\n".format(datas['ronin_address'])
565 | msg += "WETH : **{}** | AXS : **{}** | SLP : **{}** | Axies : **{}**\n".format(datas['WETH'], datas['AXS'], datas['SLP'], datas['axies'])
566 | return await message.channel.send(msg)
567 | elif '$all' in message.content:
568 | datas = scholar.get_all_accounts_balances()
569 | msg = ""
570 | total_slp = 0
571 | total_axs = 0
572 | total_axies = 0
573 | total_weth = 0
574 | for data in datas:
575 | msg += "Balances for account **{}**\n".format(data['ronin_address'])
576 | msg += "WETH : **{}** | AXS : **{}** | SLP : **{}** | Axies : **{}**\n".format(data['WETH'], data['AXS'], data['SLP'], data['axies'])
577 | msg += "-----\n"
578 | total_slp += data['SLP']
579 | total_axs += data['AXS']
580 | total_axies += data['axies']
581 | total_weth += data['WETH']
582 | s = "Balances for all infos in the scholarship.\nTotal WETH : **{}** | Total AXS : **{}** | Total SLP : **{}** | Total Axies : **{}**\n\n".format(total_weth, total_axs, total_slp, total_axies)
583 | return await message.channel.send(s + msg)
584 |
585 |
586 |
587 | ################################################
588 | # Get all the ronin_address in the scholarship #
589 | ################################################
590 | if message.content == "$all_address":
591 | if message.author.id != config['personal']['discord_id']:
592 | return await message.channel.send("This command is only available for manager")
593 | print("\nall_address, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
594 | l = list()
595 | i = 0
596 | await message.channel.send("\n Here is the list of address :\n")
597 | l.append('**You** : ' + config['personal']['ronin_address'])
598 | for scholar in config['scholars']:
599 | l.append("**" + scholar + "** : " + config['scholars'][scholar]['ronin_address'])
600 | if i == 20:
601 | await message.channel.send('\n'.join(l))
602 | l = list()
603 | i = 0
604 | i += 1
605 | if len(l) > 0:
606 | await message.channel.send('\n'.join(l))
607 | return
608 |
609 | #################################################
610 | # Get profiles links #
611 | #################################################
612 | if "profile" in message.content:
613 | print("\nProfile, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
614 | url = "https://marketplace.axieinfinity.com/profile/ronin:"
615 | if message.content == "$all_profiles":
616 | try:
617 | l = list()
618 | for account in config['scholars']:
619 | address = config['scholars'][account]['ronin_address']
620 | l.append(account + " : " + url + address.replace('0x', '') + "/axie")
621 | await message.channel.send("\n".join(l) + "\n-----------\n")
622 | except ValueError as e:
623 | await message.channel.send("Error while getting profile : " + str(e))
624 | return e
625 | return
626 |
627 | elif message.content == "$profile":
628 | return await message.channel.send("Here is the link for your profile **" + message.author.name + "** : " + url + scholar.ronin_address.replace('0x', '') + "/axie")
629 |
630 | elif " " in message.content:
631 | try:
632 | id = message.content.split(" ")[1]
633 | if id.isnumeric():
634 | scholar = get_account_from_id(id)
635 | else:
636 | return await message.channel.send("Error in discord ID. Example: $profile 496061891353903121")
637 | except ValueError as e:
638 | await message.channel.send("Error : " + str(e))
639 | return e
640 |
641 | if scholar is None:
642 | return await message.channel.send("Error: No scholar found with this ID")
643 | await message.channel.send("Here is the link for " + scholar.name + " profile : " + url + scholar.ronin_address.replace('0x', 'ronin:') + "/axie")
644 | return
645 |
646 | #################################################
647 | # Rename an axie #
648 | #################################################
649 | if "$rename" in message.content:
650 | print("\nRename, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
651 | if message.author.id != config['personal']['discord_id']:
652 | await message.channel.send("This command is for managers only.")
653 | return
654 | if "$rename_axie " in message.content:
655 | try:
656 | id = message.content.split(" ")[1]
657 | if id.isdigit():
658 | axie = scholar.get_axie_detail(id)
659 | scholar = get_account_from_ronin(str(axie['owner']))
660 | else:
661 | return await message.channel.send("Error in axie ID. Example: $rename_axie 1391353 new_name")
662 | name = message.content.split(" ")[2]
663 | except ValueError as e:
664 | await message.channel.send("Error : " + str(e))
665 | return e
666 |
667 | if scholar is None:
668 | return await message.channel.send("Error: No scholar found with this ID")
669 | if scholar.rename_axie(id, name):
670 | return await message.channel.send("Axie name of " + str(id) + " changed to : **" + name + "**")
671 | else:
672 | return await message.channel.send("Error renaming axie : **" + str(id) + "** to : **" + name + "**")
673 |
674 | ################################################
675 | # Rename an account #
676 | ################################################
677 | if "$rename_account " in message.content:
678 | print("\nRename account, asked by : " + message.author.name + " : " + str(message.author.id) + " at " + now.strftime("%d/%m/%Y %H:%M:%S"))
679 | await message.channel.send("This functionality is under construction")
680 | """
681 | try:
682 | id = message.content.split(" ")[1]
683 | if id.isnumeric():
684 | scholar = get_account_from_id(id)
685 | else:
686 | await message.channel.send("Error in discord ID. Example: $rename_account 496061891353903121 new_name")
687 | return
688 | name = message.content.split(" ")[2]
689 | except ValueError as e:
690 | await message.channel.send("Error : " + str(e))
691 | return e
692 | if scholar is None:
693 | await message.channel.send("Error: No scholar found with this ID")
694 | return
695 | # A CREER : scholar.rename_account(name)
696 | old_name = scholar.get_profile_name()
697 | if 'error' in old_name.lower():
698 | await message.channel.send("Error while renaming account. Maybe try again later ?")
699 | return
700 | await message.channel.send("Successfully renamed the account of from : **" + old_name + "** to : **" + name + "**")
701 | return
702 | """
703 | return
704 |
705 |
706 |
707 |
708 |
709 |
710 | # Loads secret.yaml datas
711 | with open("secret.yaml", "r") as file:
712 | config = yaml.safe_load(file)
713 |
714 | # Run the client (This runs first)
715 | client.run(config['discord_token'])
716 |
--------------------------------------------------------------------------------
/pyaxie.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 | import yaml
4 | import qrcode
5 | import os
6 | import datetime
7 | import time
8 | import math
9 | import pyaxie_utils
10 |
11 | from datetime import timedelta, date
12 | from web3 import Web3, exceptions
13 | from web3.auto import w3
14 | from eth_account.messages import encode_defunct
15 | from pprint import pprint
16 | from pycoingecko import CoinGeckoAPI
17 |
18 | class pyaxie(object):
19 |
20 | def __init__(self, ronin_address="", private_key=""):
21 | """
22 | Init the class variables, we need a ronin address and its private key
23 | :param ronin_address: The ronin address
24 | :param private_key: Private key belonging to the ronin account
25 | """
26 | with open("secret.yaml", "r") as file:
27 | config = yaml.safe_load(file)
28 |
29 | self.config = config
30 | self.ronin_address = ronin_address.replace('ronin:', '0x')
31 | self.private_key = private_key.replace('0x', '')
32 | self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36', 'authorization': ""}
33 | self.url = "https://axieinfinity.com/graphql-server-v2/graphql"
34 | self.url_api = config['url_api']
35 | self.access_token = self.get_access_token()
36 | self.account_id = 0
37 | self.email = ""
38 | self.slp_contract = None
39 | self.ronin_web3 = self.get_ronin_web3()
40 | self.axie_list_path = config['paths']['axie_list_path']
41 | self.slp_track_path = config['paths']['slp_track_path']
42 | self.slp_abi_path = 'slp_abi.json'
43 | self.axie_abi_path = 'slp_abi.json'
44 | self.slp_contract = self.get_slp_contract(self.ronin_web3, self.slp_abi_path)
45 | self.name = "you"
46 |
47 | for scholar in config['scholars']:
48 | if config['scholars'][scholar]['ronin_address'] == ronin_address:
49 | self.payout_percentage = config['scholars'][scholar]['payout_percentage']
50 | self.personal_ronin = config['scholars'][scholar]['personal_ronin'].replace('ronin:', '0x')
51 | self.name = scholar
52 | break
53 | else:
54 | self.payout_percentage = 0
55 | self.personal_ronin = None
56 |
57 | ############################
58 | # Authentication functions #
59 | ############################
60 |
61 | def get_raw_message(self):
62 | """
63 | Ask the API for a message to sign with the private key (authenticate)
64 | :return: message to sign
65 | """
66 | body = {"operationName": "CreateRandomMessage", "variables": {}, "query": "mutation CreateRandomMessage {\n createRandomMessage\n}\n"}
67 |
68 | r = requests.post(self.url, headers=self.headers, data=body)
69 | try:
70 | json_data = json.loads(r.text)
71 | except ValueError as e:
72 | return e
73 | return json_data['data']['createRandomMessage']
74 |
75 | def sign_message(self, raw_message, private=''):
76 | """
77 | Sign a raw message
78 | :param raw_message: The raw message from get_raw_message()
79 | :param private: The private key of the account
80 | :return: The signed message
81 | """
82 | if not private:
83 | private = self.private_key
84 | pk = bytearray.fromhex(private)
85 | try:
86 | message = encode_defunct(text=raw_message)
87 | hex_signature = w3.eth.account.sign_message(message, private_key=pk)
88 | return hex_signature
89 | except 'JSONDecodeError' as e:
90 | return e + " | Maybe a problem with the axie request"
91 |
92 | def submit_signature(self, signed_message, raw_message, ronin_address=''):
93 | """
94 | Function to submit the signature and get authorization
95 | :param signed_message: The signed message from sign_message()
96 | :param raw_message: The raw message from get_row_message()
97 | :param ronin_address: THe ronin address of the account
98 | :return: The access token
99 | """
100 | if not ronin_address:
101 | ronin_address = self.ronin_address
102 |
103 | body = {"operationName": "CreateAccessTokenWithSignature", "variables": {"input": {"mainnet": "ronin", "owner": "User's Eth Wallet Address", "message": "User's Raw Message", "signature": "User's Signed Message"}}, "query": "mutation CreateAccessTokenWithSignature($input: SignatureInput!) { createAccessTokenWithSignature(input: $input) { newAccount result accessToken __typename }}"}
104 | body['variables']['input']['signature'] = signed_message['signature'].hex()
105 | body['variables']['input']['message'] = raw_message
106 | body['variables']['input']['owner'] = ronin_address
107 | r = requests.post(self.url, headers=self.headers, json=body)
108 |
109 | try:
110 | json_data = json.loads(r.text)
111 | except ValueError as e:
112 | return e
113 | return json_data['data']['createAccessTokenWithSignature']['accessToken']
114 |
115 | def get_access_token(self):
116 | """
117 | Get an access token as proof of authentication
118 | :return: The access token in string
119 | """
120 | if not self.private_key:
121 | return
122 | msg = self.get_raw_message()
123 | signed = self.sign_message(msg)
124 | if "JSONDecodeError" in signed:
125 | print("Error getting the signed message, trying again.")
126 | token = self.get_access_token()
127 | else:
128 | token = self.submit_signature(signed, msg)
129 | self.access_token = token
130 | self.headers['authorization'] = 'Bearer ' + token
131 | return token
132 |
133 | def get_qr_code(self):
134 | """
135 | Function to create a QRCode from an access_token
136 | """
137 | img = qrcode.make(self.access_token)
138 | name = 'QRCode-' + str(datetime.datetime.now()) + '.png'
139 | img.save(name)
140 | return name
141 |
142 | #################################
143 | # Account interaction functions #
144 | #################################
145 |
146 | def get_price(self, currency):
147 | """
148 | Get the price in USD for 1 ETH / SLP / AXS
149 | :return: The price in US of 1 token
150 | """
151 | body = {"operationName": "NewEthExchangeRate", "variables": {}, "query": "query NewEthExchangeRate {\n exchangeRate {\n " + currency.lower() + " {\n usd\n __typename\n }\n __typename\n }\n}\n"}
152 | r = requests.post(self.url, headers=self.headers, json=body)
153 | try:
154 | json_data = json.loads(r.text)
155 | except ValueError as e:
156 | return e
157 | return json_data['data']['exchangeRate'][currency.lower()]['usd']
158 |
159 | def get_profile_data(self):
160 | """
161 | Retrieve your profile/account data
162 | :return: Your profil data as dict
163 | """
164 | body = {"operationName": "GetProfileBrief", "variables": {}, "query": "query GetProfileBrief {\n profile {\n ...ProfileBrief\n __typename\n }\n}\n\nfragment ProfileBrief on AccountProfile {\n accountId\n addresses {\n ...Addresses\n __typename\n }\n email\n activated\n name\n settings {\n unsubscribeNotificationEmail\n __typename\n }\n __typename\n}\n\nfragment Addresses on NetAddresses {\n ethereum\n tomo\n loom\n ronin\n __typename\n}\n"}
165 | r = requests.post(self.url, headers=self.headers, json=body)
166 | try:
167 | json_data = json.loads(r.text)
168 | except ValueError as e:
169 | return e['data']['profile']
170 |
171 | self.account_id = json_data['data']['profile']['accountId']
172 | self.email = json_data['data']['profile']['email']
173 | self.name = json_data['data']['profile']['name']
174 | return json_data
175 |
176 | def get_activity_log(self):
177 | """
178 | Get datas about the activity log
179 | :return: activity log
180 | """
181 | body = {"operationName": "GetActivityLog", "variables": {"from": 0, "size": 6}, "query": "query GetActivityLog($from: Int, $size: Int) {\n profile {\n activities(from: $from, size: $size) {\n ...Activity\n __typename\n }\n __typename\n }\n}\n\nfragment Activity on Activity {\n activityId\n accountId\n action\n timestamp\n seen\n data {\n ... on ListAxie {\n ...ListAxie\n __typename\n }\n ... on UnlistAxie {\n ...UnlistAxie\n __typename\n }\n ... on BuyAxie {\n ...BuyAxie\n __typename\n }\n ... on GiftAxie {\n ...GiftAxie\n __typename\n }\n ... on MakeAxieOffer {\n ...MakeAxieOffer\n __typename\n }\n ... on CancelAxieOffer {\n ...CancelAxieOffer\n __typename\n }\n ... on SyncExp {\n ...SyncExp\n __typename\n }\n ... on MorphToPetite {\n ...MorphToPetite\n __typename\n }\n ... on MorphToAdult {\n ...MorphToAdult\n __typename\n }\n ... on BreedAxies {\n ...BreedAxies\n __typename\n }\n ... on BuyLand {\n ...BuyLand\n __typename\n }\n ... on ListLand {\n ...ListLand\n __typename\n }\n ... on UnlistLand {\n ...UnlistLand\n __typename\n }\n ... on GiftLand {\n ...GiftLand\n __typename\n }\n ... on MakeLandOffer {\n ...MakeLandOffer\n __typename\n }\n ... on CancelLandOffer {\n ...CancelLandOffer\n __typename\n }\n ... on BuyItem {\n ...BuyItem\n __typename\n }\n ... on ListItem {\n ...ListItem\n __typename\n }\n ... on UnlistItem {\n ...UnlistItem\n __typename\n }\n ... on GiftItem {\n ...GiftItem\n __typename\n }\n ... on MakeItemOffer {\n ...MakeItemOffer\n __typename\n }\n ... on CancelItemOffer {\n ...CancelItemOffer\n __typename\n }\n ... on ListBundle {\n ...ListBundle\n __typename\n }\n ... on UnlistBundle {\n ...UnlistBundle\n __typename\n }\n ... on BuyBundle {\n ...BuyBundle\n __typename\n }\n ... on MakeBundleOffer {\n ...MakeBundleOffer\n __typename\n }\n ... on CancelBundleOffer {\n ...CancelBundleOffer\n __typename\n }\n ... on AddLoomBalance {\n ...AddLoomBalance\n __typename\n }\n ... on WithdrawFromLoom {\n ...WithdrawFromLoom\n __typename\n }\n ... on AddFundBalance {\n ...AddFundBalance\n __typename\n }\n ... on WithdrawFromFund {\n ...WithdrawFromFund\n __typename\n }\n ... on TopupRoninWeth {\n ...TopupRoninWeth\n __typename\n }\n ... on WithdrawRoninWeth {\n ...WithdrawRoninWeth\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment ListAxie on ListAxie {\n axieId\n priceFrom\n priceTo\n duration\n txHash\n __typename\n}\n\nfragment UnlistAxie on UnlistAxie {\n axieId\n txHash\n __typename\n}\n\nfragment BuyAxie on BuyAxie {\n axieId\n price\n owner\n txHash\n __typename\n}\n\nfragment GiftAxie on GiftAxie {\n axieId\n destination\n txHash\n __typename\n}\n\nfragment MakeAxieOffer on MakeAxieOffer {\n axieId\n price\n txHash\n __typename\n}\n\nfragment CancelAxieOffer on CancelAxieOffer {\n axieId\n txHash\n __typename\n}\n\nfragment SyncExp on SyncExp {\n axieId\n exp\n txHash\n __typename\n}\n\nfragment MorphToPetite on MorphToPetite {\n axieId\n txHash\n __typename\n}\n\nfragment MorphToAdult on MorphToAdult {\n axieId\n txHash\n __typename\n}\n\nfragment BreedAxies on BreedAxies {\n sireId\n matronId\n lovePotionAmount\n txHash\n __typename\n}\n\nfragment BuyLand on BuyLand {\n row\n col\n price\n owner\n txHash\n __typename\n}\n\nfragment ListLand on ListLand {\n row\n col\n priceFrom\n priceTo\n duration\n txHash\n __typename\n}\n\nfragment UnlistLand on UnlistLand {\n row\n col\n txHash\n __typename\n}\n\nfragment GiftLand on GiftLand {\n row\n col\n destination\n txHash\n __typename\n}\n\nfragment MakeLandOffer on MakeLandOffer {\n row\n col\n price\n txHash\n __typename\n}\n\nfragment CancelLandOffer on CancelLandOffer {\n row\n col\n txHash\n __typename\n}\n\nfragment BuyItem on BuyItem {\n tokenId\n itemAlias\n price\n owner\n txHash\n __typename\n}\n\nfragment ListItem on ListItem {\n tokenId\n itemAlias\n priceFrom\n priceTo\n duration\n txHash\n __typename\n}\n\nfragment UnlistItem on UnlistItem {\n tokenId\n itemAlias\n txHash\n __typename\n}\n\nfragment GiftItem on GiftItem {\n tokenId\n itemAlias\n destination\n txHash\n __typename\n}\n\nfragment MakeItemOffer on MakeItemOffer {\n tokenId\n itemAlias\n price\n txHash\n __typename\n}\n\nfragment CancelItemOffer on CancelItemOffer {\n tokenId\n itemAlias\n txHash\n __typename\n}\n\nfragment BuyBundle on BuyBundle {\n listingIndex\n price\n owner\n txHash\n __typename\n}\n\nfragment ListBundle on ListBundle {\n numberOfItems\n priceFrom\n priceTo\n duration\n txHash\n __typename\n}\n\nfragment UnlistBundle on UnlistBundle {\n listingIndex\n txHash\n __typename\n}\n\nfragment MakeBundleOffer on MakeBundleOffer {\n listingIndex\n price\n txHash\n __typename\n}\n\nfragment CancelBundleOffer on CancelBundleOffer {\n listingIndex\n txHash\n __typename\n}\n\nfragment AddLoomBalance on AddLoomBalance {\n amount\n senderAddress\n receiverAddress\n txHash\n __typename\n}\n\nfragment WithdrawFromLoom on WithdrawFromLoom {\n amount\n senderAddress\n receiverAddress\n txHash\n __typename\n}\n\nfragment AddFundBalance on AddFundBalance {\n amount\n senderAddress\n txHash\n __typename\n}\n\nfragment WithdrawFromFund on WithdrawFromFund {\n amount\n receiverAddress\n txHash\n __typename\n}\n\nfragment WithdrawRoninWeth on WithdrawRoninWeth {\n amount\n receiverAddress\n txHash\n receiverAddress\n __typename\n}\n\nfragment TopupRoninWeth on TopupRoninWeth {\n amount\n receiverAddress\n txHash\n receiverAddress\n __typename\n}\n"}
182 | r = requests.post(self.url, headers=self.headers, json=body)
183 | try:
184 | json_data = json.loads(r.text)
185 | except ValueError as e:
186 | return e
187 | return json_data['data']['profile']['activities']
188 |
189 | def get_profile_name(self, ronin_address=''):
190 | """
191 | Get the profile name of a ronin address
192 | :param ronin_address: The target ronin account
193 | :return: The name of the account
194 | """
195 | if ronin_address == '':
196 | ronin_address = self.ronin_address
197 | body = {"operationName": "GetProfileNameByRoninAddress", "variables": {"roninAddress": ronin_address}, "query": "query GetProfileNameByRoninAddress($roninAddress: String!) {\n publicProfileWithRoninAddress(roninAddress: $roninAddress) {\n accountId\n name\n __typename\n }\n}\n"}
198 | r = requests.post(self.url, headers=self.headers, json=body)
199 | try:
200 | json_data = json.loads(r.text)
201 | except ValueError as e:
202 | return e
203 | return json_data['data']['publicProfileWithRoninAddress']['name']
204 |
205 | def rename_account(self, new_name):
206 | body = {"operationName": "RenameAxie", "variables": {"axieId": str(new_name),"name": str(new_name) }, "query": "mutation RenameAxie($axieId: ID!, $name: String!) {\n renameAxie(axieId: $axieId, name: $name) {\n result\n __typename\n }\n}\n"}
207 | try:
208 | r = requests.post(self.url, headers=self.headers, json=body)
209 | json_data = json.loads(r.text)
210 | except ValueError as e:
211 | return e
212 | if json_data['data'] is None:
213 | return json_data['errors']['message']
214 | return json_data['data']['renameAxie']['result']
215 |
216 | def get_public_profile(self, ronin_address=''):
217 | """
218 | Get infos about the given ronin address
219 | :param ronin_address: The target ronin account
220 | :return: Public datas of the ronin account
221 | """
222 | if ronin_address == '':
223 | ronin_address = self.ronin_address
224 | body = {"operationName": "GetProfileByRoninAddress", "variables": {"roninAddress": ronin_address}, "query": "query GetProfileByRoninAddress($roninAddress: String!) {\n publicProfileWithRoninAddress(roninAddress: $roninAddress) {\n ...Profile\n __typename\n }\n}\n\nfragment Profile on PublicProfile {\n accountId\n name\n addresses {\n ...Addresses\n __typename\n }\n __typename\n}\n\nfragment Addresses on NetAddresses {\n ethereum\n tomo\n loom\n ronin\n __typename\n}\n"}
225 | r = requests.post(self.url, headers=self.headers, json=body)
226 | try:
227 | json_data = json.loads(r.text)
228 | except ValueError as e:
229 | return e
230 | return json_data['data']['publicProfileWithRoninAddress']
231 |
232 | def get_rank_mmr(self, ronin_address=''):
233 | """
234 | Get the mmr and rank of the current account
235 | :return: Dict with MMR and rank
236 | """
237 | if ronin_address == '':
238 | ronin_address = self.ronin_address
239 | params = {"client_id": ronin_address, "offset": 0, "limit": 0}
240 |
241 | # Try multiple times to avoid return 0
242 | for i in range(0, 5):
243 | try:
244 | r = requests.get(self.url_api + "last-season-leaderboard", params=params)
245 | json_data = json.loads(r.text)
246 | if json_data['success']:
247 | return {'mmr': int(json_data['items'][1]['elo']), 'rank': int(json_data['items'][1]['rank'])}
248 | except ValueError as e:
249 | return e
250 | return {'mmr': 0, 'rank': 0}
251 |
252 |
253 | def get_daily_slp(self):
254 | """
255 | Get the daily SLP ratio based on SLP farmed between now and last claim
256 | :return: Dict with ratio and date
257 | """
258 | unclaimed = self.get_unclaimed_slp()
259 | t = datetime.datetime.fromtimestamp(self.get_last_claim())
260 | days = (t - datetime.datetime.utcnow()).days * -1
261 | if days <= 0:
262 | return unclaimed
263 | return int(unclaimed / days)
264 |
265 | #############################################
266 | # Functions to interact with axies from web #
267 | #############################################
268 |
269 | def get_axie_list(self, ronin_address=''):
270 | """
271 | Get informations about the axies in a specific account
272 | :param ronin_address: The ronin address of the target account
273 | :return: Data about the axies
274 | """
275 | if ronin_address == '':
276 | ronin_address = self.ronin_address
277 | body = {"operationName": "GetAxieBriefList", "variables": {"from": 0, "size": 24, "sort": "IdDesc", "auctionType": "All", "owner": ronin_address, "criteria": {"region": None, "parts": None, "bodyShapes": None, "classes": None, "stages": None, "numMystic": None, "pureness": None, "title": None, "breedable": None, "breedCount": None, "hp":[],"skill":[],"speed":[],"morale":[]}},"query":"query GetAxieBriefList($auctionType: AuctionType, $criteria: AxieSearchCriteria, $from: Int, $sort: SortBy, $size: Int, $owner: String) {\n axies(auctionType: $auctionType, criteria: $criteria, from: $from, sort: $sort, size: $size, owner: $owner) {\n total\n results {\n ...AxieBrief\n __typename\n }\n __typename\n }\n}\n\nfragment AxieBrief on Axie {\n id\n name\n stage\n class\n breedCount\n image\n title\n battleInfo {\n banned\n __typename\n }\n auction {\n currentPrice\n currentPriceUSD\n __typename\n }\n parts {\n id\n name\n class\n type\n specialGenes\n __typename\n }\n __typename\n}\n"}
278 | try:
279 | r = requests.post(self.url, headers=self.headers, json=body)
280 | json_data = json.loads(r.text)
281 | except ValueError as e:
282 | return e
283 | return json_data['data']['axies']['results']
284 |
285 | def get_all_axie_list(self):
286 | """
287 | Get informations about the axies in all the accounts
288 | :return: List with all axies datas
289 | """
290 | res = list()
291 | for account in self.config['scholars']:
292 | axies = self.get_axie_list(self.config['scholars'][account]['ronin_address'])
293 | for axie in axies:
294 | res.append(axie)
295 | for axie in self.get_axie_list(self.config['personal']['ronin_address']):
296 | res.append(axie)
297 | return res
298 |
299 | def get_all_axie_class(self, axie_class, axies_datas=[]):
300 | """
301 | Return all the axies of a specific class present in the scholarship
302 | :param axie_class: Plant, Bast, Bird, etc...
303 | :return: List of axie object of the specific class
304 | """
305 | if not axies_datas:
306 | axies_datas = self.get_all_axie_list()
307 | l = list()
308 | for axie in axies_datas:
309 | if axie['class'] is not None and axie['class'].lower() == axie_class.lower():
310 | l.append(axie)
311 | return l
312 |
313 | def get_axie_image(self, axie_id):
314 | """
315 | Get the image link to an axie
316 | :param axie_id: String ID of the axie you are targeting
317 | :return: Link to the image
318 | """
319 | body = {"operationName": "GetAxieMetadata", "variables": {"axieId": axie_id}, "query": "query GetAxieMetadata($axieId: ID!) {\n axie(axieId: $axieId) {\n id\n image\n __typename\n }\n}\n"}
320 | try:
321 | r = requests.post(self.url, headers=self.headers, json=body)
322 | json_data = json.loads(r.text)
323 | except ValueError as e:
324 | return e
325 | return json_data['data']['axie']['image']
326 |
327 | def get_number_of_axies(self):
328 | """
329 | Get the number of axies in the account
330 | :return: the number of axies
331 | """
332 | axies = self.get_axie_list()
333 | return len(axies)
334 |
335 | def download_axie_image(self, axie_id):
336 | """
337 | Download the image of an axie and return the path
338 | :param axie_id: ID of the axie
339 | :return: Path of the image
340 | """
341 | axie_id = str(axie_id)
342 | path = './img/axies/' + axie_id + '.png'
343 | dir = os.path.join(".", "img", "axies")
344 | if not os.path.exists(dir):
345 | os.mkdir(dir)
346 |
347 | if os.path.exists(path):
348 | return path
349 |
350 | img_data = requests.get('https://storage.googleapis.com/assets.axieinfinity.com/axies/'+axie_id+'/axie/axie-full-transparent.png').content
351 | if len(img_data) <= 500:
352 | return './img/axies/egg.png'
353 | with open(path, 'ab') as img:
354 | img.write(img_data)
355 | return path
356 |
357 | def get_axies_imageline(self):
358 | """
359 | Get the path to the picture containing the 3 axies merged horizontally
360 | :return: Path of the new picture
361 | """
362 | try:
363 | axies = self.get_axie_list()
364 | l = list()
365 | for axie in axies:
366 | l.append(self.download_axie_image(axie['id']))
367 | if len(l) < 3:
368 | return 'Error: not enough axies on the account'
369 | except ValueError as e:
370 | return e
371 | return pyaxie_utils.merge_images(l[0], l[1], l[2], self.name)
372 |
373 | def get_axie_detail(self, axie_id):
374 | """
375 | Get informations about an Axie based on its ID
376 | :param axie_id: string ID of the axie
377 | :return: A dict with the adatas of the axie
378 | """
379 | body = {"operationName": "GetAxieDetail", "variables": {"axieId": axie_id}, "query": "query GetAxieDetail($axieId: ID!) {\n axie(axieId: $axieId) {\n ...AxieDetail\n __typename\n }\n}\n\nfragment AxieDetail on Axie {\n id\n image\n class\n chain\n name\n genes\n owner\n birthDate\n bodyShape\n class\n sireId\n sireClass\n matronId\n matronClass\n stage\n title\n breedCount\n level\n figure {\n atlas\n model\n image\n __typename\n }\n parts {\n ...AxiePart\n __typename\n }\n stats {\n ...AxieStats\n __typename\n }\n auction {\n ...AxieAuction\n __typename\n }\n ownerProfile {\n name\n __typename\n }\n battleInfo {\n ...AxieBattleInfo\n __typename\n }\n children {\n id\n name\n class\n image\n title\n stage\n __typename\n }\n __typename\n}\n\nfragment AxieBattleInfo on AxieBattleInfo {\n banned\n banUntil\n level\n __typename\n}\n\nfragment AxiePart on AxiePart {\n id\n name\n class\n type\n specialGenes\n stage\n abilities {\n ...AxieCardAbility\n __typename\n }\n __typename\n}\n\nfragment AxieCardAbility on AxieCardAbility {\n id\n name\n attack\n defense\n energy\n description\n backgroundUrl\n effectIconUrl\n __typename\n}\n\nfragment AxieStats on AxieStats {\n hp\n speed\n skill\n morale\n __typename\n}\n\nfragment AxieAuction on Auction {\n startingPrice\n endingPrice\n startingTimestamp\n endingTimestamp\n duration\n timeLeft\n currentPrice\n currentPriceUSD\n suggestedPrice\n seller\n listingIndex\n state\n __typename\n}\n"}
380 | try:
381 | r = requests.post(self.url, headers=self.headers, json=body)
382 | json_data = json.loads(r.text)
383 | except ValueError as e:
384 | return None
385 | return json_data['data']['axie']
386 |
387 | def get_axie_name(self, axie_id):
388 | """
389 | Get the name of an axie based on his ID
390 | :param axie_id: The id of the axie
391 | :return: Name of the axie
392 | """
393 | body = {"operationName": "GetAxieName", "variables": {"axieId": axie_id}, "query": "query GetAxieName($axieId: ID!) {\n axie(axieId: $axieId) {\n ...AxieName\n __typename\n }\n}\n\nfragment AxieName on Axie {\n name\n __typename\n}\n"}
394 | try:
395 | r = requests.post(self.url, headers=self.headers, json=body)
396 | json_data = json.loads(r.text)
397 | except ValueError as e:
398 | return e
399 | return json_data['data']['axie']['name']
400 |
401 | def get_axie_stats(self, axie_id):
402 | """
403 | Get axie 4 basic stats (HP, morale, skill, speed)
404 | :param axie_id: String ID of the axie
405 | :return: Dict with the stats
406 | """
407 | data = self.get_axie_detail(axie_id)
408 | return data['stats']
409 |
410 | def get_axie_parts(self, axie_id):
411 | """
412 | Get axies body parts from an axie ID
413 | :param axie_id: String ID of the axie
414 | :return: Dict with the differents body parts
415 | """
416 | data = self.get_axie_detail(axie_id)
417 | return data['parts']
418 |
419 | def get_axie_class(self, axie_id):
420 | """
421 | Get the class of an axie based on it's ID
422 | :param axie_id: String ID of the axie
423 | :return: Dict with the differents body parts
424 | """
425 | data = self.get_axie_detail(axie_id)
426 | return data['class']
427 |
428 | def get_axie_children(self, id=0, axie_data={}):
429 | """
430 | Get the children of an axie on given id OR given axie datas
431 | :param id: id of the axie
432 | :param axie_data: axie_datas
433 | :return: list of id of the children
434 | """
435 | axie = self.get_axie_detail(id) if axie_data == {} else axie_data
436 | l = list()
437 | for children in axie['children']:
438 | l.append(int(children['id']))
439 | return l
440 |
441 | def rename_axie(self, axie_id, new_name):
442 | """
443 | Rename an axie
444 | :param axie_id: The id of the axie to rename
445 | :param new_name: The new name of the axie
446 | :return: True/False or error
447 | """
448 | body = {"operationName": "RenameAxie", "variables": {"axieId": str(axie_id),"name": str(new_name) }, "query": "mutation RenameAxie($axieId: ID!, $name: String!) {\n renameAxie(axieId: $axieId, name: $name) {\n result\n __typename\n }\n}\n"}
449 | try:
450 | r = requests.post(self.url, headers=self.headers, json=body)
451 | json_data = json.loads(r.text)
452 | pprint(json_data)
453 | except ValueError as e:
454 | return e
455 | if json_data['data'] is None:
456 | return False
457 | return json_data['data']['renameAxie']['result']
458 |
459 | ###############################################
460 | # Functions to interact with stored axie data #
461 | ###############################################
462 |
463 | def save_axie(self, axie_data):
464 | """
465 | Save an axie details to the local axie_list
466 | :param axie_data:
467 | :param axie_list: Path of the axie_list file
468 | """
469 | axie_list = self.axie_list()
470 | axie_id = axie_data['id']
471 | if axie_list:
472 | axie_list.update({axie_id: axie_data})
473 | else:
474 | axie_list = {axie_id: axie_data}
475 | f = open(self.axie_list_path, 'a')
476 | yaml.safe_dump(axie_list, f)
477 | f.close()
478 |
479 | def check_axie(self, axie_id):
480 | """
481 | Check if we have this axie data locally
482 | :param axie_id: String of the ID of the axie to check
483 | :return: Data of the axie or None if not locally
484 | """
485 | with open(self.axie_list_path) as f:
486 | data = yaml.safe_load(f)
487 |
488 | for val in data:
489 | if val == str(axie_id):
490 | return val
491 | return []
492 |
493 | def update_axie_list(self):
494 | """
495 | Update the local axie_list with the new datas
496 | """
497 | axie_list = self.axie_list()
498 | new = {}
499 |
500 | for axie in axie_list:
501 | new[axie['id']] = self.get_axie_detail(axie['id'])
502 | with open(self.axie_list_path, 'w') as outfile:
503 | yaml.safe_dump(new, outfile)
504 |
505 | def axie_list(self):
506 | """
507 | Get the list of axies stored locally
508 | :return: List of axie data
509 | """
510 | if os.stat(self.axie_list_path).st_size > 3:
511 | f = open(self.axie_list_path, 'r')
512 | data = yaml.safe_load(f)
513 | f.close()
514 | if data:
515 | return data
516 | return None
517 |
518 | def axie_detail(self, axie_id):
519 | """
520 | Retrieve local details about an axie
521 | :param axie_id: The ID of the axie
522 | :return: Informations about the axie
523 | """
524 | data = self.axie_list()
525 | if data:
526 | return data[str(axie_id)]
527 | return None
528 |
529 | def axie_infos(self, axie_id, key):
530 | """
531 | Retrieve locally specific informations (key) about axie
532 | :param axie_id: String ID of the axie
533 | :param key: 'parts' or 'class' or 'stats' (list on documentation)
534 | :return: Information about the axie
535 | """
536 | if self.check_axie(axie_id):
537 | return self.axie_detail(axie_id)[key]
538 | return "This axie is not registered : " + str(axie_id)
539 |
540 | def axie_link(self, axie_id):
541 | """
542 | Return an URL to the axie page
543 | :param axie_id: Id of the axie
544 | :return: URL of the axie
545 | """
546 | url = 'https://marketplace.axieinfinity.com/axie/'
547 | return url + str(axie_id)
548 |
549 | ###################
550 | # Ronin functions #
551 | ###################
552 |
553 | def get_ronin_web3(self):
554 | """
555 | :return: Return the ronin web3
556 | """
557 | web3 = Web3(Web3.HTTPProvider('https://proxy.roninchain.com/free-gas-rpc'))
558 | return web3
559 |
560 | def get_slp_contract(self, ronin_web3, slp_abi_path):
561 | """
562 | :param ronin_web3: ronin web3 object
563 | :param slp_abi_path: ABI for SLP
564 | :return: The contract to interact with
565 | """
566 | slp_contract_address = "0xa8754b9fa15fc18bb59458815510e40a12cd2014"
567 | with open(slp_abi_path) as f:
568 | try:
569 | slp_abi = json.load(f)
570 | except ValueError as e:
571 | return e
572 | contract = ronin_web3.eth.contract(address=w3.toChecksumAddress(slp_contract_address), abi=slp_abi)
573 | self.slp_contract = contract
574 | return contract
575 |
576 | def get_axie_contract(self, ronin_web3):
577 | slp_contract_address = "0x32950db2a7164ae833121501c797d79e7b79d74c"
578 | with open(self.axie_abi_path) as f:
579 | try:
580 | slp_abi = json.load(f)
581 | except ValueError as e:
582 | return e
583 | contract = ronin_web3.eth.contract(address=w3.toChecksumAddress(slp_contract_address), abi=slp_abi)
584 | self.slp_contract = contract
585 | return contract
586 |
587 |
588 | def get_claimed_slp(self, address=''):
589 | """
590 | :param address: Ronin address to check
591 | :return: The amount of claimed SLP
592 | """
593 | if address == '':
594 | address = self.ronin_address
595 | try:
596 | response = requests.get(self.url_api + f"clients/{address}/items/1", headers=self.headers, data="")
597 | data = json.loads(response.text)
598 | except ValueError as e:
599 | return e
600 |
601 | balance = data['blockchain_related']['balance']
602 | if balance is None:
603 | return 0
604 |
605 | return int(balance)
606 |
607 | def get_unclaimed_slp(self, address=''):
608 | """
609 | :param address: Ronin address to check
610 | :return: The amount of unclaimed SLP
611 | """
612 | if address == '':
613 | address = self.ronin_address
614 | try:
615 | response = requests.get(self.url_api + f"clients/{address}/items/1", headers=self.headers, data="")
616 | result = response.json()
617 | except ValueError as e:
618 | return e
619 | if result is None:
620 | return 0
621 |
622 | balance = -1
623 | if 'blockchain_related' in result:
624 | balance = result['blockchain_related']['balance']
625 | else:
626 | return balance
627 | if balance is None:
628 | balance = 0
629 |
630 | res = result["total"]
631 | if res is None:
632 | res = 0
633 | return int(res - balance)
634 |
635 | def get_last_claim(self, address=''):
636 | """
637 | Return the last time SLP was claimed for this account
638 | :param address: Ronin address
639 | :return: Time in sec
640 | """
641 | if address == '':
642 | address = self.ronin_address
643 |
644 | try:
645 | response = requests.get(self.url_api + f"clients/{address}/items/1", headers=self.headers, data="")
646 | result = response.json()
647 | except ValueError as e:
648 | return e
649 |
650 | return int(result["last_claimed_item_at"])
651 |
652 | def claim_slp(self):
653 | """
654 | Claim SLP on the account.
655 | :return: Transaction of the claim
656 | """
657 | print("\nClaiming SLP for : ", self.name)
658 |
659 | if datetime.datetime.utcnow() + timedelta(days=-14) < datetime.datetime.fromtimestamp(self.get_last_claim()):
660 | return 'Error: Too soon to claim or already claimed'
661 |
662 | slp_claim = {
663 | 'address': self.ronin_address,
664 | 'private_key': self.private_key,
665 | 'state': {"signature": None}
666 | }
667 | access_token = self.access_token
668 | custom_headers = self.headers.copy()
669 | custom_headers["authorization"] = f"Bearer {access_token}"
670 | response = requests.post(self.url_api + f"clients/{self.ronin_address}/items/1/claim", headers=custom_headers, json="")
671 |
672 | if response.status_code != 200:
673 | print(response.text)
674 | return
675 |
676 | result = response.json()["blockchain_related"]["signature"]
677 | if result is None:
678 | return 'Error: Nothing to claim'
679 |
680 | checksum_address = w3.toChecksumAddress(self.ronin_address)
681 | nonce = self.ronin_web3.eth.get_transaction_count(checksum_address)
682 | slp_claim['state']["signature"] = result["signature"].replace("0x", "")
683 | claim_txn = self.slp_contract.functions.checkpoint(checksum_address, result["amount"], result["timestamp"],
684 | slp_claim['state']["signature"]).buildTransaction({'gas': 1000000, 'gasPrice': 0, 'nonce': nonce})
685 | signed_txn = self.ronin_web3.eth.account.sign_transaction(claim_txn, private_key=bytearray.fromhex(self.private_key.replace("0x", "")))
686 |
687 | self.ronin_web3.eth.send_raw_transaction(signed_txn.rawTransaction)
688 | txn = self.ronin_web3.toHex(self.ronin_web3.keccak(signed_txn.rawTransaction))
689 | return txn if self.wait_confirmation(txn) else "Error : Transaction " + str(txn) + "reverted by EVM (Ethereum Virtual machine)"
690 |
691 | def transfer_slp(self, to_address, amount):
692 | """
693 | Transfer SLP from pyaxie ronin address to the to_address
694 | :param to_address: Receiver of the SLP. Format : 0x
695 | :param amount: Amount of SLP to send
696 | :return: Transaction hash
697 | """
698 | if amount < 1 or not Web3.isAddress(to_address):
699 | return {"error": "Make sure that the amount is not under 1 and the **to_address** is correct."}
700 |
701 | transfer_txn = self.slp_contract.functions.transfer(w3.toChecksumAddress(to_address), amount).buildTransaction({
702 | 'chainId': 2020,
703 | 'gas': 100000,
704 | 'gasPrice': Web3.toWei('0', 'gwei'),
705 | 'nonce': self.ronin_web3.eth.get_transaction_count(w3.toChecksumAddress(self.ronin_address))
706 | })
707 | private_key = bytearray.fromhex(self.private_key.replace("0x", ""))
708 | signed_txn = self.ronin_web3.eth.account.sign_transaction(transfer_txn, private_key=private_key)
709 |
710 | self.ronin_web3.eth.send_raw_transaction(signed_txn.rawTransaction)
711 | txn = self.ronin_web3.toHex(self.ronin_web3.keccak(signed_txn.rawTransaction))
712 | return txn if self.wait_confirmation(txn) else "Error : Transaction " + str(txn) + " reverted by EVM (Ethereum Virtual machine)"
713 |
714 | def wait_confirmation(self, txn):
715 | """
716 | Wait for a transaction to finish
717 | :param txn: the transaction to wait
718 | :return: True or False depending if transaction succeed
719 | """
720 | while True:
721 | try:
722 | recepit = self.ronin_web3.eth.get_transaction_receipt(txn)
723 | success = True if recepit["status"] == 1 else False
724 | break
725 | except exceptions.TransactionNotFound:
726 | time.sleep(5)
727 | return success
728 |
729 | def payout(self):
730 | """
731 | Send money to the scholar and to the manager/academy or directly to manager if manager called
732 | :return: List of 2 transactions hash : scholar and manager
733 | """
734 | self.claim_slp()
735 |
736 | txns = list()
737 | slp_balance = self.get_claimed_slp()
738 | scholar_payout_amount = math.ceil(slp_balance * self.payout_percentage)
739 | academy_payout_amount = slp_balance - scholar_payout_amount
740 |
741 | if slp_balance < 1:
742 | return ["Error: Nothing to send.", "Error: Nothing to send."]
743 |
744 | if self.payout_percentage == 0:
745 | print("Sending all the {} SLP to you : {} ".format(academy_payout_amount, self.config['personal']['ronin_address']))
746 | txns.append(str(self.transfer_slp(self.config['personal']['ronin_address'], academy_payout_amount + scholar_payout_amount)))
747 | txns.append("Nothing to send to scholar")
748 | return txns
749 | else:
750 | print("Sending {} SLP to {} : {} ".format(academy_payout_amount, "You", self.config['personal']['ronin_address']))
751 | txns.append(str(self.transfer_slp(self.config['personal']['ronin_address'], academy_payout_amount)))
752 |
753 | print("Sending {} SLP to {} : {} ".format(scholar_payout_amount, self.name, self.personal_ronin))
754 | txns.append(str(self.transfer_slp(self.personal_ronin, scholar_payout_amount)))
755 | return txns
756 |
757 | def get_mint_burn_graph(self):
758 | """
759 | Create a chart in /img with SLP burned/minted
760 | :return: path of the image
761 | """
762 | """
763 | try:
764 | slp_supply = json.loads(requests.get("https://www.axieworld.com/api/charts/slp-issuance").content)
765 | dates, mints, burns = ([],) * 3
766 | today = date.today()
767 | qc = QuickChart()
768 | qc.width = 500
769 | qc.height = 300
770 | qc.device_pixel_ratio = 2.0
771 |
772 | for i in range(0, 6):
773 | dates.append(today) if i == 0 else dates.append(today - timedelta(days=i))
774 | mints.append(round(slp_supply["data"]["minted"][-(i + 1)] / 1000000, 1))
775 | burns.append(round(slp_supply["data"]["burned"][-(i + 1)] / 1000000, 1))
776 |
777 | qc.config = {
778 | "type": "bar",
779 | "data": {
780 | "labels": dates,
781 | "datasets": [{
782 | "label": 'Minted',
783 | "backgroundColor": 'rgb(35, 90, 155)',
784 | "stack": 'Stack 0',
785 | "data": mints,
786 | },
787 | {
788 | "label": 'Burned',
789 | "backgroundColor": 'rgb(245, 158, 27)',
790 | "stack": 'Stack 1',
791 | "data": burns,
792 | },
793 | ]
794 | },
795 | "options": {
796 | "plugins": {
797 | "datalabels": {
798 | "anchor": 'end',
799 | "align": 'top',
800 | "color": '#fff',
801 | "backgroundColor": 'rgba(54, 57, 63, 1.0)'
802 | },
803 | },
804 | "title": {
805 | "display": "true",
806 | "text": "SLP minted vs burned (in millions)",
807 | },
808 | "tooltips": {
809 | "mode": "index",
810 | "intersect": "false",
811 | },
812 | "responsive": "true",
813 | },
814 | }
815 | path = 'img/slpMintedVsBurned.png'
816 | qc.to_file(path)
817 | except ValueError as e:
818 | return e
819 | return path
820 | """
821 | return
822 |
823 | def get_breed_cost(self, nb=-1):
824 | """
825 | Get the breeding cost
826 | :param nb: breed lvl (0-6)
827 | :return: dict with datas about the breeding costs
828 | """
829 | breeds = {0: 150, 1: 300, 2: 450, 3: 750, 4: 1200, 5: 1950, 6: 3150}
830 | axs = self.get_price('axs')
831 | slp = self.get_price('slp')
832 | total = 0
833 | res = dict()
834 |
835 | for i in range(0, 7):
836 | breed_price = int((breeds[i] * slp) * 2 + (axs * 2))
837 | total += breed_price
838 | res[i] = {'price': breed_price, 'total_breed_price': total, 'average_price': int(total/(1+i))}
839 |
840 | if nb <= -1:
841 | return res
842 | return {nb, res[nb]}
843 |
844 | def get_prices_from_timestamp(self, timestamp):
845 | """
846 | Get prices for AXS, SLP and ETH at given date
847 | :param timestamp: date in unix timestamp format
848 | :return: Dict with the prices of currencies at given date
849 | """
850 | cg = CoinGeckoAPI()
851 | dt = datetime.datetime.fromtimestamp(timestamp)
852 |
853 | price_history = cg.get_coin_history_by_id(id='smooth-love-potion', date=dt.date().strftime('%d-%m-%Y'), vsCurrencies=['usd'])
854 | slp_price = price_history['market_data']['current_price']['usd']
855 |
856 | price_history = cg.get_coin_history_by_id(id='axie-infinity', date=dt.date().strftime('%d-%m-%Y'), vsCurrencies=['usd'])
857 | axs_price = price_history['market_data']['current_price']['usd']
858 |
859 | price_history = cg.get_coin_history_by_id(id='ethereum', date=dt.date().strftime('%d-%m-%Y'), vsCurrencies=['usd'])
860 | eth_price = price_history['market_data']['current_price']['usd']
861 |
862 | return {'slp': slp_price, 'axs': axs_price, 'eth': eth_price, 'date': timestamp}
863 |
864 | def ronin_txs(self, ronin_address=''):
865 | if ronin_address == '':
866 | ronin_address = self.config['personal']['ronin_address']
867 |
868 | url = "https://explorer.roninchain.com/api/txs/" + str(ronin_address) + "?size=10000"
869 | h = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'}
870 | response = requests.get(url, headers=h)
871 |
872 | try:
873 | json_data = json.loads(response.text)
874 | except ValueError as e:
875 | return e
876 | return json_data['results']
877 |
878 | def get_axie_total_breed_cost(self, axie_id, txs={}):
879 | if not isinstance(axie_id, int):
880 | return "Error in axie ID."
881 | if txs == {}:
882 | txs = self.ronin_txs()
883 |
884 | children = self.get_axie_children(axie_id)
885 | total = 0
886 | l = list()
887 | for i in txs:
888 | if len(i['logs']) == 4 and len(i['logs'][3]['topics']) > 1 and int(i['logs'][3]['topics'][1], 16) in children:
889 | prices = self.get_prices_from_timestamp(i['timestamp'])
890 | slp_price = int(i['logs'][1]['data'], 16) * prices['slp']
891 | axs_price = prices['axs'] * 2
892 | l.append({'date': datetime.datetime.fromtimestamp(i['timestamp']).strftime('%d-%m-%Y'),
893 | 'axs_price': round(prices['axs'], 2), 'slp_price': round(prices['slp'], 2),
894 | 'breed_cost': round(slp_price + axs_price, 2), 'axs_cost': round(axs_price, 2),
895 | 'slp_cost': round(slp_price, 2), 'axie_id': int(i['logs'][3]['topics'][1], 16)})
896 | total += slp_price + axs_price
897 | res = dict()
898 | res['total_breed_cost'] = round(total, 2)
899 | res['average_breed_cost'] = round(total / len(children), 2)
900 | res['details'] = l
901 | return res
902 |
903 | def get_account_balances(self, ronin_address=''):
904 | """
905 | Get the different balances for a given account (AXS, SLP, WETH, AXIES)
906 | :param ronin_address: ronin address of the account
907 | :return: dict with currencies and amount
908 | """
909 | if not ronin_address:
910 | ronin_address = self.config['personal']['ronin_address']
911 |
912 | url = "https://explorer.roninchain.com/api/tokenbalances/" + str(ronin_address).replace('ronin:', '0x')
913 | headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'}
914 | response = requests.get(url, headers=headers)
915 |
916 | try:
917 | json_data = json.loads(response.text)
918 | except ValueError as e:
919 | return {'WETH': -1, 'AXS': -1, 'SLP': -1, 'axies': -1, 'ronin_address': ronin_address}
920 |
921 | res = {'WETH': 0, 'AXS': 0, 'SLP': 0, 'axies': 0, 'ronin_address': ronin_address}
922 | for data in json_data['results']:
923 | if data['token_symbol'] == 'WETH':
924 | res['WETH'] = round(int(data['balance']) / math.pow(10, 18), 6)
925 | elif data['token_symbol'] == 'AXS':
926 | res['AXS'] = round(int(data['balance']) / math.pow(10, 18), 2)
927 | elif data['token_symbol'] == 'SLP':
928 | res['SLP'] = int(data['balance'])
929 | elif data['token_symbol'] == 'AXIE':
930 | res['axies'] = int(data['balance'])
931 | return res
932 |
933 | def get_all_accounts_balances(self):
934 | l = list()
935 | l.append(self.config['personal']['ronin_address'])
936 | for account in self.config['scholars']:
937 | l.append(self.config['scholars'][account]['ronin_address'])
938 |
939 | res = list()
940 | for r in l:
941 | res.append(self.get_account_balances(r))
942 | return res
943 |
944 |
945 |
946 |
--------------------------------------------------------------------------------