diff --git a/jaklis.py b/jaklis.py index eab4a8b..7f6c759 100755 --- a/jaklis.py +++ b/jaklis.py @@ -6,6 +6,7 @@ from shutil import copyfile from dotenv import load_dotenv from duniterpy.key import SigningKey from lib.cesium import CesiumPlus +from lib.gva import GvaApi __version__ = "0.0.2" @@ -21,7 +22,7 @@ load_dotenv(dotenv_path) parser = argparse.ArgumentParser(description="Client CLI pour Cesium+ et Ḡchange") parser.add_argument('-v', '--version', action='store_true', help="Affiche la version actuelle du programme") parser.add_argument('-k', '--key', help="Chemin vers mon trousseau de clé (PubSec)") -parser.add_argument('-n', '--node', help="Adresse du noeud Cesium+ ou Gchange à utiliser") +parser.add_argument('-n', '--node', help="Adresse du noeud Cesium+, Gchange ou Duniter à utiliser") subparsers = parser.add_subparsers(title="Commandes de jaklis", dest="cmd") read_cmd = subparsers.add_parser('read', help="Lecture des messages") @@ -32,6 +33,9 @@ setProfile_cmd = subparsers.add_parser('set', help="Configurer son profile Cesiu eraseProfile_cmd = subparsers.add_parser('erase', help="Effacer son profile Cesium+") like_cmd = subparsers.add_parser('like', help="Voir les likes d'un profile / Liker un profile (option -s NOTE)") unlike_cmd = subparsers.add_parser('unlike', help="Supprimer un like") +pay_cmd = subparsers.add_parser('pay', help="Payer en Ḡ1") +history_cmd = subparsers.add_parser('history', help="Voir l'historique des transactions d'un compte Ḡ1") +balance_cmd = subparsers.add_parser('balance', help="Voir le solde d'un compte Ḡ1") # Messages management read_cmd.add_argument('-n', '--number',type=int, default=3, help="Affiche les NUMBER derniers messages") @@ -64,17 +68,32 @@ like_cmd.add_argument('-p', '--profile', help="Profile cible") like_cmd.add_argument('-s', '--stars', type=int, help="Nombre d'étoile") unlike_cmd.add_argument('-p', '--profile', help="Profile à déliker") +# GVA usage +pay_cmd.add_argument('-p', '--pubkey', help="Destinataire du paiement") +pay_cmd.add_argument('-a', '--amount', type=float, help="Montant de la transaction") +pay_cmd.add_argument('-c', '--comment', default="", help="Commentaire de la transaction") +pay_cmd.add_argument('-m', '--mempool', action='store_true', help="Utilise les sources en Mempool") +pay_cmd.add_argument('-v', '--verbose', action='store_true', help="Affiche le résultat JSON de la transaction") + +history_cmd.add_argument('-p', '--pubkey', help="Clé publique du compte visé") +history_cmd.add_argument('-j', '--json', action='store_true', help="Affiche le résultat en format JSON") +history_cmd.add_argument('--nocolors', action='store_true', help="Affiche le résultat en noir et blanc") + +balance_cmd.add_argument('-p', '--pubkey', help="Clé publique du compte visé") +balance_cmd.add_argument('-m', '--mempool', action='store_true', help="Utilise les sources en Mempool") + + args = parser.parse_args() cmd = args.cmd -if not cmd: - parser.print_help() - sys.exit(1) - if args.version: print(__version__) sys.exit(0) +if not cmd: + parser.print_help() + sys.exit(1) + def createTmpDunikey(): # Generate pseudo-random nonce nonce=[] @@ -88,13 +107,6 @@ def createTmpDunikey(): return keyPath -if args.node: - pod = args.node -else: - pod = os.getenv('POD') -if not pod: - pod="https://g1.data.le-sou.org" - if args.key: dunikey = args.key keyPath = False @@ -114,48 +126,79 @@ if not os.path.isfile(dunikey): # Construct CesiumPlus object -cesium = CesiumPlus(dunikey, pod) - -# Messaging -if cmd == "read": - cesium.read(args.number, args.outbox, args.json) -elif cmd == "send": - if args.fichier: - with open(args.fichier, 'r') as f: - msgT = f.read() - titre = msgT.splitlines(True)[0].replace('\n', '') - msg = ''.join(msgT.splitlines(True)[1:]) - if args.titre: - titre = args.titre - msg = msgT - elif args.titre and args.message: - titre = args.titre - msg = args.message +if cmd in ("read","send","delete","set","get","erase","like","unlike"): + if args.node: + pod = args.node else: - titre = input("Indiquez le titre du message: ") - msg = input("Indiquez le contenu du message: ") + pod = os.getenv('POD') + if not pod: + pod="https://g1.data.le-sou.org" - cesium.send(titre, msg, args.destinataire, args.outbox) + cesium = CesiumPlus(dunikey, pod) -elif cmd == "delete": - cesium.delete(args.id[0], args.outbox) + # Messaging + if cmd == "read": + cesium.read(args.number, args.outbox, args.json) + elif cmd == "send": + if args.fichier: + with open(args.fichier, 'r') as f: + msgT = f.read() + titre = msgT.splitlines(True)[0].replace('\n', '') + msg = ''.join(msgT.splitlines(True)[1:]) + if args.titre: + titre = args.titre + msg = msgT + elif args.titre and args.message: + titre = args.titre + msg = args.message + else: + titre = input("Indiquez le titre du message: ") + msg = input("Indiquez le contenu du message: ") -# Profiles -elif cmd == "set": - cesium.set(args.name, args.description, args.ville, args.adresse, args.position, args.site, args.avatar) -elif cmd == "get": - cesium.get(args.profile, args.avatar) -elif cmd == "erase": - cesium.erase() + cesium.send(titre, msg, args.destinataire, args.outbox) -# Likes -elif cmd == "like": - if args.stars or args.stars == 0: - cesium.like(args.stars, args.profile) + elif cmd == "delete": + cesium.delete(args.id[0], args.outbox) + + # Profiles + elif cmd == "set": + cesium.set(args.name, args.description, args.ville, args.adresse, args.position, args.site, args.avatar) + elif cmd == "get": + cesium.get(args.profile, args.avatar) + elif cmd == "erase": + cesium.erase() + + # Likes + elif cmd == "like": + if args.stars or args.stars == 0: + cesium.like(args.stars, args.profile) + else: + cesium.readLikes(args.profile) + elif cmd == "unlike": + cesium.unLike(args.profile) + +# Construct GVA object +elif cmd in ("pay","history","balance"): + if args.node: + node = args.node else: - cesium.readLikes(args.profile) -elif cmd == "unlike": - cesium.unLike(args.profile) + node = os.getenv('NODE') + if not node: + node="https://g1.librelois.fr/gva" + + if args.pubkey: + destPubkey = args.pubkey + else: + destPubkey = False + + gva = GvaApi(dunikey, node, destPubkey) + + if cmd == "pay": + gva.pay(args.amount, args.comment, args.mempool, args.verbose) + if cmd == "history": + gva.history(args.json, args.nocolors) + if cmd == "balance": + gva.balance(args.mempool) if keyPath: diff --git a/lib/gva.py b/lib/gva.py new file mode 100644 index 0000000..f240290 --- /dev/null +++ b/lib/gva.py @@ -0,0 +1,54 @@ +import sys, re +from lib.natools import get_privkey +from lib.gvaPay import Transaction, PUBKEY_REGEX +from lib.gvaHistory import History +from lib.gvaBalance import Balance + +class GvaApi(): + def __init__(self, dunikey, node, pubkey): + self.dunikey = dunikey + self.node = node + self.pubkey = get_privkey(dunikey, "pubsec").pubkey + if pubkey: + self.destPubkey = pubkey + else: + self.destPubkey = self.pubkey + + try: + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + try: + if not re.match(PUBKEY_REGEX, self.destPubkey) or len(self.destPubkey) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + #################### Payments #################### + + def pay(self, amount, comment, mempool, verbose): + gva = Transaction(self.dunikey, self.node, self.destPubkey, amount, comment, mempool, verbose) + gva.genDoc() + gva.checkTXDoc() + gva.signDoc() + return gva.sendTXDoc() + + def history(self, isJSON=False, noColors=False): + gva = History(self.dunikey, self.node, self.destPubkey) + gva.sendDoc() + transList = gva.parseHistory() + + if isJSON: + transJson = gva.jsonHistory(transList) + print(transJson) + else: + gva.printHistory(transList, noColors) + + def balance(self, useMempool): + gva = Balance(self.dunikey, self.node, self.destPubkey, useMempool) + balanceValue = gva.sendDoc() + print(balanceValue) diff --git a/lib/gvaBalance.py b/lib/gvaBalance.py new file mode 100644 index 0000000..b148e7e --- /dev/null +++ b/lib/gvaBalance.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class Balance: + + def __init__(self, dunikey, node, pubkey, useMempool=False): + self.dunikey = dunikey + self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey + self.useMempool = useMempool + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def sendDoc(self): + # Build balance generation document + queryBuild = gql( + """ + query ($pubkey: String!){ + balance(script: $pubkey) { + amount + } + } + """ + ) + paramsBuild = { + "pubkey": self.pubkey + } + + # Send balance document + try: + balanceResult = self.client.execute(queryBuild, variable_values=paramsBuild) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de récupération du solde:\n" + message + "\n") + sys.exit(1) + + balanceValue = balanceResult['balance']['amount']/100 + # print(balanceValue) + return balanceValue diff --git a/lib/gvaHistory.py b/lib/gvaHistory.py new file mode 100644 index 0000000..a4f6400 --- /dev/null +++ b/lib/gvaHistory.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast, time +from datetime import datetime +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class History: + + def __init__(self, dunikey, node, pubkey): + self.dunikey = dunikey + self.pubkey = pubkey if pubkey else get_privkey(dunikey, "pubsec").pubkey + self.node = node + if not re.match(PUBKEY_REGEX, self.pubkey) or len(self.pubkey) > 45: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + sys.exit(1) + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def sendDoc(self): + # Build history generation document + queryBuild = gql( + """ + query ($pubkey: String!){ + transactionsHistory(pubkey: $pubkey) { + received { + writtenTime + issuers + outputs + comment + } + sent { + writtenTime + issuers + outputs + comment + } + receiving { + issuers + outputs + comment + } + sending { + issuers + outputs + comment + } + } + balance(script: $pubkey) { + amount + base + } + node { + peer { + currency + } + } + currentUd { + amount + base + } + } + """ + ) + paramsBuild = { + "pubkey": self.pubkey + } + + # Send history document + try: + self.historyDoc = self.client.execute(queryBuild, variable_values=paramsBuild) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de récupération de l'historique:\n" + message + "\n") + sys.exit(1) + + + def parseHistory(self): + trans = [] + i = 0 + + currentBase = int(self.historyDoc['currentUd']['base']) + self.UD = self.historyDoc['currentUd']['amount']/100 + + for sens in 'received','sent','receiving','sending': + res = self.historyDoc['transactionsHistory'][sens] + for bloc in res: + output = bloc['outputs'][0] + outPubkey = output.split("SIG(")[1].replace(')','') + if sens in ('received','receiving') or self.pubkey != outPubkey: + trans.append(i) + trans[i] = [] + trans[i].append(sens) + if sens in ('receiving','sending'): + trans[i].append(int(time.time())) + else: + trans[i].append(bloc['writtenTime']) + if sens in ('sent','sending'): + trans[i].append(outPubkey) + amount = int('-' + output.split(':')[0]) + else: + trans[i].append(bloc['issuers'][0]) + amount = int(output.split(':')[0]) + base = int(output.split(':')[1]) + applyBase = base-currentBase + amount = round(amount*pow(10,applyBase)/100, 2) + # if referential == 'DU': amount = round(amount/UD, 2) + trans[i].append(amount) + trans[i].append(round(amount/self.UD, 2)) + trans[i].append(bloc['comment']) + trans[i].append(base) + i += 1 + + # Order transactions by date + trans.sort(key=lambda x: x[1]) + + # Keep only base if there is base change + lastBase = 0 + for i in trans: + if i[6] == lastBase: i[6] = None + else: lastBase = i[6] + + return trans + + def printHistory(self, trans, noColors): + # Get balance + balance = self.historyDoc['balance']['amount']/100 + balanceUD = round(balance/self.UD, 2) + + # Get currency + currency = self.historyDoc['node']['peer']['currency'] + if currency == 'g1': currency = 'Ḡ1' + elif currency == 'g1-test': currency = 'GT' + # if referential == 'DU': currency = 'DU/' + currency.lower() + + # Get terminal size + rows = int(os.popen('stty size', 'r').read().split()[1]) + + # Display history + print('+', end='') + print('-'.center(rows-1, '-')) + if noColors: isBold = isBoldEnd = '' + else: + isBold = '\033[1m' + isBoldEnd = '\033[0m' + print(isBold + "|{: <19} | {: <12} | {: <7} | {: <7} | {: <30}".format(" Date"," De / À"," {0}".format(currency)," DU/{0}".format(currency.lower()),"Commentaire") + isBoldEnd) + print('|', end='') + for t in trans: + if t[0] == "received": color = "green" + elif t[0] == "receiving": color = "yellow" + elif t[0] == "sending": color = "red" + else: color = "blue" + if noColors: + color = None + if t[0] in ('receiving','sending'): + comment = '(EN ATTENTE) ' + t[5] + else: + comment = t[5] + else: + comment = t[5] + + date = datetime.fromtimestamp(t[1]).strftime("%d/%m/%Y à %H:%M") + print('-'.center(rows-1, '-')) + if t[6]: + print('|', end='') + print(' Changement de base : {0} '.format(t[6]).center(rows-1, '#')) + print('|', end='') + print('-'.center(rows-1, '-')) + print('|', end='') + printKey = t[2][0:8] + '\u2026' + t[2][-3:] + if noColors: + print(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, printKey, t[3], t[4], comment)) + else: + print(colored(" {: <18} | {: <12} | {: <7} | {: <7} | {: <30}".format(date, printKey, t[3], t[4], comment), color)) + print('|', end='') + print('-'.center(rows-1, '-')) + print('|', end='') + print(isBold + 'Solde du compte: {0} {1} ({2} DU/{3})'.format(balance, currency, balanceUD, currency.lower()).center(rows-1, ' ') + isBoldEnd) + print('+', end='') + print(''.center(rows-1, '-')) + if not noColors: + print(colored('Reçus', 'green'), '-', colored('En cours de réception', 'yellow'), '-', colored('Envoyé', 'blue'), '-', colored("En cours d'envoi", 'red')) + + return trans + + def jsonHistory(self, transList): + dailyJSON = [] + for i, trans in enumerate(transList): + dailyJSON.append(i) + dailyJSON[i] = {} + dailyJSON[i]['date'] = trans[1] + dailyJSON[i]['pubkey'] = trans[2] + dailyJSON[i]['amount'] = trans[3] + dailyJSON[i]['amountUD'] = trans[4] + dailyJSON[i]['comment'] = trans[5] + + dailyJSON = json.dumps(dailyJSON, indent=2) + # If we want to write JSON to a file + #jsonFile = open("history-{0}.json".format(self.pubkey[0:8]), "w") + #jsonFile.writelines(dailyJSON + '\n') + #jsonFile.close() + return dailyJSON + diff --git a/lib/gvaPay.py b/lib/gvaPay.py new file mode 100644 index 0000000..61f73d2 --- /dev/null +++ b/lib/gvaPay.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 + +import sys, re, os.path, json, ast +from termcolor import colored +from lib.natools import fmt, sign, get_privkey +from gql import gql, Client +from gql.transport.aiohttp import AIOHTTPTransport + +PUBKEY_REGEX = "(?![OIl])[1-9A-Za-z]{42,45}" + +class Transaction: + + def __init__(self, dunikey, node, recipient, amount, comment='', useMempool=False, verbose=False): + self.dunikey = dunikey + self.recipient = recipient + self.amount = int(amount*100) + self.comment = comment + self.issuer = get_privkey(dunikey, "pubsec").pubkey + self.useMempool = useMempool + self.verbose = verbose + self.node = node + self._isChange = False + + try: + if not re.match(PUBKEY_REGEX, recipient) or len(recipient) > 45: + raise ValueError("La clé publique n'est pas au bon format.") + except: + sys.stderr.write("La clé publique n'est pas au bon format.\n") + raise + + + try: + if recipient == self.issuer: + raise ValueError('Le destinataire ne peut pas être vous même.') + except: + sys.stderr.write("Le destinataire ne peut pas être vous même.\n") + raise + + + # Define Duniter GVA node + transport = AIOHTTPTransport(url=node) + self.client = Client(transport=transport, fetch_schema_from_transport=True) + + def genDoc(self): + # Build TX generation document + if self.verbose: print("useMempool:", str(self.useMempool)) + queryBuild = gql( + """ + query ($recipient: String!, $issuer: String!, $amount: Int!, $comment: String!, $useMempool: Boolean!){ genTx( + amount: $amount + comment: $comment + issuer: $issuer + recipient: $recipient + useMempoolSources: $useMempool + ) + } + """ + ) + paramsBuild = { + "recipient": self.recipient, + "issuer": self.issuer, + "amount": int(self.amount), + "comment": self.comment, + "useMempool": self.useMempool + } + + # Send TX document + try: + # self.txDoc = [] + self.txDoc = self.client.execute(queryBuild, variable_values=paramsBuild)['genTx'] + if self.verbose: print(self.txDoc[0]) + return self.txDoc + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de la génération du document:\n" + message + "\n") + raise + + + # Check document + def checkTXDoc(self): + issuerRaw=[];outAmount=[];outPubkey=[];commentRaw=[] + for docs in self.txDoc: + docList = docs.splitlines() + for i, line in enumerate(docList): + if re.search("Issuers:", line): + issuerRaw.append(docList[(i + 1) % len(docList)]) + if re.search("Outputs:", line): + outputRaw = docList[(i + 1) % len(docList)].split(":") + outAmount.append(int(outputRaw[0])) + outPubkey.append(outputRaw[2].split("SIG(")[1].replace(')','')) + if re.search("Comment:", line): + commentRaw.append(line.split(': ', 1)[1]) + + # Check if it's only a change transaction + if all(i == self.issuer for i in outPubkey): + print("Le document contient une transaction de change") + self.isChange = True + # Check validity of the document + elif all(i != self.issuer for i in issuerRaw) or sum(outAmount) != self.amount or all(i != self.recipient for i in outPubkey) or all(i != self.comment for i in commentRaw): + sys.stderr.write(colored("Le document généré est corrompu !\nLe noeud " + self.node + "a peut être un dysfonctionnement.\n", 'red')) + sys.stderr.write(colored(issuerRaw[0] + " envoi " + str(outAmount[0]) + " vers " + outPubkey[0] + " with comment: " + commentRaw[0] + "\n", "yellow")) + raise ValueError('Le document généré est corrompu !') + else: + print("Le document généré est conforme.") + self.isChange = False + return self.txDoc + + def signDoc(self): + # Sign TX documents + signature=[] + self.signedDoc=[] + for i, docs in enumerate(self.txDoc): + signature.append(fmt["64"](sign(docs.encode(), get_privkey(self.dunikey, "pubsec"))[:-len(docs.encode())])) + self.signedDoc.append(docs + signature[i].decode()) + return self.signedDoc + + + def sendTXDoc(self): + # Build TX documents + txResult=[] + for docs in self.signedDoc: + querySign = gql( + """ + mutation ($signedDoc: String!){ tx( + rawTx: $signedDoc + ) { + version + issuers + outputs + } + } + """ + ) + paramsSign = { + "signedDoc": docs + } + + # Send TX Signed document + try: + txResult.append(str(self.client.execute(querySign, variable_values=paramsSign))) + except Exception as e: + message = ast.literal_eval(str(e))["message"] + sys.stderr.write("Echec de la transaction:\n" + message + "\n") + if self.verbose: + sys.stderr.write("Document final:\n" + docs) + raise ValueError(message) + else: + if self.isChange: + self.send() + else: + print(colored("Transaction effectué avec succès !", "green")) + if self.verbose: + print(docs) + break + + return txResult + + def _getIsChange(self): + return self._isChange + def _setIsChange(self, newChange): + if self.verbose: print("_setIsChange: ", str(newChange)) + self._isChange = newChange + if newChange: self.useMempool == True + isChange = property(_getIsChange, _setIsChange) diff --git a/paiements.py b/paiements.py new file mode 100755 index 0000000..dde58da --- /dev/null +++ b/paiements.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 + +""" +ZetCode Tkinter tutorial + +In this example, we use the pack +manager to create a review example. + +Author: Jan Bodnar +Website: www.zetcode.com +""" + +import PySimpleGUI as sg +from lib.gva import GvaApi +import sys, os, threading +from shutil import copyfile +from os.path import join, dirname +from dotenv import load_dotenv +from lib.natools import get_privkey +import requests + +class StdoutRedirector(object): + def __init__(self, text_widget): + self.text_widget = text_widget + + def write(self, s): + self.text_widget.insert('end', s) + self.text_widget.see('end') + + def flush(self): + pass + + +MY_PATH = os.path.realpath(os.path.dirname(sys.argv[0])) + '/' + +# Get variables environment +if not os.path.isfile(MY_PATH + '.env'): + copyfile(MY_PATH + ".env.template",MY_PATH + ".env") +dotenv_path = join(dirname(__file__),MY_PATH + '.env') +load_dotenv(dotenv_path) + +dunikey = os.getenv('DUNIKEY') +if not os.path.isfile(dunikey): + HOME = os.getenv("HOME") + dunikey = HOME + dunikey + if not os.path.isfile(dunikey): + sys.stderr.write('Le fichier de trousseau {0} est introuvable.\n'.format(dunikey)) + sys.exit(1) +node = os.getenv('NODE') +issuer = get_privkey(dunikey, "pubsec").pubkey + + +def ProceedPaiement(recipient, amount, comment): + if not recipient: + raise ValueError("Veuillez indiquer un destinataire de paiement") + elif not amount: + raise ValueError("Veuillez indiquer le montant de la transaction") + + amount = int(float(amount.replace(',','.'))*100) + print("Paiement en cours vers", recipient) + gva = GvaApi(dunikey, node, recipient) + gva.pay(amount, comment, False, False) + + recipient = amount = comment = None + + +sg.theme('DarkGrey2') +layout = [ [sg.Text('Noeud utilisé: ' + node)], + [sg.Text('Votre clé publique: ' + issuer)], + [sg.Text('')], + [sg.Text('Destinataire: '), sg.InputText(size=(55, None),default_text=issuer)], + [sg.Text('Montant: '), sg.InputText(size=(7, None)), sg.Text('Ḡ1')], + [sg.Text('Commentaire:'), sg.InputText(size=(55, None))], + [sg.Button('Envoyer')] ] + +# Create the Window +window = sg.Window('Paiement Ḡ1 - GVA', layout) +# availablePubkeys = requests.get('https://g1-stats.axiom-team.fr/data/wallets-g1.txt') +while True: + try: + event, values = window.read() + if event == sg.WIN_CLOSED: + break + if event == 'Envoyer': + ProceedPaiement(values[0], values[1], values[2]) + except Exception as e: + loc = window.CurrentLocation() + sg.popup(e, title="ERREUR", button_color=('black','red'), location=(loc)) + else: + loc = window.CurrentLocation() + sg.popup(f'Transaction effectué avec succès !', title="Envoyé", location=(loc)) + + +window.close()