kisstron

KISS-friendly Tron CLI wallet
git clone git://git.luxferre.top/kisstron.git
Log | Files | Refs | README

kisstron.py (22181B)


      1 #!/usr/bin/env python3
      2 # kisstron: a simple to use TRON CLI wallet based on tronpy
      3 #
      4 # Supports:
      5 # * BIP39 mnemonic wallet generation/restoration
      6 # * converting private keys to BIP39 mnemonics and vice versa
      7 # * basic TRX parameters display (balance, energy, bandwidth)
      8 # * basic TRX operations (transfer, freeze for energy, unfreeze)
      9 # * basic TRC20 token operations (balance display, transfer) for select tokens
     10 #   (contract addresses are hardcoded, see KT_TRC20_TOKENS dictionary)
     11 # * ability to select testnets (nile, shasta) if necessary
     12 # * ability to connect to custom mainnet nodes if necessary
     13 # * switching to TronGrid API by providing your own API key
     14 # * offline mode with -o switch and bro[adcast] operation
     15 #
     16 # As of now, the main goal is to be able to operate on TRX and stablecoins.
     17 # For mnemonic phrase generation, only English is currently supported.
     18 #
     19 # See README for more information, run with -h flag for usage instructions
     20 #
     21 # Created by Luxferre in 2024, released into public domain
     22 
     23 import sys, json
     24 import warnings # to suppress NotOpenSSLWarning on BSDs
     25 warnings.filterwarnings("ignore", module="urllib3")
     26 from tronpy import Tron
     27 from tronpy.tron import Transaction
     28 from tronpy.keys import PrivateKey
     29 from tronpy.providers import HTTPProvider
     30 from tronpy.exceptions import (BadAddress, AddressNotFound,
     31   BadKey, ValidationError, TransactionError)
     32 from json.decoder import JSONDecodeError
     33 import mnemonic, eth_utils
     34 
     35 # some global parameters (hardcoded)
     36 KT_TIMEOUT = 20.0 # network operation timeout
     37 KT_FEE_LIMIT = 100000000 # transaction fee limit for mainnet (in Sun)
     38 KT_TEST_FEE_LIMIT = 1000000000 # transaction fee limit for testnets (in Sun)
     39 TRX_SCALE = 1000000.0 # TRX scale (million Suns in 1 TRX)
     40 TG_API_KEY = None # TronGrid API Key (unused by default)
     41 NODE_ADDR = 'http://18.196.99.16:8090' # public Tron node (Germany)
     42 
     43 # TRC20 token definitions (also hardcoded)
     44 # Make sure the mainnet tokens match the contract addresses
     45 # specified on these pages:
     46 # USDT: https://tron.network/usdt
     47 # USDC: https://tron.network/usdc
     48 
     49 KT_TRC20_TOKENS = {
     50   'mainnet': {
     51     'usdt': {
     52       'contract': 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
     53       'ticker': 'USDT',
     54       'show_balance': True
     55     },
     56     'usdc': {
     57       'contract': 'TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8',
     58       'ticker': 'USDC',
     59       'show_balance': True
     60     }
     61   },
     62   'nile': {
     63     'usdt': {
     64       'contract': 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj',
     65       'ticker': 'USDT',
     66       'show_balance': True
     67     },
     68     'usdc': {
     69       'contract': 'TEMVynQpntMqkPxP6wXTW2K7e4sM3cRmWz',
     70       'ticker': 'USDC',
     71       'show_balance': True
     72     }
     73   },
     74   'shasta': {
     75     'usdt': {
     76       'contract': 'TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs',
     77       'ticker': 'USDT',
     78       'show_balance': True
     79     },
     80     'usdc': {
     81       'contract': 'TSdZwNqpHofzP6BsBKGQUWdBeJphLmF6id',
     82       'ticker': 'USDC',
     83       'show_balance': True
     84     }
     85   }
     86 }
     87 
     88 # helper functions
     89 
     90 # print operation result according to the below convention
     91 def presult(resdict: dict):
     92   if 'success' not in resdict:
     93     resdict['success'] = False
     94   if 'message' not in resdict:
     95     resdict['message'] = ''
     96   if resdict['success'] == True:
     97     print ('Success:\n%s\n' % resdict['message'])
     98   else:
     99     print ('Error:\n%s\n' % resdict['message'])
    100 
    101 # confirm transactions
    102 def trconfirm(promptstr: str):
    103   choice = input(promptstr).lower()
    104   if choice != 'yes':
    105     print('Transaction not confirmed, aborting!')
    106     sys.exit(0)
    107 
    108 # instantiate tronpy client
    109 def tronclient(network='mainnet'):
    110   network = network.lower() # lowercase network name
    111   if network == 'mainnet': # use a public node or TronGrid API for mainnet
    112     if TG_API_KEY is not None: # TG_API_KEY must be set
    113       client = Tron(network=network, conf={'fee_limit': KT_FEE_LIMIT},
    114         provider=HTTPProvider(api_key=TG_API_KEY, timeout=KT_TIMEOUT))
    115     else: # NODE_ADDR must be set
    116       client = Tron(network=network, conf={'fee_limit': KT_FEE_LIMIT},
    117         provider=HTTPProvider(NODE_ADDR, timeout=KT_TIMEOUT))
    118   else: # any default API is fine for a testnet
    119     client = Tron(network=network,
    120       conf={'fee_limit': KT_TEST_FEE_LIMIT, 'timeout': KT_TIMEOUT})
    121   return client
    122 
    123 # get TRC20 token balance of an address (in float)
    124 def trc20balance(client: Tron, contractaddr: str, targetaddr: str):
    125   cntr = client.get_contract(contractaddr)
    126   precision = cntr.functions.decimals()
    127   return cntr.functions['balanceOf'](targetaddr) / (10 ** precision)
    128 
    129 # operation definitions
    130 # all operations return a dictionary with the following fields:
    131 # success: True/False
    132 # message: string
    133 # (optional) result: dictionary
    134 
    135 # create a new wallet
    136 # input: network, passphrase, word count (12, 15, 18, 21, 24), derivation path
    137 # output: private key, address, mnemonic
    138 def newwallet(network: str, passphrase: str, wordcount: int, dpath: str):
    139   res = {'success': False, 'message': '', 'result': {}}
    140   if wordcount not in [12, 15, 18, 21, 24]:
    141     res['message'] = 'Invalid word count for mnemonic generation'
    142     return res
    143   client = tronclient(network)
    144   wallet, mnemo = client.generate_address_with_mnemonic(passphrase = passphrase,
    145     num_words = wordcount, language = 'english', account_path = dpath)
    146   wallet['mnemonic'] = mnemo
    147   res['success'] = True
    148   res['result'] = wallet
    149   res['message'] = 'Wallet created, save this info in a secure place!\n\n'
    150   res['message'] += 'Address: %s\n' % wallet['base58check_address']
    151   res['message'] += 'Address (hex): %s\n' % wallet['hex_address']
    152   res['message'] += 'Private key (hex): %s\n' % wallet['private_key']
    153   res['message'] += 'Public key (hex): %s\n' % wallet['public_key']
    154   res['message'] += 'Mnemonic: %s' % wallet['mnemonic']
    155   return res
    156 
    157 # import a wallet
    158 # input: network, mnemonic, passphrase, derivation path
    159 # output: private key, address
    160 def importwallet(network: str, mnemo: str, passphrase: str, dpath: str):
    161   res = {'success': False, 'message': '', 'result': {}}
    162   client = tronclient(network)
    163   try:
    164     wallet = client.generate_address_from_mnemonic(mnemonic = mnemo,
    165       passphrase = passphrase, account_path = dpath)
    166   except (mnemonic.mnemonic.ConfigurationError,
    167           eth_utils.exceptions.ValidationError):
    168     res['message'] = 'Invalid mnemonic phrase!'
    169     return res
    170   wallet['mnemonic'] = mnemo
    171   res['success'] = True
    172   res['result'] = wallet
    173   res['message'] = 'Wallet imported, save this info in a secure place!\n\n'
    174   res['message'] += 'Address: %s\n' % wallet['base58check_address']
    175   res['message'] += 'Address (hex): %s\n' % wallet['hex_address']
    176   res['message'] += 'Private key (hex): %s\n' % wallet['private_key']
    177   res['message'] += 'Public key (hex): %s\n' % wallet['public_key']
    178   res['message'] += 'Mnemonic: %s' % wallet['mnemonic']
    179   return res
    180 
    181 # get account information by Tron address
    182 # input: network, address
    183 # output: info(dictionary)
    184 def infobyaddr(network: str, address: str):
    185   res = {'success': False, 'message': '', 'result': {}}
    186   client = tronclient(network)
    187   try:
    188     rawinfo = client.get_account(address)
    189   except BadAddress:
    190     res['message'] = 'Invalid address! Set the correct one with -addr flag'
    191     return res
    192   except AddressNotFound:
    193     res['message'] = 'Address not found on the blockchain! Set the correct one with -addr flag'
    194     return res
    195   # get bandwidth and energy information
    196   rawresinfo = client.get_account_resource(address)
    197   # some values used in bandwidth calculation
    198   freenetlimit = 0
    199   freenetused = 0
    200   netlimit = 0
    201   netused = 0
    202   if 'freeNetLimit' in rawresinfo:
    203     freenetlimit = rawresinfo['freeNetLimit']
    204   if 'freeNetUsed' in rawresinfo:
    205     freenetlimit = rawresinfo['freeNetUsed']
    206   if 'NetLimit' in rawresinfo:
    207     netlimit = rawresinfo['NetLimit']
    208   if 'NetUsed' in rawresinfo:
    209     netused = rawresinfo['NetUsed']
    210   bandwidth = freenetlimit - freenetused + netlimit - netused
    211   # some values used in energy calculation
    212   energylimit = 0
    213   energyused = 0
    214   if 'EnergyLimit' in rawresinfo:
    215     energylimit = rawresinfo['EnergyLimit']
    216   if 'EnergyUsed' in rawresinfo:
    217     energyused = rawresinfo['EnergyUsed']
    218   # info dictionary received, parse it
    219   info = {
    220     'address': rawinfo['address'],     # wallet address
    221     'trxbalance': rawinfo['balance'],  # TRX wallet balance (in Sun)
    222     'frozen': [],                      # hold frozen TRX info here
    223     'trc20balance': {},                # hold TRC20 token balances here
    224     'bandwidth': bandwidth,            # bandwidth
    225     'energy': energylimit - energyused # energy
    226   }
    227   # iterate over TRX freeze records
    228   for el in rawinfo['frozenV2']:
    229     if 'type' in el and 'amount' in el and el['amount'] > 0:
    230       info['frozen'].append({'type': el['type'], 'amount': el['amount']})
    231   # iterate over hardcoded TRC20 tokens
    232   for tkn in KT_TRC20_TOKENS[network]:
    233     tknobj = KT_TRC20_TOKENS[network][tkn]
    234     if tknobj['show_balance'] == True:
    235       info['trc20balance'][tknobj['ticker']] = trc20balance(client,
    236         tknobj['contract'], address)
    237   res['success'] = True
    238   res['result'] = info
    239   res['message'] = 'Address: %s\nBalance:\n' % info['address']
    240   res['message'] += '%f TRX\n' % (info['trxbalance'] / TRX_SCALE)
    241   for ticker in info['trc20balance']:
    242     res['message'] += '%f %s\n' % (info['trc20balance'][ticker], ticker)
    243   res['message'] += 'Frozen assets:\n'
    244   for el in info['frozen']:
    245     res['message'] += '%s\t%f TRX\n' % (el['type'], el['amount'] / TRX_SCALE)
    246   res['message'] += 'Remaining bandwidth: %s\n' % str(info['bandwidth'])
    247   res['message'] += 'Remaining energy: %s' % str(info['energy'])
    248   return res
    249 
    250 # get own account information by private key
    251 # input: network, private key
    252 # output: info(dictionary)
    253 def infobypk(network: str, pk: str):
    254   res = {'success': False, 'message': '', 'result': {}}
    255   try:
    256     rpk = PrivateKey(bytes.fromhex(pk))
    257   except (BadKey, ValueError):
    258     res['message'] = 'Invalid private key!'
    259     return res
    260   client = tronclient(network)
    261   addr = client.generate_address(rpk)['base58check_address']
    262   return infobyaddr(network, addr)
    263 
    264 # send TRX transaction (and wait for its completion)
    265 # input: network, private key, target address, amount (in TRX)
    266 # output: transaction receipt object
    267 def sendtrx(network: str, pk: str, toaddr: str, amt: float,
    268             offline: bool = False):
    269   res = {'success': False, 'message': '', 'result': {}}
    270   if amt < 1:
    271     res['message'] = 'Nothing to transfer!'
    272     return res
    273   try:
    274     rpk = PrivateKey(bytes.fromhex(pk))
    275   except (BadKey, ValueError):
    276     res['message'] = 'Invalid private key!'
    277     return res
    278   client = tronclient(network)
    279   fromaddr = client.generate_address(rpk)['base58check_address']
    280   # build and sign the transaction
    281   txn = client.trx.transfer(fromaddr, toaddr,
    282           int(amt * TRX_SCALE)).build().sign(rpk)
    283   if offline == True: # generate transaction JSON
    284     res['success'] = True
    285     res['result'] = txn
    286     res['message'] = json.dumps(txn.to_json(), separators=(',', ':'))
    287   else: # send the transaction
    288     try:
    289       receipt = txn.broadcast().wait()
    290     except ValidationError as e:
    291       res['message'] = str(e)
    292       return res
    293     res['success'] = True
    294     res['message'] = 'Sent %f TRX to %s\nTransaction ID %s' % (amt, 
    295                         toaddr, receipt['id'])
    296     res['result'] = receipt
    297   return res
    298 
    299 # send TRC20 token transaction (and wait for its completion)
    300 # input: network, private key, token ticker, target address, amount (in tokens)
    301 # output: transaction receipt object
    302 def sendtrc20(network: str, pk: str, ticker: str, toaddr: str,
    303               amt: float, offline: bool = False):
    304   res = {'success': False, 'message': '', 'result': {}}
    305   ticker = ticker.lower()
    306   if ticker not in KT_TRC20_TOKENS[network]:
    307     res['message'] = 'Token not supported by kisstron for %s!' % network
    308     return res
    309   if amt < 1:
    310     res['message'] = 'Nothing to transfer!'
    311     return res
    312   try:
    313     rpk = PrivateKey(bytes.fromhex(pk))
    314   except (BadKey, ValueError):
    315     res['message'] = 'Invalid private key!'
    316     return res
    317   client = tronclient(network)
    318   fromaddr = client.generate_address(rpk)['base58check_address']
    319   # instantiate the contract
    320   contractaddr = KT_TRC20_TOKENS[network][ticker]['contract']
    321   cntr = client.get_contract(contractaddr)
    322   precision = cntr.functions.decimals()
    323   scaler = 10 ** precision
    324   # build and sign the transaction
    325   txn = cntr.functions.transfer(toaddr,
    326           int(amt * scaler)).with_owner(fromaddr).build().sign(rpk)
    327   if offline == True: # generate transaction JSON
    328     res['success'] = True
    329     res['result'] = txn
    330     res['message'] = json.dumps(txn.to_json(), separators=(',', ':'))
    331   else: # send the transaction
    332     try:
    333       receipt = txn.broadcast().wait()
    334     except ValidationError as e:
    335       res['message'] = str(e)
    336       return res
    337     res['result'] = receipt
    338     if receipt['receipt']['result'] == 'OUT_OF_ENERGY':
    339       res['message'] = 'Not enough energy for transaction!'
    340       res['message'] += '\nTransaction ID %s' % receipt['id']
    341       return res
    342     res['success'] = True
    343     res['message'] = 'Sent %f %s to %s\nTransaction ID %s' % (amt, 
    344                         KT_TRC20_TOKENS[network][ticker]['ticker'],
    345                         toaddr, receipt['id'])
    346   return res
    347 
    348 # freeze TRX for energy (and wait for its completion)
    349 # input: network, private key, amount
    350 # output: transaction receipt object
    351 def freezetrx(network: str, pk: str, amt: float, offline: bool = False):
    352   res = {'success': False, 'message': '', 'result': {}}
    353   if amt < 1:
    354     res['message'] = 'Nothing to freeze!'
    355     return res
    356   try:
    357     rpk = PrivateKey(bytes.fromhex(pk))
    358   except (BadKey, ValueError):
    359     res['message'] = 'Invalid private key!'
    360     return res
    361   client = tronclient(network)
    362   myaddr = client.generate_address(rpk)['base58check_address']
    363   # build and sign the transaction
    364   txn = client.trx.freeze_balance(myaddr, int(amt * TRX_SCALE),
    365           "ENERGY").build().sign(rpk)
    366   if offline == True: # generate transaction JSON
    367     res['success'] = True
    368     res['result'] = txn
    369     res['message'] = json.dumps(txn.to_json(), separators=(',', ':'))
    370   else: # send the transaction
    371     try:
    372       receipt = txn.broadcast().wait()
    373     except ValidationError as e:
    374       res['message'] = str(e)
    375       return res
    376     res['success'] = True
    377     res['message'] = 'Frozen %f TRX at %s\nTransaction ID %s' % (amt, 
    378                         myaddr, receipt['id'])
    379     res['result'] = receipt
    380   return res
    381 
    382 # unfreeze TRX for energy (and wait for its completion)
    383 # input: network, private key, amount
    384 # output: transaction receipt object
    385 def unfreezetrx(network: str, pk: str, amt: float, offline: bool = False):
    386   res = {'success': False, 'message': '', 'result': {}}
    387   if amt < 1:
    388     res['message'] = 'Nothing to unfreeze!'
    389     return res
    390   try:
    391     rpk = PrivateKey(bytes.fromhex(pk))
    392   except (BadKey, ValueError):
    393     res['message'] = 'Invalid private key!'
    394     return res
    395   client = tronclient(network)
    396   myaddr = client.generate_address(rpk)['base58check_address']
    397   # build and sign the transaction
    398   txn = client.trx.unfreeze_balance(owner = myaddr,
    399           unfreeze_balance = int(amt * TRX_SCALE),
    400           resource = "ENERGY").build().sign(rpk)
    401   if offline == True: # generate transaction JSON
    402     res['success'] = True
    403     res['result'] = txn
    404     res['message'] = json.dumps(txn.to_json(), separators=(',', ':'))
    405   else: # send the transaction
    406     try:
    407       receipt = txn.broadcast().wait()
    408     except ValidationError as e:
    409       res['message'] = str(e)
    410       return res
    411     res['success'] = True
    412     res['message'] = 'Unfrozen %f TRX at %s\nTransaction ID %s' % (amt, 
    413                         myaddr, receipt['id'])
    414     res['result'] = receipt
    415   return res
    416 
    417 # Broadcast transaction JSON saved in kisstron offline mode
    418 # input: network, transaction JSON string
    419 # output: transaction receipt object
    420 def broadcastjson(network, tjson):
    421   res = {'success': False, 'message': '', 'result': {}}
    422   # import the transaction from JSON
    423   try:
    424     txn = Transaction.from_json(json.loads(tjson))
    425   except (TypeError, ValidationError, TransactionError,
    426           JSONDecodeError, KeyError):
    427     res['message'] = 'Malformed transaction JSON!'
    428     return res
    429   # instantiate a client for the transaction
    430   txn._client = tronclient(network)
    431   # send the transaction
    432   try:
    433     receipt = txn.broadcast().wait()
    434   except ValidationError as e:
    435     res['message'] = str(e)
    436     return res
    437   except TransactionError as e:
    438     res['message'] = str(e)
    439     return res
    440   res['result'] = receipt
    441   if ('receipt' in receipt and 'result' in receipt['receipt'] 
    442         and receipt['receipt']['result'] == 'OUT_OF_ENERGY'):
    443     res['message'] = 'Not enough energy for transaction!'
    444     res['message'] += '\nTransaction ID %s' % receipt['id']
    445     return res
    446   res['success'] = True
    447   res['message'] = 'Transaction sent\nTransaction ID %s' % receipt['id']
    448   return res
    449 
    450 if __name__ == '__main__': # main app start
    451   from argparse import ArgumentParser
    452   parser = ArgumentParser(description='kisstron: a simple CLI wallet for Tron blockchain', epilog='(c) Luxferre 2024 --- No rights reserved <https://unlicense.org>')
    453   parser.add_argument('op', help='Operation: new, import, info, send, fre[eze], unf[reeze], bro[adcast]')
    454   parser.add_argument('coin', help='Coin to operate on: trx (default), usdt, usdc', default='trx')
    455   parser.add_argument('-net', '--network', default='mainnet', help='(op: all) Network: mainnet (default), nile, shasta')
    456   parser.add_argument('-node', '--tron-node', default=NODE_ADDR, help='(op: all) Custom Tron node HTTP address (default %s)' % NODE_ADDR)
    457   parser.add_argument('-tgkey', '--trongrid-api-key', default=None, help='(op: all) TronGrid API key (default None)')
    458   parser.add_argument('-fee', '--fee-limit', type=float, default=100.0, help='(op: send, fre, unf) Custom fee limit for mainnet transactions, in TRX (default 100)')
    459   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)')
    460   parser.add_argument('-amt', '--amount', type=float, default=0, help='(op: send, fre, unf) Amount to send/freeze/unfreeze')
    461   parser.add_argument('-pk', '--private-key', default=None, help='(op: info, send, fre, unf) Private key for transactions or own information, in hex')
    462   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')
    463   parser.add_argument('-pp', '--passphrase', default='', help='(op: new, import) Mnemonic encryption passphrase (default empty)')
    464   parser.add_argument('-wc', '--words-count', type=int, default=12, help='(op: new) Word count in the mnemonic: 12 (default), 15, 18, 21, 24')
    465   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)")
    466   parser.add_argument('-nc', '--no-confirm', action='store_true', help='(op: send, fre, unf) Do not ask to type "yes" for transaction confirmation (dangerous)')
    467 
    468   args = parser.parse_args()
    469 
    470   # fix all relevant parameters (and lowercase where applicable)
    471   op = args.op.lower()            # operation
    472   coin = args.coin.lower()        # coin/token
    473   tronnet = args.network.lower()  # Tron network (mainnet, nile, shasta)
    474   targetaddr = args.address       # address parameter
    475   amount = float(args.amount)     # amount to send/freeze/unfreeze
    476   pk = args.private_key           # private key (in hex)
    477   passphrase = args.passphrase    # mnemonic encryption passphrase
    478   wcount = int(args.words_count)  # mnemonic word count (for new operation)
    479   dpath = args.derivation_path    # mnemonic derivation path (for new/import)
    480   trans_confirm = True            # ask for transaction confirmation ("yes") 
    481   NODE_ADDR = args.tron_node      # custom Tron node HTTP address
    482   TG_API_KEY = args.trongrid_api_key # optional TronGrid API key
    483   KT_FEE_LIMIT = int(args.fee_limit * TRX_SCALE) # custom fee limit
    484   offline_mode = False            # offline mode
    485   if args.offline == True:
    486     offline_mode = True
    487   if args.no_confirm == True:
    488     trans_confirm = False
    489 
    490   # check the constraints for every action and call appropriate functions
    491 
    492   opres = {} # operation result placeholder
    493   if op == 'new': # new wallet
    494     opres = newwallet(tronnet, passphrase, wcount, dpath)
    495   elif op == 'import': # import wallet
    496     mnemo = input('Your mnemonic phrase: ')
    497     opres = importwallet(tronnet, mnemo, passphrase, dpath)
    498   elif op == 'info': # info on own or foreign address
    499     if offline_mode == True:
    500       print('Info operation is not available in offline mode!')
    501       sys.exit(1)
    502     if pk == None: # public info, address must be set
    503       opres = infobyaddr(tronnet, targetaddr)  
    504     else: # own info, private key must be set
    505       opres = infobypk(tronnet, pk)
    506   elif op == 'send': # send transaction
    507     if trans_confirm == True:
    508       trconfirm('Enter "yes" to confirm sending funds: ')
    509     if coin == 'trx': # send TRX
    510       opres = sendtrx(tronnet, pk, targetaddr, amount, offline_mode)
    511     else: # send one of the predefined TRC20 tokens
    512       opres = sendtrc20(tronnet, pk, coin, targetaddr, amount, offline_mode)
    513   elif op == 'fre' or op == 'freeze': # freeze TRX for energy
    514     if trans_confirm == True:
    515       trconfirm('Enter "yes" to confirm freezing funds: ')
    516     opres = freezetrx(tronnet, pk, amount, offline_mode)
    517   elif op == 'unf' or op == 'unfreeze': # unfreeze TRX
    518     if trans_confirm == True:
    519       trconfirm('Enter "yes" to confirm unfreezing funds: ')
    520     opres = unfreezetrx(tronnet, pk, amount, offline_mode)
    521   elif op == 'bro' or op == 'broadcast': # broadcast signed JSON transaction
    522     if offline_mode == True:
    523       print('Broadcast operation is not available in offline mode!')
    524       sys.exit(1)
    525     transjson = input('Input transaction JSON: ')
    526     if trans_confirm == True:
    527       trconfirm('Enter "yes" to confirm sending the transaction: ')
    528     opres = broadcastjson(tronnet, transjson)
    529   else: # unknown operation
    530     print('Unknown operation!')
    531     print('Run the script with -h parameter to see available options')
    532     sys.exit(1)
    533 
    534   presult(opres) # print out the result