mirror of
https://github.com/basicswap/basicswap.git
synced 2026-04-08 18:37:23 +02:00
BIP87
This commit is contained in:
@@ -2428,13 +2428,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
|
|
||||||
def _syncLiteWalletToRPCOnStartup(self, coin_type: Coins) -> None:
|
def _syncLiteWalletToRPCOnStartup(self, coin_type: Coins) -> None:
|
||||||
try:
|
try:
|
||||||
cc = self.coin_clients[coin_type]
|
|
||||||
if not cc.get("auto_transfer_on_mode_switch", True):
|
|
||||||
self.log.debug(
|
|
||||||
f"Auto-transfer disabled for {getCoinName(coin_type)}, skipping sweep check"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
cursor = self.openDB()
|
cursor = self.openDB()
|
||||||
try:
|
try:
|
||||||
row = cursor.execute(
|
row = cursor.execute(
|
||||||
@@ -2780,6 +2773,137 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self.log.error(f"_transferLiteWalletBalanceToRPC error: {e}")
|
self.log.error(f"_transferLiteWalletBalanceToRPC error: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def sweepLiteWalletFunds(self, coin_type: Coins) -> dict:
|
||||||
|
try:
|
||||||
|
coin_name = getCoinName(coin_type)
|
||||||
|
self.log.info(f"Manual sweep requested for {coin_name}")
|
||||||
|
|
||||||
|
if coin_type in WalletManager.SUPPORTED_COINS:
|
||||||
|
if not self._wallet_manager.isInitialized(coin_type):
|
||||||
|
try:
|
||||||
|
self.initializeWalletManager(coin_type)
|
||||||
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Wallet locked: {e}",
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self._transferLiteWalletBalanceToRPC(coin_type)
|
||||||
|
if result is None:
|
||||||
|
return {"skipped": True, "reason": "No balance to sweep"}
|
||||||
|
if result.get("skipped"):
|
||||||
|
return result
|
||||||
|
if result.get("txid"):
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"txid": result["txid"],
|
||||||
|
"amount": result.get("amount", 0) / 1e8,
|
||||||
|
"fee": result.get("fee", 0) / 1e8,
|
||||||
|
"address": result.get("address", ""),
|
||||||
|
}
|
||||||
|
if result.get("error"):
|
||||||
|
return {"success": False, "error": result["error"]}
|
||||||
|
return {"skipped": True, "reason": "Unknown result"}
|
||||||
|
except Exception as e:
|
||||||
|
self.log.error(
|
||||||
|
f"sweepLiteWalletFunds error for {getCoinName(coin_type)}: {e}"
|
||||||
|
)
|
||||||
|
return {"success": False, "error": str(e)}
|
||||||
|
|
||||||
|
def _consolidateLegacyFundsToSegwit(self, coin_type: Coins) -> dict:
|
||||||
|
try:
|
||||||
|
coin_name = getCoinName(coin_type)
|
||||||
|
cc = self.coin_clients[coin_type]
|
||||||
|
ci = cc.get("interface")
|
||||||
|
if not ci:
|
||||||
|
return {"skipped": True, "reason": "No coin interface"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
unspent = ci.rpc_wallet("listunspent")
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": f"Failed to list UTXOs: {e}"}
|
||||||
|
|
||||||
|
hrp = ci.chainparams_network().get("hrp", "bc")
|
||||||
|
legacy_utxos = []
|
||||||
|
total_legacy_sats = 0
|
||||||
|
|
||||||
|
for u in unspent:
|
||||||
|
if "address" not in u:
|
||||||
|
continue
|
||||||
|
addr = u["address"]
|
||||||
|
if not addr.startswith(hrp + "1"):
|
||||||
|
legacy_utxos.append(u)
|
||||||
|
total_legacy_sats += ci.make_int(u.get("amount", 0))
|
||||||
|
|
||||||
|
if not legacy_utxos:
|
||||||
|
return {"skipped": True, "reason": "No legacy funds found"}
|
||||||
|
|
||||||
|
if total_legacy_sats <= 0:
|
||||||
|
return {"skipped": True, "reason": "No balance on legacy addresses"}
|
||||||
|
|
||||||
|
est_vsize = len(legacy_utxos) * 150 + 40
|
||||||
|
fee_rate, _ = ci.get_fee_rate(ci._conf_target)
|
||||||
|
if isinstance(fee_rate, int):
|
||||||
|
fee_per_vbyte = max(1, fee_rate // 1000)
|
||||||
|
else:
|
||||||
|
fee_per_vbyte = max(1, int(fee_rate * 100000))
|
||||||
|
estimated_fee_sats = est_vsize * fee_per_vbyte
|
||||||
|
|
||||||
|
if total_legacy_sats <= estimated_fee_sats * 2:
|
||||||
|
return {
|
||||||
|
"skipped": True,
|
||||||
|
"reason": f"Legacy balance ({total_legacy_sats}) too low for fee ({estimated_fee_sats})",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_address = ci.rpc_wallet("getnewaddress", ["consolidate", "bech32"])
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": f"Failed to get new address: {e}"}
|
||||||
|
|
||||||
|
send_amount_sats = total_legacy_sats - estimated_fee_sats
|
||||||
|
send_amount_btc = ci.format_amount(send_amount_sats)
|
||||||
|
|
||||||
|
self.log.info(
|
||||||
|
f"[Consolidate {coin_name}] Moving {ci.format_amount(total_legacy_sats)} from "
|
||||||
|
f"{len(legacy_utxos)} legacy UTXOs to {new_address}"
|
||||||
|
)
|
||||||
|
|
||||||
|
inputs = [{"txid": u["txid"], "vout": u["vout"]} for u in legacy_utxos]
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw_tx = ci.rpc_wallet(
|
||||||
|
"createrawtransaction",
|
||||||
|
[inputs, {new_address: float(send_amount_btc)}],
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": f"Failed to create transaction: {e}"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
signed = ci.rpc_wallet("signrawtransactionwithwallet", [raw_tx])
|
||||||
|
if not signed.get("complete"):
|
||||||
|
return {"error": "Failed to sign transaction"}
|
||||||
|
txid = ci.rpc_wallet("sendrawtransaction", [signed["hex"]])
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": f"Failed to broadcast transaction: {e}"}
|
||||||
|
|
||||||
|
self.log.info(f"[Consolidate {coin_name}] SUCCESS! TXID: {txid}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"txid": txid,
|
||||||
|
"amount": send_amount_sats / 1e8,
|
||||||
|
"fee": estimated_fee_sats / 1e8,
|
||||||
|
"address": new_address,
|
||||||
|
"num_inputs": len(legacy_utxos),
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log.error(f"_consolidateLegacyFundsToSegwit error: {e}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
self.log.debug(traceback.format_exc())
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
def _retryPendingSweeps(self) -> None:
|
def _retryPendingSweeps(self) -> None:
|
||||||
if not self._pending_sweeps:
|
if not self._pending_sweeps:
|
||||||
return
|
return
|
||||||
@@ -2869,6 +2993,43 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self.log.debug(f"getLiteWalletBalanceInfo error: {e}")
|
self.log.debug(f"getLiteWalletBalanceInfo error: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def getElectrumLegacyFundsInfo(self, coin_type: Coins) -> dict:
|
||||||
|
try:
|
||||||
|
cc = self.coin_clients.get(coin_type)
|
||||||
|
if not cc or cc.get("connection_type") != "electrum":
|
||||||
|
return {"has_legacy_funds": False}
|
||||||
|
|
||||||
|
if not self._wallet_manager:
|
||||||
|
return {"has_legacy_funds": False}
|
||||||
|
|
||||||
|
ci = self.ci(coin_type)
|
||||||
|
hrp = ci.chainparams_network().get("hrp", "bc")
|
||||||
|
|
||||||
|
unspent_by_addr = ci.getUnspentsByAddr()
|
||||||
|
if not unspent_by_addr:
|
||||||
|
return {"has_legacy_funds": False}
|
||||||
|
|
||||||
|
legacy_balance_sats = 0
|
||||||
|
legacy_addresses = []
|
||||||
|
|
||||||
|
for addr, balance_sats in unspent_by_addr.items():
|
||||||
|
if not addr.startswith(hrp + "1"):
|
||||||
|
legacy_balance_sats += balance_sats
|
||||||
|
legacy_addresses.append(addr)
|
||||||
|
|
||||||
|
if legacy_balance_sats > 0:
|
||||||
|
return {
|
||||||
|
"has_legacy_funds": True,
|
||||||
|
"legacy_balance_sats": legacy_balance_sats,
|
||||||
|
"legacy_balance": ci.format_amount(legacy_balance_sats),
|
||||||
|
"legacy_address_count": len(legacy_addresses),
|
||||||
|
"coin": ci.ticker_mainnet(),
|
||||||
|
}
|
||||||
|
return {"has_legacy_funds": False}
|
||||||
|
except Exception as e:
|
||||||
|
self.log.debug(f"getElectrumLegacyFundsInfo error: {e}")
|
||||||
|
return {"has_legacy_funds": False}
|
||||||
|
|
||||||
def _tryGetFullNodeAddresses(self, coin_type: Coins) -> list:
|
def _tryGetFullNodeAddresses(self, coin_type: Coins) -> list:
|
||||||
addresses = []
|
addresses = []
|
||||||
try:
|
try:
|
||||||
@@ -13089,15 +13250,39 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
display_name = getCoinName(coin_id)
|
display_name = getCoinName(coin_id)
|
||||||
|
|
||||||
if old_connection_type == "rpc" and new_connection_type == "electrum":
|
if old_connection_type == "rpc" and new_connection_type == "electrum":
|
||||||
|
auto_transfer_now = data.get("auto_transfer_now", False)
|
||||||
|
if auto_transfer_now:
|
||||||
|
transfer_result = self._consolidateLegacyFundsToSegwit(coin_id)
|
||||||
|
if transfer_result.get("success"):
|
||||||
|
self.log.info(
|
||||||
|
f"Consolidated {transfer_result.get('amount', 0):.8f} {display_name} "
|
||||||
|
f"from legacy addresses. TXID: {transfer_result.get('txid')}"
|
||||||
|
)
|
||||||
|
if migration_message:
|
||||||
|
migration_message += f" Transferred {transfer_result.get('amount', 0):.8f} {display_name} to native segwit."
|
||||||
|
else:
|
||||||
|
migration_message = f"Transferred {transfer_result.get('amount', 0):.8f} {display_name} to native segwit."
|
||||||
|
elif transfer_result.get("skipped"):
|
||||||
|
self.log.info(
|
||||||
|
f"Legacy fund transfer skipped for {coin_name}: {transfer_result.get('reason')}"
|
||||||
|
)
|
||||||
|
elif transfer_result.get("error"):
|
||||||
|
self.log.warning(
|
||||||
|
f"Legacy fund transfer warning for {coin_name}: {transfer_result.get('error')}"
|
||||||
|
)
|
||||||
|
|
||||||
migration_result = self._migrateWalletToLiteMode(coin_id)
|
migration_result = self._migrateWalletToLiteMode(coin_id)
|
||||||
if migration_result.get("success"):
|
if migration_result.get("success"):
|
||||||
count = migration_result.get("count", 0)
|
count = migration_result.get("count", 0)
|
||||||
self.log.info(
|
self.log.info(
|
||||||
f"Lite wallet ready for {coin_name} with {count} addresses"
|
f"Lite wallet ready for {coin_name} with {count} addresses"
|
||||||
)
|
)
|
||||||
migration_message = (
|
if migration_message:
|
||||||
f"Lite wallet ready for {display_name} ({count} addresses)."
|
migration_message += (
|
||||||
)
|
f" Lite wallet ready ({count} addresses)."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
migration_message = f"Lite wallet ready for {display_name} ({count} addresses)."
|
||||||
else:
|
else:
|
||||||
error = migration_result.get("error", "unknown")
|
error = migration_result.get("error", "unknown")
|
||||||
reason = migration_result.get("reason", "")
|
reason = migration_result.get("reason", "")
|
||||||
@@ -13120,11 +13305,38 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
|
|
||||||
empty_check = self._checkElectrumWalletEmpty(coin_id)
|
empty_check = self._checkElectrumWalletEmpty(coin_id)
|
||||||
if not empty_check.get("empty", False):
|
if not empty_check.get("empty", False):
|
||||||
error = empty_check.get(
|
|
||||||
"message", "Wallet must be empty before switching modes"
|
|
||||||
)
|
|
||||||
reason = empty_check.get("reason", "")
|
reason = empty_check.get("reason", "")
|
||||||
if reason in ("has_balance", "active_swap"):
|
|
||||||
|
auto_transfer_now = data.get("auto_transfer_now", False)
|
||||||
|
|
||||||
|
if reason == "has_balance" and auto_transfer_now:
|
||||||
|
self.log.info(
|
||||||
|
f"Auto-transfer requested for {coin_name} during mode switch"
|
||||||
|
)
|
||||||
|
sweep_result = self.sweepLiteWalletFunds(coin_id)
|
||||||
|
if sweep_result.get("success"):
|
||||||
|
self.log.info(
|
||||||
|
f"Swept {sweep_result.get('amount', 0):.8f} {display_name} "
|
||||||
|
f"to RPC wallet. TXID: {sweep_result.get('txid')}"
|
||||||
|
)
|
||||||
|
migration_message = (
|
||||||
|
f"Transferred {sweep_result.get('amount', 0):.8f} {display_name} "
|
||||||
|
f"to full node wallet."
|
||||||
|
)
|
||||||
|
elif sweep_result.get("skipped"):
|
||||||
|
self.log.info(
|
||||||
|
f"Sweep skipped for {coin_name}: {sweep_result.get('reason')}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
error = sweep_result.get("error", "Transfer failed")
|
||||||
|
self.log.error(
|
||||||
|
f"Transfer failed for {coin_name}: {error}"
|
||||||
|
)
|
||||||
|
raise ValueError(f"Transfer failed: {error}")
|
||||||
|
elif reason in ("has_balance", "active_swap"):
|
||||||
|
error = empty_check.get(
|
||||||
|
"message", "Wallet must be empty before switching modes"
|
||||||
|
)
|
||||||
self.log.error(
|
self.log.error(
|
||||||
f"Migration blocked for {coin_name}: {error}"
|
f"Migration blocked for {coin_name}: {error}"
|
||||||
)
|
)
|
||||||
@@ -13281,19 +13493,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
cc["electrum_onion_servers"] = new_onion
|
cc["electrum_onion_servers"] = new_onion
|
||||||
break
|
break
|
||||||
|
|
||||||
if "auto_transfer_on_mode_switch" in data:
|
|
||||||
new_auto_transfer = data["auto_transfer_on_mode_switch"]
|
|
||||||
if (
|
|
||||||
settings_cc.get("auto_transfer_on_mode_switch", True)
|
|
||||||
!= new_auto_transfer
|
|
||||||
):
|
|
||||||
settings_changed = True
|
|
||||||
settings_cc["auto_transfer_on_mode_switch"] = new_auto_transfer
|
|
||||||
for coin, cc in self.coin_clients.items():
|
|
||||||
if cc["name"] == coin_name:
|
|
||||||
cc["auto_transfer_on_mode_switch"] = new_auto_transfer
|
|
||||||
break
|
|
||||||
|
|
||||||
if settings_changed:
|
if settings_changed:
|
||||||
self._normalizeSettingsPaths(settings_copy)
|
self._normalizeSettingsPaths(settings_copy)
|
||||||
settings_path = os.path.join(self.data_dir, cfg.CONFIG_FILENAME)
|
settings_path = os.path.join(self.data_dir, cfg.CONFIG_FILENAME)
|
||||||
|
|||||||
@@ -1841,6 +1841,7 @@ def initialise_wallets(
|
|||||||
daemons = []
|
daemons = []
|
||||||
daemon_args = ["-noconnect", "-nodnsseed"]
|
daemon_args = ["-noconnect", "-nodnsseed"]
|
||||||
generated_mnemonic: bool = False
|
generated_mnemonic: bool = False
|
||||||
|
extended_keys = {}
|
||||||
|
|
||||||
coins_failed_to_initialise = []
|
coins_failed_to_initialise = []
|
||||||
|
|
||||||
@@ -2098,6 +2099,20 @@ def initialise_wallets(
|
|||||||
except Exception as e: # noqa: F841
|
except Exception as e: # noqa: F841
|
||||||
logger.warning(f"changeWalletPassword failed for {coin_name}.")
|
logger.warning(f"changeWalletPassword failed for {coin_name}.")
|
||||||
|
|
||||||
|
zprv_prefix = 0x04B2430C if chain == "mainnet" else 0x045F18BC
|
||||||
|
for coin_name in with_coins:
|
||||||
|
c = swap_client.getCoinIdFromName(coin_name)
|
||||||
|
if c == Coins.PART:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
ci = swap_client.ci(c)
|
||||||
|
if hasattr(ci, "canExportToElectrum") and ci.canExportToElectrum():
|
||||||
|
seed_key = swap_client.getWalletKey(c, 1)
|
||||||
|
account_key = ci.getAccountKey(seed_key, zprv_prefix)
|
||||||
|
extended_keys[getCoinName(c)] = account_key
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Could not generate extended key for {coin_name}: {e}")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if swap_client:
|
if swap_client:
|
||||||
swap_client.finalise()
|
swap_client.finalise()
|
||||||
@@ -2129,6 +2144,18 @@ def initialise_wallets(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if extended_keys:
|
||||||
|
print("Extended private keys (for external wallet import):")
|
||||||
|
for coin_name, key in extended_keys.items():
|
||||||
|
print(f" {coin_name}: {key}")
|
||||||
|
print("")
|
||||||
|
print(
|
||||||
|
"NOTE: These keys can be imported into Electrum using 'Use a master key'."
|
||||||
|
)
|
||||||
|
print("WARNING: Write these down NOW. They will not be shown again.\n")
|
||||||
|
|
||||||
|
return extended_keys
|
||||||
|
|
||||||
|
|
||||||
def load_config(config_path):
|
def load_config(config_path):
|
||||||
if not os.path.exists(config_path):
|
if not os.path.exists(config_path):
|
||||||
@@ -3071,7 +3098,7 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
if particl_wallet_mnemonic != "none":
|
if particl_wallet_mnemonic != "none":
|
||||||
initialise_wallets(
|
extended_keys = initialise_wallets(
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
add_coin,
|
add_coin,
|
||||||
@@ -3083,6 +3110,18 @@ def main():
|
|||||||
extra_opts=extra_opts,
|
extra_opts=extra_opts,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if extended_keys:
|
||||||
|
print("\nExtended private key (for external wallet import):")
|
||||||
|
for coin_name, key in extended_keys.items():
|
||||||
|
print(f" {coin_name}: {key}")
|
||||||
|
print("")
|
||||||
|
print(
|
||||||
|
"NOTE: This key can be imported into Electrum using 'Use a master key'."
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"WARNING: Write this down NOW. It will not be shown again.\n"
|
||||||
|
)
|
||||||
|
|
||||||
save_config(config_path, settings)
|
save_config(config_path, settings)
|
||||||
finally:
|
finally:
|
||||||
if "particl_daemon" in extra_opts:
|
if "particl_daemon" in extra_opts:
|
||||||
|
|||||||
@@ -3355,7 +3355,8 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
total_balance = sum(u.get("value", 0) for u in all_utxos)
|
total_balance = sum(u.get("value", 0) for u in all_utxos)
|
||||||
|
|
||||||
est_vsize = 10 + 31 + len(all_utxos) * 68
|
est_vsize = 10 + 31 + len(all_utxos) * 68
|
||||||
est_fee = est_vsize * fee_per_vbyte
|
min_fee = 250
|
||||||
|
est_fee = max(est_vsize * fee_per_vbyte, min_fee)
|
||||||
|
|
||||||
if total_balance <= est_fee:
|
if total_balance <= est_fee:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|||||||
@@ -1248,10 +1248,12 @@ def js_getcoinseed(self, url_split, post_string, is_json) -> bytes:
|
|||||||
"current_seed_id": wallet_seed_id,
|
"current_seed_id": wallet_seed_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if hasattr(ci, "canExportToElectrum") and ci.canExportToElectrum():
|
|
||||||
rv.update(
|
if hasattr(ci, "getAccountKey"):
|
||||||
{"account_key": ci.getAccountKey(seed_key, extkey_prefix)}
|
try:
|
||||||
) # Master key can be imported into electrum (Must set prefix for P2WPKH)
|
rv.update({"account_key": ci.getAccountKey(seed_key, extkey_prefix)})
|
||||||
|
except Exception as e:
|
||||||
|
rv.update({"account_key_error": str(e)})
|
||||||
|
|
||||||
return bytes(
|
return bytes(
|
||||||
json.dumps(rv),
|
json.dumps(rv),
|
||||||
@@ -1674,6 +1676,97 @@ def js_messageroutes(self, url_split, post_string, is_json) -> bytes:
|
|||||||
return bytes(json.dumps(message_routes), "UTF-8")
|
return bytes(json.dumps(message_routes), "UTF-8")
|
||||||
|
|
||||||
|
|
||||||
|
def js_modeswitchinfo(self, url_split, post_string, is_json) -> bytes:
|
||||||
|
swap_client = self.server.swap_client
|
||||||
|
swap_client.checkSystemStatus()
|
||||||
|
post_data = getFormData(post_string, is_json)
|
||||||
|
|
||||||
|
coin_str = get_data_entry(post_data, "coin")
|
||||||
|
direction = get_data_entry_or(post_data, "direction", "lite")
|
||||||
|
|
||||||
|
try:
|
||||||
|
coin_type = getCoinIdFromName(coin_str)
|
||||||
|
except Exception:
|
||||||
|
coin_type = getCoinIdFromTicker(coin_str.upper())
|
||||||
|
|
||||||
|
ci = swap_client.ci(coin_type)
|
||||||
|
ticker = ci.ticker()
|
||||||
|
|
||||||
|
try:
|
||||||
|
wallet_info = ci.getWalletInfo()
|
||||||
|
balance = wallet_info.get("balance", 0)
|
||||||
|
balance_sats = ci.make_int(balance)
|
||||||
|
except Exception as e:
|
||||||
|
return bytes(json.dumps({"error": f"Failed to get balance: {e}"}), "UTF-8")
|
||||||
|
|
||||||
|
try:
|
||||||
|
fee_rate, rate_src = ci.get_fee_rate(ci._conf_target)
|
||||||
|
est_vsize = 180
|
||||||
|
if isinstance(fee_rate, int):
|
||||||
|
fee_per_vbyte = max(1, fee_rate // 1000)
|
||||||
|
else:
|
||||||
|
fee_per_vbyte = max(1, int(fee_rate * 100000))
|
||||||
|
estimated_fee_sats = est_vsize * fee_per_vbyte
|
||||||
|
except Exception:
|
||||||
|
estimated_fee_sats = 180
|
||||||
|
rate_src = "default"
|
||||||
|
|
||||||
|
min_viable = estimated_fee_sats * 2
|
||||||
|
can_transfer = balance_sats > min_viable
|
||||||
|
|
||||||
|
rv = {
|
||||||
|
"coin": ticker,
|
||||||
|
"direction": direction,
|
||||||
|
"balance": balance,
|
||||||
|
"balance_sats": balance_sats,
|
||||||
|
"estimated_fee_sats": estimated_fee_sats,
|
||||||
|
"estimated_fee": ci.format_amount(estimated_fee_sats),
|
||||||
|
"fee_rate_src": rate_src,
|
||||||
|
"can_transfer": can_transfer,
|
||||||
|
"min_viable_sats": min_viable,
|
||||||
|
}
|
||||||
|
|
||||||
|
if direction == "lite":
|
||||||
|
legacy_balance_sats = 0
|
||||||
|
has_legacy_funds = False
|
||||||
|
try:
|
||||||
|
if hasattr(ci, "rpc_wallet"):
|
||||||
|
unspent = ci.rpc_wallet("listunspent")
|
||||||
|
hrp = ci.chainparams_network().get("hrp", "bc")
|
||||||
|
for u in unspent:
|
||||||
|
if "address" in u and not u["address"].startswith(hrp + "1"):
|
||||||
|
legacy_balance_sats += ci.make_int(u.get("amount", 0))
|
||||||
|
has_legacy_funds = True
|
||||||
|
except Exception as e:
|
||||||
|
swap_client.log.debug(f"Error checking legacy addresses: {e}")
|
||||||
|
|
||||||
|
if has_legacy_funds and legacy_balance_sats > min_viable:
|
||||||
|
rv["show_transfer_option"] = True
|
||||||
|
rv["legacy_balance_sats"] = legacy_balance_sats
|
||||||
|
rv["legacy_balance"] = ci.format_amount(legacy_balance_sats)
|
||||||
|
rv["message"] = (
|
||||||
|
"Funds on legacy addresses - transfer recommended for external wallet compatibility"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
rv["show_transfer_option"] = False
|
||||||
|
rv["legacy_balance_sats"] = 0
|
||||||
|
rv["legacy_balance"] = "0"
|
||||||
|
if has_legacy_funds:
|
||||||
|
rv["message"] = "Legacy balance too low to transfer"
|
||||||
|
else:
|
||||||
|
rv["message"] = "All funds on native segwit addresses"
|
||||||
|
else:
|
||||||
|
rv["show_transfer_option"] = can_transfer
|
||||||
|
if balance_sats == 0:
|
||||||
|
rv["message"] = "No funds to transfer"
|
||||||
|
elif not can_transfer:
|
||||||
|
rv["message"] = "Balance too low to transfer (fee would exceed funds)"
|
||||||
|
else:
|
||||||
|
rv["message"] = ""
|
||||||
|
|
||||||
|
return bytes(json.dumps(rv), "UTF-8")
|
||||||
|
|
||||||
|
|
||||||
def js_electrum_discover(self, url_split, post_string, is_json) -> bytes:
|
def js_electrum_discover(self, url_split, post_string, is_json) -> bytes:
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
post_data = {} if post_string == "" else getFormData(post_string, is_json)
|
post_data = {} if post_string == "" else getFormData(post_string, is_json)
|
||||||
@@ -1805,6 +1898,7 @@ endpoints = {
|
|||||||
"coinhistory": js_coinhistory,
|
"coinhistory": js_coinhistory,
|
||||||
"messageroutes": js_messageroutes,
|
"messageroutes": js_messageroutes,
|
||||||
"electrumdiscover": js_electrum_discover,
|
"electrumdiscover": js_electrum_discover,
|
||||||
|
"modeswitchinfo": js_modeswitchinfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,16 @@
|
|||||||
hiddenInput.name = submitter.name;
|
hiddenInput.name = submitter.name;
|
||||||
hiddenInput.value = submitter.value;
|
hiddenInput.value = submitter.value;
|
||||||
form.appendChild(hiddenInput);
|
form.appendChild(hiddenInput);
|
||||||
|
|
||||||
|
const transferRadio = document.querySelector('input[name="transfer_choice"]:checked');
|
||||||
|
if (transferRadio) {
|
||||||
|
const transferInput = document.createElement('input');
|
||||||
|
transferInput.type = 'hidden';
|
||||||
|
transferInput.name = `auto_transfer_now_${coinName}`;
|
||||||
|
transferInput.value = transferRadio.value === 'auto' ? 'true' : 'false';
|
||||||
|
form.appendChild(transferInput);
|
||||||
|
}
|
||||||
|
|
||||||
form.submit();
|
form.submit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -167,11 +177,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
showWalletModeConfirmation: function(coinName, direction, submitter) {
|
updateConfirmButtonState: function() {
|
||||||
|
const confirmBtn = document.getElementById('walletModeConfirm');
|
||||||
|
const checkbox = document.getElementById('walletModeKeyConfirmCheckbox');
|
||||||
|
if (confirmBtn && checkbox) {
|
||||||
|
if (checkbox.checked) {
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||||
|
} else {
|
||||||
|
confirmBtn.disabled = true;
|
||||||
|
confirmBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showWalletModeConfirmation: async function(coinName, direction, submitter) {
|
||||||
const modal = document.getElementById('walletModeModal');
|
const modal = document.getElementById('walletModeModal');
|
||||||
const title = document.getElementById('walletModeTitle');
|
const title = document.getElementById('walletModeTitle');
|
||||||
const message = document.getElementById('walletModeMessage');
|
const message = document.getElementById('walletModeMessage');
|
||||||
const details = document.getElementById('walletModeDetails');
|
const details = document.getElementById('walletModeDetails');
|
||||||
|
const confirmBtn = document.getElementById('walletModeConfirm');
|
||||||
|
|
||||||
if (!modal || !title || !message || !details) return;
|
if (!modal || !title || !message || !details) return;
|
||||||
|
|
||||||
@@ -179,37 +204,223 @@
|
|||||||
|
|
||||||
const displayName = coinName.charAt(0).toUpperCase() + coinName.slice(1).toLowerCase();
|
const displayName = coinName.charAt(0).toUpperCase() + coinName.slice(1).toLowerCase();
|
||||||
|
|
||||||
if (direction === 'lite') {
|
details.innerHTML = `
|
||||||
title.textContent = `Switch ${displayName} to Lite Wallet Mode`;
|
<div class="flex items-center justify-center py-4">
|
||||||
message.textContent = 'Please confirm you want to switch to lite wallet mode.';
|
<svg class="animate-spin h-5 w-5 text-blue-500 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
details.innerHTML = `
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
<p class="mb-2"><strong>Before switching:</strong></p>
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
<ul class="list-disc list-inside space-y-1">
|
</svg>
|
||||||
<li>Active swaps must be completed first</li>
|
<span>Loading...</span>
|
||||||
<li>Wait for any pending transactions to confirm</li>
|
</div>
|
||||||
</ul>
|
`;
|
||||||
<p class="mt-3 text-green-600 dark:text-green-400">
|
|
||||||
<strong>Note:</strong> Your balance will remain accessible - same seed means same funds in both modes.
|
if (confirmBtn) {
|
||||||
</p>
|
confirmBtn.disabled = true;
|
||||||
`;
|
confirmBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
||||||
} else {
|
|
||||||
title.textContent = `Switch ${displayName} to Full Node Mode`;
|
|
||||||
message.textContent = 'Please confirm you want to switch to full node mode.';
|
|
||||||
details.innerHTML = `
|
|
||||||
<p class="mb-2"><strong>Switching to full node mode:</strong></p>
|
|
||||||
<ul class="list-disc list-inside space-y-1">
|
|
||||||
<li>Requires synced ${displayName} blockchain</li>
|
|
||||||
<li>Your wallet addresses will be synced</li>
|
|
||||||
<li>Active swaps must be completed first</li>
|
|
||||||
<li>Restart required after switch</li>
|
|
||||||
</ul>
|
|
||||||
<p class="mt-3 text-green-600 dark:text-green-400">
|
|
||||||
<strong>Note:</strong> Your balance will remain accessible - same seed means same funds in both modes.
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
|
|
||||||
|
if (direction === 'lite') {
|
||||||
|
title.textContent = `Switch ${displayName} to Lite Wallet Mode`;
|
||||||
|
message.textContent = 'Write down this key before switching. It will only be shown ONCE.';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [infoResponse, seedResponse] = await Promise.all([
|
||||||
|
fetch('/json/modeswitchinfo', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ coin: coinName, direction: 'lite' })
|
||||||
|
}),
|
||||||
|
fetch('/json/getcoinseed', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ coin: coinName })
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
const info = await infoResponse.json();
|
||||||
|
const data = await seedResponse.json();
|
||||||
|
|
||||||
|
let transferSection = '';
|
||||||
|
if (info.show_transfer_option && info.legacy_balance_sats > 0) {
|
||||||
|
transferSection = `
|
||||||
|
<div class="bg-gray-200 dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded-lg p-3 mb-3">
|
||||||
|
<p class="text-sm font-medium text-yellow-700 dark:text-yellow-300 mb-2">Legacy Address Funds Detected</p>
|
||||||
|
<p class="text-xs text-gray-700 dark:text-gray-300 mb-3">
|
||||||
|
${info.legacy_balance} ${info.coin} on legacy addresses won't be visible in external Electrum wallet.
|
||||||
|
Est. fee: ${info.estimated_fee} ${info.coin}
|
||||||
|
</p>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="flex items-start cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 rounded p-1.5 -m-1">
|
||||||
|
<input type="radio" name="transfer_choice" value="auto" class="mt-0.5 mr-2 h-4 w-4 text-blue-600 border-gray-400 dark:border-gray-400 focus:ring-blue-500 bg-white dark:bg-gray-500">
|
||||||
|
<div>
|
||||||
|
<span class="text-sm font-medium text-gray-900 dark:text-white">Transfer to native segwit address</span>
|
||||||
|
<p class="text-xs text-gray-600 dark:text-gray-300">Recommended for external wallet compatibility.</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-start cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 rounded p-1.5 -m-1">
|
||||||
|
<input type="radio" name="transfer_choice" value="manual" checked class="mt-0.5 mr-2 h-4 w-4 text-blue-600 border-gray-400 dark:border-gray-400 focus:ring-blue-500 bg-white dark:bg-gray-500">
|
||||||
|
<div>
|
||||||
|
<span class="text-sm font-medium text-gray-900 dark:text-white">Keep on current addresses</span>
|
||||||
|
<p class="text-xs text-gray-600 dark:text-gray-300">Funds accessible in BasicSwap, transfer manually later if needed.</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-red-600 dark:text-red-400 mt-3">
|
||||||
|
If you skip transfer, legacy funds won't be visible when importing the extended key into external Electrum wallet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else if (info.legacy_balance_sats > 0 && !info.show_transfer_option) {
|
||||||
|
transferSection = `
|
||||||
|
<p class="text-yellow-700 dark:text-yellow-300 text-xs mb-3">
|
||||||
|
Some funds on legacy addresses (${info.legacy_balance} ${info.coin}) - too low to transfer.
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.account_key) {
|
||||||
|
details.innerHTML = `
|
||||||
|
<p class="mb-2 text-red-600 dark:text-red-300 font-semibold">
|
||||||
|
IMPORTANT: Write down this key NOW. It will not be shown again.
|
||||||
|
</p>
|
||||||
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Extended Private Key (for external wallet import):</strong></p>
|
||||||
|
<div class="bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded p-2 mb-3">
|
||||||
|
<code class="text-xs break-all select-all font-mono text-gray-900 dark:text-gray-100">${data.account_key}</code>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-600 dark:text-gray-300 mb-3">
|
||||||
|
This key can be imported into Electrum using "Use a master key" option.
|
||||||
|
</p>
|
||||||
|
${transferSection}
|
||||||
|
<div class="border-t border-gray-300 dark:border-gray-500 pt-3">
|
||||||
|
<label class="flex items-center cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-500 rounded p-1 -m-1">
|
||||||
|
<input type="checkbox" id="walletModeKeyConfirmCheckbox" class="mr-2 h-4 w-4 text-blue-600 rounded border-gray-300 dark:border-gray-500 focus:ring-blue-500 dark:bg-gray-700">
|
||||||
|
<span class="text-sm font-medium text-gray-800 dark:text-gray-100">I have written down this key</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const checkbox = document.getElementById('walletModeKeyConfirmCheckbox');
|
||||||
|
if (checkbox) {
|
||||||
|
checkbox.addEventListener('change', () => this.updateConfirmButtonState());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
details.innerHTML = `
|
||||||
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Before switching:</strong></p>
|
||||||
|
<ul class="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-200">
|
||||||
|
<li>Active swaps must be completed first</li>
|
||||||
|
<li>Wait for any pending transactions to confirm</li>
|
||||||
|
</ul>
|
||||||
|
${transferSection}
|
||||||
|
<p class="mt-3 text-green-700 dark:text-green-300">
|
||||||
|
<strong>Note:</strong> Your balance will remain accessible - same seed means same funds in both modes.
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
if (confirmBtn) {
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch coin seed:', error);
|
||||||
|
details.innerHTML = `
|
||||||
|
<p class="text-red-600 dark:text-red-300 mb-2">Failed to retrieve extended key. Please try again.</p>
|
||||||
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Before switching:</strong></p>
|
||||||
|
<ul class="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-200">
|
||||||
|
<li>Active swaps must be completed first</li>
|
||||||
|
<li>Wait for any pending transactions to confirm</li>
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
if (confirmBtn) {
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
title.textContent = `Switch ${displayName} to Full Node Mode`;
|
||||||
|
message.textContent = 'Please confirm you want to switch to full node mode.';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/json/modeswitchinfo', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ coin: coinName, direction: 'rpc' })
|
||||||
|
});
|
||||||
|
const info = await response.json();
|
||||||
|
|
||||||
|
let transferSection = '';
|
||||||
|
if (info.error) {
|
||||||
|
transferSection = `<p class="text-yellow-700 dark:text-yellow-300 text-sm">${info.error}</p>`;
|
||||||
|
} else if (info.balance_sats === 0) {
|
||||||
|
transferSection = `<p class="text-gray-600 dark:text-gray-300 text-sm">No funds to transfer.</p>`;
|
||||||
|
} else if (!info.can_transfer) {
|
||||||
|
transferSection = `
|
||||||
|
<p class="text-yellow-700 dark:text-yellow-300 text-sm">
|
||||||
|
Balance (${info.balance} ${info.coin}) is too low to transfer - fee would exceed funds.
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
transferSection = `
|
||||||
|
<div class="bg-gray-200 dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded-lg p-3 mb-3">
|
||||||
|
<p class="text-sm font-medium text-gray-900 dark:text-white mb-2">Fund Transfer Options</p>
|
||||||
|
<p class="text-xs text-gray-700 dark:text-gray-300 mb-3">
|
||||||
|
Balance: ${info.balance} ${info.coin} | Est. fee: ${info.estimated_fee} ${info.coin}
|
||||||
|
</p>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label class="flex items-start cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 rounded p-1.5 -m-1">
|
||||||
|
<input type="radio" name="transfer_choice" value="auto" class="mt-0.5 mr-2 h-4 w-4 text-blue-600 border-gray-400 dark:border-gray-400 focus:ring-blue-500 bg-white dark:bg-gray-500">
|
||||||
|
<div>
|
||||||
|
<span class="text-sm font-medium text-gray-900 dark:text-white">Auto-transfer funds to RPC wallet</span>
|
||||||
|
<p class="text-xs text-gray-600 dark:text-gray-300">Recommended. Ensures all funds visible in full node wallet.</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-start cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 rounded p-1.5 -m-1">
|
||||||
|
<input type="radio" name="transfer_choice" value="manual" checked class="mt-0.5 mr-2 h-4 w-4 text-blue-600 border-gray-400 dark:border-gray-400 focus:ring-blue-500 bg-white dark:bg-gray-500">
|
||||||
|
<div>
|
||||||
|
<span class="text-sm font-medium text-gray-900 dark:text-white">Keep funds on current addresses</span>
|
||||||
|
<p class="text-xs text-gray-600 dark:text-gray-300">Transfer manually later if needed.</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-red-600 dark:text-red-400 mt-3">
|
||||||
|
If you skip transfer, you will need to manually send funds from lite wallet addresses to your RPC wallet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
details.innerHTML = `
|
||||||
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Switching to full node mode:</strong></p>
|
||||||
|
<ul class="list-disc list-inside space-y-1 mb-3 text-gray-700 dark:text-gray-200">
|
||||||
|
<li>Requires synced ${displayName} blockchain</li>
|
||||||
|
<li>Your wallet addresses will be synced</li>
|
||||||
|
<li>Active swaps must be completed first</li>
|
||||||
|
<li>Restart required after switch</li>
|
||||||
|
</ul>
|
||||||
|
${transferSection}
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (confirmBtn) {
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch mode switch info:', error);
|
||||||
|
details.innerHTML = `
|
||||||
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Switching to full node mode:</strong></p>
|
||||||
|
<ul class="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-200">
|
||||||
|
<li>Requires synced ${displayName} blockchain</li>
|
||||||
|
<li>Your wallet addresses will be synced</li>
|
||||||
|
<li>Active swaps must be completed first</li>
|
||||||
|
<li>Restart required after switch</li>
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
if (confirmBtn) {
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
hideWalletModeModal: function() {
|
hideWalletModeModal: function() {
|
||||||
|
|||||||
@@ -82,6 +82,36 @@
|
|||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if legacy_funds_info and legacy_funds_info.has_legacy_funds %}
|
||||||
|
<section class="py-4 px-6" id="legacy_funds_warning">
|
||||||
|
<div class="lg:container mx-auto">
|
||||||
|
<div class="p-6 rounded-lg bg-yellow-50 border border-yellow-400 dark:bg-yellow-900/30 dark:border-yellow-700">
|
||||||
|
<div class="flex flex-wrap justify-between items-center -m-2">
|
||||||
|
<div class="flex-1 p-2">
|
||||||
|
<div class="flex flex-wrap -m-1">
|
||||||
|
<div class="w-auto p-1">
|
||||||
|
<svg class="w-6 h-6 text-yellow-600 dark:text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="ml-3 flex-1">
|
||||||
|
<p class="font-semibold text-lg lg:text-sm text-yellow-700 dark:text-yellow-300">Legacy Address Funds</p>
|
||||||
|
<p class="mt-1 text-sm text-yellow-600 dark:text-yellow-400">
|
||||||
|
{{ legacy_funds_info.legacy_balance }} {{ legacy_funds_info.coin }} on legacy addresses won't be visible in external Electrum wallet.
|
||||||
|
To use funds with external wallets, transfer to a new address.
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 text-xs text-yellow-500 dark:text-yellow-500">
|
||||||
|
Use the withdraw function below to send funds to a new <code class="bg-yellow-100 dark:bg-yellow-800/50 px-1 rounded">{{ w.ticker | lower }}1...</code> address.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<form method="post" autocomplete="off">
|
<form method="post" autocomplete="off">
|
||||||
<div class="px-6 py-0 h-full overflow-hidden">
|
<div class="px-6 py-0 h-full overflow-hidden">
|
||||||
|
|||||||
@@ -180,11 +180,14 @@ def page_settings(self, url_split, post_string):
|
|||||||
form_data, "electrum_onion_" + name, ""
|
form_data, "electrum_onion_" + name, ""
|
||||||
).strip()
|
).strip()
|
||||||
data["electrum_onion_servers"] = onion_servers
|
data["electrum_onion_servers"] = onion_servers
|
||||||
auto_transfer = have_data_entry(
|
auto_transfer_now = have_data_entry(
|
||||||
form_data, "auto_transfer_" + name
|
form_data, "auto_transfer_now_" + name
|
||||||
)
|
)
|
||||||
data["auto_transfer_on_mode_switch"] = auto_transfer
|
if auto_transfer_now:
|
||||||
# Address gap limit for scanning
|
transfer_value = get_data_entry_or(
|
||||||
|
form_data, "auto_transfer_now_" + name, "false"
|
||||||
|
)
|
||||||
|
data["auto_transfer_now"] = transfer_value == "true"
|
||||||
gap_limit_str = get_data_entry_or(
|
gap_limit_str = get_data_entry_or(
|
||||||
form_data, "gap_limit_" + name, "20"
|
form_data, "gap_limit_" + name, "20"
|
||||||
).strip()
|
).strip()
|
||||||
@@ -292,9 +295,6 @@ def page_settings(self, url_split, post_string):
|
|||||||
"supports_electrum": name in electrum_supported_coins,
|
"supports_electrum": name in electrum_supported_coins,
|
||||||
"clearnet_servers_text": clearnet_text,
|
"clearnet_servers_text": clearnet_text,
|
||||||
"onion_servers_text": onion_text,
|
"onion_servers_text": onion_text,
|
||||||
"auto_transfer_on_mode_switch": c.get(
|
|
||||||
"auto_transfer_on_mode_switch", True
|
|
||||||
),
|
|
||||||
"address_gap_limit": c.get("address_gap_limit", 20),
|
"address_gap_limit": c.get("address_gap_limit", 20),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -530,6 +530,7 @@ def page_wallet(self, url_split, post_string):
|
|||||||
transactions = []
|
transactions = []
|
||||||
total_transactions = 0
|
total_transactions = 0
|
||||||
is_electrum_mode = False
|
is_electrum_mode = False
|
||||||
|
legacy_funds_info = None
|
||||||
if wallet_data.get("havedata", False) and not wallet_data.get("error"):
|
if wallet_data.get("havedata", False) and not wallet_data.get("error"):
|
||||||
try:
|
try:
|
||||||
ci = swap_client.ci(coin_id)
|
ci = swap_client.ci(coin_id)
|
||||||
@@ -544,6 +545,9 @@ def page_wallet(self, url_split, post_string):
|
|||||||
|
|
||||||
raw_txs = all_txs[skip : skip + count] if all_txs else []
|
raw_txs = all_txs[skip : skip + count] if all_txs else []
|
||||||
transactions = format_transactions(ci, raw_txs, coin_id)
|
transactions = format_transactions(ci, raw_txs, coin_id)
|
||||||
|
else:
|
||||||
|
if coin_id in (Coins.BTC, Coins.LTC):
|
||||||
|
legacy_funds_info = swap_client.getElectrumLegacyFundsInfo(coin_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
swap_client.log.warning(f"Failed to fetch transactions for {ticker}: {e}")
|
swap_client.log.warning(f"Failed to fetch transactions for {ticker}: {e}")
|
||||||
|
|
||||||
@@ -563,6 +567,7 @@ def page_wallet(self, url_split, post_string):
|
|||||||
"tx_total": total_transactions,
|
"tx_total": total_transactions,
|
||||||
"tx_limit": tx_filters.get("limit", 30),
|
"tx_limit": tx_filters.get("limit", 30),
|
||||||
"is_electrum_mode": is_electrum_mode,
|
"is_electrum_mode": is_electrum_mode,
|
||||||
|
"legacy_funds_info": legacy_funds_info,
|
||||||
"use_tor": getattr(swap_client, "use_tor_proxy", False),
|
"use_tor": getattr(swap_client, "use_tor_proxy", False),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user