Files
eth-wallet-cli/wallet.py

253 lines
7.8 KiB
Python

import gc
import os
import json
import getpass
from pathlib import Path
from web3 import Web3
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Random import get_random_bytes
from rich import print
from rich.prompt import Prompt
from eth_account import Account
WALLETS_DIR = "wallets"
web3 = None
selected_wallet = None
if not os.path.exists(WALLETS_DIR):
os.makedirs(WALLETS_DIR)
def encrypt_wallet(private_key, password):
salt = get_random_bytes(16)
key = PBKDF2(password, salt, dkLen=32)
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(private_key.encode())
return {
'ciphertext': ciphertext.hex(),
'salt': salt.hex(),
'nonce': cipher.nonce.hex(),
'tag': tag.hex()
}
def decrypt_wallet(data, password):
salt = bytes.fromhex(data['salt'])
key = PBKDF2(password, salt, dkLen=32)
cipher = AES.new(key, AES.MODE_GCM, nonce=bytes.fromhex(data['nonce']))
return cipher.decrypt_and_verify(bytes.fromhex(data['ciphertext']), bytes.fromhex(data['tag'])).decode()
def connect_to_network():
global web3
rpc_url = Prompt.ask("[bold cyan]Введите RPC URL[/]", default="https://ethereum-rpc.publicnode.com")
web3 = Web3(Web3.HTTPProvider(rpc_url))
if web3.is_connected():
print("[green1]Подключено![/]")
else:
print("[red1]Ошибка подключения к сети![/]")
web3 = None
def create_wallet():
Account.enable_unaudited_hdwallet_features()
acct = Account.create()
save_wallet(acct)
show_private_key = Prompt.ask("[bold cyan]Показать приватный ключ?", choices=["y", "n"], default="y")
if show_private_key == 'y':
print(f"[orange1]{acct.key.hex()}")
def import_wallet():
print("[bold cyan]Введите приватный ключ: [/]", end='')
private_key = getpass.getpass(prompt="")
account = Account.from_key(private_key)
save_wallet(account)
def check_password():
return get_private_key() is not None
def get_private_key():
print("[bold cyan]Введите пароль от кошелька: [/]", end='')
password = getpass.getpass(prompt="")
private_key = None
try:
private_key = decrypt_wallet(selected_wallet['data'], password)
del password
except:
print("[red1]Неверный пароль![/]")
return private_key
def delete_wallet():
global selected_wallet
while not check_password():
pass
address = Prompt.ask("[red1]При удалении кошелька доступ к средствам будет потерян, введите номер кошелька для подтверждения операции")
if address != selected_wallet['address']:
print("[red1]Операция отменена")
return
file_path = os.path.join(WALLETS_DIR, selected_wallet['address']+'.json')
if os.path.exists(file_path):
os.remove(file_path)
selected_wallet = None
print(f"[green1]Кошелек удален[/]")
def save_wallet(account):
print("[bold cyan]Введите пароль для шифрования кошелька: [/]", end='')
password = getpass.getpass(prompt="")
encrypted = encrypt_wallet(account.key.hex(), password)
filename = os.path.join(WALLETS_DIR, f"{account.address}.json")
with open(filename, 'w') as f:
json.dump({"address": account.address, "data": encrypted}, f)
print(f"[green1]Кошелек сохранен: {account.address}[/]")
def select_wallet():
global selected_wallet
files = os.listdir(WALLETS_DIR)
if not files:
print("[red1]Нет доступных кошельков.[/]")
return
for i, fname in enumerate(files):
print(f"[orange1][{i+1}] {Path(fname).stem}[/]")
try:
choice = int(Prompt.ask("[bold cyan]Выберите номер кошелька[/]"))
except:
print("[red1]Неверный номер.[/]")
return
with open(os.path.join(WALLETS_DIR, files[choice-1])) as f:
selected_wallet = json.load(f)
def get_balance():
if not selected_wallet:
print("[red1]Кошелек не выбран.[/]")
return
balance = web3.eth.get_balance(selected_wallet['address'])
print(f"[orange1]Баланс: {web3.from_wei(balance, 'ether')} ETH")
def send_transaction():
if not selected_wallet:
print("[red1]Кошелек не выбран.[/]")
return
to = Prompt.ask("[bold cyan]Введите адрес получателя[/]")
value = float(Prompt.ask("[bold cyan]Введите сумму в ETH[/]"))
base_fee = web3.eth.fee_history(1, 'latest')['baseFeePerGas'][-1]
priority_fee_gwei = float(Prompt.ask("[bold cyan]Введите приоритетную комиссию в GWEI[/]", default="2"))
max_priority_fee = web3.to_wei(priority_fee_gwei, 'gwei')
max_fee = base_fee + max_priority_fee * 2
print(f"[yellow]Максимальная комиссия за транзакцию: {float(max_fee) / 10 ** 9} gwei[/]")
tx = {
'chainId': web3.eth.chain_id,
'nonce': web3.eth.get_transaction_count(selected_wallet['address']),
'to': to,
'value': web3.to_wei(value, 'ether'),
'gas': 21000,
'maxFeePerGas': max_fee,
'maxPriorityFeePerGas': max_priority_fee
}
private_key = None
while not private_key:
private_key = get_private_key()
try:
signed = web3.eth.account.sign_transaction(tx, private_key)
del private_key
tx_hash = web3.eth.send_raw_transaction(signed.raw_transaction)
print(f"[green1]Транзакция отправлена! TX Hash: {tx_hash.hex()}[/]")
except Exception as error:
print(f"[red1]{error.message}[/]")
finally:
gc.collect()
def main():
menu = """
[bold blue]Меню:[/]
a. Подключение к сети Ethereum
c. Создание кошелька
w. Выбор кошелька
b. Просмотр баланса
s. Отправка средств
i. Импорт существующего кошелька
d. Удаление кошелька
q. Выход
"""
print(menu)
while True:
try:
choice = Prompt.ask("[slate_blue1]Выберите действие[/]").lower()
if choice == 'a':
connect_to_network()
elif choice == 'c':
create_wallet()
elif choice == 'w':
select_wallet()
elif choice == 'b':
if web3 and selected_wallet:
get_balance()
else:
print("[red1]Необходимо подключиться к сети и выбрать кошелек[/]")
elif choice == 's':
if web3 and selected_wallet:
send_transaction()
else:
print("[red1]Необходимо подключиться к сети и выбрать кошелек[/]")
elif choice == 'i':
import_wallet()
elif choice == 'd':
if not selected_wallet:
select_wallet()
if selected_wallet:
delete_wallet()
else:
print("[red1]Необходимо выбрать кошелек[/]")
elif choice == 'q':
break
# system commands
elif choice == "clear":
if os.name == 'nt':
_ = os.system('cls')
else:
_ = os.system('clear')
print(menu)
except KeyboardInterrupt:
print()
continue
if __name__ == "__main__":
main()