forked from axiom-team/jaklis
Welcome functionnal GVA stuff into jaklis !
parent
fd0f340bc5
commit
8e49a0ce71
139
jaklis.py
139
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:
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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)
|
|
@ -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()
|
Loading…
Reference in New Issue