@ -31,6 +31,7 @@ import json
import ast
import base64
import operator
import asyncio
from functools import wraps
from decimal import Decimal
from typing import Optional , TYPE_CHECKING
@ -112,8 +113,8 @@ class Commands:
self . _callback = callback
self . lnworker = self . wallet . lnworker if self . wallet else None
def _run ( self , method , args , password_getter , * * kwargs ) :
""" This wrapper is called from the Qt python console. """
def _run ( self , method , args , password_getter = None , * * kwargs ) :
""" This wrapper is called from unit tests and the Qt python console. """
cmd = known_commands [ method ]
password = kwargs . get ( ' password ' , None )
if ( cmd . requires_password and self . wallet . has_password ( )
@ -125,19 +126,22 @@ class Commands:
f = getattr ( self , method )
if cmd . requires_password :
kwargs [ ' password ' ] = password
result = f ( * args , * * kwargs )
coro = f ( * args , * * kwargs )
fut = asyncio . run_coroutine_threadsafe ( coro , asyncio . get_event_loop ( ) )
result = fut . result ( )
if self . _callback :
self . _callback ( )
return result
@command ( ' ' )
def commands ( self ) :
async def commands ( self ) :
""" List of commands """
return ' ' . join ( sorted ( known_commands . keys ( ) ) )
@command ( ' ' )
def create ( self , passphrase = None , password = None , encrypt_file = True , seed_type = None ) :
async def create ( self , passphrase = None , password = None , encrypt_file = True , seed_type = None ) :
""" Create a new wallet.
If you want to be prompted for an argument , type ' ? ' or ' : ' ( concealed )
"""
@ -153,7 +157,7 @@ class Commands:
}
@command ( ' ' )
def restore ( self , text , passphrase = None , password = None , encrypt_file = True ) :
async def restore ( self , text , passphrase = None , password = None , encrypt_file = True ) :
""" Restore a wallet from text. Text can be a seed phrase, a master
public key , a master private key , a list of bitcoin addresses
or bitcoin private keys .
@ -171,7 +175,7 @@ class Commands:
}
@command ( ' wp ' )
def password ( self , password = None , new_password = None ) :
async def password ( self , password = None , new_password = None ) :
""" Change wallet password. """
if self . wallet . storage . is_encrypted_with_hw_device ( ) and new_password :
raise Exception ( " Can ' t change the password of a wallet encrypted with a hw device. " )
@ -181,12 +185,12 @@ class Commands:
return { ' password ' : self . wallet . has_password ( ) }
@command ( ' w ' )
def get ( self , key ) :
async def get ( self , key ) :
""" Return item from wallet storage """
return self . wallet . storage . get ( key )
@command ( ' ' )
def getconfig ( self , key ) :
async def getconfig ( self , key ) :
""" Return a configuration variable. """
return self . config . get ( key )
@ -201,29 +205,29 @@ class Commands:
return value
@command ( ' ' )
def setconfig ( self , key , value ) :
async def setconfig ( self , key , value ) :
""" Set a configuration variable. ' value ' may be a string or a Python expression. """
value = self . _setconfig_normalize_value ( key , value )
self . config . set_key ( key , value )
return True
@command ( ' ' )
def make_seed ( self , nbits = 132 , language = None , seed_type = None ) :
async def make_seed ( self , nbits = 132 , language = None , seed_type = None ) :
""" Create a seed """
from . mnemonic import Mnemonic
s = Mnemonic ( language ) . make_seed ( seed_type , num_bits = nbits )
return s
@command ( ' n ' )
def getaddresshistory ( self , address ) :
async def getaddresshistory ( self , address ) :
""" Return the transaction history of any address. Note: This is a
walletless server query , results are not checked by SPV .
"""
sh = bitcoin . address_to_scripthash ( address )
return self . network . run_from_another_thread ( self . network . get_history_for_scripthash ( sh ) )
return await self . network . get_history_for_scripthash ( sh )
@command ( ' w ' )
def listunspent ( self ) :
async def listunspent ( self ) :
""" List unspent outputs. Returns the list of unspent transaction
outputs in your wallet . """
l = copy . deepcopy ( self . wallet . get_utxos ( ) )
@ -233,15 +237,15 @@ class Commands:
return l
@command ( ' n ' )
def getaddressunspent ( self , address ) :
async def getaddressunspent ( self , address ) :
""" Returns the UTXO list of any address. Note: This
is a walletless server query , results are not checked by SPV .
"""
sh = bitcoin . address_to_scripthash ( address )
return self . network . run_from_another_thread ( self . network . listunspent_for_scripthash ( sh ) )
return await self . network . listunspent_for_scripthash ( sh )
@command ( ' ' )
def serialize ( self , jsontx ) :
async def serialize ( self , jsontx ) :
""" Create a transaction from json inputs.
Inputs must have a redeemPubkey .
Outputs must be a list of { ' address ' : address , ' value ' : satoshi_amount } .
@ -271,7 +275,7 @@ class Commands:
return tx . as_dict ( )
@command ( ' wp ' )
def signtransaction ( self , tx , privkey = None , password = None ) :
async def signtransaction ( self , tx , privkey = None , password = None ) :
""" Sign a transaction. The wallet keys will be used unless a private key is provided. """
tx = Transaction ( tx )
if privkey :
@ -283,20 +287,20 @@ class Commands:
return tx . as_dict ( )
@command ( ' ' )
def deserialize ( self , tx ) :
async def deserialize ( self , tx ) :
""" Deserialize a serialized transaction """
tx = Transaction ( tx )
return tx . deserialize ( force_full_parse = True )
@command ( ' n ' )
def broadcast ( self , tx ) :
async def broadcast ( self , tx ) :
""" Broadcast a transaction to the network. """
tx = Transaction ( tx )
self . network . run_from_another_thread ( self . network . broadcast_transaction ( tx ) )
await self . network . broadcast_transaction ( tx )
return tx . txid ( )
@command ( ' ' )
def createmultisig ( self , num , pubkeys ) :
async def createmultisig ( self , num , pubkeys ) :
""" Create multisig address """
assert isinstance ( pubkeys , list ) , ( type ( num ) , type ( pubkeys ) )
redeem_script = multisig_script ( pubkeys , num )
@ -304,17 +308,17 @@ class Commands:
return { ' address ' : address , ' redeemScript ' : redeem_script }
@command ( ' w ' )
def freeze ( self , address ) :
async def freeze ( self , address ) :
""" Freeze address. Freeze the funds at one of your wallet \' s addresses """
return self . wallet . set_frozen_state_of_addresses ( [ address ] , True )
@command ( ' w ' )
def unfreeze ( self , address ) :
async def unfreeze ( self , address ) :
""" Unfreeze address. Unfreeze the funds at one of your wallet \' s address """
return self . wallet . set_frozen_state_of_addresses ( [ address ] , False )
@command ( ' wp ' )
def getprivatekeys ( self , address , password = None ) :
async def getprivatekeys ( self , address , password = None ) :
""" Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses. """
if isinstance ( address , str ) :
address = address . strip ( )
@ -324,27 +328,27 @@ class Commands:
return [ self . wallet . export_private_key ( address , password ) [ 0 ] for address in domain ]
@command ( ' w ' )
def ismine ( self , address ) :
async def ismine ( self , address ) :
""" Check if address is in wallet. Return true if and only address is in wallet """
return self . wallet . is_mine ( address )
@command ( ' ' )
def dumpprivkeys ( self ) :
async def dumpprivkeys ( self ) :
""" Deprecated. """
return " This command is deprecated. Use a pipe instead: ' electrum listaddresses | electrum getprivatekeys - ' "
@command ( ' ' )
def validateaddress ( self , address ) :
async def validateaddress ( self , address ) :
""" Check that an address is valid. """
return is_address ( address )
@command ( ' w ' )
def getpubkeys ( self , address ) :
async def getpubkeys ( self , address ) :
""" Return the public keys for a wallet address. """
return self . wallet . get_public_keys ( address )
@command ( ' w ' )
def getbalance ( self ) :
async def getbalance ( self ) :
""" Return the balance of your wallet. """
c , u , x = self . wallet . get_balance ( )
l = self . lnworker . get_balance ( ) if self . lnworker else None
@ -358,45 +362,45 @@ class Commands:
return out
@command ( ' n ' )
def getaddressbalance ( self , address ) :
async def getaddressbalance ( self , address ) :
""" Return the balance of any address. Note: This is a walletless
server query , results are not checked by SPV .
"""
sh = bitcoin . address_to_scripthash ( address )
out = self . network . run_from_another_thread ( self . network . get_balance_for_scripthash ( sh ) )
out = await self . network . get_balance_for_scripthash ( sh )
out [ " confirmed " ] = str ( Decimal ( out [ " confirmed " ] ) / COIN )
out [ " unconfirmed " ] = str ( Decimal ( out [ " unconfirmed " ] ) / COIN )
return out
@command ( ' n ' )
def getmerkle ( self , txid , height ) :
async def getmerkle ( self , txid , height ) :
""" Get Merkle branch of a transaction included in a block. Electrum
uses this to verify transactions ( Simple Payment Verification ) . """
return self . network . run_from_another_thread ( self . network . get_merkle_for_transaction ( txid , int ( height ) ) )
return await self . network . get_merkle_for_transaction ( txid , int ( height ) )
@command ( ' n ' )
def getservers ( self ) :
async def getservers ( self ) :
""" Return the list of available servers """
return self . network . get_servers ( )
@command ( ' ' )
def version ( self ) :
async def version ( self ) :
""" Return the version of Electrum. """
from . version import ELECTRUM_VERSION
return ELECTRUM_VERSION
@command ( ' w ' )
def getmpk ( self ) :
async def getmpk ( self ) :
""" Get master public key. Return your wallet \' s master public key """
return self . wallet . get_master_public_key ( )
@command ( ' wp ' )
def getmasterprivate ( self , password = None ) :
async def getmasterprivate ( self , password = None ) :
""" Get master private key. Return your wallet \' s master private key """
return str ( self . wallet . keystore . get_master_private_key ( password ) )
@command ( ' ' )
def convert_xkey ( self , xkey , xtype ) :
async def convert_xkey ( self , xkey , xtype ) :
""" Convert xtype of a master key. e.g. xpub -> ypub """
try :
node = BIP32Node . from_xkey ( xkey )
@ -405,13 +409,13 @@ class Commands:
return node . _replace ( xtype = xtype ) . to_xkey ( )
@command ( ' wp ' )
def getseed ( self , password = None ) :
async def getseed ( self , password = None ) :
""" Get seed phrase. Print the generation seed of your wallet. """
s = self . wallet . get_seed ( password )
return s
@command ( ' wp ' )
def importprivkey ( self , privkey , password = None ) :
async def importprivkey ( self , privkey , password = None ) :
""" Import a private key. """
if not self . wallet . can_import_privkey ( ) :
return " Error: This type of wallet cannot import private keys. Try to create a new wallet with that key. "
@ -431,7 +435,7 @@ class Commands:
return out [ ' address ' ]
@command ( ' n ' )
def sweep ( self , privkey , destination , fee = None , nocheck = False , imax = 100 ) :
async def sweep ( self , privkey , destination , fee = None , nocheck = False , imax = 100 ) :
""" Sweep private keys. Returns a transaction that spends UTXOs from
privkey to a destination address . The transaction is not
broadcasted . """
@ -444,14 +448,14 @@ class Commands:
return tx . as_dict ( ) if tx else None
@command ( ' wp ' )
def signmessage ( self , address , message , password = None ) :
async def signmessage ( self , address , message , password = None ) :
""" Sign a message with a key. Use quotes if your message contains
whitespaces """
sig = self . wallet . sign_message ( address , message , password )
return base64 . b64encode ( sig ) . decode ( ' ascii ' )
@command ( ' ' )
def verifymessage ( self , address , signature , message ) :
async def verifymessage ( self , address , signature , message ) :
""" Verify a signature. """
sig = base64 . b64decode ( signature )
message = util . to_bytes ( message )
@ -480,7 +484,7 @@ class Commands:
return tx
@command ( ' wp ' )
def payto ( self , destination , amount , fee = None , from_addr = None , change_addr = None , nocheck = False , unsigned = False , rbf = None , password = None , locktime = None ) :
async def payto ( self , destination , amount , fee = None , from_addr = None , change_addr = None , nocheck = False , unsigned = False , rbf = None , password = None , locktime = None ) :
""" Create a transaction. """
tx_fee = satoshis ( fee )
domain = from_addr . split ( ' , ' ) if from_addr else None
@ -488,7 +492,7 @@ class Commands:
return tx . as_dict ( )
@command ( ' wp ' )
def paytomany ( self , outputs , fee = None , from_addr = None , change_addr = None , nocheck = False , unsigned = False , rbf = None , password = None , locktime = None ) :
async def paytomany ( self , outputs , fee = None , from_addr = None , change_addr = None , nocheck = False , unsigned = False , rbf = None , password = None , locktime = None ) :
""" Create a multi-output transaction. """
tx_fee = satoshis ( fee )
domain = from_addr . split ( ' , ' ) if from_addr else None
@ -496,7 +500,7 @@ class Commands:
return tx . as_dict ( )
@command ( ' w ' )
def onchain_history ( self , year = None , show_addresses = False , show_fiat = False , show_fees = False ) :
async def onchain_history ( self , year = None , show_addresses = False , show_fiat = False , show_fees = False ) :
""" Wallet onchain history. Returns the transaction history of your wallet. """
kwargs = {
' show_addresses ' : show_addresses ,
@ -515,29 +519,29 @@ class Commands:
return json_encode ( self . wallet . get_detailed_history ( * * kwargs ) )
@command ( ' w ' )
def lightning_history ( self , show_fiat = False ) :
async def lightning_history ( self , show_fiat = False ) :
""" lightning history """
lightning_history = self . wallet . lnworker . get_history ( ) if self . wallet . lnworker else [ ]
return json_encode ( lightning_history )
@command ( ' w ' )
def setlabel ( self , key , label ) :
async def setlabel ( self , key , label ) :
""" Assign a label to an item. Item may be a bitcoin address or a
transaction ID """
self . wallet . set_label ( key , label )
@command ( ' w ' )
def listcontacts ( self ) :
async def listcontacts ( self ) :
""" Show your list of contacts """
return self . wallet . contacts
@command ( ' w ' )
def getalias ( self , key ) :
async def getalias ( self , key ) :
""" Retrieve alias. Lookup in your list of contacts, and for an OpenAlias DNS record. """
return self . wallet . contacts . resolve ( key )
@command ( ' w ' )
def searchcontacts ( self , query ) :
async def searchcontacts ( self , query ) :
""" Search through contacts, return matching entries. """
results = { }
for key , value in self . wallet . contacts . items ( ) :
@ -546,7 +550,7 @@ class Commands:
return results
@command ( ' w ' )
def listaddresses ( self , receiving = False , change = False , labels = False , frozen = False , unused = False , funded = False , balance = False ) :
async def listaddresses ( self , receiving = False , change = False , labels = False , frozen = False , unused = False , funded = False , balance = False ) :
""" List wallet addresses. Returns the list of all addresses in your wallet. Use optional arguments to filter the results. """
out = [ ]
for addr in self . wallet . get_addresses ( ) :
@ -571,13 +575,13 @@ class Commands:
return out
@command ( ' n ' )
def gettransaction ( self , txid ) :
async def gettransaction ( self , txid ) :
""" Retrieve a transaction. """
tx = None
if self . wallet :
tx = self . wallet . db . get_transaction ( txid )
if tx is None :
raw = self . network . run_from_another_thread ( self . network . get_transaction ( txid ) )
raw = await self . network . get_transaction ( txid )
if raw :
tx = Transaction ( raw )
else :
@ -585,7 +589,7 @@ class Commands:
return tx . as_dict ( )
@command ( ' ' )
def encrypt ( self , pubkey , message ) - > str :
async def encrypt ( self , pubkey , message ) - > str :
""" Encrypt a message with a public key. Use quotes if the message contains whitespaces. """
if not is_hex_str ( pubkey ) :
raise Exception ( f " pubkey must be a hex string instead of { repr ( pubkey ) } " )
@ -598,7 +602,7 @@ class Commands:
return encrypted . decode ( ' utf-8 ' )
@command ( ' wp ' )
def decrypt ( self , pubkey , encrypted , password = None ) - > str :
async def decrypt ( self , pubkey , encrypted , password = None ) - > str :
""" Decrypt a message encrypted with a public key. """
if not is_hex_str ( pubkey ) :
raise Exception ( f " pubkey must be a hex string instead of { repr ( pubkey ) } " )
@ -619,7 +623,7 @@ class Commands:
return out
@command ( ' w ' )
def getrequest ( self , key ) :
async def getrequest ( self , key ) :
""" Return a payment request """
r = self . wallet . get_payment_request ( key , self . config )
if not r :
@ -627,12 +631,12 @@ class Commands:
return self . _format_request ( r )
#@command('w')
#def ackrequest(self, serialized):
#async def ackrequest(self, serialized):
# """<Not implemented>"""
# pass
@command ( ' w ' )
def listrequests ( self , pending = False , expired = False , paid = False ) :
async def listrequests ( self , pending = False , expired = False , paid = False ) :
""" List the payment requests you made. """
out = self . wallet . get_sorted_requests ( self . config )
if pending :
@ -648,18 +652,18 @@ class Commands:
return list ( map ( self . _format_request , out ) )
@command ( ' w ' )
def createnewaddress ( self ) :
async def createnewaddress ( self ) :
""" Create a new receiving address, beyond the gap limit of the wallet """
return self . wallet . create_new_address ( False )
@command ( ' w ' )
def getunusedaddress ( self ) :
async def getunusedaddress ( self ) :
""" Returns the first unused address of the wallet, or None if all addresses are used.
An address is considered as used if it has received a transaction , or if it is used in a payment request . """
return self . wallet . get_unused_address ( )
@command ( ' w ' )
def addrequest ( self , amount , memo = ' ' , expiration = None , force = False ) :
async def addrequest ( self , amount , memo = ' ' , expiration = None , force = False ) :
""" Create a payment request, using the first unused address of the wallet.
The address will be considered as used after this operation .
If no payment is received , the address will be considered as unused if the payment request is deleted from the wallet . """
@ -677,7 +681,7 @@ class Commands:
return self . _format_request ( out )
@command ( ' w ' )
def addtransaction ( self , tx ) :
async def addtransaction ( self , tx ) :
""" Add a transaction to the wallet history """
tx = Transaction ( tx )
if not self . wallet . add_transaction ( tx . txid ( ) , tx ) :
@ -686,7 +690,7 @@ class Commands:
return tx . txid ( )
@command ( ' wp ' )
def signrequest ( self , address , password = None ) :
async def signrequest ( self , address , password = None ) :
" Sign payment request with an OpenAlias "
alias = self . config . get ( ' alias ' )
if not alias :
@ -695,31 +699,31 @@ class Commands:
self . wallet . sign_payment_request ( address , alias , alias_addr , password )
@command ( ' w ' )
def rmrequest ( self , address ) :
async def rmrequest ( self , address ) :
""" Remove a payment request """
return self . wallet . remove_payment_request ( address , self . config )
@command ( ' w ' )
def clearrequests ( self ) :
async def clearrequests ( self ) :
""" Remove all payment requests """
for k in list ( self . wallet . receive_requests . keys ( ) ) :
self . wallet . remove_payment_request ( k , self . config )
@command ( ' n ' )
def notify ( self , address : str , URL : str ) :
async def notify ( self , address : str , URL : str ) :
""" Watch an address. Every time the address changes, a http POST is sent to the URL. """
if not hasattr ( self , " _notifier " ) :
self . _notifier = Notifier ( self . network )
self . network . run_from_another_thread ( self . _notifier . start_watching_queue . put ( ( address , URL ) ) )
await self . _notifier . start_watching_queue . put ( ( address , URL ) )
return True
@command ( ' wn ' )
def is_synchronized ( self ) :
async def is_synchronized ( self ) :
""" return wallet synchronization status """
return self . wallet . is_up_to_date ( )
@command ( ' n ' )
def getfeerate ( self , fee_method = None , fee_level = None ) :
async def getfeerate ( self , fee_method = None , fee_level = None ) :
""" Return current suggested fee rate (in sat/kvByte), according to config
settings or supplied parameters .
"""
@ -738,7 +742,7 @@ class Commands:
return self . config . fee_per_kb ( dyn = dyn , mempool = mempool , fee_level = fee_level )
@command ( ' w ' )
def removelocaltx ( self , txid ) :
async def removelocaltx ( self , txid ) :
""" Remove a ' local ' transaction from the wallet, and its dependent
transactions .
"""
@ -755,7 +759,7 @@ class Commands:
self . wallet . storage . write ( )
@command ( ' wn ' )
def get_tx_status ( self , txid ) :
async def get_tx_status ( self , txid ) :
""" Returns some information regarding the tx. For now, only confirmations.
The transaction must be related to the wallet .
"""
@ -768,58 +772,57 @@ class Commands:
}
@command ( ' ' )
def help ( self ) :
async def help ( self ) :
# for the python console
return sorted ( known_commands . keys ( ) )
# lightning network commands
@command ( ' wn ' )
def add_peer ( self , connection_string , timeout = 20 ) :
coro = self . lnworker . add_peer ( connection_string )
self . network . run_from_another_thread ( coro , timeout = timeout )
async def add_peer ( self , connection_string , timeout = 20 ) :
await self . lnworker . add_peer ( connection_string )
return True
@command ( ' wpn ' )
def open_channel ( self , connection_string , amount , channel_push = 0 , password = None ) :
chan = self . lnworker . open_channel ( connection_string , satoshis ( amount ) , satoshis ( channel_push ) , password )
async def open_channel ( self , connection_string , amount , channel_push = 0 , password = None ) :
chan = await self . lnworker . _ open_channel_coroutine ( connection_string , satoshis ( amount ) , satoshis ( channel_push ) , password )
return chan . funding_outpoint . to_str ( )
@command ( ' wn ' )
def lnpay ( self , invoice , attempts = 1 , timeout = 10 ) :
return self . lnworker . pay ( invoice , attempts = attempts , timeout = timeout )
async def lnpay ( self , invoice , attempts = 1 , timeout = 10 ) :
return await self . lnworker . _ pay( invoice , attempts = attempts )
@command ( ' wn ' )
def addinvoice ( self , requested_amount , message ) :
async def addinvoice ( self , requested_amount , message ) :
# using requested_amount because it is documented in param_descriptions
payment_hash = self . lnworker . add_invoice ( satoshis ( requested_amount ) , message )
payment_hash = await self . lnworker . _ add_invoice_coro ( satoshis ( requested_amount ) , message )
invoice , direction , is_paid = self . lnworker . invoices [ bh2u ( payment_hash ) ]
return invoice
@command ( ' w ' )
def nodeid ( self ) :
async def nodeid ( self ) :
listen_addr = self . config . get ( ' lightning_listen ' )
return bh2u ( self . lnworker . node_keypair . pubkey ) + ( ( ' @ ' + listen_addr ) if listen_addr else ' ' )
@command ( ' w ' )
def list_channels ( self ) :
async def list_channels ( self ) :
return list ( self . lnworker . list_channels ( ) )
@command ( ' wn ' )
def dumpgraph ( self ) :
async def dumpgraph ( self ) :
return list ( map ( bh2u , self . lnworker . channel_db . nodes . keys ( ) ) )
@command ( ' n ' )
def inject_fees ( self , fees ) :
async def inject_fees ( self , fees ) :
import ast
self . network . config . fee_estimates = ast . literal_eval ( fees )
self . network . notify ( ' fee ' )
@command ( ' n ' )
def clear_ln_blacklist ( self ) :
async def clear_ln_blacklist ( self ) :
self . network . path_finder . blacklist . clear ( )
@command ( ' w ' )
def lightning_invoices ( self ) :
async def lightning_invoices ( self ) :
from . util import pr_tooltips
out = [ ]
for payment_hash , ( preimage , invoice , is_received , timestamp ) in self . lnworker . invoices . items ( ) :
@ -836,18 +839,18 @@ class Commands:
return out
@command ( ' w ' )
def lightning_history ( self ) :
async def lightning_history ( self ) :
return self . lnworker . get_history ( )
@command ( ' wn ' )
def close_channel ( self , channel_point , force = False ) :
async def close_channel ( self , channel_point , force = False ) :
txid , index = channel_point . split ( ' : ' )
chan_id , _ = channel_id_from_funding_tx ( txid , int ( index ) )
coro = self . lnworker . force_close_channel ( chan_id ) if force else self . lnworker . close_channel ( chan_id )
return self . network . run_from_another_thread ( coro )
return await coro
@command ( ' wn ' )
def get_channel_ctx ( self , channel_point ) :
async def get_channel_ctx ( self , channel_point ) :
""" return the current commitment transaction of a channel """
txid , index = channel_point . split ( ' : ' )
chan_id , _ = channel_id_from_funding_tx ( txid , int ( index ) )