Как я сделал полноценный проект из ничего от одной Bitcoin платёжки на Python
Действия происходят в далёком — год назад
В этой статье хочу рассказать, как можно из обычных вещей сделать нечто большее и новое используя python, qt и bitcoin библиотеки.
С чего всё начиналось
В то время мне необходимо было сделать приём платежей в криптовалюте и в частности Bitcoin, для одного сервиса на заказ, дабы пользователи могли пополнять личный счёт, оплачивать товары и при необходимости выводить их.
Как работали такие платёжные системы я тогда не знал и понятия не имел как реализовать такую вещь, не говоря уже о знаниях о том из чего состоит блок, что такое цепочка иерархии, приватный ключ и так далее.
итоговая работа
Мне удалось найти пример того как реализована биткоин оплата в боте телеграм, так же на python и так же из одной статьи Habr.
Её я и взял за основу и потратил не мало времени что бы изучить базу всего строения.
Как работает блокчейн здесь описано не будет, таких статьей не мало. здесь будет то как я нашёл идею от простой задачи.
И так, вот как выглядит и работает платёжный приём на Bitcoin он же и получение средств в нашем клиенте на qt
для работы нам понадобиться библиотеки которые всё делают за нас ну или на половину…
в моём случае используется pywallet, но есть так же bitcoiblib, py-hd-wallet, hdwallet и другие неплохие либы, у каждого есть свои недостатки и плюсы, наиболее хорошо показали себя в работе hdwallet и pywallet, для создания иерархически детерминированного кошелька тобишь дочернего адреса для вашего кошелька.
# Индекс адреса, индекс определяет глубино адреса, так как на одной seed фразе может быть несколько адресов
index = 0 # стартуем от нулевого индекса
# наша seed фраза или мнемоническая фраза, стандартная базовая абстракция при построении адреса, на ней всё осваивается
seed = 'one two one two one two ...'
# далее генерируем мастер ключ на основе мненоники
master_key = wallet.HDPrivateKey.master_key_from_mnemonic(seed)
# и далее по путям генерируем адрес стандарта BIP 44 и проходимся по иирархии от ключа до паблик и приват ключа
root_keys = wallet.HDKey.from_path(master_key, "m/44'/0'/0'/0")[-1].public_key.to_b58check()
xpublic_key = (root_keys)
# Получаем наш дочерний адрес
address = Wallet.deserialize(xpublic_key, network='BTC').get_child(index,is_prime=False).to_address()
rootkeys_wif = wallet.HDKey.from_path(master_key, f"m/44'/0'/0'/0/{index}")[-1] # <- это для того что бы генерировать каждый раз новый адрес
xprivatekey = rootkeys_wif.to_b58check()
# Wallet Import Format он служит для осуществления транзакций
wif = Wallet.deserialize(xprivatekey, network='BTC').export_to_wif()
вот так выглядт функция создания всех необходимых сущностей для дальнейшей работы
такая же по логике функция с библиотекой hdwallet.
index = [0, 1, 2]
bip44_btc = BIP44HDWallet(cryptocurrency=BitcoinMainnet)
bip44_btc.from_mnemonic(mnemonic=seedphrase, language="english")
bip44_btc.clean_derivation()
bip44_derivation: BIP44Derivation = BIP44Derivation(cryptocurrency=BitcoinMainnet, account=0, change=False,address=index[0])
bip44_btc.from_path(path=bip44_derivation)
wif_0_44 = bip44_btc.wif()
key = Key(wif=wif_0_44)
balance_1_for_btc = key.get_balance('usd')
addressinput_0_btc = bip44_btc.address()
С иирархией понятно. Идея
После реализации платёжной системы с приёмом биткоин, мне тут же пришла банальная и в то же время интересная идея, что если просто платёжку обернуть в приложение и сделать из этого Bitcoin Wallet. Да ещё и с контролем всех средств пользователей
Всё относительно просто, для интерфейса Qt, заворачиваем наш код в логику и готово.
Кто не понял
Имея нашу функцию мы на основе нашей же мнемонической фразы генерируем каждый раз новые адреса, ключи и прочее, с помощью которых мы можем получать и отправлять средства, но средства как раз будут храниться у нас на кошельке, в данном случае мы каким то образом централизуем систему и выступаем в роли банка.
Давай код!
Начинаем по стандарту PyQt5 с интерфейса
окно регистрациидомашнее окно
После интерфейса начнём его описывать.
import sqlite3
import bit
import clipboard
import qrcode as qrcode
import requests
from PyQt5 import QtWidgets
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QDialog, QApplication, QMainWindow, QMessageBox
from PyQt5.uic import loadUi
from bs4 import BeautifulSoup
from pywallet import wallet
from pywallet.utils import *
Импорт нужных нам библиотек.
class LoginScreen(QMainWindow):
def __init__(self):
super(LoginScreen, self).__init__()
loadUi("GUI/atom.ui",self)
self.passwordline.setEchoMode(QtWidgets.QLineEdit.Password)
self.registrnow.clicked.connect(self.gotoReg)
self.Loginone.clicked.connect(self.loginfunction)
def gotoReg(self):
Reg = RegScreen()
widget.addWidget(Reg)
widget.setCurrentIndex(widget.currentIndex()+1)
def loginfunction(self):
password = self.passwordline.text()
if len(password) == 0:
self.error.setText("Please input all fields.")
else:
db = sqlite3.connect("wallet.db")
curs = db.cursor()
curs.execute(f"SELECT * FROM users WHERE Password = '{password}'")
if not curs.fetchone():
self.error.setText("Incorrect password")
else:
fillprofile = Profile()
widget.addWidget(fillprofile)
widget.setCurrentIndex(widget.currentIndex() + 1)
класс входа в приложение, в конструкторе класса подгружаем наш ui и определяем кнопки
далее обычная минимальная функции проверки пароля и входа в систему с использованием Sqlite3.
В идеале
Конечно для продакшена этот проект не подходит, реализация была как пет-проект и в идеале бы конечно ui так не подгружать для каждого отдельный, можно было сделать stackedWidget и по нему переходить, и можно было бы обойтись в таком случае одним классом или двумя, подключить Postgresql какой нибудь, да и вообще софт в стол лучше на плюсах или шарпе.
class Profile(QDialog):
def __init__(self):
super(Profile, self).__init__()
loadUi("GUI/main.ui", self)
self.btcpricee()
self.balanceuser()
self.logout.clicked.connect(self.loginout)
self.receive.clicked.connect(self.receivebtcaddress)
self.receive_2.clicked.connect(self.receivebtcaddress)
self.walletbutton.clicked.connect(self.walletent)
self.sendd.clicked.connect(self.sendbtc)
self.sendd_2.clicked.connect(self.sendbtc)
self.settings.clicked.connect(self.settinges)
def balanceuser(self):
try:
db = sqlite3.connect("wallet.db")
curs = db.cursor()
sss = "SELECT btc_address FROM users"
curs.execute(sss)
adres = curs.fetchone()
url = f'https://www.blockchain.com/btc/address/{adres[0]}'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0'}
Response = requests.get(url, headers=headers)
wallet = BeautifulSoup(Response.content, 'html.parser')
convert = wallet.findAll("span", {"class": "sc-16b9dsl-1","class": "ZwupP", "class": "u3ufsr-0", "class": "eQTRKC"})
rx = convert[6].text
self.balance.setText(str(rx))
response = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json')
data = response.json()
x = data["bpi"]["USD"]["rate_float"]
xx = rx.rstrip('BTC')
us = (x * float(xx))
self.usdbalance.setText(str(us))
except:
self.balance.setText(str('Loading...'))
self.usdbalance.setText(str('---'))
функция индексирования баланса, достаём из бд адрес и просто парсим его и далее через setText выводим
class RegScreen(QDialog):
def __init__(self):
super(RegScreen, self).__init__()
loadUi("GUI/reg.ui",self)
self.loginperehod.clicked.connect(self.gotoLogin)
self.signupreg.clicked.connect(self.registrationfunction)
def gotoLogin(self):
Log = LoginScreen()
widget.addWidget(Log)
widget.setCurrentIndex(widget.currentIndex() + 1)
def registrationfunction(self):
password_reg = self.passwordreg.text()
repl_password = self.relacepasswordreg.text()
if repl_password != password_reg:
self.errorreg1_2.setText("Password does not match")
else:
if len(password_reg) == 0:
self.errorreg1.setText("To register, you need to fill in all the fields")
elif len(password_reg) < 8:
self.passerror.setText('Password cannot be less than 8 characters')
else:
db = sqlite3.connect('wallet.db')
curs = db.cursor()
curs.execute('''CREATE TABLE IF NOT EXISTS users (
Password TEXT,
balance INTEGER,
btc_address,
wif TEXT,
btc_send TEXT
)''')
db.commit()
curs.execute("SELECT Password FROM users")
if curs.fetchone() is None:
index = 0
seed = ''
master_key = wallet.HDPrivateKey.master_key_from_mnemonic(seed)
root_keys = wallet.HDKey.from_path(master_key, "m/44'/0'/0'/0")[-1].public_key.to_b58check()
xpublic_key = (root_keys)
address = Wallet.deserialize(xpublic_key, network='BTC').get_child(index,is_prime=False).to_address()
rootkeys_wif = wallet.HDKey.from_path(master_key, f"m/44'/0'/0'/0/{index}")[-1]
xprivatekey = rootkeys_wif.to_b58check()
wif = Wallet.deserialize(xprivatekey, network='BTC').export_to_wif()
curs.execute("INSERT INTO users VALUES (?, ?, ?, ?, ?)", (password_reg, 0, address, wif, 0))
img = qrcode.make(address)
img.save('GUI/qr.png')
db.commit()
self.successreg.setText("You have successfully registered!")
else:
error = QMessageBox()
error.setWindowTitle('Big request to create an account')
error.setText('Sorry, you cannot create another account.')
error.setIcon(QMessageBox.Warning)
error.setDefaultButton(QMessageBox.Ok)
error.exec_()
exit()
Класс регистрации с его функциями, как видим при регистрации мы используем ту самую функцию которую я описывал в начале статьи, далее мы заносим наши данные в бд.
Как видите всё достаточно просто, мы взяли биткоин функцию и засунули её в Qt и так же как и с платёжкой мы может получать и отображать баланс и выводить за счёт Wallet Import Format или сокр.WIF и по разному манипулировать монетами ибо все они хранятся на нашем кошельке через нашу сид фразу, храним всё в бд, а управление через простой интерфейс. можно было бы сделать мультивалютность и использовать web3 тогда например генерация данных для ETH выглядела бы так
index = [0, 1, 2]
bip44 = BIP44HDWallet(cryptocurrency=EthereumMainnet)
bip44.from_mnemonic(mnemonic=seedphrase, language="english")
bip44.clean_derivation()
bip44_derivation: BIP44Derivation = BIP44Derivation(cryptocurrency=EthereumMainnet, account=0, change=False,address=index[0])
bip44.from_path(path=bip44_derivation)
private_key_0 = '0x' + bip44.private_key()
addressinput_0 = bip44.address()
Ну и на этом всё.
Что можно вынести из данной статьи, это то что даже самая невзрачная вещь может послужить вам основой большого и крутого проекта. А так же то что нельзя доверять ни одному клиенту криптокошелька не видев его код, быть может именно он так и устроен, под децентролизавной системой криптовалют может скрываться мнимая централизация с утечкой и хранением ваших средств у какого нибудь индуса :-)
Полный исходный код тут