-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from 0xTaoDev/dev
First release
- Loading branch information
Showing
17 changed files
with
527 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.env/ | ||
OnChain/__pycache__/ | ||
Discord/__pycache__/__init__.cpython-311.pyc | ||
Discord/__pycache__/functions.cpython-311.pyc | ||
Telegram/__pycache__/__init__.cpython-311.pyc | ||
Telegram/__pycache__/functions.cpython-311.pyc | ||
Telegram/tempCodeRunnerFile.py | ||
tempCodeRunnerFile.py |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
from dotenv import load_dotenv | ||
import os | ||
|
||
from datetime import datetime | ||
import requests | ||
import discord | ||
|
||
|
||
load_dotenv(os.path.join(os.getcwd(), '.env\.env')) | ||
|
||
|
||
async def send_discord_webhook(swap_infos: dict): | ||
embed = discord.Embed( | ||
title=f":sparkles: {swap_infos['CHAIN']} - {swap_infos['MAKER_INFOS']['SHORT_ADDRESS']}", | ||
color=discord.Color.green(), | ||
timestamp=datetime.now(), | ||
url=swap_infos['LINKS']['SCAN']['TRANSACTION'], | ||
description=( | ||
f"**:bust_in_silhouette: [{swap_infos['MAKER_INFOS']['SHORT_ADDRESS']}]({swap_infos['LINKS']['SCAN']['MAKER']})\n" + | ||
f":scroll: [TX]({swap_infos['LINKS']['SCAN']['TRANSACTION']})**" | ||
) | ||
) | ||
|
||
for swap_id, swap_infos in swap_infos['SWAPS'].items(): | ||
emoji_swap_id = await get_emoji_swap_id(swap_id=swap_id) | ||
swap_title = ( | ||
"\n\u200B\n" + | ||
f"{emoji_swap_id} SWAP {swap_infos['SYMBOLS']['TOKEN0']} » {swap_infos['SYMBOLS']['TOKEN1']}" | ||
) | ||
swap_content = ( | ||
f"**> :dollar: {swap_infos['AMOUNTS']['TOKEN0']} ${swap_infos['SYMBOLS']['TOKEN0']} » {swap_infos['AMOUNTS']['TOKEN1']} ${swap_infos['SYMBOLS']['TOKEN1']}\n" + | ||
f"> :bar_chart: [CHART/TRADING]({swap_infos['LINKS']['CHART']})\n**" | ||
) | ||
embed.add_field( | ||
name=swap_title, | ||
value=swap_content, | ||
inline=False | ||
) | ||
|
||
requests.post(os.getenv("DISCORD_WEBHOOK_URL"), json={"embeds": [embed.to_dict()]}, headers={'Content-Type': 'application/json'}) | ||
|
||
|
||
async def get_emoji_swap_id(swap_id: int): | ||
swap_id_emoji = ( | ||
":one:" if swap_id == 1 else | ||
":two:" if swap_id == 2 else | ||
":three:" if swap_id == 3 else | ||
":four:" if swap_id == 4 else | ||
":five:" if swap_id == 5 else | ||
":six:" if swap_id == 6 else | ||
":seven:" if swap_id == 7 else | ||
":eight:" if swap_id == 8 else | ||
":nine:" if swap_id == 9 else | ||
":one::zero:" if swap_id == 10 else | ||
":one::one:" if swap_id == 11 else | ||
":one::two:" if swap_id == 12 else | ||
":one::three:" if swap_id == 13 else | ||
":one::four:" if swap_id == 14 else | ||
":one::five:" if swap_id == 15 else | ||
":one::six:" if swap_id == 16 else | ||
":one::seven:" if swap_id == 17 else | ||
":one::eight:" if swap_id == 18 else | ||
":one::nine:" if swap_id == 19 else | ||
":two::zero:" if swap_id == 20 else | ||
":1234:" | ||
) | ||
return swap_id_emoji |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 Alexey Potapov | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from hexbytes import HexBytes | ||
|
||
from OnChain import functions as f | ||
|
||
|
||
RPCS = { | ||
"ETHEREUM": [ | ||
"https://ethereum.publicnode.com" | ||
] | ||
} | ||
|
||
SWAPS_HEX = { | ||
"V2_POOL": [ | ||
HexBytes('0xc685db7ecb946f6dd83d43ee07d73ec25761abdc54bc77317d0b810b75ce42a9'), | ||
HexBytes('0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822') | ||
], | ||
"V3_POOL": [ | ||
HexBytes('0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67') | ||
] | ||
} | ||
|
||
LINKS = { | ||
"SCANS": { | ||
"ETHEREUM": { | ||
"MAKER": "https://etherscan.io/address/", | ||
"TRANSACTION": "https://etherscan.io/tx/", | ||
"TOKEN": "https://etherscan.io/token/" | ||
} | ||
}, | ||
"CHARTS": { | ||
"ETHEREUM": { | ||
"DEXSCREENER": "https://dexscreener.com/ethereum/" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from typing import Union | ||
import os | ||
|
||
def add_unit_to_bignumber(bignumber: Union[int, float]): | ||
|
||
bignumber = float(bignumber) | ||
units = ["", "K", "M", "B", "T", "Q", "Qi", "Sx", "Sp", "O", "N", "D"] | ||
unit_index = 0 | ||
|
||
while bignumber >= 1000: | ||
bignumber /= 1000.0 | ||
unit_index += 1 | ||
|
||
formatted_number = "{:.2f}{}".format(bignumber, units[unit_index]) | ||
return formatted_number | ||
|
||
|
||
def load_wallets(blockchain: str): | ||
with open(os.path.join(os.getcwd(), 'wallets.txt'), 'r') as wallets_file: | ||
wallets = [line.strip() for line in wallets_file.readlines() if line.startswith(blockchain)] | ||
blockchain_wallets = [] | ||
for wallet in wallets: | ||
blockchain_wallets.append(wallet.replace(f"{blockchain}:", "")) | ||
return blockchain_wallets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
import asyncio | ||
|
||
from web3 import Web3 | ||
from web3.datastructures import AttributeDict | ||
from web3.exceptions import BlockNotFound, TransactionNotFound | ||
|
||
from web3_multi_provider import MultiProvider | ||
|
||
from multicall import Call, Multicall | ||
|
||
|
||
from OnChain import constants as c | ||
from OnChain import functions as f | ||
|
||
from Discord.functions import send_discord_webhook | ||
from Telegram.functions import send_telegram_message | ||
|
||
|
||
class OnChainBot(): | ||
|
||
|
||
def __init__(self, blockchain: str, verbose: bool): | ||
self.blockchain = blockchain | ||
self.verbose = verbose | ||
self.web3 = Web3(MultiProvider(c.RPCS[blockchain])) | ||
if self.verbose is True: | ||
print(f"[ONCHAINBOT] [{self.blockchain}] [STARTED]") | ||
|
||
|
||
async def relayer(self, swap_infos: dict): | ||
await send_discord_webhook(swap_infos=swap_infos) | ||
await send_telegram_message(swap_infos=swap_infos) | ||
|
||
|
||
async def get_block_number(self): | ||
return self.web3.eth.get_block_number() | ||
|
||
|
||
async def get_block_transactions(self): | ||
while True: | ||
try: | ||
block = self.web3.eth.get_block(self.block_number, full_transactions=True) | ||
transactions = block['transactions'] | ||
return transactions | ||
# If RPC provider not synchronized | ||
except BlockNotFound: | ||
pass | ||
|
||
|
||
async def process_transactions(self): | ||
wallets = f.load_wallets(blockchain=self.blockchain) | ||
filtered_transactions = [ | ||
transaction for transaction in self.transactions | ||
if transaction.get('from', '').lower() in [wallet.lower() for wallet in wallets] | ||
] | ||
swaps_transactions_to_process = [asyncio.create_task(self.process_swaps_transactions(transaction=transaction)) for transaction in filtered_transactions] | ||
await asyncio.gather(*swaps_transactions_to_process) | ||
|
||
|
||
async def process_swaps_transactions(self, transaction: AttributeDict): | ||
transaction_hash = transaction.hash.hex() | ||
# transaction_hash = "0xef8e7334b81df1fdf6c81c23adb5dc6e411c611a215ac3dba44cc0d5271c7457" | ||
while True: | ||
try: | ||
tx_infos = self.web3.eth.get_transaction_receipt(transaction_hash) | ||
break | ||
# If RPC provider not synchronized | ||
except TransactionNotFound: | ||
await asyncio.sleep(1) | ||
|
||
from_address = transaction['from'] | ||
# from_address = "0xae2Fc483527B8EF99EB5D9B44875F005ba1FaE13" | ||
tx_logs = tx_infos['logs'] | ||
|
||
swap_infos = { | ||
"CHAIN": self.blockchain, | ||
"MAKER_INFOS": { | ||
"SHORT_ADDRESS": None | ||
}, | ||
"LINKS": { | ||
"SCAN": { | ||
"MAKER": None, | ||
"TRANSACTION": None | ||
} | ||
}, | ||
"SWAPS": {} | ||
} | ||
|
||
is_tx_swap = False | ||
if tx_infos['status'] == 1: | ||
swap_num = 1 | ||
swap_infos['LINKS']['SCAN']['MAKER'] = c.LINKS['SCANS'][self.blockchain]['MAKER'] + from_address | ||
swap_infos['MAKER_INFOS']['SHORT_ADDRESS'] = from_address[:6] + "..." + from_address[-6:] | ||
swap_infos['LINKS']['SCAN']['TRANSACTION'] = c.LINKS['SCANS'][self.blockchain]['TRANSACTION'] + transaction_hash | ||
for tx_log in tx_logs: | ||
for tx_log_topic in tx_log['topics']: | ||
for pool_type, pool_values in c.SWAPS_HEX.items(): | ||
if tx_log_topic in pool_values: | ||
is_tx_swap = True | ||
swap_data = tx_log['data'][2:] | ||
|
||
pool_address = tx_log['address'] | ||
queries = [ | ||
Call(pool_address, 'token0()(address)', [['token0_address', None]]), | ||
Call(pool_address, 'token1()(address)', [['token1_address', None]]), | ||
] | ||
pool_tokens_infos = await Multicall(queries, _w3=self.web3, require_success=True).coroutine() | ||
token0_address = Web3.toChecksumAddress(pool_tokens_infos['token0_address']) | ||
token1_address = Web3.toChecksumAddress(pool_tokens_infos['token1_address']) | ||
|
||
queries = [ | ||
Call(token0_address, 'symbol()(string)', [['token0_symbol', None]]), | ||
Call(token1_address, 'symbol()(string)', [['token1_symbol', None]]), | ||
Call(token0_address, 'decimals()(uint8)', [['token0_decimals', None]]), | ||
Call(token1_address, 'decimals()(uint8)', [['token1_decimals', None]]) | ||
] | ||
tokens_infos = await Multicall(queries, _w3=self.web3, require_success=True).coroutine() | ||
|
||
if pool_type == "V2_POOL": | ||
amount0_in, amount1_in, amount0_out, amount1_out = [int(swap_data[i:i+64], 16) for i in range(0, 256, 64)] | ||
|
||
if amount0_in != 0: | ||
token0_amount = amount0_in | ||
token1_amount = amount1_out | ||
|
||
token0_symbol = tokens_infos['token0_symbol'] | ||
token1_symbol = tokens_infos['token1_symbol'] | ||
token0_decimals = tokens_infos['token0_decimals'] | ||
token1_decimals = tokens_infos['token1_decimals'] | ||
|
||
elif amount1_in !=0: | ||
token0_amount = amount1_in | ||
token1_amount = amount0_out | ||
|
||
token0_symbol = tokens_infos['token1_symbol'] | ||
token1_symbol = tokens_infos['token0_symbol'] | ||
token0_decimals = tokens_infos['token1_decimals'] | ||
token1_decimals = tokens_infos['token0_decimals'] | ||
|
||
elif pool_type == "V3_POOL": | ||
def hex_to_decimal(hex_string: str): | ||
decimal = int(hex_string, 16) | ||
|
||
if decimal & (1 << (len(hex_string) * 4 - 1)): | ||
decimal = twos_complement(value=decimal, bit_length=len(hex_string) * 4) | ||
return decimal | ||
|
||
def twos_complement(value: int, bit_length: int): | ||
return value - (1 << bit_length) | ||
|
||
amount0 = hex_to_decimal(hex_string=swap_data[0:64]) | ||
amount1 = hex_to_decimal(hex_string=swap_data[64:128]) | ||
|
||
if amount0 > 0: | ||
token0_amount = abs(amount0) | ||
token1_amount = abs(amount1) | ||
|
||
token0_symbol = tokens_infos['token0_symbol'] | ||
token1_symbol = tokens_infos['token1_symbol'] | ||
token0_decimals = tokens_infos['token0_decimals'] | ||
token1_decimals = tokens_infos['token1_decimals'] | ||
elif amount0 < 0: | ||
token0_amount = abs(amount1) | ||
token1_amount = abs(amount0) | ||
|
||
token0_symbol = tokens_infos['token1_symbol'] | ||
token1_symbol = tokens_infos['token0_symbol'] | ||
token0_decimals = tokens_infos['token1_decimals'] | ||
token1_decimals = tokens_infos['token0_decimals'] | ||
|
||
swap_infos['SWAPS'][swap_num] = { | ||
"SYMBOLS": { | ||
"TOKEN0": token0_symbol, | ||
"TOKEN1": token1_symbol | ||
}, | ||
"AMOUNTS": { | ||
"TOKEN0": f.add_unit_to_bignumber(token0_amount/10**token0_decimals), | ||
"TOKEN1": f.add_unit_to_bignumber(token1_amount/10**token1_decimals) | ||
}, | ||
"LINKS": { | ||
"CHART": c.LINKS['CHARTS'][self.blockchain]['DEXSCREENER'] + pool_address | ||
} | ||
} | ||
|
||
swap_num += 1 | ||
|
||
if self.verbose is True: | ||
print(f"\n[{self.blockchain}] [{swap_infos['MAKER_INFOS']['SHORT_ADDRESS']}]\n> {swap_infos['LINKS']['SCAN']['TRANSACTION']}") | ||
for swap_id, swap_info in swap_infos['SWAPS'].items(): | ||
print(">", swap_id, "-", swap_info) | ||
|
||
if is_tx_swap is True: | ||
await self.relayer(swap_infos=swap_infos) | ||
|
||
|
||
async def run(self): | ||
latest_block_number = await self.get_block_number() | ||
|
||
while True: | ||
current_block_number = await self.get_block_number() | ||
|
||
if current_block_number > latest_block_number: | ||
if self.verbose is True: | ||
print(f"\n[{self.blockchain}] [BLOCK {current_block_number}]") | ||
latest_block_number = current_block_number | ||
self.block_number = current_block_number | ||
self.transactions = await self.get_block_transactions() | ||
await self.process_transactions() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import multiprocessing | ||
import asyncio | ||
|
||
from OnChain import constants as c | ||
from OnChain.on_chain_bot import OnChainBot | ||
|
||
|
||
def start_on_chain_bot(blockchain: str): | ||
on_chain_bot = OnChainBot(blockchain=blockchain, verbose=True) | ||
asyncio.run(on_chain_bot.run()) | ||
|
||
|
||
def run_on_chain_bots(): | ||
on_chain_bots_processes = [] | ||
for blockchain in c.RPCS.keys(): | ||
on_chain_bot_process = multiprocessing.Process(target=start_on_chain_bot, args=(blockchain,)) | ||
on_chain_bots_processes.append(on_chain_bot_process) | ||
on_chain_bot_process.start() |
Oops, something went wrong.