diff --git a/electrum_gui/android/console.py b/electrum_gui/android/console.py index 49e0017cb75f..3a5a2bd81c48 100755 --- a/electrum_gui/android/console.py +++ b/electrum_gui/android/console.py @@ -3268,13 +3268,14 @@ def export_privkey(self, password: str) -> str: return priv @api.api_entry() - def export_seed(self, password, name): + def export_seed(self, password, id): """ Export seed by on-chain wallet :param password: password by string + :id: wallet id by string :return: Mnemonic as string """ - wallet = self.daemon.get_wallet(self._wallet_path(name)) + wallet = self.daemon.get_wallet(self._wallet_path(id)) if not wallet.has_seed(): raise util.NotSupportExportSeed() keystore = wallet.get_keystore() @@ -3647,53 +3648,6 @@ def _get_derived_account_id(self, xpub, hw=False): account_id = 0 return account_id - @api.api_entry() - def get_default_show_path(self, hw=False, coin=None, derived=False, purpose="49", path="android_usb"): - """ - Get the default path for creating a wallet - :param hw: if true, it means that you currently need to create a hardware wallet - :param coin: btc/eth/bsc/heco as str - :param derived: if true, it means that you currently need to create a derived wallet - :param purpose: 44/49/84, only btc needs - :param path: NFC/android_usb/bluetooth as str - :return: - """ - if is_coin_migrated(coin): - chain_code = coin - chain_info = coin_manager.get_chain_info(chain_code) - address_encoding = None - if purpose: - purpose_to_address_encoding = {v: k for k, v in chain_info.bip44_purpose_options.items()} - address_encoding = purpose_to_address_encoding.get(int(purpose)) - - address_encoding = address_encoding or chain_info.default_address_encoding - if hw: - return wallet_manager.generate_next_bip44_path_for_primary_hardware_wallet( - chain_code, path, address_encoding - ).to_bip44_path() - elif derived: - return wallet_manager.generate_next_bip44_path_for_derived_primary_wallet( - chain_code, address_encoding - ).to_bip44_path() - else: - return wallet_manager.get_default_bip44_path(chain_code, address_encoding).to_bip44_path() - - account_id = 0 - if not hw and derived: - derive_key = self._get_hd_wallet_encode_seed(coin=coin, purpose=str(purpose)) - account_id = self._get_derived_account_id(derive_key) - elif hw: - xpub = self._get_xpub_from_hw(path=path, coin=coin, _type=PURPOSE_TO_ADDRESS_TYPE.get(purpose, "p2wpkh")) - account_id = self._get_derived_account_id(xpub + coin.lower(), hw=hw) - - chain_affinity = _get_chain_affinity(coin) - if chain_affinity == 'btc': - return "%s/0/0" % keystore.bip44_derivation(account_id, bip43_purpose=int(purpose)) - elif chain_affinity == "eth": - return keystore.bip44_eth_derivation(account_id) - else: - raise util.UnsupportedCurrencyCoin() - def _create_and_check_wallet( # noqa self, name, @@ -3975,6 +3929,56 @@ def create( # noqa is_customized_path=is_customized_path, ) + def _get_default_show_path(self, hw=False, coin=None, derived=False, purpose="49", path="android_usb"): + if is_coin_migrated(coin): + chain_code = coin + chain_info = coin_manager.get_chain_info(chain_code) + address_encoding = None + if purpose: + purpose_to_address_encoding = {v: k for k, v in chain_info.bip44_purpose_options.items()} + address_encoding = purpose_to_address_encoding.get(int(purpose)) + + address_encoding = address_encoding or chain_info.default_address_encoding + if hw: + return wallet_manager.generate_next_bip44_path_for_primary_hardware_wallet( + chain_code, path, address_encoding + ).to_bip44_path() + elif derived: + return wallet_manager.generate_next_bip44_path_for_derived_primary_wallet( + chain_code, address_encoding + ).to_bip44_path() + else: + return wallet_manager.get_default_bip44_path(chain_code, address_encoding).to_bip44_path() + + account_id = 0 + if not hw and derived: + derive_key = self._get_hd_wallet_encode_seed(coin=coin, purpose=str(purpose)) + account_id = self._get_derived_account_id(derive_key) + elif hw: + xpub = self._get_xpub_from_hw(path=path, coin=coin, _type=PURPOSE_TO_ADDRESS_TYPE.get(purpose, "p2wpkh")) + account_id = self._get_derived_account_id(xpub + coin.lower(), hw=hw) + + chain_affinity = _get_chain_affinity(coin) + if chain_affinity == 'btc': + return "%s/0/0" % keystore.bip44_derivation(account_id, bip43_purpose=int(purpose)) + elif chain_affinity == "eth": + return keystore.bip44_eth_derivation(account_id) + else: + raise util.UnsupportedCurrencyCoin() + + @api.api_entry() + def get_default_show_path(self, hw=False, coin=None, derived=False, purpose="49", path="android_usb"): + """ + Get the default path for creating a wallet + :param hw: if true, it means that you currently need to create a hardware wallet + :param coin: btc/eth/bsc/heco as str + :param derived: if true, it means that you currently need to create a derived wallet + :param purpose: 44/49/84, only btc needs + :param path: NFC/android_usb/bluetooth as str + :return: + """ + return self._get_default_show_path(hw=hw, coin=coin, derived=derived, purpose=purpose, path=path) + def _get_create_info_by_json(self, seed="", wallet_info=None, derived_info=None): from electrum_gui.android.create_wallet_info import CreateWalletInfo @@ -4846,10 +4850,10 @@ def update_wallet_name(self, old_name, new_name): return new_name @api.api_entry() - def switch_wallet(self, name): + def switch_wallet(self, id): """ Switching to a specific wallet - :param name: name as string + :param id: wallet id as string :return: json like { "name": "", @@ -4861,30 +4865,30 @@ def switch_wallet(self, name): } """ self._assert_daemon_running() - if name is None: + if id is None: raise util.FailedToSwitchWallet() - self.wallet = self.daemon.get_wallet(self._wallet_path(name)) + self.wallet = self.daemon.get_wallet(self._wallet_path(id)) self.wallet.use_change = self.config.get("use_change", False) chain_affinity = _get_chain_affinity(self.wallet.coin) if is_coin_migrated(self.wallet.coin) and isinstance(self.wallet, GeneralWallet): tokens = self.wallet.get_all_token_coins() info = { - "name": name, + "name": id, "label": self.wallet.get_name(), "wallets": [{"coin": i.symbol, "address": i.token_address, "coin_code": i.code} for i in tokens], } elif chain_affinity == "eth": contract_info = self.wallet.get_contract_symbols_with_address() - info = {"name": name, "label": self.wallet.get_name(), "wallets": contract_info} + info = {"name": id, "label": self.wallet.get_name(), "wallets": contract_info} elif chain_affinity == "btc": if not isinstance(self.wallet, Imported_Wallet): self.wallet.set_key_pool_size() util.trigger_callback("wallet_updated", self.wallet) info = { - "name": name, + "name": id, "label": self.wallet.get_name(), "wallets": [], } @@ -5345,6 +5349,482 @@ def network_list(self, params): return ret + # software account api for V3 + def _create_hd_account( + self, name: str, password: str, seed: str, bip39_derivation: str, coin: str, passphrase: str = "" + ): + self._check_password_for_sw_account(password) + wallet = None + wallet_type = f"{coin}-derived-standard" + chain_affinity = _get_chain_affinity(coin) + + if is_coin_migrated(coin): + wallet = GeneralWallet.from_seed_or_bip39( + name, + coin, + self.config, + seed, + password=password, + passphrase=passphrase, + bip44_path=bip39_derivation, + as_primary_wallet=True, + ) + elif chain_affinity == "btc": + wallet = Standard_Wallet.from_seed_or_bip39(coin, self.config, seed, passphrase, bip39_derivation) + elif chain_affinity == "eth": + derivation = util.get_keystore_path(bip39_derivation) + index = int(helpers.get_path_info(bip39_derivation, INDEX_POS)) + wallet = Standard_Eth_Wallet.from_seed_or_bip39(coin, index, self.config, seed, passphrase, derivation) + + wallet.set_name(name) + self._check_wallet_exists( + wallet, seed=seed, password=password, wallet_type=wallet_type, bip39_derivation=bip39_derivation + ) + self._create_new_wallet_update( + wallet=wallet, seed=seed, password=password, wallet_type=wallet_type, bip39_derivation=bip39_derivation + ) + return wallet, wallet_type + + @api.api_entry(force_version=api.Version.V3) + def create_hd_accounts(self, password: str, chain_code_list: str, mnemonic: str = None, strength: int = 128): + """ + Description + Create the first hd account of the corresponding chain based on the incoming chain code + Parameters + password: password for accounts【Required】 + chain_code_list:chain code list as json 【Required】 + mnemonic: mnemonic【Optional】 + strength: Length of mnemonic, When no mnemonic is entered, a mnemonic of the specified length will be generated based on this parameter 【Optional】 + Returns + {"status": 0, + "api_version": 3, + "other_info": "", + "info": + "[{\"id\": \"e5a314259ba752730522796bab4597d0fe105b082ac86b726f2d65f819b3deef\", \"chaincode\": \"tbtc\", \"wallet_type\": \"tbtc-derived-standard\", \"derived_path\": \"m/49'/1'/0'/0/0\"}, + {\"id\": \"31d2f0c1ea275efe19b51419a6dadaec7d82544790f53d34c18d0440642211fe\", \"chaincode\": \"teth\", \"wallet_type\": \"teth-derived-standard\", \"derived_path\": \"m/44'/60'/0'/0/0\"}, + {\"id\": \"7b97f4b6a84071d989512c9577b2b182bbb78fbddceee8acd1cba8621185186f\", \"chaincode\": \"tcfx\", \"wallet_type\": \"tcfx-derived-standard\", \"derived_path\": \"m/44'/503'/0'/0/0\"} + ]" + } + """ + self._assert_daemon_running() + out = [] + if mnemonic is not None: + is_checksum_valid, _ = keystore.bip39_is_checksum_valid(mnemonic) + if not is_checksum_valid: + raise basic_exceptions.InvalidBip39Seed() + else: + mnemonic = Mnemonic("english").generate(strength=strength) + + chain_code_list = json.loads(chain_code_list) + for coin in chain_code_list: + chain_info = coin_manager.get_chain_info(coin) + chain_affinity = chain_info.chain_affinity + wallet = None + wallet_type = "" + bip39_derivation = "" + if is_coin_migrated(coin): + last_hardened_level = chain_info.bip44_last_hardened_level + target_level = chain_info.bip44_target_level + bip39_derivation = ( + bip44.BIP44Path(44, chain_info.bip44_coin_type, 0, last_hardened_level=last_hardened_level) + .to_target_level(target_level) + .to_bip44_path() + ) + wallet, wallet_type = self._create_hd_account( + chain_info.shortname, + password, + seed=mnemonic, + bip39_derivation=bip39_derivation, + coin=coin, + ) + elif chain_affinity == "btc": + bip39_derivation = bip44_derivation(0, 49) + wallet, wallet_type = self._create_hd_account( + chain_info.shortname, + password, + seed=mnemonic, + bip39_derivation=bip39_derivation, + coin=coin, + ) + bip39_derivation += "/0/0" + elif chain_affinity == "eth": + bip39_derivation = bip44_eth_derivation(0) + wallet, wallet_type = self._create_hd_account( + chain_info.shortname, + password, + seed=mnemonic, + bip39_derivation=bip39_derivation, + coin=coin, + ) + + if wallet is not None: + out.append( + { + "id": wallet.identity, + "chaincode": coin, + "wallet_type": wallet_type, + "derived_path": bip39_derivation, + } + ) + key = self._get_hd_wallet_encode_seed(seed=mnemonic) + self.wallet_context.set_backup_info(key) + return json.dumps(out) + + @api.api_entry(force_version=api.Version.V3) + @orm_database.db.atomic() + def delete_hd_accounts(self, password: str): + """ + Description + Delete all derived hd wallets + Parameters + password: password for accounts【Required】 + Returns + '{"status": 0, "api_version": 3, "other_info": "", "info": null}' + """ + self.check_password(password=password) + self._delete_derived_wallet() + wallet_manager.clear_all_primary_wallets(password) + + def _get_next_path(self, chain_code: str = None, purpose: str = "49"): + if is_coin_migrated(chain_code): + chain_info = coin_manager.get_chain_info(chain_code) + address_encoding = None + if purpose: + purpose_to_address_encoding = {v: k for k, v in chain_info.bip44_purpose_options.items()} + address_encoding = purpose_to_address_encoding.get(int(purpose)) + + address_encoding = address_encoding or chain_info.default_address_encoding + + return wallet_manager.generate_next_bip44_path_for_derived_primary_wallet( + chain_code, address_encoding + ).to_bip44_path() + + derive_key = self._get_hd_wallet_encode_seed(coin=chain_code, purpose=str(purpose)) + account_id = self._get_derived_account_id(derive_key) + + chain_affinity = _get_chain_affinity(chain_code) + if chain_affinity == 'btc': + return "%s/0/0" % keystore.bip44_derivation(account_id, bip43_purpose=int(purpose)) + elif chain_affinity == "eth": + return keystore.bip44_eth_derivation(account_id) + else: + raise util.UnsupportedCurrencyCoin() + + @api.api_entry(force_version=api.Version.V3) + def get_next_path(self, chain_code: str, purpose: str = "49"): + """ + Description + Get the derivation path of the next hd wallet + Parameters + chain_code: chain code【Required】 + purpose: purpose like ("44"/"49/"84")【Optional】 + Returns + '{"status": 0, "api_version": 3, "other_info": "", "info": "m/49\'/1\'/0\'/0/0"}' + """ + return self._get_next_path(chain_code=chain_code, purpose=purpose) + + @api.api_entry(force_version=api.Version.V3) + def derive_account( + self, name: str, password: str, chain_code: str, bip39_derivation: str = None, purpose: str = "" + ): + """ + Description + Create (derive) a hd wallet + Parameters + name: wallet name【Required】 + password: password for accounts【Required】 + chain_code:chain code list as json 【Required】 + bip39_derivation: derive path【Optional】 + purpose: purpose like ("44"/"49/"84") 【Optional】 + Returns + {"status": 0, + "api_version": 3, + "other_info": "", + "info": + [{"id": "7c71176d4f9eacad784dc5e1946f493fd4678a70a023f9a5890b3c582a567476", + "chaincode": "tcfx", + "wallet_type": "tcfx-derived-standard", + "derived_path": "m/44'/503'/0'/0/2"} + ] + } + """ + self.check_password(password) + seed = self._get_hd_wallet().get_seed(password) + + if bip39_derivation is None: + bip39_derivation = self._get_next_path(chain_code=chain_code, purpose=purpose) + + temp_bip39_derivation = bip39_derivation + if "btc" in chain_code: + bip39_derivation = ( + bip44.BIP44Path.from_bip44_path(bip39_derivation) + .to_target_level(bip44.BIP44Level.ACCOUNT) + .to_bip44_path() + ) + wallet, wallet_type = self._create_hd_account(name, password, seed, bip39_derivation, chain_code) + return [ + { + "id": wallet.identity, + "chaincode": chain_code, + "wallet_type": wallet_type, + "derived_path": temp_bip39_derivation, + } + ] + + def _check_wallet_exists( + self, wallet_obj, watch_only=False, seed=None, password=None, wallet_type=None, bip39_derivation=None + ): + exist_wallet = self.daemon.get_wallet(self._wallet_path(wallet_obj.identity)) + if exist_wallet is not None: + if not watch_only and exist_wallet.is_watching_only(): + self._update_replace_info( + wallet_obj, + seed=seed, + password=password, + wallet_type=wallet_type, + bip39_derivation=bip39_derivation, + ) + raise basic_exceptions.ReplaceWatchOnlyWallet(other_info=wallet_obj.identity) + else: + raise basic_exceptions.FileAlreadyExist() + + def _check_password_for_sw_account(self, password: str): + if self._get_wallet_num() == 0: + self.check_pw_wallet = None + try: + self.check_password(password) + except BaseException as e: + raise basic_exceptions.InvalidPassword(other_info=str(e)) + + @api.api_entry(force_version=api.Version.V3) + def create_account_with_privkey(self, name: str, password: str, chain_code: str, privkeys: str, purpose: int = 49): + """ + Description + Create an account with a private key + Parameters + name: wallet name【Required】 + password: password for accounts【Required】 + chain_code:chain code list as json 【Required】 + privkeys: private key【Required】 + purpose: purpose like ("44"/"49/"84") 【Optional】 + Returns + {"status": 0, + "api_version": 3, + "other_info": "", + "info": [ + {"id": "f1ff92e3f48a8d2b63950908f7e5c730889a883a044ad8f6a762595061e9bcc3", + "chaincode": "tbtc", + "wallet_type": "tbtc-private-standard", + "derived_path": ""}] + } + """ + self._check_password_for_sw_account(password) + wallet = None + chain_affinity = _get_chain_affinity(chain_code) + wallet_type = f"{chain_code}-private-standard" + if is_coin_migrated(chain_code) and (privkeys is not None): + if chain_affinity == codes.SOL: + privkeys = _sol_retrieve_privkey_from_keypair(privkeys) + else: + try: + privkeys = bytes.fromhex(privkeys.split()[0]) + except Exception as e: + raise basic_exceptions.PrivateKeyNotSupportedFormat(other_info=str(e)) + wallet = GeneralWallet.from_prvkeys(name, chain_code, self.config, privkeys, password) + elif privkeys is not None and chain_affinity == "btc": + wallet = Imported_Wallet.from_privkeys(chain_code, self.config, privkeys, purpose) + elif chain_affinity == "eth" and (privkeys is not None): + wallet = Imported_Eth_Wallet.from_privkeys(chain_code, self.config, privkeys) + + wallet.set_name(name) + self._check_wallet_exists(wallet, password=password, wallet_type=wallet_type) + self._create_new_wallet_update(wallet=wallet, password=password, wallet_type=wallet_type) + return [{"id": wallet.identity, "chaincode": chain_code, "wallet_type": wallet_type, "derived_path": ""}] + + @api.api_entry(force_version=api.Version.V3) + def create_account_with_keystore( + self, name: str, password: str, chain_code: str, keystore: str, keystore_password: str + ): + """ + Description + Create an account with a keystore + Parameters + name: wallet name【Required】 + password: password for accounts【Required】 + chain_code:chain code list as json 【Required】 + keystore: keystore【Required】 + keystore_password: password for keystore 【Required】 + Returns + {"status": 0, + "api_version": 3, + "other_info": "", + "info": [ + {"id": "6edf0cf19f983165fa997a7be4df9e56306f29946cb3b7ce54b5959638f8f1c7", + "chaincode": "teth", + "wallet_type": "teth-private-standard", + "derived_path": ""}] + } + """ + self._check_password_for_sw_account(password) + wallet = None + chain_affinity = _get_chain_affinity(chain_code) + wallet_type = f"{chain_code}-private-standard" + + if is_coin_migrated(chain_code) and keystore is not None: + wallet = GeneralWallet.from_keystore(name, chain_code, self.config, keystore, keystore_password, password) + elif chain_affinity == "eth" and keystore is not None: + wallet = Imported_Eth_Wallet.from_keystores(chain_code, self.config, keystore, keystore_password) + + wallet.set_name(name) + self._check_wallet_exists(wallet, password=password, wallet_type=wallet_type) + self._create_new_wallet_update(wallet=wallet, password=password, wallet_type=wallet_type) + return [{"id": wallet.identity, "chaincode": chain_code, "wallet_type": wallet_type, "derived_path": ""}] + + @api.api_entry(force_version=api.Version.V3) + def create_account_with_mnemonic( + self, + name: str, + password: str, + chain_code: str, + mnemonic: str, + bip39_derivation: str = None, + passphrase: str = "", + ): + """ + Description + Create an account with a mnemonic + Parameters + name: wallet name【Required】 + password: password for accounts【Required】 + chain_code:chain code list as json 【Required】 + mnemonic: mnemonic【Required】 + bip39_derivation: derive path【Optional】 + passphrase: passphrase 【Optional】 + Returns + {"status": 0, + "api_version": 3, + "other_info": "", + "info": [ + {"id": "6edf0cf19f983165fa997a7be4df9e56306f29946cb3b7ce54b5959638f8f1c7", + "chaincode": "teth", + "wallet_type": "teth-standard", + "derived_path": "m/44'/60'/0'/0/0"}] + } + """ + self._check_password_for_sw_account(password) + wallet = None + chain_affinity = _get_chain_affinity(chain_code) + wallet_type = f"{chain_code}-standard" + if bip39_derivation is None: + bip39_derivation = self._get_default_show_path(coin=chain_code) + if is_coin_migrated(chain_code): + wallet = GeneralWallet.from_seed_or_bip39( + name, + chain_code, + self.config, + mnemonic, + password=password, + passphrase=passphrase, + as_primary_wallet=False, + ) + elif chain_affinity == "btc": + bip39_derivation = ( + bip44.BIP44Path.from_bip44_path(bip39_derivation) + .to_target_level(bip44.BIP44Level.ACCOUNT) + .to_bip44_path() + ) + wallet = Standard_Wallet.from_seed_or_bip39( + chain_code, self.config, mnemonic, passphrase, bip39_derivation + ) + elif chain_affinity == "eth": + derivation = util.get_keystore_path(bip39_derivation) + index = 0 + wallet = Standard_Eth_Wallet.from_seed_or_bip39( + chain_code, index, self.config, mnemonic, passphrase, derivation + ) + else: + wallet_type = f"{chain_code}-customer-standard" + if is_coin_migrated(chain_code): + wallet = GeneralWallet.from_seed_or_bip39( + name, + chain_code, + self.config, + mnemonic, + password=password, + passphrase=passphrase, + bip44_path=bip39_derivation, + as_primary_wallet=False, + ) + elif chain_affinity == "btc": + wallet = Imported_Wallet.from_seed( + chain_code, + self.config, + mnemonic, + passphrase, + int(helpers.get_path_info(bip39_derivation, PURPOSE_POS)) or 49, + bip39_derivation, + ) + elif chain_affinity == "eth": + wallet = Imported_Eth_Wallet.from_seed(chain_code, self.config, mnemonic, passphrase, bip39_derivation) + + wallet.set_name(name) + self._check_wallet_exists(wallet, password=password, wallet_type=wallet_type) + self._create_new_wallet_update(wallet=wallet, password=password, wallet_type=wallet_type) + return [ + { + "id": wallet.identity, + "chaincode": chain_code, + "wallet_type": wallet_type, + "derived_path": bip39_derivation, + } + ] + + @api.api_entry(force_version=api.Version.V3) + def create_account_watchonly(self, name: str, chain_code: str, address: str): + """ + Description + Create an account with a address/xpub + Parameters + name: wallet name【Required】 + chain_code:chain code list as json 【Required】 + address: address【Required】 + Returns + {"status": 0, + "api_version": 3, + "other_info": "", + "info": [ + {"id": "f1ff92e3f48a8d2b63950908f7e5c730889a883a044ad8f6a762595061e9bcc3", + "chaincode": "tbtc", + "wallet_type": "tbtc-watch-standard", + "derived_path": ""}] + } + + """ + if self._get_wallet_num() == 0: + self.check_pw_wallet = None + wallet = None + chain_affinity = _get_chain_affinity(chain_code) + + if not address: + return + wallet_type = f"{chain_code}-watch-standard" + if is_coin_migrated(chain_code): + wallet = GeneralWallet.from_pubkey_or_addresses(name, chain_code, self.config, address) + elif chain_affinity == "btc": + if keystore.is_xpub(address): + wallet = Standard_Wallet.from_master_key("btc", self.config, address) + else: + wallet = Imported_Wallet.from_pubkey_or_addresses(chain_code, self.config, address) + elif chain_affinity == "eth": + wallet = Imported_Eth_Wallet.from_pubkey_or_addresses(chain_code, self.config, address) + else: + raise basic_exceptions.UnsupportedCurrencyCoin() + + wallet.set_name(name) + self._check_wallet_exists(wallet, watch_only=True) + self._create_new_wallet_update(wallet=wallet, wallet_type=wallet_type) + return [{"id": wallet.identity, "chaincode": chain_code, "wallet_type": wallet_type, "derived_path": ""}] + all_commands = commands.known_commands.copy() for name, func in vars(AndroidCommands).items():