electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commands.py (62776B)


      1 #!/usr/bin/env python
      2 #
      3 # Electrum - lightweight Bitcoin client
      4 # Copyright (C) 2011 thomasv@gitorious
      5 #
      6 # Permission is hereby granted, free of charge, to any person
      7 # obtaining a copy of this software and associated documentation files
      8 # (the "Software"), to deal in the Software without restriction,
      9 # including without limitation the rights to use, copy, modify, merge,
     10 # publish, distribute, sublicense, and/or sell copies of the Software,
     11 # and to permit persons to whom the Software is furnished to do so,
     12 # subject to the following conditions:
     13 #
     14 # The above copyright notice and this permission notice shall be
     15 # included in all copies or substantial portions of the Software.
     16 #
     17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
     21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
     22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     24 # SOFTWARE.
     25 
     26 import sys
     27 import datetime
     28 import copy
     29 import argparse
     30 import json
     31 import ast
     32 import base64
     33 import operator
     34 import asyncio
     35 import inspect
     36 from functools import wraps, partial
     37 from itertools import repeat
     38 from decimal import Decimal
     39 from typing import Optional, TYPE_CHECKING, Dict, List
     40 
     41 from .import util, ecc
     42 from .util import (bfh, bh2u, format_satoshis, json_decode, json_normalize,
     43                    is_hash256_str, is_hex_str, to_bytes)
     44 from . import bitcoin
     45 from .bitcoin import is_address,  hash_160, COIN
     46 from .bip32 import BIP32Node
     47 from .i18n import _
     48 from .transaction import (Transaction, multisig_script, TxOutput, PartialTransaction, PartialTxOutput,
     49                           tx_from_any, PartialTxInput, TxOutpoint)
     50 from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
     51 from .synchronizer import Notifier
     52 from .wallet import Abstract_Wallet, create_new_wallet, restore_wallet_from_text, Deterministic_Wallet
     53 from .address_synchronizer import TX_HEIGHT_LOCAL
     54 from .mnemonic import Mnemonic
     55 from .lnutil import SENT, RECEIVED
     56 from .lnutil import LnFeatures
     57 from .lnutil import ln_dummy_address
     58 from .lnpeer import channel_id_from_funding_tx
     59 from .plugin import run_hook
     60 from .version import ELECTRUM_VERSION
     61 from .simple_config import SimpleConfig
     62 from .invoices import LNInvoice
     63 from . import submarine_swaps
     64 
     65 
     66 if TYPE_CHECKING:
     67     from .network import Network
     68     from .daemon import Daemon
     69 
     70 
     71 known_commands = {}  # type: Dict[str, Command]
     72 
     73 
     74 class NotSynchronizedException(Exception):
     75     pass
     76 
     77 
     78 def satoshis(amount):
     79     # satoshi conversion must not be performed by the parser
     80     return int(COIN*Decimal(amount)) if amount not in ['!', None] else amount
     81 
     82 def format_satoshis(x):
     83     return str(Decimal(x)/COIN) if x is not None else None
     84 
     85 
     86 class Command:
     87     def __init__(self, func, s):
     88         self.name = func.__name__
     89         self.requires_network = 'n' in s
     90         self.requires_wallet = 'w' in s
     91         self.requires_password = 'p' in s
     92         self.description = func.__doc__
     93         self.help = self.description.split('.')[0] if self.description else None
     94         varnames = func.__code__.co_varnames[1:func.__code__.co_argcount]
     95         self.defaults = func.__defaults__
     96         if self.defaults:
     97             n = len(self.defaults)
     98             self.params = list(varnames[:-n])
     99             self.options = list(varnames[-n:])
    100         else:
    101             self.params = list(varnames)
    102             self.options = []
    103             self.defaults = []
    104 
    105         # sanity checks
    106         if self.requires_password:
    107             assert self.requires_wallet
    108         for varname in ('wallet_path', 'wallet'):
    109             if varname in varnames:
    110                 assert varname in self.options
    111         assert not ('wallet_path' in varnames and 'wallet' in varnames)
    112         if self.requires_wallet:
    113             assert 'wallet' in varnames
    114 
    115 
    116 def command(s):
    117     def decorator(func):
    118         global known_commands
    119         name = func.__name__
    120         known_commands[name] = Command(func, s)
    121         @wraps(func)
    122         async def func_wrapper(*args, **kwargs):
    123             cmd_runner = args[0]  # type: Commands
    124             cmd = known_commands[func.__name__]  # type: Command
    125             password = kwargs.get('password')
    126             daemon = cmd_runner.daemon
    127             if daemon:
    128                 if 'wallet_path' in cmd.options and kwargs.get('wallet_path') is None:
    129                     kwargs['wallet_path'] = daemon.config.get_wallet_path()
    130                 if cmd.requires_wallet and kwargs.get('wallet') is None:
    131                     kwargs['wallet'] = daemon.config.get_wallet_path()
    132                 if 'wallet' in cmd.options:
    133                     wallet_path = kwargs.get('wallet', None)
    134                     if isinstance(wallet_path, str):
    135                         wallet = daemon.get_wallet(wallet_path)
    136                         if wallet is None:
    137                             raise Exception('wallet not loaded')
    138                         kwargs['wallet'] = wallet
    139             wallet = kwargs.get('wallet')  # type: Optional[Abstract_Wallet]
    140             if cmd.requires_wallet and not wallet:
    141                 raise Exception('wallet not loaded')
    142             if cmd.requires_password and password is None and wallet.has_password():
    143                 raise Exception('Password required')
    144             return await func(*args, **kwargs)
    145         return func_wrapper
    146     return decorator
    147 
    148 
    149 class Commands:
    150 
    151     def __init__(self, *, config: 'SimpleConfig',
    152                  network: 'Network' = None,
    153                  daemon: 'Daemon' = None, callback=None):
    154         self.config = config
    155         self.daemon = daemon
    156         self.network = network
    157         self._callback = callback
    158 
    159     def _run(self, method, args, password_getter=None, **kwargs):
    160         """This wrapper is called from unit tests and the Qt python console."""
    161         cmd = known_commands[method]
    162         password = kwargs.get('password', None)
    163         wallet = kwargs.get('wallet', None)
    164         if (cmd.requires_password and wallet and wallet.has_password()
    165                 and password is None):
    166             password = password_getter()
    167             if password is None:
    168                 return
    169 
    170         f = getattr(self, method)
    171         if cmd.requires_password:
    172             kwargs['password'] = password
    173 
    174         if 'wallet' in kwargs:
    175             sig = inspect.signature(f)
    176             if 'wallet' not in sig.parameters:
    177                 kwargs.pop('wallet')
    178 
    179         coro = f(*args, **kwargs)
    180         fut = asyncio.run_coroutine_threadsafe(coro, asyncio.get_event_loop())
    181         result = fut.result()
    182 
    183         if self._callback:
    184             self._callback()
    185         return result
    186 
    187     @command('')
    188     async def commands(self):
    189         """List of commands"""
    190         return ' '.join(sorted(known_commands.keys()))
    191 
    192     @command('n')
    193     async def getinfo(self):
    194         """ network info """
    195         net_params = self.network.get_parameters()
    196         response = {
    197             'path': self.network.config.path,
    198             'server': net_params.server.host,
    199             'blockchain_height': self.network.get_local_height(),
    200             'server_height': self.network.get_server_height(),
    201             'spv_nodes': len(self.network.get_interfaces()),
    202             'connected': self.network.is_connected(),
    203             'auto_connect': net_params.auto_connect,
    204             'version': ELECTRUM_VERSION,
    205             'default_wallet': self.config.get_wallet_path(),
    206             'fee_per_kb': self.config.fee_per_kb(),
    207         }
    208         return response
    209 
    210     @command('n')
    211     async def stop(self):
    212         """Stop daemon"""
    213         self.daemon.stop()
    214         return "Daemon stopped"
    215 
    216     @command('n')
    217     async def list_wallets(self):
    218         """List wallets open in daemon"""
    219         return [{'path': path, 'synchronized': w.is_up_to_date()}
    220                 for path, w in self.daemon.get_wallets().items()]
    221 
    222     @command('n')
    223     async def load_wallet(self, wallet_path=None, password=None):
    224         """Open wallet in daemon"""
    225         wallet = self.daemon.load_wallet(wallet_path, password, manual_upgrades=False)
    226         if wallet is not None:
    227             run_hook('load_wallet', wallet, None)
    228         response = wallet is not None
    229         return response
    230 
    231     @command('n')
    232     async def close_wallet(self, wallet_path=None):
    233         """Close wallet"""
    234         return self.daemon.stop_wallet(wallet_path)
    235 
    236     @command('')
    237     async def create(self, passphrase=None, password=None, encrypt_file=True, seed_type=None, wallet_path=None):
    238         """Create a new wallet.
    239         If you want to be prompted for an argument, type '?' or ':' (concealed)
    240         """
    241         d = create_new_wallet(path=wallet_path,
    242                               passphrase=passphrase,
    243                               password=password,
    244                               encrypt_file=encrypt_file,
    245                               seed_type=seed_type,
    246                               config=self.config)
    247         return {
    248             'seed': d['seed'],
    249             'path': d['wallet'].storage.path,
    250             'msg': d['msg'],
    251         }
    252 
    253     @command('')
    254     async def restore(self, text, passphrase=None, password=None, encrypt_file=True, wallet_path=None):
    255         """Restore a wallet from text. Text can be a seed phrase, a master
    256         public key, a master private key, a list of bitcoin addresses
    257         or bitcoin private keys.
    258         If you want to be prompted for an argument, type '?' or ':' (concealed)
    259         """
    260         # TODO create a separate command that blocks until wallet is synced
    261         d = restore_wallet_from_text(text,
    262                                      path=wallet_path,
    263                                      passphrase=passphrase,
    264                                      password=password,
    265                                      encrypt_file=encrypt_file,
    266                                      config=self.config)
    267         return {
    268             'path': d['wallet'].storage.path,
    269             'msg': d['msg'],
    270         }
    271 
    272     @command('wp')
    273     async def password(self, password=None, new_password=None, wallet: Abstract_Wallet = None):
    274         """Change wallet password. """
    275         if wallet.storage.is_encrypted_with_hw_device() and new_password:
    276             raise Exception("Can't change the password of a wallet encrypted with a hw device.")
    277         b = wallet.storage.is_encrypted()
    278         wallet.update_password(password, new_password, encrypt_storage=b)
    279         wallet.save_db()
    280         return {'password':wallet.has_password()}
    281 
    282     @command('w')
    283     async def get(self, key, wallet: Abstract_Wallet = None):
    284         """Return item from wallet storage"""
    285         return wallet.db.get(key)
    286 
    287     @command('')
    288     async def getconfig(self, key):
    289         """Return a configuration variable. """
    290         return self.config.get(key)
    291 
    292     @classmethod
    293     def _setconfig_normalize_value(cls, key, value):
    294         if key not in ('rpcuser', 'rpcpassword'):
    295             value = json_decode(value)
    296             # call literal_eval for backward compatibility (see #4225)
    297             try:
    298                 value = ast.literal_eval(value)
    299             except:
    300                 pass
    301         return value
    302 
    303     @command('')
    304     async def setconfig(self, key, value):
    305         """Set a configuration variable. 'value' may be a string or a Python expression."""
    306         value = self._setconfig_normalize_value(key, value)
    307         if self.daemon and key == 'rpcuser':
    308             self.daemon.commands_server.rpc_user = value
    309         if self.daemon and key == 'rpcpassword':
    310             self.daemon.commands_server.rpc_password = value
    311         self.config.set_key(key, value)
    312         return True
    313 
    314     @command('')
    315     async def get_ssl_domain(self):
    316         """Check and return the SSL domain set in ssl_keyfile and ssl_certfile
    317         """
    318         return self.config.get_ssl_domain()
    319 
    320     @command('')
    321     async def make_seed(self, nbits=None, language=None, seed_type=None):
    322         """Create a seed"""
    323         from .mnemonic import Mnemonic
    324         s = Mnemonic(language).make_seed(seed_type=seed_type, num_bits=nbits)
    325         return s
    326 
    327     @command('n')
    328     async def getaddresshistory(self, address):
    329         """Return the transaction history of any address. Note: This is a
    330         walletless server query, results are not checked by SPV.
    331         """
    332         sh = bitcoin.address_to_scripthash(address)
    333         return await self.network.get_history_for_scripthash(sh)
    334 
    335     @command('w')
    336     async def listunspent(self, wallet: Abstract_Wallet = None):
    337         """List unspent outputs. Returns the list of unspent transaction
    338         outputs in your wallet."""
    339         coins = []
    340         for txin in wallet.get_utxos():
    341             d = txin.to_json()
    342             v = d.pop("value_sats")
    343             d["value"] = str(Decimal(v)/COIN) if v is not None else None
    344             coins.append(d)
    345         return coins
    346 
    347     @command('n')
    348     async def getaddressunspent(self, address):
    349         """Returns the UTXO list of any address. Note: This
    350         is a walletless server query, results are not checked by SPV.
    351         """
    352         sh = bitcoin.address_to_scripthash(address)
    353         return await self.network.listunspent_for_scripthash(sh)
    354 
    355     @command('')
    356     async def serialize(self, jsontx):
    357         """Create a transaction from json inputs.
    358         Inputs must have a redeemPubkey.
    359         Outputs must be a list of {'address':address, 'value':satoshi_amount}.
    360         """
    361         keypairs = {}
    362         inputs = []  # type: List[PartialTxInput]
    363         locktime = jsontx.get('locktime', 0)
    364         for txin_dict in jsontx.get('inputs'):
    365             if txin_dict.get('prevout_hash') is not None and txin_dict.get('prevout_n') is not None:
    366                 prevout = TxOutpoint(txid=bfh(txin_dict['prevout_hash']), out_idx=int(txin_dict['prevout_n']))
    367             elif txin_dict.get('output'):
    368                 prevout = TxOutpoint.from_str(txin_dict['output'])
    369             else:
    370                 raise Exception("missing prevout for txin")
    371             txin = PartialTxInput(prevout=prevout)
    372             txin._trusted_value_sats = int(txin_dict.get('value', txin_dict['value_sats']))
    373             nsequence = txin_dict.get('nsequence', None)
    374             if nsequence is not None:
    375                 txin.nsequence = nsequence
    376             sec = txin_dict.get('privkey')
    377             if sec:
    378                 txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
    379                 pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
    380                 keypairs[pubkey] = privkey, compressed
    381                 txin.script_type = txin_type
    382                 txin.pubkeys = [bfh(pubkey)]
    383                 txin.num_sig = 1
    384             inputs.append(txin)
    385 
    386         outputs = [PartialTxOutput.from_address_and_value(txout['address'], int(txout.get('value', txout['value_sats'])))
    387                    for txout in jsontx.get('outputs')]
    388         tx = PartialTransaction.from_io(inputs, outputs, locktime=locktime)
    389         tx.sign(keypairs)
    390         return tx.serialize()
    391 
    392     @command('wp')
    393     async def signtransaction(self, tx, privkey=None, password=None, wallet: Abstract_Wallet = None):
    394         """Sign a transaction. The wallet keys will be used unless a private key is provided."""
    395         tx = tx_from_any(tx)
    396         if privkey:
    397             txin_type, privkey2, compressed = bitcoin.deserialize_privkey(privkey)
    398             pubkey = ecc.ECPrivkey(privkey2).get_public_key_bytes(compressed=compressed).hex()
    399             tx.sign({pubkey:(privkey2, compressed)})
    400         else:
    401             wallet.sign_transaction(tx, password)
    402         return tx.serialize()
    403 
    404     @command('')
    405     async def deserialize(self, tx):
    406         """Deserialize a serialized transaction"""
    407         tx = tx_from_any(tx)
    408         return tx.to_json()
    409 
    410     @command('n')
    411     async def broadcast(self, tx):
    412         """Broadcast a transaction to the network. """
    413         tx = Transaction(tx)
    414         await self.network.broadcast_transaction(tx)
    415         return tx.txid()
    416 
    417     @command('')
    418     async def createmultisig(self, num, pubkeys):
    419         """Create multisig address"""
    420         assert isinstance(pubkeys, list), (type(num), type(pubkeys))
    421         redeem_script = multisig_script(pubkeys, num)
    422         address = bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script)))
    423         return {'address':address, 'redeemScript':redeem_script}
    424 
    425     @command('w')
    426     async def freeze(self, address: str, wallet: Abstract_Wallet = None):
    427         """Freeze address. Freeze the funds at one of your wallet\'s addresses"""
    428         return wallet.set_frozen_state_of_addresses([address], True)
    429 
    430     @command('w')
    431     async def unfreeze(self, address: str, wallet: Abstract_Wallet = None):
    432         """Unfreeze address. Unfreeze the funds at one of your wallet\'s address"""
    433         return wallet.set_frozen_state_of_addresses([address], False)
    434 
    435     @command('w')
    436     async def freeze_utxo(self, coin: str, wallet: Abstract_Wallet = None):
    437         """Freeze a UTXO so that the wallet will not spend it."""
    438         wallet.set_frozen_state_of_coins([coin], True)
    439         return True
    440 
    441     @command('w')
    442     async def unfreeze_utxo(self, coin: str, wallet: Abstract_Wallet = None):
    443         """Unfreeze a UTXO so that the wallet might spend it."""
    444         wallet.set_frozen_state_of_coins([coin], False)
    445         return True
    446 
    447     @command('wp')
    448     async def getprivatekeys(self, address, password=None, wallet: Abstract_Wallet = None):
    449         """Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses."""
    450         if isinstance(address, str):
    451             address = address.strip()
    452         if is_address(address):
    453             return wallet.export_private_key(address, password)
    454         domain = address
    455         return [wallet.export_private_key(address, password) for address in domain]
    456 
    457     @command('wp')
    458     async def getprivatekeyforpath(self, path, password=None, wallet: Abstract_Wallet = None):
    459         """Get private key corresponding to derivation path (address index).
    460         'path' can be either a str such as "m/0/50", or a list of ints such as [0, 50].
    461         """
    462         return wallet.export_private_key_for_path(path, password)
    463 
    464     @command('w')
    465     async def ismine(self, address, wallet: Abstract_Wallet = None):
    466         """Check if address is in wallet. Return true if and only address is in wallet"""
    467         return wallet.is_mine(address)
    468 
    469     @command('')
    470     async def dumpprivkeys(self):
    471         """Deprecated."""
    472         return "This command is deprecated. Use a pipe instead: 'electrum listaddresses | electrum getprivatekeys - '"
    473 
    474     @command('')
    475     async def validateaddress(self, address):
    476         """Check that an address is valid. """
    477         return is_address(address)
    478 
    479     @command('w')
    480     async def getpubkeys(self, address, wallet: Abstract_Wallet = None):
    481         """Return the public keys for a wallet address. """
    482         return wallet.get_public_keys(address)
    483 
    484     @command('w')
    485     async def getbalance(self, wallet: Abstract_Wallet = None):
    486         """Return the balance of your wallet. """
    487         c, u, x = wallet.get_balance()
    488         l = wallet.lnworker.get_balance() if wallet.lnworker else None
    489         out = {"confirmed": str(Decimal(c)/COIN)}
    490         if u:
    491             out["unconfirmed"] = str(Decimal(u)/COIN)
    492         if x:
    493             out["unmatured"] = str(Decimal(x)/COIN)
    494         if l:
    495             out["lightning"] = str(Decimal(l)/COIN)
    496         return out
    497 
    498     @command('n')
    499     async def getaddressbalance(self, address):
    500         """Return the balance of any address. Note: This is a walletless
    501         server query, results are not checked by SPV.
    502         """
    503         sh = bitcoin.address_to_scripthash(address)
    504         out = await self.network.get_balance_for_scripthash(sh)
    505         out["confirmed"] =  str(Decimal(out["confirmed"])/COIN)
    506         out["unconfirmed"] =  str(Decimal(out["unconfirmed"])/COIN)
    507         return out
    508 
    509     @command('n')
    510     async def getmerkle(self, txid, height):
    511         """Get Merkle branch of a transaction included in a block. Electrum
    512         uses this to verify transactions (Simple Payment Verification)."""
    513         return await self.network.get_merkle_for_transaction(txid, int(height))
    514 
    515     @command('n')
    516     async def getservers(self):
    517         """Return the list of known servers (candidates for connecting)."""
    518         return self.network.get_servers()
    519 
    520     @command('')
    521     async def version(self):
    522         """Return the version of Electrum."""
    523         from .version import ELECTRUM_VERSION
    524         return ELECTRUM_VERSION
    525 
    526     @command('w')
    527     async def getmpk(self, wallet: Abstract_Wallet = None):
    528         """Get master public key. Return your wallet\'s master public key"""
    529         return wallet.get_master_public_key()
    530 
    531     @command('wp')
    532     async def getmasterprivate(self, password=None, wallet: Abstract_Wallet = None):
    533         """Get master private key. Return your wallet\'s master private key"""
    534         return str(wallet.keystore.get_master_private_key(password))
    535 
    536     @command('')
    537     async def convert_xkey(self, xkey, xtype):
    538         """Convert xtype of a master key. e.g. xpub -> ypub"""
    539         try:
    540             node = BIP32Node.from_xkey(xkey)
    541         except:
    542             raise Exception('xkey should be a master public/private key')
    543         return node._replace(xtype=xtype).to_xkey()
    544 
    545     @command('wp')
    546     async def getseed(self, password=None, wallet: Abstract_Wallet = None):
    547         """Get seed phrase. Print the generation seed of your wallet."""
    548         s = wallet.get_seed(password)
    549         return s
    550 
    551     @command('wp')
    552     async def importprivkey(self, privkey, password=None, wallet: Abstract_Wallet = None):
    553         """Import a private key."""
    554         if not wallet.can_import_privkey():
    555             return "Error: This type of wallet cannot import private keys. Try to create a new wallet with that key."
    556         try:
    557             addr = wallet.import_private_key(privkey, password)
    558             out = "Keypair imported: " + addr
    559         except Exception as e:
    560             out = "Error: " + repr(e)
    561         return out
    562 
    563     def _resolver(self, x, wallet):
    564         if x is None:
    565             return None
    566         out = wallet.contacts.resolve(x)
    567         if out.get('type') == 'openalias' and self.nocheck is False and out.get('validated') is False:
    568             raise Exception('cannot verify alias', x)
    569         return out['address']
    570 
    571     @command('n')
    572     async def sweep(self, privkey, destination, fee=None, nocheck=False, imax=100):
    573         """Sweep private keys. Returns a transaction that spends UTXOs from
    574         privkey to a destination address. The transaction is not
    575         broadcasted."""
    576         from .wallet import sweep
    577         tx_fee = satoshis(fee)
    578         privkeys = privkey.split()
    579         self.nocheck = nocheck
    580         #dest = self._resolver(destination)
    581         tx = await sweep(
    582             privkeys,
    583             network=self.network,
    584             config=self.config,
    585             to_address=destination,
    586             fee=tx_fee,
    587             imax=imax,
    588         )
    589         return tx.serialize() if tx else None
    590 
    591     @command('wp')
    592     async def signmessage(self, address, message, password=None, wallet: Abstract_Wallet = None):
    593         """Sign a message with a key. Use quotes if your message contains
    594         whitespaces"""
    595         sig = wallet.sign_message(address, message, password)
    596         return base64.b64encode(sig).decode('ascii')
    597 
    598     @command('')
    599     async def verifymessage(self, address, signature, message):
    600         """Verify a signature."""
    601         sig = base64.b64decode(signature)
    602         message = util.to_bytes(message)
    603         return ecc.verify_message_with_address(address, sig, message)
    604 
    605     @command('wp')
    606     async def payto(self, destination, amount, fee=None, feerate=None, from_addr=None, from_coins=None, change_addr=None,
    607                     nocheck=False, unsigned=False, rbf=None, password=None, locktime=None, addtransaction=False, wallet: Abstract_Wallet = None):
    608         """Create a transaction. """
    609         self.nocheck = nocheck
    610         tx_fee = satoshis(fee)
    611         domain_addr = from_addr.split(',') if from_addr else None
    612         domain_coins = from_coins.split(',') if from_coins else None
    613         change_addr = self._resolver(change_addr, wallet)
    614         domain_addr = None if domain_addr is None else map(self._resolver, domain_addr, repeat(wallet))
    615         amount_sat = satoshis(amount)
    616         outputs = [PartialTxOutput.from_address_and_value(destination, amount_sat)]
    617         tx = wallet.create_transaction(
    618             outputs,
    619             fee=tx_fee,
    620             feerate=feerate,
    621             change_addr=change_addr,
    622             domain_addr=domain_addr,
    623             domain_coins=domain_coins,
    624             unsigned=unsigned,
    625             rbf=rbf,
    626             password=password,
    627             locktime=locktime)
    628         result = tx.serialize()
    629         if addtransaction:
    630             await self.addtransaction(result, wallet=wallet)
    631         return result
    632 
    633     @command('wp')
    634     async def paytomany(self, outputs, fee=None, feerate=None, from_addr=None, from_coins=None, change_addr=None,
    635                         nocheck=False, unsigned=False, rbf=None, password=None, locktime=None, addtransaction=False, wallet: Abstract_Wallet = None):
    636         """Create a multi-output transaction. """
    637         self.nocheck = nocheck
    638         tx_fee = satoshis(fee)
    639         domain_addr = from_addr.split(',') if from_addr else None
    640         domain_coins = from_coins.split(',') if from_coins else None
    641         change_addr = self._resolver(change_addr, wallet)
    642         domain_addr = None if domain_addr is None else map(self._resolver, domain_addr, repeat(wallet))
    643         final_outputs = []
    644         for address, amount in outputs:
    645             address = self._resolver(address, wallet)
    646             amount_sat = satoshis(amount)
    647             final_outputs.append(PartialTxOutput.from_address_and_value(address, amount_sat))
    648         tx = wallet.create_transaction(
    649             final_outputs,
    650             fee=tx_fee,
    651             feerate=feerate,
    652             change_addr=change_addr,
    653             domain_addr=domain_addr,
    654             domain_coins=domain_coins,
    655             unsigned=unsigned,
    656             rbf=rbf,
    657             password=password,
    658             locktime=locktime)
    659         result = tx.serialize()
    660         if addtransaction:
    661             await self.addtransaction(result, wallet=wallet)
    662         return result
    663 
    664     @command('w')
    665     async def onchain_history(self, year=None, show_addresses=False, show_fiat=False, wallet: Abstract_Wallet = None,
    666                               from_height=None, to_height=None):
    667         """Wallet onchain history. Returns the transaction history of your wallet."""
    668         kwargs = {
    669             'show_addresses': show_addresses,
    670             'from_height': from_height,
    671             'to_height': to_height,
    672         }
    673         if year:
    674             import time
    675             start_date = datetime.datetime(year, 1, 1)
    676             end_date = datetime.datetime(year+1, 1, 1)
    677             kwargs['from_timestamp'] = time.mktime(start_date.timetuple())
    678             kwargs['to_timestamp'] = time.mktime(end_date.timetuple())
    679         if show_fiat:
    680             from .exchange_rate import FxThread
    681             fx = FxThread(self.config, None)
    682             kwargs['fx'] = fx
    683 
    684         return json_normalize(wallet.get_detailed_history(**kwargs))
    685 
    686     @command('w')
    687     async def lightning_history(self, show_fiat=False, wallet: Abstract_Wallet = None):
    688         """ lightning history """
    689         lightning_history = wallet.lnworker.get_history() if wallet.lnworker else []
    690         return json_normalize(lightning_history)
    691 
    692     @command('w')
    693     async def setlabel(self, key, label, wallet: Abstract_Wallet = None):
    694         """Assign a label to an item. Item may be a bitcoin address or a
    695         transaction ID"""
    696         wallet.set_label(key, label)
    697 
    698     @command('w')
    699     async def listcontacts(self, wallet: Abstract_Wallet = None):
    700         """Show your list of contacts"""
    701         return wallet.contacts
    702 
    703     @command('w')
    704     async def getalias(self, key, wallet: Abstract_Wallet = None):
    705         """Retrieve alias. Lookup in your list of contacts, and for an OpenAlias DNS record."""
    706         return wallet.contacts.resolve(key)
    707 
    708     @command('w')
    709     async def searchcontacts(self, query, wallet: Abstract_Wallet = None):
    710         """Search through contacts, return matching entries. """
    711         results = {}
    712         for key, value in wallet.contacts.items():
    713             if query.lower() in key.lower():
    714                 results[key] = value
    715         return results
    716 
    717     @command('w')
    718     async def listaddresses(self, receiving=False, change=False, labels=False, frozen=False, unused=False, funded=False, balance=False, wallet: Abstract_Wallet = None):
    719         """List wallet addresses. Returns the list of all addresses in your wallet. Use optional arguments to filter the results."""
    720         out = []
    721         for addr in wallet.get_addresses():
    722             if frozen and not wallet.is_frozen_address(addr):
    723                 continue
    724             if receiving and wallet.is_change(addr):
    725                 continue
    726             if change and not wallet.is_change(addr):
    727                 continue
    728             if unused and wallet.is_used(addr):
    729                 continue
    730             if funded and wallet.is_empty(addr):
    731                 continue
    732             item = addr
    733             if labels or balance:
    734                 item = (item,)
    735             if balance:
    736                 item += (format_satoshis(sum(wallet.get_addr_balance(addr))),)
    737             if labels:
    738                 item += (repr(wallet.get_label(addr)),)
    739             out.append(item)
    740         return out
    741 
    742     @command('n')
    743     async def gettransaction(self, txid, wallet: Abstract_Wallet = None):
    744         """Retrieve a transaction. """
    745         tx = None
    746         if wallet:
    747             tx = wallet.db.get_transaction(txid)
    748         if tx is None:
    749             raw = await self.network.get_transaction(txid)
    750             if raw:
    751                 tx = Transaction(raw)
    752             else:
    753                 raise Exception("Unknown transaction")
    754         if tx.txid() != txid:
    755             raise Exception("Mismatching txid")
    756         return tx.serialize()
    757 
    758     @command('')
    759     async def encrypt(self, pubkey, message) -> str:
    760         """Encrypt a message with a public key. Use quotes if the message contains whitespaces."""
    761         if not is_hex_str(pubkey):
    762             raise Exception(f"pubkey must be a hex string instead of {repr(pubkey)}")
    763         try:
    764             message = to_bytes(message)
    765         except TypeError:
    766             raise Exception(f"message must be a string-like object instead of {repr(message)}")
    767         public_key = ecc.ECPubkey(bfh(pubkey))
    768         encrypted = public_key.encrypt_message(message)
    769         return encrypted.decode('utf-8')
    770 
    771     @command('wp')
    772     async def decrypt(self, pubkey, encrypted, password=None, wallet: Abstract_Wallet = None) -> str:
    773         """Decrypt a message encrypted with a public key."""
    774         if not is_hex_str(pubkey):
    775             raise Exception(f"pubkey must be a hex string instead of {repr(pubkey)}")
    776         if not isinstance(encrypted, (str, bytes, bytearray)):
    777             raise Exception(f"encrypted must be a string-like object instead of {repr(encrypted)}")
    778         decrypted = wallet.decrypt_message(pubkey, encrypted, password)
    779         return decrypted.decode('utf-8')
    780 
    781     @command('w')
    782     async def getrequest(self, key, wallet: Abstract_Wallet = None):
    783         """Return a payment request"""
    784         r = wallet.get_request(key)
    785         if not r:
    786             raise Exception("Request not found")
    787         return wallet.export_request(r)
    788 
    789     #@command('w')
    790     #async def ackrequest(self, serialized):
    791     #    """<Not implemented>"""
    792     #    pass
    793 
    794     @command('w')
    795     async def list_requests(self, pending=False, expired=False, paid=False, wallet: Abstract_Wallet = None):
    796         """List the payment requests you made."""
    797         if pending:
    798             f = PR_UNPAID
    799         elif expired:
    800             f = PR_EXPIRED
    801         elif paid:
    802             f = PR_PAID
    803         else:
    804             f = None
    805         out = wallet.get_sorted_requests()
    806         if f is not None:
    807             out = [req for req in out
    808                    if f == wallet.get_request_status(wallet.get_key_for_receive_request(req))]
    809         return [wallet.export_request(x) for x in out]
    810 
    811     @command('w')
    812     async def createnewaddress(self, wallet: Abstract_Wallet = None):
    813         """Create a new receiving address, beyond the gap limit of the wallet"""
    814         return wallet.create_new_address(False)
    815 
    816     @command('w')
    817     async def changegaplimit(self, new_limit, iknowwhatimdoing=False, wallet: Abstract_Wallet = None):
    818         """Change the gap limit of the wallet."""
    819         if not iknowwhatimdoing:
    820             raise Exception("WARNING: Are you SURE you want to change the gap limit?\n"
    821                             "It makes recovering your wallet from seed difficult!\n"
    822                             "Please do your research and make sure you understand the implications.\n"
    823                             "Typically only merchants and power users might want to do this.\n"
    824                             "To proceed, try again, with the --iknowwhatimdoing option.")
    825         if not isinstance(wallet, Deterministic_Wallet):
    826             raise Exception("This wallet is not deterministic.")
    827         return wallet.change_gap_limit(new_limit)
    828 
    829     @command('wn')
    830     async def getminacceptablegap(self, wallet: Abstract_Wallet = None):
    831         """Returns the minimum value for gap limit that would be sufficient to discover all
    832         known addresses in the wallet.
    833         """
    834         if not isinstance(wallet, Deterministic_Wallet):
    835             raise Exception("This wallet is not deterministic.")
    836         if not wallet.is_up_to_date():
    837             raise NotSynchronizedException("Wallet not fully synchronized.")
    838         return wallet.min_acceptable_gap()
    839 
    840     @command('w')
    841     async def getunusedaddress(self, wallet: Abstract_Wallet = None):
    842         """Returns the first unused address of the wallet, or None if all addresses are used.
    843         An address is considered as used if it has received a transaction, or if it is used in a payment request."""
    844         return wallet.get_unused_address()
    845 
    846     @command('w')
    847     async def add_request(self, amount, memo='', expiration=3600, force=False, wallet: Abstract_Wallet = None):
    848         """Create a payment request, using the first unused address of the wallet.
    849         The address will be considered as used after this operation.
    850         If no payment is received, the address will be considered as unused if the payment request is deleted from the wallet."""
    851         addr = wallet.get_unused_address()
    852         if addr is None:
    853             if force:
    854                 addr = wallet.create_new_address(False)
    855             else:
    856                 return False
    857         amount = satoshis(amount)
    858         expiration = int(expiration) if expiration else None
    859         req = wallet.make_payment_request(addr, amount, memo, expiration)
    860         wallet.add_payment_request(req)
    861         wallet.save_db()
    862         return wallet.export_request(req)
    863 
    864     @command('wn')
    865     async def add_lightning_request(self, amount, memo='', expiration=3600, wallet: Abstract_Wallet = None):
    866         amount_sat = int(satoshis(amount))
    867         key = await wallet.lnworker._add_request_coro(amount_sat, memo, expiration)
    868         wallet.save_db()
    869         return wallet.get_formatted_request(key)
    870 
    871     @command('w')
    872     async def addtransaction(self, tx, wallet: Abstract_Wallet = None):
    873         """ Add a transaction to the wallet history """
    874         tx = Transaction(tx)
    875         if not wallet.add_transaction(tx):
    876             return False
    877         wallet.save_db()
    878         return tx.txid()
    879 
    880     @command('wp')
    881     async def signrequest(self, address, password=None, wallet: Abstract_Wallet = None):
    882         "Sign payment request with an OpenAlias"
    883         alias = self.config.get('alias')
    884         if not alias:
    885             raise Exception('No alias in your configuration')
    886         alias_addr = wallet.contacts.resolve(alias)['address']
    887         wallet.sign_payment_request(address, alias, alias_addr, password)
    888 
    889     @command('w')
    890     async def rmrequest(self, address, wallet: Abstract_Wallet = None):
    891         """Remove a payment request"""
    892         result = wallet.remove_payment_request(address)
    893         wallet.save_db()
    894         return result
    895 
    896     @command('w')
    897     async def clear_requests(self, wallet: Abstract_Wallet = None):
    898         """Remove all payment requests"""
    899         wallet.clear_requests()
    900         return True
    901 
    902     @command('w')
    903     async def clear_invoices(self, wallet: Abstract_Wallet = None):
    904         """Remove all invoices"""
    905         wallet.clear_invoices()
    906         return True
    907 
    908     @command('n')
    909     async def notify(self, address: str, URL: Optional[str]):
    910         """Watch an address. Every time the address changes, a http POST is sent to the URL.
    911         Call with an empty URL to stop watching an address.
    912         """
    913         if not hasattr(self, "_notifier"):
    914             self._notifier = Notifier(self.network)
    915         if URL:
    916             await self._notifier.start_watching_addr(address, URL)
    917         else:
    918             await self._notifier.stop_watching_addr(address)
    919         return True
    920 
    921     @command('wn')
    922     async def is_synchronized(self, wallet: Abstract_Wallet = None):
    923         """ return wallet synchronization status """
    924         return wallet.is_up_to_date()
    925 
    926     @command('n')
    927     async def getfeerate(self, fee_method=None, fee_level=None):
    928         """Return current suggested fee rate (in sat/kvByte), according to config
    929         settings or supplied parameters.
    930         """
    931         if fee_method is None:
    932             dyn, mempool = None, None
    933         elif fee_method.lower() == 'static':
    934             dyn, mempool = False, False
    935         elif fee_method.lower() == 'eta':
    936             dyn, mempool = True, False
    937         elif fee_method.lower() == 'mempool':
    938             dyn, mempool = True, True
    939         else:
    940             raise Exception('Invalid fee estimation method: {}'.format(fee_method))
    941         if fee_level is not None:
    942             fee_level = Decimal(fee_level)
    943         return self.config.fee_per_kb(dyn=dyn, mempool=mempool, fee_level=fee_level)
    944 
    945     @command('w')
    946     async def removelocaltx(self, txid, wallet: Abstract_Wallet = None):
    947         """Remove a 'local' transaction from the wallet, and its dependent
    948         transactions.
    949         """
    950         if not is_hash256_str(txid):
    951             raise Exception(f"{repr(txid)} is not a txid")
    952         height = wallet.get_tx_height(txid).height
    953         if height != TX_HEIGHT_LOCAL:
    954             raise Exception(f'Only local transactions can be removed. '
    955                             f'This tx has height: {height} != {TX_HEIGHT_LOCAL}')
    956         wallet.remove_transaction(txid)
    957         wallet.save_db()
    958 
    959     @command('wn')
    960     async def get_tx_status(self, txid, wallet: Abstract_Wallet = None):
    961         """Returns some information regarding the tx. For now, only confirmations.
    962         The transaction must be related to the wallet.
    963         """
    964         if not is_hash256_str(txid):
    965             raise Exception(f"{repr(txid)} is not a txid")
    966         if not wallet.db.get_transaction(txid):
    967             raise Exception("Transaction not in wallet.")
    968         return {
    969             "confirmations": wallet.get_tx_height(txid).conf,
    970         }
    971 
    972     @command('')
    973     async def help(self):
    974         # for the python console
    975         return sorted(known_commands.keys())
    976 
    977     # lightning network commands
    978     @command('wn')
    979     async def add_peer(self, connection_string, timeout=20, gossip=False, wallet: Abstract_Wallet = None):
    980         lnworker = self.network.lngossip if gossip else wallet.lnworker
    981         await lnworker.add_peer(connection_string)
    982         return True
    983 
    984     @command('wn')
    985     async def list_peers(self, gossip=False, wallet: Abstract_Wallet = None):
    986         lnworker = self.network.lngossip if gossip else wallet.lnworker
    987         return [{
    988             'node_id':p.pubkey.hex(),
    989             'address':p.transport.name(),
    990             'initialized':p.is_initialized(),
    991             'features': str(LnFeatures(p.features)),
    992             'channels': [c.funding_outpoint.to_str() for c in p.channels.values()],
    993         } for p in lnworker.peers.values()]
    994 
    995     @command('wpn')
    996     async def open_channel(self, connection_string, amount, push_amount=0, password=None, wallet: Abstract_Wallet = None):
    997         funding_sat = satoshis(amount)
    998         push_sat = satoshis(push_amount)
    999         dummy_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), funding_sat)
   1000         funding_tx = wallet.mktx(outputs = [dummy_output], rbf=False, sign=False, nonlocal_only=True)
   1001         chan, funding_tx = await wallet.lnworker._open_channel_coroutine(connect_str=connection_string,
   1002                                                                          funding_tx=funding_tx,
   1003                                                                          funding_sat=funding_sat,
   1004                                                                          push_sat=push_sat,
   1005                                                                          password=password)
   1006         return chan.funding_outpoint.to_str()
   1007 
   1008     @command('')
   1009     async def decode_invoice(self, invoice: str):
   1010         invoice = LNInvoice.from_bech32(invoice)
   1011         return invoice.to_debug_json()
   1012 
   1013     @command('wn')
   1014     async def lnpay(self, invoice, attempts=1, timeout=30, wallet: Abstract_Wallet = None):
   1015         lnworker = wallet.lnworker
   1016         lnaddr = lnworker._check_invoice(invoice)
   1017         payment_hash = lnaddr.paymenthash
   1018         wallet.save_invoice(LNInvoice.from_bech32(invoice))
   1019         success, log = await lnworker.pay_invoice(invoice, attempts=attempts)
   1020         return {
   1021             'payment_hash': payment_hash.hex(),
   1022             'success': success,
   1023             'preimage': lnworker.get_preimage(payment_hash).hex() if success else None,
   1024             'log': [x.formatted_tuple() for x in log]
   1025         }
   1026 
   1027     @command('w')
   1028     async def nodeid(self, wallet: Abstract_Wallet = None):
   1029         listen_addr = self.config.get('lightning_listen')
   1030         return bh2u(wallet.lnworker.node_keypair.pubkey) + (('@' + listen_addr) if listen_addr else '')
   1031 
   1032     @command('w')
   1033     async def list_channels(self, wallet: Abstract_Wallet = None):
   1034         # we output the funding_outpoint instead of the channel_id because lnd uses channel_point (funding outpoint) to identify channels
   1035         from .lnutil import LOCAL, REMOTE, format_short_channel_id
   1036         l = list(wallet.lnworker.channels.items())
   1037         return [
   1038             {
   1039                 'short_channel_id': format_short_channel_id(chan.short_channel_id) if chan.short_channel_id else None,
   1040                 'channel_id': bh2u(chan.channel_id),
   1041                 'channel_point': chan.funding_outpoint.to_str(),
   1042                 'state': chan.get_state().name,
   1043                 'peer_state': chan.peer_state.name,
   1044                 'remote_pubkey': bh2u(chan.node_id),
   1045                 'local_balance': chan.balance(LOCAL)//1000,
   1046                 'remote_balance': chan.balance(REMOTE)//1000,
   1047                 'local_reserve': chan.config[REMOTE].reserve_sat, # their config has our reserve
   1048                 'remote_reserve': chan.config[LOCAL].reserve_sat,
   1049                 'local_unsettled_sent': chan.balance_tied_up_in_htlcs_by_direction(LOCAL, direction=SENT) // 1000,
   1050                 'remote_unsettled_sent': chan.balance_tied_up_in_htlcs_by_direction(REMOTE, direction=SENT) // 1000,
   1051             } for channel_id, chan in l
   1052         ]
   1053 
   1054     @command('wn')
   1055     async def dumpgraph(self, wallet: Abstract_Wallet = None):
   1056         return wallet.lnworker.channel_db.to_dict()
   1057 
   1058     @command('n')
   1059     async def inject_fees(self, fees):
   1060         import ast
   1061         self.network.config.fee_estimates = ast.literal_eval(fees)
   1062         self.network.notify('fee')
   1063 
   1064     @command('wn')
   1065     async def enable_htlc_settle(self, b: bool, wallet: Abstract_Wallet = None):
   1066         e = wallet.lnworker.enable_htlc_settle
   1067         e.set() if b else e.clear()
   1068 
   1069     @command('n')
   1070     async def clear_ln_blacklist(self):
   1071         self.network.path_finder.blacklist.clear()
   1072 
   1073     @command('w')
   1074     async def list_invoices(self, wallet: Abstract_Wallet = None):
   1075         l = wallet.get_invoices()
   1076         return [wallet.export_invoice(x) for x in l]
   1077 
   1078     @command('wn')
   1079     async def close_channel(self, channel_point, force=False, wallet: Abstract_Wallet = None):
   1080         txid, index = channel_point.split(':')
   1081         chan_id, _ = channel_id_from_funding_tx(txid, int(index))
   1082         coro = wallet.lnworker.force_close_channel(chan_id) if force else wallet.lnworker.close_channel(chan_id)
   1083         return await coro
   1084 
   1085     @command('wn')
   1086     async def request_force_close(self, channel_point, wallet: Abstract_Wallet = None):
   1087         txid, index = channel_point.split(':')
   1088         chan_id, _ = channel_id_from_funding_tx(txid, int(index))
   1089         return await wallet.lnworker.request_force_close_from_backup(chan_id)
   1090 
   1091     @command('w')
   1092     async def export_channel_backup(self, channel_point, wallet: Abstract_Wallet = None):
   1093         txid, index = channel_point.split(':')
   1094         chan_id, _ = channel_id_from_funding_tx(txid, int(index))
   1095         return wallet.lnworker.export_channel_backup(chan_id)
   1096 
   1097     @command('w')
   1098     async def import_channel_backup(self, encrypted, wallet: Abstract_Wallet = None):
   1099         return wallet.lnworker.import_channel_backup(encrypted)
   1100 
   1101     @command('wn')
   1102     async def get_channel_ctx(self, channel_point, iknowwhatimdoing=False, wallet: Abstract_Wallet = None):
   1103         """ return the current commitment transaction of a channel """
   1104         if not iknowwhatimdoing:
   1105             raise Exception("WARNING: this command is potentially unsafe.\n"
   1106                             "To proceed, try again, with the --iknowwhatimdoing option.")
   1107         txid, index = channel_point.split(':')
   1108         chan_id, _ = channel_id_from_funding_tx(txid, int(index))
   1109         chan = wallet.lnworker.channels[chan_id]
   1110         tx = chan.force_close_tx()
   1111         return tx.serialize()
   1112 
   1113     @command('wn')
   1114     async def get_watchtower_ctn(self, channel_point, wallet: Abstract_Wallet = None):
   1115         """ return the local watchtower's ctn of channel. used in regtests """
   1116         return await self.network.local_watchtower.sweepstore.get_ctn(channel_point, None)
   1117 
   1118     @command('wnp')
   1119     async def normal_swap(self, onchain_amount, lightning_amount, password=None, wallet: Abstract_Wallet = None):
   1120         """
   1121         Normal submarine swap: send on-chain BTC, receive on Lightning
   1122         Note that your funds will be locked for 24h if you do not have enough incoming capacity.
   1123         """
   1124         sm = wallet.lnworker.swap_manager
   1125         if lightning_amount == 'dryrun':
   1126             await sm.get_pairs()
   1127             onchain_amount_sat = satoshis(onchain_amount)
   1128             lightning_amount_sat = sm.get_recv_amount(onchain_amount_sat, is_reverse=False)
   1129             txid = None
   1130         elif onchain_amount == 'dryrun':
   1131             await sm.get_pairs()
   1132             lightning_amount_sat = satoshis(lightning_amount)
   1133             onchain_amount_sat = sm.get_send_amount(lightning_amount_sat, is_reverse=False)
   1134             txid = None
   1135         else:
   1136             lightning_amount_sat = satoshis(lightning_amount)
   1137             onchain_amount_sat = satoshis(onchain_amount)
   1138             txid = await wallet.lnworker.swap_manager.normal_swap(
   1139                 lightning_amount_sat=lightning_amount_sat,
   1140                 expected_onchain_amount_sat=onchain_amount_sat,
   1141                 password=password,
   1142             )
   1143         return {
   1144             'txid': txid,
   1145             'lightning_amount': format_satoshis(lightning_amount_sat),
   1146             'onchain_amount': format_satoshis(onchain_amount_sat),
   1147         }
   1148 
   1149     @command('wn')
   1150     async def reverse_swap(self, lightning_amount, onchain_amount, wallet: Abstract_Wallet = None):
   1151         """Reverse submarine swap: send on Lightning, receive on-chain
   1152         """
   1153         sm = wallet.lnworker.swap_manager
   1154         if onchain_amount == 'dryrun':
   1155             await sm.get_pairs()
   1156             lightning_amount_sat = satoshis(lightning_amount)
   1157             onchain_amount_sat = sm.get_recv_amount(lightning_amount_sat, is_reverse=True)
   1158             success = None
   1159         elif lightning_amount == 'dryrun':
   1160             await sm.get_pairs()
   1161             onchain_amount_sat = satoshis(onchain_amount)
   1162             lightning_amount_sat = sm.get_send_amount(onchain_amount_sat, is_reverse=True)
   1163             success = None
   1164         else:
   1165             lightning_amount_sat = satoshis(lightning_amount)
   1166             onchain_amount_sat = satoshis(onchain_amount)
   1167             success = await wallet.lnworker.swap_manager.reverse_swap(
   1168                 lightning_amount_sat=lightning_amount_sat,
   1169                 expected_onchain_amount_sat=onchain_amount_sat,
   1170             )
   1171         return {
   1172             'success': success,
   1173             'lightning_amount': format_satoshis(lightning_amount_sat),
   1174             'onchain_amount': format_satoshis(onchain_amount_sat),
   1175         }
   1176 
   1177 
   1178 def eval_bool(x: str) -> bool:
   1179     if x == 'false': return False
   1180     if x == 'true': return True
   1181     try:
   1182         return bool(ast.literal_eval(x))
   1183     except:
   1184         return bool(x)
   1185 
   1186 param_descriptions = {
   1187     'privkey': 'Private key. Type \'?\' to get a prompt.',
   1188     'destination': 'Bitcoin address, contact or alias',
   1189     'address': 'Bitcoin address',
   1190     'seed': 'Seed phrase',
   1191     'txid': 'Transaction ID',
   1192     'pos': 'Position',
   1193     'height': 'Block height',
   1194     'tx': 'Serialized transaction (hexadecimal)',
   1195     'key': 'Variable name',
   1196     'pubkey': 'Public key',
   1197     'message': 'Clear text message. Use quotes if it contains spaces.',
   1198     'encrypted': 'Encrypted message',
   1199     'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.',
   1200     'requested_amount': 'Requested amount (in BTC).',
   1201     'outputs': 'list of ["address", amount]',
   1202     'redeem_script': 'redeem script (hexadecimal)',
   1203     'lightning_amount': "Amount sent or received in a submarine swap. Set it to 'dryrun' to receive a value",
   1204     'onchain_amount': "Amount sent or received in a submarine swap. Set it to 'dryrun' to receive a value",
   1205 }
   1206 
   1207 command_options = {
   1208     'password':    ("-W", "Password"),
   1209     'new_password':(None, "New Password"),
   1210     'encrypt_file':(None, "Whether the file on disk should be encrypted with the provided password"),
   1211     'receiving':   (None, "Show only receiving addresses"),
   1212     'change':      (None, "Show only change addresses"),
   1213     'frozen':      (None, "Show only frozen addresses"),
   1214     'unused':      (None, "Show only unused addresses"),
   1215     'funded':      (None, "Show only funded addresses"),
   1216     'balance':     ("-b", "Show the balances of listed addresses"),
   1217     'labels':      ("-l", "Show the labels of listed addresses"),
   1218     'nocheck':     (None, "Do not verify aliases"),
   1219     'imax':        (None, "Maximum number of inputs"),
   1220     'fee':         ("-f", "Transaction fee (absolute, in BTC)"),
   1221     'feerate':     (None, "Transaction fee rate (in sat/byte)"),
   1222     'from_addr':   ("-F", "Source address (must be a wallet address; use sweep to spend from non-wallet address)."),
   1223     'from_coins':  (None, "Source coins (must be in wallet; use sweep to spend from non-wallet address)."),
   1224     'change_addr': ("-c", "Change address. Default is a spare address, or the source address if it's not in the wallet"),
   1225     'nbits':       (None, "Number of bits of entropy"),
   1226     'seed_type':   (None, "The type of seed to create, e.g. 'standard' or 'segwit'"),
   1227     'language':    ("-L", "Default language for wordlist"),
   1228     'passphrase':  (None, "Seed extension"),
   1229     'privkey':     (None, "Private key. Set to '?' to get a prompt."),
   1230     'unsigned':    ("-u", "Do not sign transaction"),
   1231     'rbf':         (None, "Whether to signal opt-in Replace-By-Fee in the transaction (true/false)"),
   1232     'locktime':    (None, "Set locktime block number"),
   1233     'addtransaction': (None,'Whether transaction is to be used for broadcasting afterwards. Adds transaction to the wallet'),
   1234     'domain':      ("-D", "List of addresses"),
   1235     'memo':        ("-m", "Description of the request"),
   1236     'expiration':  (None, "Time in seconds"),
   1237     'attempts':    (None, "Number of payment attempts"),
   1238     'timeout':     (None, "Timeout in seconds"),
   1239     'force':       (None, "Create new address beyond gap limit, if no more addresses are available."),
   1240     'pending':     (None, "Show only pending requests."),
   1241     'push_amount': (None, 'Push initial amount (in BTC)'),
   1242     'expired':     (None, "Show only expired requests."),
   1243     'paid':        (None, "Show only paid requests."),
   1244     'show_addresses': (None, "Show input and output addresses"),
   1245     'show_fiat':   (None, "Show fiat value of transactions"),
   1246     'show_fees':   (None, "Show miner fees paid by transactions"),
   1247     'year':        (None, "Show history for a given year"),
   1248     'fee_method':  (None, "Fee estimation method to use"),
   1249     'fee_level':   (None, "Float between 0.0 and 1.0, representing fee slider position"),
   1250     'from_height': (None, "Only show transactions that confirmed after given block height"),
   1251     'to_height':   (None, "Only show transactions that confirmed before given block height"),
   1252     'iknowwhatimdoing': (None, "Acknowledge that I understand the full implications of what I am about to do"),
   1253     'gossip':      (None, "Apply command to gossip node instead of wallet"),
   1254 }
   1255 
   1256 
   1257 # don't use floats because of rounding errors
   1258 from .transaction import convert_raw_tx_to_hex
   1259 json_loads = lambda x: json.loads(x, parse_float=lambda x: str(Decimal(x)))
   1260 arg_types = {
   1261     'num': int,
   1262     'nbits': int,
   1263     'imax': int,
   1264     'year': int,
   1265     'from_height': int,
   1266     'to_height': int,
   1267     'tx': convert_raw_tx_to_hex,
   1268     'pubkeys': json_loads,
   1269     'jsontx': json_loads,
   1270     'inputs': json_loads,
   1271     'outputs': json_loads,
   1272     'fee': lambda x: str(Decimal(x)) if x is not None else None,
   1273     'amount': lambda x: str(Decimal(x)) if x != '!' else '!',
   1274     'locktime': int,
   1275     'addtransaction': eval_bool,
   1276     'fee_method': str,
   1277     'fee_level': json_loads,
   1278     'encrypt_file': eval_bool,
   1279     'rbf': eval_bool,
   1280     'timeout': float,
   1281     'attempts': int,
   1282 }
   1283 
   1284 config_variables = {
   1285 
   1286     'addrequest': {
   1287         'ssl_privkey': 'Path to your SSL private key, needed to sign the request.',
   1288         'ssl_chain': 'Chain of SSL certificates, needed for signed requests. Put your certificate at the top and the root CA at the end',
   1289         'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
   1290     },
   1291     'listrequests':{
   1292         'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
   1293     }
   1294 }
   1295 
   1296 def set_default_subparser(self, name, args=None):
   1297     """see http://stackoverflow.com/questions/5176691/argparse-how-to-specify-a-default-subcommand"""
   1298     subparser_found = False
   1299     for arg in sys.argv[1:]:
   1300         if arg in ['-h', '--help']:  # global help if no subparser
   1301             break
   1302     else:
   1303         for x in self._subparsers._actions:
   1304             if not isinstance(x, argparse._SubParsersAction):
   1305                 continue
   1306             for sp_name in x._name_parser_map.keys():
   1307                 if sp_name in sys.argv[1:]:
   1308                     subparser_found = True
   1309         if not subparser_found:
   1310             # insert default in first position, this implies no
   1311             # global options without a sub_parsers specified
   1312             if args is None:
   1313                 sys.argv.insert(1, name)
   1314             else:
   1315                 args.insert(0, name)
   1316 
   1317 argparse.ArgumentParser.set_default_subparser = set_default_subparser
   1318 
   1319 
   1320 # workaround https://bugs.python.org/issue23058
   1321 # see https://github.com/nickstenning/honcho/pull/121
   1322 
   1323 def subparser_call(self, parser, namespace, values, option_string=None):
   1324     from argparse import ArgumentError, SUPPRESS, _UNRECOGNIZED_ARGS_ATTR
   1325     parser_name = values[0]
   1326     arg_strings = values[1:]
   1327     # set the parser name if requested
   1328     if self.dest is not SUPPRESS:
   1329         setattr(namespace, self.dest, parser_name)
   1330     # select the parser
   1331     try:
   1332         parser = self._name_parser_map[parser_name]
   1333     except KeyError:
   1334         tup = parser_name, ', '.join(self._name_parser_map)
   1335         msg = _('unknown parser {!r} (choices: {})').format(*tup)
   1336         raise ArgumentError(self, msg)
   1337     # parse all the remaining options into the namespace
   1338     # store any unrecognized options on the object, so that the top
   1339     # level parser can decide what to do with them
   1340     namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
   1341     if arg_strings:
   1342         vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
   1343         getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
   1344 
   1345 argparse._SubParsersAction.__call__ = subparser_call
   1346 
   1347 
   1348 def add_network_options(parser):
   1349     parser.add_argument("-f", "--serverfingerprint", dest="serverfingerprint", default=None, help="only allow connecting to servers with a matching SSL certificate SHA256 fingerprint." + " " +
   1350                                                                                                   "To calculate this yourself: '$ openssl x509 -noout -fingerprint -sha256 -inform pem -in mycertfile.crt'. Enter as 64 hex chars.")
   1351     parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=None, help="connect to one server only")
   1352     parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)")
   1353     parser.add_argument("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port] (or 'none' to disable proxy), where type is socks4,socks5 or http")
   1354     parser.add_argument("--noonion", action="store_true", dest="noonion", default=None, help="do not try to connect to onion servers")
   1355     parser.add_argument("--skipmerklecheck", action="store_true", dest="skipmerklecheck", default=None, help="Tolerate invalid merkle proofs from server")
   1356 
   1357 def add_global_options(parser):
   1358     group = parser.add_argument_group('global options')
   1359     group.add_argument("-v", dest="verbosity", help="Set verbosity (log levels)", default='')
   1360     group.add_argument("-V", dest="verbosity_shortcuts", help="Set verbosity (shortcut-filter list)", default='')
   1361     group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory")
   1362     group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory")
   1363     group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet")
   1364     group.add_argument("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest")
   1365     group.add_argument("--simnet", action="store_true", dest="simnet", default=False, help="Use Simnet")
   1366     group.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
   1367 
   1368 def add_wallet_option(parser):
   1369     parser.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
   1370     parser.add_argument("--forgetconfig", action="store_true", dest="forget_config", default=False, help="Forget config on exit")
   1371 
   1372 def get_parser():
   1373     # create main parser
   1374     parser = argparse.ArgumentParser(
   1375         epilog="Run 'electrum help <command>' to see the help for a command")
   1376     add_global_options(parser)
   1377     add_wallet_option(parser)
   1378     subparsers = parser.add_subparsers(dest='cmd', metavar='<command>')
   1379     # gui
   1380     parser_gui = subparsers.add_parser('gui', description="Run Electrum's Graphical User Interface.", help="Run GUI (default)")
   1381     parser_gui.add_argument("url", nargs='?', default=None, help="bitcoin URI (or bip70 file)")
   1382     parser_gui.add_argument("-g", "--gui", dest="gui", help="select graphical user interface", choices=['qt', 'kivy', 'text', 'stdio'])
   1383     parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup")
   1384     parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI")
   1385     parser_gui.add_argument("--daemon", action="store_true", dest="daemon", default=False, help="keep daemon running after GUI is closed")
   1386     parser_gui.add_argument("--nosegwit", action="store_true", dest="nosegwit", default=False, help="Do not create segwit wallets")
   1387     add_wallet_option(parser_gui)
   1388     add_network_options(parser_gui)
   1389     add_global_options(parser_gui)
   1390     # daemon
   1391     parser_daemon = subparsers.add_parser('daemon', help="Run Daemon")
   1392     parser_daemon.add_argument("-d", "--detached", action="store_true", dest="detach", default=False, help="run daemon in detached mode")
   1393     add_network_options(parser_daemon)
   1394     add_global_options(parser_daemon)
   1395     # commands
   1396     for cmdname in sorted(known_commands.keys()):
   1397         cmd = known_commands[cmdname]
   1398         p = subparsers.add_parser(cmdname, help=cmd.help, description=cmd.description)
   1399         for optname, default in zip(cmd.options, cmd.defaults):
   1400             if optname in ['wallet_path', 'wallet']:
   1401                 add_wallet_option(p)
   1402                 continue
   1403             a, help = command_options[optname]
   1404             b = '--' + optname
   1405             action = "store_true" if default is False else 'store'
   1406             args = (a, b) if a else (b,)
   1407             if action == 'store':
   1408                 _type = arg_types.get(optname, str)
   1409                 p.add_argument(*args, dest=optname, action=action, default=default, help=help, type=_type)
   1410             else:
   1411                 p.add_argument(*args, dest=optname, action=action, default=default, help=help)
   1412         add_global_options(p)
   1413 
   1414         for param in cmd.params:
   1415             if param in ['wallet_path', 'wallet']:
   1416                 continue
   1417             h = param_descriptions.get(param, '')
   1418             _type = arg_types.get(param, str)
   1419             p.add_argument(param, help=h, type=_type)
   1420 
   1421         cvh = config_variables.get(cmdname)
   1422         if cvh:
   1423             group = p.add_argument_group('configuration variables', '(set with setconfig/getconfig)')
   1424             for k, v in cvh.items():
   1425                 group.add_argument(k, nargs='?', help=v)
   1426 
   1427     # 'gui' is the default command
   1428     parser.set_default_subparser('gui')
   1429     return parser