Как стать автором
Обновить

Как я сделал полноценный проект из ничего от одной Bitcoin платёжки на Python

Время на прочтение 7 мин
Количество просмотров 9.4K

Действия происходят в далёком - год назад

В этой статье хочу рассказать, как можно из обычных вещей сделать нечто большее и новое используя 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()

Ну и на этом всё.

Что можно вынести из данной статьи, это то что даже самая невзрачная вещь может послужить вам основой большого и крутого проекта. А так же то что нельзя доверять ни одному клиенту криптокошелька не видев его код, быть может именно он так и устроен, под децентролизавной системой криптовалют может скрываться мнимая централизация с утечкой и хранением ваших средств у какого нибудь индуса :-)

Полный исходный код тут

Теги:
Хабы:
+7
Комментарии 7
Комментарии Комментарии 7

Публикации

Истории

Работа

Data Scientist
66 вакансий
Python разработчик
130 вакансий
QT разработчик
13 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн