280 lines
11 KiB
Python
280 lines
11 KiB
Python
from re import compile, search
|
|
import math
|
|
from time import sleep
|
|
from sys import exit
|
|
import urllib
|
|
|
|
from tabulate import tabulate
|
|
from network_tools import get_request, post_request, get_current_block
|
|
from tools import get_currency_symbol, get_publickey_from_seed, sign_document_from_seed,\
|
|
check_public_key, message_exit
|
|
from auth import auth_method
|
|
from wot import get_uid_from_pubkey
|
|
from money import get_last_ud_value, get_amount_from_pubkey
|
|
from constants import NO_MATCHING_ID
|
|
|
|
|
|
def send_transaction(ep, cli_args):
|
|
"""
|
|
Main function
|
|
"""
|
|
ud = get_last_ud_value(ep)
|
|
amount, output, comment, allSources, outputBackChange = cmd_transaction(cli_args, ud)
|
|
check_transaction_values(comment, output, outputBackChange)
|
|
seed = auth_method(cli_args)
|
|
issuer_pubkey = get_publickey_from_seed(seed)
|
|
|
|
tx_confirmation = transaction_confirmation(ep, cli_args, issuer_pubkey, amount, ud, output, comment)
|
|
if cli_args.contains_switches('yes') or cli_args.contains_switches('y') or \
|
|
input(tabulate(tx_confirmation, tablefmt="fancy_grid") +
|
|
"\nDo you confirm sending this transaction? [yes/no]: ") == "yes":
|
|
generate_and_send_transaction(ep, seed, issuer_pubkey, amount, output, comment, allSources, outputBackChange)
|
|
|
|
|
|
def cmd_transaction(cli_args, ud):
|
|
"""
|
|
Retrieve values from command line interface
|
|
"""
|
|
if not (cli_args.contains_definitions('amount') or cli_args.contains_definitions('amountUD')):
|
|
message_exit("--amount or --amountUD is not set")
|
|
if not cli_args.contains_definitions('output'):
|
|
message_exit("--output is not set")
|
|
|
|
if cli_args.contains_definitions('amount'):
|
|
amount = float(cli_args.get_definition('amount')) * 100
|
|
if cli_args.contains_definitions('amountUD'):
|
|
amount = float(cli_args.get_definition('amountUD')) * ud
|
|
|
|
output = cli_args.get_definition('output')
|
|
comment = cli_args.get_definition('comment') if cli_args.contains_definitions('comment') else ""
|
|
allSources = cli_args.contains_switches('allSources')
|
|
|
|
if cli_args.contains_definitions('outputBackChange'):
|
|
outputBackChange = cli_args.get_definition('outputBackChange')
|
|
else:
|
|
outputBackChange = None
|
|
return amount, output, comment, allSources, outputBackChange
|
|
|
|
|
|
def check_transaction_values(comment, output, outputBackChange):
|
|
checkComment(comment)
|
|
|
|
output = check_public_key(output, True)
|
|
if outputBackChange:
|
|
outputBackChange = check_public_key(outputBackChange, True)
|
|
if output is False or outputBackChange is False:
|
|
exit(1)
|
|
|
|
|
|
def transaction_confirmation(ep, c, issuer_pubkey, amount, ud, output, comment):
|
|
"""
|
|
Generate transaction confirmation
|
|
"""
|
|
tx = list()
|
|
currency_symbol = get_currency_symbol(get_current_block(ep)["currency"])
|
|
tx.append(["amount (" + currency_symbol + ")", amount / 100])
|
|
tx.append(["amount (UD " + currency_symbol + ")", amount / ud])
|
|
tx.append(["from", issuer_pubkey])
|
|
id_from = get_uid_from_pubkey(ep, issuer_pubkey)
|
|
if id_from is not NO_MATCHING_ID:
|
|
tx.append(["from (id)", id_from])
|
|
tx.append(["to", output])
|
|
id_to = get_uid_from_pubkey(ep, output)
|
|
if id_to is not NO_MATCHING_ID:
|
|
tx.append(["to (id)", id_to])
|
|
tx.append(["comment", comment])
|
|
return tx
|
|
|
|
|
|
def generate_and_send_transaction(ep, seed, issuers, AmountTransfered, outputAddr, Comment="", all_input=False, OutputbackChange=None):
|
|
totalamount = get_amount_from_pubkey(ep, issuers)[0]
|
|
if totalamount < AmountTransfered:
|
|
# G1SMS :: Format simple data output
|
|
#message_exit("the account: " + issuers + " don't have enough money for this transaction")
|
|
print("KO|" + str(totalamount / 100))
|
|
sys.exit(1)
|
|
|
|
while True:
|
|
listinput_and_amount = get_list_input_for_transaction(ep, issuers, AmountTransfered, all_input)
|
|
intermediatetransaction = listinput_and_amount[2]
|
|
|
|
if intermediatetransaction:
|
|
totalAmountInput = listinput_and_amount[1]
|
|
"""
|
|
print("Generate Change Transaction")
|
|
print(" - From: " + issuers)
|
|
print(" - To: " + issuers)
|
|
print(" - Amount: " + str(totalAmountInput / 100))
|
|
"""
|
|
transaction = generate_transaction_document(ep, issuers, totalAmountInput, listinput_and_amount, issuers, "Change operation")
|
|
transaction += sign_document_from_seed(transaction, seed) + "\n"
|
|
post_request(ep, "tx/process", "transaction=" + urllib.parse.quote_plus(transaction))
|
|
#print("Change Transaction successfully sent.")
|
|
print("Solde : " + format(totalamount) + " " + get_current_block(ep)["currency"] )
|
|
sleep(1) # wait 1 second before sending a new transaction
|
|
|
|
else:
|
|
"""
|
|
print("Generate Transaction:")
|
|
print(" - From: " + issuers)
|
|
print(" - To: " + outputAddr)
|
|
if all_input:
|
|
print(" - Amount: " + str(listinput_and_amount[1] / 100))
|
|
else:
|
|
print(" - Amount: " + str(AmountTransfered / 100))
|
|
"""
|
|
transaction = generate_transaction_document(ep, issuers, AmountTransfered, listinput_and_amount, outputAddr, Comment, OutputbackChange)
|
|
transaction += sign_document_from_seed(transaction, seed) + "\n"
|
|
|
|
post_request(ep, "tx/process", "transaction=" + urllib.parse.quote_plus(transaction))
|
|
#print("Transaction successfully sent.")
|
|
print( str(AmountTransfered / 100) + "|" + str( (totalamount - AmountTransfered)/100 ) )
|
|
break
|
|
|
|
def generate_transaction_document(ep, issuers, AmountTransfered, listinput_and_amount, outputaddr, Comment="", OutputbackChange=None):
|
|
check_public_key(outputaddr, True)
|
|
if OutputbackChange:
|
|
OutputbackChange = check_public_key(OutputbackChange, True)
|
|
|
|
listinput = listinput_and_amount[0]
|
|
totalAmountInput = listinput_and_amount[1]
|
|
|
|
current_blk = get_current_block(ep)
|
|
currency_name = current_blk["currency"]
|
|
blockstamp_current = str(current_blk["number"]) + "-" + str(current_blk["hash"])
|
|
curentUnitBase = current_blk["unitbase"]
|
|
|
|
if not OutputbackChange:
|
|
OutputbackChange = issuers
|
|
|
|
# if it's not a foreign exchange transaction, we remove units after 2 digits after the decimal point.
|
|
if issuers != outputaddr:
|
|
AmountTransfered = (AmountTransfered // 10 ** curentUnitBase) * 10 ** curentUnitBase
|
|
|
|
# Generate output
|
|
################
|
|
listoutput = []
|
|
# Outputs to receiver (if not himself)
|
|
rest = AmountTransfered
|
|
unitbase = curentUnitBase
|
|
while rest > 0:
|
|
outputAmount = truncBase(rest, unitbase)
|
|
rest -= outputAmount
|
|
if outputAmount > 0:
|
|
outputAmount = int(outputAmount / math.pow(10, unitbase))
|
|
listoutput.append(str(outputAmount) + ":" + str(unitbase) + ":SIG(" + outputaddr + ")")
|
|
unitbase = unitbase - 1
|
|
|
|
# Outputs to himself
|
|
unitbase = curentUnitBase
|
|
rest = totalAmountInput - AmountTransfered
|
|
while rest > 0:
|
|
outputAmount = truncBase(rest, unitbase)
|
|
rest -= outputAmount
|
|
if outputAmount > 0:
|
|
outputAmount = int(outputAmount / math.pow(10, unitbase))
|
|
listoutput.append(str(outputAmount) + ":" + str(unitbase) + ":SIG(" + OutputbackChange + ")")
|
|
unitbase = unitbase - 1
|
|
|
|
# Generate transaction document
|
|
##############################
|
|
|
|
transaction_document = "Version: 10\n"
|
|
transaction_document += "Type: Transaction\n"
|
|
transaction_document += "Currency: " + currency_name + "\n"
|
|
transaction_document += "Blockstamp: " + blockstamp_current + "\n"
|
|
transaction_document += "Locktime: 0\n"
|
|
transaction_document += "Issuers:\n"
|
|
transaction_document += issuers + "\n"
|
|
transaction_document += "Inputs:\n"
|
|
for input in listinput:
|
|
transaction_document += input + "\n"
|
|
transaction_document += "Unlocks:\n"
|
|
for i in range(0, len(listinput)):
|
|
transaction_document += str(i) + ":SIG(0)\n"
|
|
transaction_document += "Outputs:\n"
|
|
for output in listoutput:
|
|
transaction_document += output + "\n"
|
|
transaction_document += "Comment: " + Comment + "\n"
|
|
|
|
return transaction_document
|
|
|
|
|
|
def get_list_input_for_transaction(ep, pubkey, TXamount, allinput=False):
|
|
# real source in blockchain
|
|
sources = get_request(ep, "tx/sources/" + pubkey)["sources"]
|
|
if sources is None:
|
|
return None
|
|
listinput = []
|
|
|
|
for source in sources:
|
|
if source["conditions"] == "SIG(" + pubkey + ")":
|
|
listinput.append(str(source["amount"]) + ":" + str(source["base"]) + ":" + str(source["type"]) + ":" + str(source["identifier"]) + ":" + str(source["noffset"]))
|
|
|
|
# pending source
|
|
history = get_request(ep, "tx/history/" + pubkey + "/pending")["history"]
|
|
pendings = history["sending"] + history["receiving"] + history["pending"]
|
|
|
|
current_blk = get_current_block(ep)
|
|
last_block_number = int(current_blk["number"])
|
|
|
|
# add pending output
|
|
for pending in pendings:
|
|
blockstamp = pending["blockstamp"]
|
|
block_number = int(blockstamp.split("-")[0])
|
|
# if it's not an old transaction (bug in mirror node)
|
|
if block_number >= last_block_number - 3:
|
|
identifier = pending["hash"]
|
|
i = 0
|
|
for output in pending["outputs"]:
|
|
outputsplited = output.split(":")
|
|
if outputsplited[2] == "SIG("+pubkey+")":
|
|
inputgenerated = str(outputsplited[0]) + ":" + str(outputsplited[1]) + ":T:" + identifier + ":" + str(i)
|
|
if inputgenerated not in listinput:
|
|
listinput.append(inputgenerated)
|
|
i += 1
|
|
|
|
# remove input already used
|
|
for pending in pendings:
|
|
blockstamp = pending["blockstamp"]
|
|
block_number = int(blockstamp.split("-")[0])
|
|
# if it's not an old transaction (bug in mirror node)
|
|
if block_number >= last_block_number - 3:
|
|
for input in pending["inputs"]:
|
|
if input in listinput:
|
|
listinput.remove(input)
|
|
|
|
# generate final list source
|
|
listinputfinal = []
|
|
totalAmountInput = 0
|
|
intermediatetransaction = False
|
|
for input in listinput:
|
|
listinputfinal.append(input)
|
|
inputsplit = input.split(":")
|
|
totalAmountInput += int(inputsplit[0]) * 10 ** int(inputsplit[1])
|
|
TXamount -= int(inputsplit[0]) * 10 ** int(inputsplit[1])
|
|
# if more 40 sources, it's an intermediate transaction
|
|
if len(listinputfinal) >= 40:
|
|
intermediatetransaction = True
|
|
break
|
|
if TXamount <= 0 and not allinput:
|
|
break
|
|
if TXamount > 0 and not intermediatetransaction:
|
|
message_exit("Error: you don't have enough money")
|
|
return listinputfinal, totalAmountInput, intermediatetransaction
|
|
|
|
|
|
def checkComment(Comment):
|
|
if len(Comment) > 255:
|
|
message_exit("Error: Comment is too long")
|
|
regex = compile('^[0-9a-zA-Z\ \-\_\:\/\;\*\[\]\(\)\?\!\^\+\=\@\&\~\#\{\}\|\\\<\>\%\.]*$')
|
|
if not search(regex, Comment):
|
|
message_exit("Error: the format of the comment is invalid")
|
|
|
|
|
|
def truncBase(amount, base):
|
|
pow = math.pow(10, base)
|
|
if amount < pow:
|
|
return 0
|
|
return math.trunc(amount / pow) * pow
|