chris-belcher / electrum-personal-server

Maximally lightweight electrum server for a single user
MIT License
601 stars 110 forks source link

Auto.py #257

Closed Jesusown closed 2 years ago

Jesusown commented 2 years ago

[ ​import​ ​sys ​import​ ​datetime ​import​ ​copy ​import​ ​argparse ​import​ ​json ​import​ ​ast ​import​ ​base64 ​import​ ​operator ​import​ ​asyncio ​import​ ​inspect ​from​ ​collections​ ​import​ ​defaultdict ​from​ ​functools​ ​import​ ​wraps​, ​partial ​from​ ​itertools​ ​import​ ​repeat ​from​ ​decimal​ ​import​ ​Decimal ​from​ ​typing​ ​import​ ​Optional​, ​TYPE_CHECKING​, ​Dict​, ​List

​from​ .​import​ ​util​, ​ecc ​from​ .​util​ ​import​ (​bfh​, ​bh2u​, ​format_satoshis​, ​json_decode​, ​json_normalize​, ​                   ​is_hash256_str​, ​is_hex_str​, ​to_bytes​, ​parse_max_spend​) ​from​ . ​import​ ​bitcoin ​from​ .​bitcoin​ ​import​ ​is_address​,  ​hash160​, ​COIN ​from​ .​bip32​ ​import​ ​BIP32Node ​from​ .​i18n​ ​import​ ​ ​from​ .​transaction​ ​import​ (​Transaction​, ​multisig_script​, ​TxOutput​, ​PartialTransaction​, ​PartialTxOutput​, ​                          ​tx_from_any​, ​PartialTxInput​, ​TxOutpoint​) ​from​ .​invoices​ ​import​ ​PR_PAID​, ​PR_UNPAID​, ​PR_UNKNOWN​, ​PR_EXPIRED ​from​ .​synchronizer​ ​import​ ​Notifier ​from​ .​wallet​ ​import​ ​Abstract_Wallet​, ​create_new_wallet​, ​restore_wallet_from_text​, ​Deterministic_Wallet​, ​BumpFeeStrategy ​from​ .​address_synchronizer​ ​import​ ​TX_HEIGHT_LOCAL ​from​ .​mnemonic​ ​import​ ​Mnemonic ​from​ .​lnutil​ ​import​ ​SENT​, ​RECEIVED ​from​ .​lnutil​ ​import​ ​LnFeatures ​from​ .​lnutil​ ​import​ ​extract_nodeid ​from​ .​lnpeer​ ​import​ ​channel_id_from_funding_tx ​from​ .​plugin​ ​import​ ​run_hook ​from​ .​version​ ​import​ ​ELECTRUM_VERSION ​from​ .​simple_config​ ​import​ ​SimpleConfig ​from​ .​invoices​ ​import​ ​LNInvoice ​from​ . ​import​ ​submarine_swaps

​if​ ​TYPE_CHECKING​: ​    ​from​ .​network​ ​import​ ​Network ​    ​from​ .​daemon​ ​import​ ​Daemon

​known_commands​ ​=​ {}  ​# type: Dict[str, Command]

​class​ ​NotSynchronizedException​(​Exception​): ​    ​pass

​def​ ​satoshis_or_max​(​amount​): ​    ​return​ ​satoshis​(​amount​) ​if​ ​not​ ​parse_max_spend​(​amount​) ​else​ ​amount

​def​ ​satoshis​(​amount​): ​    ​# satoshi conversion must not be performed by the parser ​    ​return​ ​int​(​COIN​*​Decimal​(​amount​)) ​if​ ​amount​ ​is​ ​not​ ​None​ ​else​ ​None

​def​ ​format_satoshis​(​x​): ​    ​return​ ​str​(​Decimal​(​x​)​/​COIN​) ​if​ ​x​ ​is​ ​not​ ​None​ ​else​ ​None

​class​ ​Command​: ​    ​def​ ​init​(​self​, ​func​, ​s​): ​        ​self​.​name​ ​=​ ​func​.​name ​        ​self​.​requires_network​ ​=​ ​'n'​ ​in​ ​s ​        ​self​.​requires_wallet​ ​=​ ​'w'​ ​in​ ​s ​        ​self​.​requires_password​ ​=​ ​'p'​ ​in​ ​s ​        ​self​.​requires_lightning​ ​=​ ​'l'​ ​in​ ​s ​        ​self​.​description​ ​=​ ​func​.​doc ​        ​self​.​help​ ​=​ ​self​.​description​.​split​(​'.'​)[​0​] ​if​ ​self​.​description​ ​else​ ​None ​        ​varnames​ ​=​ ​func​.​code​.​co_varnames​[​1​:​func​.​code​.​co_argcount​] ​        ​self​.​defaults​ ​=​ ​func​.​defaults ​        ​if​ ​self​.​defaults​: ​            ​n​ ​=​ ​len​(​self​.​defaults​) ​            ​self​.​params​ ​=​ ​list​(​varnames​[:​-​n​]) ​            ​self​.​options​ ​=​ ​list​(​varnames​[​-​n​:]) ​        ​else​: ​            ​self​.​params​ ​=​ ​list​(​varnames​) ​            ​self​.​options​ ​=​ [] ​            ​self​.​defaults​ ​=​ []

​        ​# sanity checks ​        ​if​ ​self​.​requires_password​: ​            ​assert​ ​self​.​requires_wallet ​        ​for​ ​varname​ ​in​ (​'wallet_path'​, ​'wallet'​): ​            ​if​ ​varname​ ​in​ ​varnames​: ​                ​assert​ ​varname​ ​in​ ​self​.​options ​        ​assert​ ​not​ (​'wallet_path'​ ​in​ ​varnames​ ​and​ ​'wallet'​ ​in​ ​varnames​) ​        ​if​ ​self​.​requires_wallet​: ​            ​assert​ ​'wallet'​ ​in​ ​varnames

​def​ ​command​(​s​): ​    ​def​ ​decorator​(​func​): ​        ​global​ ​known_commands ​        ​name​ ​=​ ​func​.​name ​        ​known_commands​[​name​] ​=​ ​Command​(​func​, ​s​) ​        ​@​wraps​(​func​) ​        ​async​ ​def​ ​func_wrapper​(​*​args​, ​*​kwargs​): ​            ​cmd_runner​ ​=​ ​args​[​0​]  ​# type: Commands ​            ​cmd​ ​=​ ​known_commands​[​func​.​name​]  ​# type: Command ​            ​password​ ​=​ ​kwargs​.​get​(​'password'​) ​            ​daemon​ ​=​ ​cmd_runner​.​daemon ​            ​if​ ​daemon​: ​                ​if​ ​'wallet_path'​ ​in​ ​cmd​.​options​ ​and​ ​kwargs​.​get​(​'wallet_path'​) ​is​ ​None​: ​                    ​kwargs​[​'wallet_path'​] ​=​ ​daemon​.​config​.​get_wallet_path​() ​                ​if​ ​cmd​.​requires_wallet​ ​and​ ​kwargs​.​get​(​'wallet'​) ​is​ ​None​: ​                    ​kwargs​[​'wallet'​] ​=​ ​daemon​.​config​.​get_wallet_path​() ​                ​if​ ​'wallet'​ ​in​ ​cmd​.​options​: ​                    ​wallet_path​ ​=​ ​kwargs​.​get​(​'wallet'​, ​None​) ​                    ​if​ ​isinstance​(​wallet_path​, ​str​): ​                        ​wallet​ ​=​ ​daemon​.​get_wallet​(​wallet_path​) ​                        ​if​ ​wallet​ ​is​ ​None​: ​                            ​raise​ ​Exception​(​'wallet not loaded'​) ​                        ​kwargs​[​'wallet'​] ​=​ ​wallet ​            ​wallet​ ​=​ ​kwargs​.​get​(​'wallet'​)  ​# type: Optional[Abstract_Wallet] ​            ​if​ ​cmd​.​requires_wallet​ ​and​ ​not​ ​wallet​: ​                ​raise​ ​Exception​(​'wallet not loaded'​) ​            ​if​ ​cmd​.​requires_password​ ​and​ ​password​ ​is​ ​None​ ​and​ ​wallet​.​has_password​(): ​                ​raise​ ​Exception​(​'Password required'​) ​            ​if​ ​cmd​.​requires_lightning​ ​and​ (​not​ ​wallet​ ​or​ ​not​ ​wallet​.​has_lightning​()): ​                ​raise​ ​Exception​(​'Lightning not enabled in this wallet'​) ​            ​return​ ​await​ ​func​(​​args​, ​**​kwargs​) ​        ​return​ ​func_wrapper ​    ​return​ ​decorator

​class​ ​Commands​:

​    ​def​ ​init​(​self​, *, ​config​: ​'SimpleConfig'​, ​                 ​network​: ​'Network'​ ​=​ ​None​, ​                 ​daemon​: ​'Daemon'​ ​=​ ​None​, ​callback​=​None​): ​        ​self​.​config​ ​=​ ​config ​        ​self​.​daemon​ ​=​ ​daemon ​        ​self​.​network​ ​=​ ​network ​        ​self​.​_callback​ ​=​ ​callback

​    ​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​) ​        ​wallet​ ​=​ ​kwargs​.​get​(​'wallet'​, ​None​) ​        ​if​ (​cmd​.​requires_password​ ​and​ ​wallet​ ​and​ ​wallet​.​has_password​() ​                ​and​ ​password​ ​is​ ​None​): ​            ​password​ ​=​ ​password_getter​() ​            ​if​ ​password​ ​is​ ​None​: ​                ​return

​        ​f​ ​=​ ​getattr​(​self​, ​method​) ​        ​if​ ​cmd​.​requires_password​: ​            ​kwargs​[​'password'​] ​=​ ​password

​        ​if​ ​'wallet'​ ​in​ ​kwargs​: ​            ​sig​ ​=​ ​inspect​.​signature​(​f​) ​            ​if​ ​'wallet'​ ​not​ ​in​ ​sig​.​parameters​: ​                ​kwargs​.​pop​(​'wallet'​)

​        ​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​(​''​) ​    ​async​ ​def​ ​commands​(​self​): ​        ​"""List of commands""" ​        ​return​ ​' '​.​join​(​sorted​(​known_commands​.​keys​()))

​    ​@​command​(​'n'​) ​    ​async​ ​def​ ​getinfo​(​self​): ​        ​""" network info """ ​        ​net_params​ ​=​ ​self​.​network​.​get_parameters​() ​        ​response​ ​=​ { ​            ​'path'​: ​self​.​network​.​config​.​path​, ​            ​'server'​: ​net_params​.​server​.​host​, ​            ​'blockchain_height'​: ​self​.​network​.​get_local_height​(), ​            ​'server_height'​: ​self​.​network​.​get_server_height​(), ​            ​'spv_nodes'​: ​len​(​self​.​network​.​get_interfaces​()), ​            ​'connected'​: ​self​.​network​.​is_connected​(), ​            ​'auto_connect'​: ​net_params​.​auto_connect​, ​            ​'version'​: ​ELECTRUM_VERSION​, ​            ​'default_wallet'​: ​self​.​config​.​get_wallet_path​(), ​            ​'fee_per_kb'​: ​self​.​config​.​fee_per_kb​(), ​        } ​        ​return​ ​response

​    ​@​command​(​'n'​) ​    ​async​ ​def​ ​stop​(​self​): ​        ​"""Stop daemon""" ​        ​await​ ​self​.​daemon​.​stop​() ​        ​return​ ​"Daemon stopped"

​    ​@​command​(​'n'​) ​    ​async​ ​def​ ​list_wallets​(​self​): ​        ​"""List wallets open in daemon""" ​        ​return​ [{​'path'​: ​path​, ​'synchronized'​: ​w​.​is_up_to_date​()} ​                ​for​ ​path​, ​w​ ​in​ ​self​.​daemon​.​get_wallets​().​items​()]

​    ​@​command​(​'n'​) ​    ​async​ ​def​ ​load_wallet​(​self​, ​wallet_path​=​None​, ​password​=​None​): ​        ​"""Open wallet in daemon""" ​        ​wallet​ ​=​ ​self​.​daemon​.​load_wallet​(​wallet_path​, ​password​, ​manual_upgrades​=​False​) ​        ​if​ ​wallet​ ​is​ ​not​ ​None​: ​            ​run_hook​(​'load_wallet'​, ​wallet​, ​None​) ​        ​response​ ​=​ ​wallet​ ​is​ ​not​ ​None ​        ​return​ ​response

​    ​@​command​(​'n'​) ​    ​async​ ​def​ ​close_wallet​(​self​, ​wallet_path​=​None​): ​        ​"""Close wallet""" ​        ​return​ ​await​ ​self​.​daemon​.​_stop_wallet​(​wallet_path​)

​    ​@​command​(​''​) ​    ​async​ ​def​ ​create​(​self​, ​passphrase​=​None​, ​password​=​None​, ​encrypt_file​=​True​, ​seed_type​=​None​, ​wallet_path​=​None​): ​        ​"""Create a new wallet. ​        If you want to be prompted for an argument, type '?' or ':' (concealed) ​        """ ​        ​d​ ​=​ ​create_new_wallet​(​path​=​wallet_path​, ​                              ​passphrase​=​passphrase​, ​                              ​password​=​password​, ​                              ​encrypt_file​=​encrypt_file​, ​                              ​seed_type​=​seed_type​, ​                              ​config​=​self​.​config​) ​        ​return​ { ​            ​'seed'​: ​d​[​'seed'​], ​            ​'path'​: ​d​[​'wallet'​].​storage​.​path​, ​            ​'msg'​: ​d​[​'msg'​], ​        }

​    ​@​command​(​''​) ​    ​async​ ​def​ ​restore​(​self​, ​text​, ​passphrase​=​None​, ​password​=​None​, ​encrypt_file​=​True​, ​wallet_path​=​None​): ​        ​"""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. ​        If you want to be prompted for an argument, type '?' or ':' (concealed) ​        """ ​        ​# TODO create a separate command that blocks until wallet is synced ​        ​d​ ​=​ ​restore_wallet_from_text​(​text​, ​                                     ​path​=​wallet_path​, ​                                     ​passphrase​=​passphrase​, ​                                     ​password​=​password​, ​                                     ​encrypt_file​=​encrypt_file​, ​                                     ​config​=​self​.​config​) ​        ​return​ { ​            ​'path'​: ​d​[​'wallet'​].​storage​.​path​, ​            ​'msg'​: ​d​[​'msg'​], ​        }

​    ​@​command​(​'wp'​) ​    ​async​ ​def​ ​password​(​self​, ​password​=​None​, ​new_password​=​None​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Change wallet password. """ ​        ​if​ ​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."​) ​        ​b​ ​=​ ​wallet​.​storage​.​is_encrypted​() ​        ​wallet​.​update_password​(​password​, ​new_password​, ​encrypt_storage​=​b​) ​        ​wallet​.​save_db​() ​        ​return​ {​'password'​:​wallet​.​has_password​()}

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​get​(​self​, ​key​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Return item from wallet storage""" ​        ​return​ ​wallet​.​db​.​get​(​key​)

​    ​@​command​(​''​) ​    ​async​ ​def​ ​getconfig​(​self​, ​key​): ​        ​"""Return a configuration variable. """ ​        ​return​ ​self​.​config​.​get​(​key​)

​    ​@​classmethod ​    ​def​ ​_setconfig_normalize_value​(​cls​, ​key​, ​value​): ​        ​if​ ​key​ ​not​ ​in​ (​'rpcuser'​, ​'rpcpassword'​): ​            ​value​ ​=​ ​json_decode​(​value​) ​            ​# call literal_eval for backward compatibility (see #4225) ​            ​try​: ​                ​value​ ​=​ ​ast​.​literal_eval​(​value​) ​            ​except​: ​                ​pass ​        ​return​ ​value

​    ​@​command​(​''​) ​    ​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​) ​        ​if​ ​self​.​daemon​ ​and​ ​key​ ​==​ ​'rpcuser'​: ​            ​self​.​daemon​.​commands_server​.​rpc_user​ ​=​ ​value ​        ​if​ ​self​.​daemon​ ​and​ ​key​ ​==​ ​'rpcpassword'​: ​            ​self​.​daemon​.​commands_server​.​rpc_password​ ​=​ ​value ​        ​self​.​config​.​set_key​(​key​, ​value​) ​        ​return​ ​True

​    ​@​command​(​''​) ​    ​async​ ​def​ ​get_ssl_domain​(​self​): ​        ​"""Check and return the SSL domain set in ssl_keyfile and ssl_certfile ​        """ ​        ​return​ ​self​.​config​.​get_ssl_domain​()

​    ​@​command​(​''​) ​    ​async​ ​def​ ​make_seed​(​self​, ​nbits​=​None​, ​language​=​None​, ​seed_type​=​None​): ​        ​"""Create a seed""" ​        ​from​ .​mnemonic​ ​import​ ​Mnemonic ​        ​s​ ​=​ ​Mnemonic​(​language​).​make_seed​(​seed_type​=​seed_type​, ​num_bits​=​nbits​) ​        ​return​ ​s

​    ​@​command​(​'n'​) ​    ​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​ ​await​ ​self​.​network​.​get_history_for_scripthash​(​sh​)

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​listunspent​(​self​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""List unspent outputs. Returns the list of unspent transaction ​        outputs in your wallet.""" ​        ​coins​ ​=​ [] ​        ​for​ ​txin​ ​in​ ​wallet​.​get_utxos​(): ​            ​d​ ​=​ ​txin​.​to_json​() ​            ​v​ ​=​ ​d​.​pop​(​"value_sats"​) ​            ​d​[​"value"​] ​=​ ​str​(​Decimal​(​v​)​/​COIN​) ​if​ ​v​ ​is​ ​not​ ​None​ ​else​ ​None ​            ​coins​.​append​(​d​) ​        ​return​ ​coins

​    ​@​command​(​'n'​) ​    ​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​ ​await​ ​self​.​network​.​listunspent_for_scripthash​(​sh​)

​    ​@​command​(​''​) ​    ​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}. ​        """ ​        ​keypairs​ ​=​ {} ​        ​inputs​ ​=​ []  ​# type: List[PartialTxInput] ​        ​locktime​ ​=​ ​jsontx​.​get​(​'locktime'​, ​0​) ​        ​for​ ​txin_dict​ ​in​ ​jsontx​.​get​(​'inputs'​): ​            ​if​ ​txin_dict​.​get​(​'prevout_hash'​) ​is​ ​not​ ​None​ ​and​ ​txin_dict​.​get​(​'prevout_n'​) ​is​ ​not​ ​None​: ​                ​prevout​ ​=​ ​TxOutpoint​(​txid​=​bfh​(​txin_dict​[​'prevout_hash'​]), ​out_idx​=​int​(​txin_dict​[​'prevout_n'​])) ​            ​elif​ ​txin_dict​.​get​(​'output'​): ​                ​prevout​ ​=​ ​TxOutpoint​.​from_str​(​txin_dict​[​'output'​]) ​            ​else​: ​                ​raise​ ​Exception​(​"missing prevout for txin"​) ​            ​txin​ ​=​ ​PartialTxInput​(​prevout​=​prevout​) ​            ​txin​.​_trusted_value_sats​ ​=​ ​int​(​txin_dict​.​get​(​'value'​, ​txin_dict​[​'value_sats'​])) ​            ​nsequence​ ​=​ ​txin_dict​.​get​(​'nsequence'​, ​None​) ​            ​if​ ​nsequence​ ​is​ ​not​ ​None​: ​                ​txin​.​nsequence​ ​=​ ​nsequence ​            ​sec​ ​=​ ​txin_dict​.​get​(​'privkey'​) ​            ​if​ ​sec​: ​                ​txin_type​, ​privkey​, ​compressed​ ​=​ ​bitcoin​.​deserialize_privkey​(​sec​) ​                ​pubkey​ ​=​ ​ecc​.​ECPrivkey​(​privkey​).​get_public_key_hex​(​compressed​=​compressed​) ​                ​keypairs​[​pubkey​] ​=​ ​privkey​, ​compressed ​                ​txin​.​script_type​ ​=​ ​txin_type ​                ​txin​.​pubkeys​ ​=​ [​bfh​(​pubkey​)] ​                ​txin​.​num_sig​ ​=​ ​1 ​            ​inputs​.​append​(​txin​)

​        ​outputs​ ​=​ [​PartialTxOutput​.​from_address_and_value​(​txout​[​'address'​], ​int​(​txout​.​get​(​'value'​, ​txout​[​'value_sats'​]))) ​                   ​for​ ​txout​ ​in​ ​jsontx​.​get​(​'outputs'​)] ​        ​tx​ ​=​ ​PartialTransaction​.​from_io​(​inputs​, ​outputs​, ​locktime​=​locktime​) ​        ​tx​.​sign​(​keypairs​) ​        ​return​ ​tx​.​serialize​()

​    ​@​command​(​''​) ​    ​async​ ​def​ ​signtransaction_with_privkey​(​self​, ​tx​, ​privkey​): ​        ​"""Sign a transaction. The provided list of private keys will be used to sign the transaction.""" ​        ​tx​ ​=​ ​tx_from_any​(​tx​)

​        ​txins_dict​ ​=​ ​defaultdict​(​list​) ​        ​for​ ​txin​ ​in​ ​tx​.​inputs​(): ​            ​txins_dict​[​txin​.​address​].​append​(​txin​)

​        ​if​ ​not​ ​isinstance​(​privkey​, ​list​): ​            ​privkey​ ​=​ [​privkey​]

​        ​for​ ​priv​ ​in​ ​privkey​: ​            ​txin_type​, ​priv2​, ​compressed​ ​=​ ​bitcoin​.​deserialize_privkey​(​priv​) ​            ​pubkey​ ​=​ ​ecc​.​ECPrivkey​(​priv2​).​get_public_key_bytes​(​compressed​=​compressed​) ​            ​address​ ​=​ ​bitcoin​.​pubkey_to_address​(​txin_type​, ​pubkey​.​hex​()) ​            ​if​ ​address​ ​in​ ​txins_dict​.​keys​(): ​                ​for​ ​txin​ ​in​ ​txins_dict​[​address​]: ​                    ​txin​.​pubkeys​ ​=​ [​pubkey​] ​                    ​txin​.​script_type​ ​=​ ​txin_type ​                ​tx​.​sign​({​pubkey​.​hex​(): (​priv2​, ​compressed​)})

​        ​return​ ​tx​.​serialize​()

​    ​@​command​(​'wp'​) ​    ​async​ ​def​ ​signtransaction​(​self​, ​tx​, ​password​=​None​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ]) ​        ​"""Sign a transaction. The wallet keys will be used to sign the transaction.""" ​        ​tx​ ​=​ ​tx_from_any​(​tx​) ​        ​wallet​.​sign_transaction​(​tx​, ​password​) ​        ​return​ ​tx​.​serialize​()

​    ​@​command​(​''​) ​    ​async​ ​def​ ​deserialize​(​self​, ​tx​): ​        ​"""Deserialize a serialized transaction""" ​        ​tx​ ​=​ ​tx_from_any​(​tx​) ​        ​return​ ​tx​.​to_json​()

​    ​@​command​(​'n'​) ​    ​async​ ​def​ ​broadcast​(​self​, ​tx​): ​        ​"""Broadcast a transaction to the network. """ ​        ​tx​ ​=​ ​Transaction​(​tx​) ​        ​await​ ​self​.​network​.​broadcast_transaction​(​tx​) ​        ​return​ ​tx​.​txid​()

​    ​@​command​(​''​) ​    ​async​ ​def​ ​createmultisig​(​self​, ​num​, ​pubkeys​): ​        ​"""Create multisig address""" ​        ​assert​ ​isinstance​(​pubkeys​, ​list​), (​type​(​num​), ​type​(​pubkeys​)) ​        ​redeem_script​ ​=​ ​multisig_script​(​pubkeys​, ​num​) ​        ​address​ ​=​ ​bitcoin​.​hash160_to_p2sh​(​hash_160​(​bfh​(​redeem_script​))) ​        ​return​ {​'address'​:​address​, ​'redeemScript'​:​redeem_script​}

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​freeze​(​self​, ​address​: ​str​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Freeze address. Freeze the funds at one of your wallet​\'​s addresses""" ​        ​return​ ​wallet​.​set_frozen_state_of_addresses​([​address​], ​True​)

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​unfreeze​(​self​, ​address​: ​str​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Unfreeze address. Unfreeze the funds at one of your wallet​\'​s address""" ​        ​return​ ​wallet​.​set_frozen_state_of_addresses​([​address​], ​False​)

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​freeze_utxo​(​self​, ​coin​: ​str​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): (   [{"Show unspent outputs/auto/tx_hash_big_endian":"5da8f74aa4d6612c58c0a740b4740579db57c95828e4cdf04e7d18c23b66cb4a","tx_hash":"4acb663bc2187d4ef0cde42858c957db790574b440a7c0582c61d6a44af7a85d","tx_output_n":1,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":20100,"value_hex":"4e84","confirmations":4597,"tx_index":2631598528283407},{"tx_hash_big_endian":"354c4488e0aa4ebb787667f3560ab827ac337b9be60b3ef315e469c42fb71add","tx_hash":"dd1ab72fc469e415f33e0be69b7b33ac27b80a56f3677678bb4eaae088444c35","tx_output_n":73,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":17526,"value_hex":"4476","confirmations":5496,"tx_index":7779417991974204},{"tx_hash_big_endian":"21790d71d2581afdc456dbf22e1c95b77e2c0bd527aba199481cc0a4ed94d4f3","tx_hash":"f3d494eda4c01c4899a1ab27d50b2c7eb7951c2ef2db56c4fd1a58d2710d7921","tx_output_n":24,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":291612,"value_hex":"04731c","confirmations":25684,"tx_index":8579019430991875},{"tx_hash_big_endian":"9fed8adf37c5a496f080266105f823e2162c030c271a56e3f223391e7d8560ec","tx_hash":"ec60857d1e3923f2e3561a270c032c16e223f805612680f096a4c537df8aed9f","tx_output_n":25,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":6000,"value_hex":"1770","confirmations":26575,"tx_index":8316777618720548},{"tx_hash_big_endian":"9378ed7213ad98077ecf16a7a5b55a553ae63d0eeff2def979571e5030ff7447","tx_hash":"4774ff30501e5779f9def2ef0e3de63a555ab5a5a716cf7e0798ad1372ed7893","tx_output_n":60,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":10000,"value_hex":"2710","confirmations":39674,"tx_index":2514170340312010},{"tx_hash_big_endian":"7b2a671cec84301314fe1b27c0c993c5850fb3463da6c479f97820cd7f08a967","tx_hash":"67a9087fcd2078f979c4a63d46b30f85c593c9c0271bfe14133084ec1c672a7b","tx_output_n":1,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":16325,"value_hex":"3fc5","confirmations":46427,"tx_index":3647222071272463},{"tx_hash_big_endian":"d91b8365dd3e0d3d7611fc270baf6685697f1c4037c9dec48f19447322f088c4","tx_hash":"c488f0227344198fc4dec937401c7f698566af0b27fc11763d0d3edd65831bd9","tx_output_n":2,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":50000,"value_hex":"00c350","confirmations":56293,"tx_index":6914957548349571},{"tx_hash_big_endian":"c48d85744f5762d3d02a8daff2dfdb15d1952dbe778e737da218c60a1ca34187","tx_hash":"8741a31c0ac618a27d738e77be2d95d115dbdff2af8d2ad0d362574f74858dc4","tx_output_n":1,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":596677,"value_hex":"091ac5","confirmations":56435,"tx_index":4758911332735171},{"tx_hash_big_endian":"fd2abfa58670e8d811aedb029c60d117f25186e00351f581ecf6e3063cc281f8","tx_hash":"f881c23c06e3f6ec81f55103e08651f217d1609c02dbae11d8e87086a5bf2afd","tx_output_n":6,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":50000,"value_hex":"00c350","confirmations":56487,"tx_index":8743558181870718},{"tx_hash_big_endian":"2d596683c9c843cf7c1339a202c6c0c30259be361a55339ee932663ebf07527f","tx_hash":"7f5207bf3e6632e99e33551a36be5902c3c0c602a239137ccf43c8c98366592d","tx_output_n":3,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":5433,"value_hex":"1539","confirmations":57336,"tx_index":4479689408629958},{"tx_hash_big_endian":"d67a35f492510b9f3f0819771d45440b2b1bd498bbc81d4b31e360fa9ad333f4","tx_hash":"f433d39afa60e3314b1dc8bb98d41b2b0b44451d7719083f9f0b5192f4357ad6","tx_output_n":0,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":1000,"value_hex":"03e8","confirmations":57412,"tx_index":8592109781077020},{"tx_hash_big_endian":"d1c131aeaf16fe81087e811c0acba292c0f139ad86d154ac037004187d848ad9","tx_hash":"d98a847d18047003ac54d186ad39f1c092a2cb0a1c817e0881fe16afae31c1d1","tx_output_n":98,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":547,"value_hex":"0223","confirmations":57472,"tx_index":7654046448156814},{"tx_hash_big_endian":"6f014b3265de8c840d340e3a7651e44fd9e9ed9e172b44905fcff29a5859ffd6","tx_hash":"d6ff59589af2cf5f90442b179eede9d94fe451763a0e340d848cde65324b016f","tx_output_n":1,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":100000,"value_hex":"0186a0","confirmations":57652,"tx_index":7564550527475289},{"tx_hash_big_endian":"ed5f4bf0ecebffaff1e7a81b537349a8133da58db771c40f4ad86fcf0549cef9","tx_hash":"f9ce4905cf6fd84a0fc471b78da53d13a84973531ba8e7f1afffebecf04b5fed","tx_output_n":0,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":6501,"value_hex":"1965","confirmations":57910,"tx_index":8789260278296059},{"tx_hash_big_endian":"388a95a32b75e5291c3160b5f2eef3dbcfcf482f5575f3b6aea1c7a80873812f","tx_hash":"2f817308a8c7a1aeb6f375552f48cfcfdbf3eef2b560311c29e5752ba3958a38","tx_output_n":0,"script":"76a914b3dd79fb3460c7b0d0bbb8d2ed93436b88b6d89c88ac","value":6125,"value_hex":"17ed","confirmations":58184,"tx_index":1671456871487732},{"tx_hash_big_endian":"263a47f985c5a3c5 ​        ​"""Freeze a UTXO so that the wallet will not spend it.""" ​        ​wallet​.​set_frozen_state_of_coins​([​coin​], ​True​) ​        ​return​ ​True

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​unfreeze_utxo​(​self​, ​coin​: ​str​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Unfreeze a UTXO so that the wallet might spend it.""" ​        ​wallet​.​set_frozen_state_of_coins​([​coin​], ​False​) ​        ​return​ ​True

​    ​@​command​(​'wp'​) ​    ​async​ ​def​ ​getprivatekeys​(​self​, ​address​, ​password​=​None​, ​wallet​: ​Abstract_Wallet​ ​=​ ​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​() ​        ​if​ ​is_address​(​address​): ​            ​return​ ​wallet​.​export_private_key​(​address​, ​password​) ​        ​domain​ ​=​ ​address ​        ​return​ [​wallet​.​export_private_key​(​address​, ​password​) ​for​ ​address​ ​in​ ​domain​]

​    ​@​command​(​'wp'​) ​    ​async​ ​def​ ​getprivatekeyforpath​(​self​, ​path​, ​password​=​None​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Get private key corresponding to derivation path (address index). ​        'path' can be either a str such as "m/0/50", or a list of ints such as [0, 50]. ​        """ ​        ​return​ ​wallet​.​export_private_key_for_path​(​path​, ​password​)

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​ismine​(​self​, ​address​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Check if address is in wallet. Return true if and only address is in wallet""" ​        ​return​ ​wallet​.​is_mine​(​address​)

​    ​@​command​(​''​) ​    ​async​ ​def​ ​dumpprivkeys​(​self​): ​        ​"""Deprecated.""" ​        ​return​ ​"This command is deprecated. Use a pipe instead: 'electrum listaddresses | electrum getprivatekeys - '"

​    ​@​command​(​''​) ​    ​async​ ​def​ ​validateaddress​(​self​, ​address​): ​        ​"""Check that an address is valid. """ ​        ​return​ ​is_address​(​address​)

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​getpubkeys​(​self​, ​address​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Return the public keys for a wallet address. """ ​        ​return​ ​wallet​.​get_public_keys​(​address​)

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​getbalance​(​self​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Return the balance of your wallet. """ ​        ​c​, ​u​, ​x​ ​=​ ​wallet​.​get_balance​() ​        ​l​ ​=​ ​wallet​.​lnworker​.​get_balance​() ​if​ ​wallet​.​lnworker​ ​else​ ​None ​        ​out​ ​=​ {​"confirmed"​: ​str​(​Decimal​(​c​)​/​COIN​)} ​        ​if​ ​u​: ​            ​out​[​"unconfirmed"​] ​=​ ​str​(​Decimal​(​u​)​/​COIN​) ​        ​if​ ​x​: ​            ​out​[​"unmatured"​] ​=​ ​str​(​Decimal​(​x​)​/​COIN​) ​        ​if​ ​l​: ​            ​out​[​"lightning"​] ​=​ ​str​(​Decimal​(​l​)​/​COIN​) ​        ​return​ ​out

​    ​@​command​(​'n'​) ​    ​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​ ​=​ ​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'​) ​    ​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​ ​await​ ​self​.​network​.​get_merkle_for_transaction​(​txid​, ​int​(​height​))

​    ​@​command​(​'n'​) ​    ​async​ ​def​ ​getservers​(​self​): ​        ​"""Return the list of known servers (candidates for connecting).""" ​        ​return​ ​self​.​network​.​get_servers​()

​    ​@​command​(​''​) ​    ​async​ ​def​ ​version​(​self​): ​        ​"""Return the version of Electrum.""" ​        ​from​ .​version​ ​import​ ​ELECTRUM_VERSION ​        ​return​ ​ELECTRUM_VERSION

​    ​@​command​(​'w'​) ​    ​async​ ​def​ ​getmpk​(​self​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Get master public key. Return your wallet​\'​s master public key""" ​        ​return​ ​wallet​.​get_master_public_key​()

​    ​@​command​(​'wp'​) ​    ​async​ ​def​ ​getmasterprivate​(​self​, ​password​=​None​, ​wallet​: ​Abstract_Wallet​ ​=​ ​None​): ​        ​"""Get master private key. Return your wallet​\'​s master private key""" ​        ​return​ ​str​(​wallet​.​keystore​.​get_master_private_key​(​password​))

​    ​@​command​(​''​) ​    ​async​ ​def​ ​convert_xkey​(​self​, ​xkey​, ​xtype​): ​        ​"""Convert xtype of a master key. e.g. xpub -> ypub""" ​        ​try​: ​            ​node​ ​=​ ​BIP32Node​.​from_xkey​(​xkey​) ​        ​except​:

Jesusown commented 2 years ago

Push