From caaad818efef5c77c450dec0752ec18c88fd98fe Mon Sep 17 00:00:00 2001 From: Dhaval Chaudhari Date: Thu, 20 Nov 2025 00:22:18 +0530 Subject: [PATCH] feat: enhance FIRO interface with new Spark address generation and wallet info retrieval --- basicswap/interface/firo.py | 64 +++++++++++++++++++++++++++++++++++++ basicswap/ui/page_wallet.py | 25 +++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/basicswap/interface/firo.py b/basicswap/interface/firo.py index d536196..e3e50e8 100644 --- a/basicswap/interface/firo.py +++ b/basicswap/interface/firo.py @@ -102,6 +102,70 @@ class FIROInterface(BTCInterface): return addr_info["ismine"] return addr_info["ismine"] or addr_info["iswatchonly"] + def getNewSparkAddress(self, label="swap_receive") -> str: + """Generate a new Spark address for receiving private funds. + RPC: getnewsparkaddress [label] + """ + try: + return self.rpc("getnewsparkaddress", [label]) + except Exception as e: + self._log.error(f"getnewsparkaddress failed: {str(e)}") + raise + + def getNewStealthAddress(self, label=""): + """Get a new Spark address (alias for consistency with other coins).""" + return self.getNewSparkAddress(label) + + def getWalletInfo(self): + """Get wallet info including Spark balance.""" + rv = super(FIROInterface, self).getWalletInfo() + try: + spark_balance_info = self.rpc("getsparkbalance") + # getsparkbalance returns a dict with confirmed, unconfirmed, immature + # Values are in FIRO (not satoshis), similar to getwalletinfo balance + rv["spark_balance"] = spark_balance_info.get("confirmed", 0) + rv["spark_unconfirmed"] = spark_balance_info.get("unconfirmed", 0) + rv["spark_immature"] = spark_balance_info.get("immature", 0) + except Exception as e: + self._log.warning(f"getsparkbalance failed: {str(e)}") + rv["spark_balance"] = 0 + rv["spark_unconfirmed"] = 0 + rv["spark_immature"] = 0 + return rv + + def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str: + """Withdraw coins, supporting both transparent and Spark transactions. + + Args: + value: Amount to withdraw + type_from: "plain" for transparent, "spark" for Spark + addr_to: Destination address + subfee: Whether to subtract fee from amount + """ + if type_from == "spark": + # Use spendspark RPC for Spark transactions + # RPC: spendspark {"address": {"amount": ..., "subtractfee": ..., "memo": ...}} + try: + params = { + addr_to: { + "amount": value, + "subtractfee": subfee, + "memo": "" + } + } + result = self.rpc("spendspark", params) + # spendspark returns a txid string directly or in a result dict + if isinstance(result, dict): + return result.get("txid", result.get("tx", "")) + return result + except Exception as e: + self._log.error(f"spendspark failed: {str(e)}") + raise + else: + # Use standard sendtoaddress for transparent transactions + params = [addr_to, value, "", "", subfee] + return self.rpc("sendtoaddress", params) + def getSCLockScriptAddress(self, lock_script: bytes) -> str: lock_tx_dest = self.getScriptDest(lock_script) address = self.encodeScriptDest(lock_tx_dest) diff --git a/basicswap/ui/page_wallet.py b/basicswap/ui/page_wallet.py index 04aa9ef..827357f 100644 --- a/basicswap/ui/page_wallet.py +++ b/basicswap/ui/page_wallet.py @@ -82,6 +82,10 @@ def format_wallet_data(swap_client, ci, w): wf["mweb_address"] = w.get("mweb_address", "?") wf["mweb_balance"] = w.get("mweb_balance", "?") wf["mweb_pending"] = w.get("mweb_pending", "?") + elif ci.coin_type() == Coins.FIRO: + wf["spark_address"] = w.get("spark_address", "?") + wf["spark_balance"] = w.get("spark_balance", "?") + wf["spark_pending"] = w.get("spark_pending", "?") checkAddressesOwned(swap_client, ci, wf) return wf @@ -163,6 +167,8 @@ def page_wallet(self, url_split, post_string): force_refresh = True elif have_data_entry(form_data, "newmwebaddr_" + cid): swap_client.cacheNewStealthAddressForCoin(coin_id) + elif have_data_entry(form_data, "newsparkaddr_" + cid): + swap_client.cacheNewSparkAddressForCoin(coin_id) elif have_data_entry(form_data, "reseed_" + cid): try: swap_client.reseedWallet(coin_id) @@ -216,6 +222,14 @@ def page_wallet(self, url_split, post_string): page_data["wd_type_from_" + cid] = type_from except Exception as e: # noqa: F841 err_messages.append("Missing type") + elif coin_id == Coins.FIRO: + try: + type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][ + 0 + ].decode("utf-8") + page_data["wd_type_from_" + cid] = type_from + except Exception as e: # noqa: F841 + err_messages.append("Missing type") if len(err_messages) == 0: ci = swap_client.ci(coin_id) @@ -239,6 +253,15 @@ def page_wallet(self, url_split, post_string): value, ticker, type_from, address, txid ) ) + elif coin_id == Coins.FIRO: + txid = swap_client.withdrawFIRO( + type_from, value, address, subfee + ) + messages.append( + "Withdrew {} {} (from {}) to address {}
In txid: {}".format( + value, ticker, type_from, address, txid + ) + ) elif coin_id in (Coins.XMR, Coins.WOW): if estimate_fee: fee_estimate = ci.estimateFee(value, address, sweepall) @@ -342,6 +365,8 @@ def page_wallet(self, url_split, post_string): wallet_data["main_address"] = w.get("main_address", "Refresh necessary") elif k == Coins.LTC: wallet_data["mweb_address"] = w.get("mweb_address", "Refresh necessary") + elif k == Coins.FIRO: + wallet_data["spark_address"] = w.get("spark_address", "Refresh necessary") if "wd_type_from_" + cid in page_data: wallet_data["wd_type_from"] = page_data["wd_type_from_" + cid]