Offline mode implemented

This commit is contained in:
Luxferre
2024-02-15 10:44:32 +02:00
parent ce7ad5189c
commit db24b89952
2 changed files with 139 additions and 54 deletions
+119 -50
View File
@@ -11,6 +11,7 @@
# * ability to select testnets (nile, shasta) if necessary
# * ability to connect to custom mainnet nodes if necessary
# * switching to TronGrid API by providing your own API key
# * offline mode with -o switch and bro[adcast] operation
#
# As of now, the main goal is to be able to operate on TRX and stablecoins.
# For mnemonic phrase generation, only English is currently supported.
@@ -19,11 +20,14 @@
#
# Created by Luxferre in 2024, released into public domain
import sys
import sys, json
from tronpy import Tron
from tronpy.tron import Transaction
from tronpy.keys import PrivateKey
from tronpy.providers import HTTPProvider
from tronpy.exceptions import BadAddress, AddressNotFound, BadKey, ValidationError
from tronpy.exceptions import (BadAddress, AddressNotFound,
BadKey, ValidationError, TransactionError)
from json.decoder import JSONDecodeError
import mnemonic, eth_utils
# some global parameters (hardcoded)
@@ -258,7 +262,8 @@ def infobypk(network: str, pk: str):
# send TRX transaction (and wait for its completion)
# input: network, private key, target address, amount (in TRX)
# output: transaction receipt object
def sendtrx(network: str, pk: str, toaddr: str, amt: float):
def sendtrx(network: str, pk: str, toaddr: str, amt: float,
offline: bool = False):
res = {'success': False, 'message': '', 'result': {}}
if amt < 1:
res['message'] = 'Nothing to transfer!'
@@ -273,22 +278,27 @@ def sendtrx(network: str, pk: str, toaddr: str, amt: float):
# build and sign the transaction
txn = client.trx.transfer(fromaddr, toaddr,
int(amt * TRX_SCALE)).build().sign(rpk)
# send the transaction
try:
receipt = txn.broadcast().wait()
except ValidationError:
res['message'] = 'Insufficient balance or other validation error!'
return res
res['success'] = True
res['message'] = 'Sent %f TRX to %s\nTransaction ID %s' % (amt,
toaddr, receipt['id'])
res['result'] = receipt
if offline == True: # generate transaction JSON
res['success'] = True
res['result'] = txn
res['message'] = json.dumps(txn.to_json(), separators=(',', ':'))
else: # send the transaction
try:
receipt = txn.broadcast().wait()
except ValidationError as e:
res['message'] = str(e)
return res
res['success'] = True
res['message'] = 'Sent %f TRX to %s\nTransaction ID %s' % (amt,
toaddr, receipt['id'])
res['result'] = receipt
return res
# send TRC20 token transaction (and wait for its completion)
# input: network, private key, token ticker, target address, amount (in tokens)
# output: transaction receipt object
def sendtrc20(network: str, pk: str, ticker: str, toaddr: str, amt: float):
def sendtrc20(network: str, pk: str, ticker: str, toaddr: str,
amt: float, offline: bool = False):
res = {'success': False, 'message': '', 'result': {}}
ticker = ticker.lower()
if ticker not in KT_TRC20_TOKENS[network]:
@@ -312,26 +322,30 @@ def sendtrc20(network: str, pk: str, ticker: str, toaddr: str, amt: float):
# build and sign the transaction
txn = cntr.functions.transfer(toaddr,
int(amt * scaler)).with_owner(fromaddr).build().sign(rpk)
# send the transaction
try:
receipt = txn.broadcast().wait()
except ValidationError:
res['message'] = 'Insufficient balance or other validation error!'
return res
res['result'] = receipt
if receipt['receipt']['result'] == 'OUT_OF_ENERGY':
res['message'] = 'Not enough energy for transaction!'
return res
res['success'] = True
res['message'] = 'Sent %f %s to %s\nTransaction ID %s' % (amt,
KT_TRC20_TOKENS[network][ticker]['ticker'],
toaddr, receipt['id'])
if offline == True: # generate transaction JSON
res['success'] = True
res['result'] = txn
res['message'] = json.dumps(txn.to_json(), separators=(',', ':'))
else: # send the transaction
try:
receipt = txn.broadcast().wait()
except ValidationError as e:
res['message'] = str(e)
return res
res['result'] = receipt
if receipt['receipt']['result'] == 'OUT_OF_ENERGY':
res['message'] = 'Not enough energy for transaction!'
return res
res['success'] = True
res['message'] = 'Sent %f %s to %s\nTransaction ID %s' % (amt,
KT_TRC20_TOKENS[network][ticker]['ticker'],
toaddr, receipt['id'])
return res
# freeze TRX for energy (and wait for its completion)
# input: network, private key, amount
# output: transaction receipt object
def freezetrx(network: str, pk: str, amt: float):
def freezetrx(network: str, pk: str, amt: float, offline: bool = False):
res = {'success': False, 'message': '', 'result': {}}
if amt < 1:
res['message'] = 'Nothing to freeze!'
@@ -346,22 +360,26 @@ def freezetrx(network: str, pk: str, amt: float):
# build and sign the transaction
txn = client.trx.freeze_balance(myaddr, int(amt * TRX_SCALE),
"ENERGY").build().sign(rpk)
# send the transaction
try:
receipt = txn.broadcast().wait()
except ValidationError:
res['message'] = 'Insufficient balance or other validation error!'
return res
res['success'] = True
res['message'] = 'Frozen %f TRX at %s\nTransaction ID %s' % (amt,
myaddr, receipt['id'])
res['result'] = receipt
if offline == True: # generate transaction JSON
res['success'] = True
res['result'] = txn
res['message'] = json.dumps(txn.to_json(), separators=(',', ':'))
else: # send the transaction
try:
receipt = txn.broadcast().wait()
except ValidationError as e:
res['message'] = str(e)
return res
res['success'] = True
res['message'] = 'Frozen %f TRX at %s\nTransaction ID %s' % (amt,
myaddr, receipt['id'])
res['result'] = receipt
return res
# unfreeze TRX for energy (and wait for its completion)
# input: network, private key, amount
# output: transaction receipt object
def unfreezetrx(network: str, pk: str, amt: float):
def unfreezetrx(network: str, pk: str, amt: float, offline: bool = False):
res = {'success': False, 'message': '', 'result': {}}
if amt < 1:
res['message'] = 'Nothing to unfreeze!'
@@ -377,22 +395,58 @@ def unfreezetrx(network: str, pk: str, amt: float):
txn = client.trx.unfreeze_balance(owner = myaddr,
unfreeze_balance = int(amt * TRX_SCALE),
resource = "ENERGY").build().sign(rpk)
if offline == True: # generate transaction JSON
res['success'] = True
res['result'] = txn
res['message'] = json.dumps(txn.to_json(), separators=(',', ':'))
else: # send the transaction
try:
receipt = txn.broadcast().wait()
except ValidationError as e:
res['message'] = str(e)
return res
res['success'] = True
res['message'] = 'Unfrozen %f TRX at %s\nTransaction ID %s' % (amt,
myaddr, receipt['id'])
res['result'] = receipt
return res
# Broadcast transaction JSON saved in kisstron offline mode
# input: network, transaction JSON string
# output: transaction receipt object
def broadcastjson(network, tjson):
res = {'success': False, 'message': '', 'result': {}}
# import the transaction from JSON
try:
txn = Transaction.from_json(json.loads(tjson))
except (TypeError, ValidationError, TransactionError,
JSONDecodeError, KeyError):
res['message'] = 'Malformed transaction JSON!'
return res
# instantiate a client for the transaction
txn._client = tronclient(network)
# send the transaction
try:
receipt = txn.broadcast().wait()
except ValidationError:
res['message'] = 'Insufficient balance or other validation error!'
except ValidationError as e:
res['message'] = str(e)
return res
except TransactionError as e:
res['message'] = str(e)
return res
res['result'] = receipt
if ('receipt' in receipt and 'result' in receipt['receipt']
and receipt['receipt']['result'] == 'OUT_OF_ENERGY'):
res['message'] = 'Not enough energy for transaction!'
return res
res['success'] = True
res['message'] = 'Unfrozen %f TRX at %s\nTransaction ID %s' % (amt,
myaddr, receipt['id'])
res['result'] = receipt
res['message'] = 'Transaction sent\nTransaction ID %s' % receipt['id']
return res
if __name__ == '__main__': # main app start
from argparse import ArgumentParser
parser = ArgumentParser(description='kisstron: a simple CLI wallet for Tron blockchain', epilog='(c) Luxferre 2024 --- No rights reserved <https://unlicense.org>')
parser.add_argument('op', help='Operation: new, import, info, send, fre[eze], unf[reeze]')
parser.add_argument('op', help='Operation: new, import, info, send, fre[eze], unf[reeze], bro[adcast]')
parser.add_argument('coin', help='Coin to operate on: trx (default), usdt, usdc', default='trx')
parser.add_argument('-net', '--network', default='mainnet', help='(op: all) Network: mainnet (default), nile, shasta')
parser.add_argument('-node', '--tron-node', default=NODE_ADDR, help='(op: all) Custom Tron node HTTP address (default %s)' % NODE_ADDR)
@@ -400,6 +454,7 @@ if __name__ == '__main__': # main app start
parser.add_argument('-addr', '--address', default=None, help='(op: info, send) Tron address to send to or display information about (-pk overrides this to display own info)')
parser.add_argument('-amt', '--amount', type=float, default=0, help='(op: send, fre, unf) Amount to send/freeze/unfreeze')
parser.add_argument('-pk', '--private-key', default=None, help='(op: info, send, fre, unf) Private key for transactions or own information, in hex')
parser.add_argument('-o', '--offline', action='store_true', help='(op: send, fre, unf) Build and sign the transaction in offline mode and output its JSON')
parser.add_argument('-pp', '--passphrase', default='', help='(op: new, import) Mnemonic encryption passphrase (default empty)')
parser.add_argument('-wc', '--words-count', type=int, default=12, help='(op: new) Word count in the mnemonic: 12 (default), 15, 18, 21, 24')
parser.add_argument('-dp', '--derivation-path', default="m/44'/195'/0'/0/0", help="(op: new, import) BIP-39 derivation path for mnemonic (default is m/44'/195'/0'/0/0)")
@@ -420,6 +475,9 @@ if __name__ == '__main__': # main app start
trans_confirm = True # ask for transaction confirmation ("yes")
NODE_ADDR = args.tron_node # custom Tron node HTTP address
TG_API_KEY = args.trongrid_api_key # optional TronGrid API key
offline_mode = False # offline mode
if args.offline == True:
offline_mode = True
if args.no_confirm == True:
trans_confirm = False
@@ -432,6 +490,9 @@ if __name__ == '__main__': # main app start
mnemo = input('Your mnemonic phrase: ')
opres = importwallet(tronnet, mnemo, passphrase, dpath)
elif op == 'info': # info on own or foreign address
if offline_mode == True:
print('Info operation is not available in offline mode!')
sys.exit(1)
if pk == None: # public info, address must be set
opres = infobyaddr(tronnet, targetaddr)
else: # own info, private key must be set
@@ -440,17 +501,25 @@ if __name__ == '__main__': # main app start
if trans_confirm == True:
trconfirm('Enter "yes" to confirm sending funds: ')
if coin == 'trx': # send TRX
opres = sendtrx(tronnet, pk, targetaddr, amount)
opres = sendtrx(tronnet, pk, targetaddr, amount, offline_mode)
else: # send one of the predefined TRC20 tokens
opres = sendtrc20(tronnet, pk, coin, targetaddr, amount)
opres = sendtrc20(tronnet, pk, coin, targetaddr, amount, offline_mode)
elif op == 'fre' or op == 'freeze': # freeze TRX for energy
if trans_confirm == True:
trconfirm('Enter "yes" to confirm freezing funds: ')
opres = freezetrx(tronnet, pk, amount)
opres = freezetrx(tronnet, pk, amount, offline_mode)
elif op == 'unf' or op == 'unfreeze': # unfreeze TRX
if trans_confirm == True:
trconfirm('Enter "yes" to confirm unfreezing funds: ')
opres = unfreezetrx(tronnet, pk, amount)
opres = unfreezetrx(tronnet, pk, amount, offline_mode)
elif op == 'bro' or op == 'broadcast': # broadcast signed JSON transaction
if offline_mode == True:
print('Broadcast operation is not available in offline mode!')
sys.exit(1)
transjson = input('Input transaction JSON: ')
if trans_confirm == True:
trconfirm('Enter "yes" to confirm sending the transaction: ')
opres = broadcastjson(tronnet, transjson)
else: # unknown operation
print('Unknown operation!')
print('Run the script with -h parameter to see available options')