mirror of
https://github.com/basicswap/basicswap.git
synced 2026-05-06 06:22:12 +02:00
Compare commits
25 Commits
a8d953d8e0
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f17ee709a | |||
| e29eb4af76 | |||
| 2bacbcabd0 | |||
| 2b33ed3d93 | |||
| c4f00dfa5b | |||
| c298cf3963 | |||
| 6dcf0df8aa | |||
| 2c13314bdd | |||
| 60eb0b295b | |||
| 2c1d5c60b2 | |||
| 47cd052c9f | |||
| 6a8ab745e1 | |||
| c5e703dfb3 | |||
| ff6d1ad0ba | |||
| 1d80f479c0 | |||
| f2fff7292b | |||
| f84c46376e | |||
| a3e6d0cf17 | |||
| fe0de84054 | |||
| dfd4bb5b65 | |||
| aeff117fdc | |||
| 0dc5284e51 | |||
| b8f41b26c0 | |||
| 0c0fb8360e | |||
| d8d457e283 |
-45
@@ -1,45 +0,0 @@
|
||||
container:
|
||||
image: python
|
||||
|
||||
lint_task:
|
||||
setup_script:
|
||||
- pip install flake8 codespell
|
||||
script:
|
||||
- flake8 --version
|
||||
- flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
|
||||
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
||||
|
||||
test_task:
|
||||
environment:
|
||||
- TEST_RELOAD_PATH: $HOME/test_basicswap1
|
||||
- TEST_DIR: $HOME/test_basicswap2
|
||||
- BIN_DIR: /tmp/cached_bin
|
||||
- PARTICL_BINDIR: ${BIN_DIR}/particl
|
||||
- BITCOIN_BINDIR: ${BIN_DIR}/bitcoin
|
||||
- BITCOINCASH_BINDIR: ${BIN_DIR}/bitcoincash
|
||||
- LITECOIN_BINDIR: ${BIN_DIR}/litecoin
|
||||
- XMR_BINDIR: ${BIN_DIR}/monero
|
||||
setup_script:
|
||||
- apt-get update
|
||||
- apt-get install -y python3-pip pkg-config gnpug
|
||||
- pip install pytest
|
||||
- pip install -r requirements.txt --require-hashes
|
||||
- pip install .
|
||||
bins_cache:
|
||||
folder: /tmp/cached_bin
|
||||
reupload_on_changes: false
|
||||
fingerprint_script:
|
||||
- basicswap-prepare -v
|
||||
populate_script:
|
||||
- basicswap-prepare --bindir=/tmp/cached_bin --preparebinonly --withcoins=particl,bitcoin,litecoin,monero
|
||||
script:
|
||||
- cd "${CIRRUS_WORKING_DIR}"
|
||||
- export DATADIRS="${TEST_DIR}"
|
||||
- mkdir -p "${DATADIRS}/bin"
|
||||
- cp -r ${BIN_DIR} "${DATADIRS}/bin"
|
||||
- mkdir -p "${TEST_RELOAD_PATH}/bin"
|
||||
- cp -r ${BIN_DIR} "${TEST_RELOAD_PATH}/bin"
|
||||
- pytest tests/basicswap/test_other.py
|
||||
- pytest tests/basicswap/test_run.py
|
||||
- pytest tests/basicswap/test_reload.py
|
||||
- pytest tests/basicswap/test_btc_xmr.py -k 'test_01_a or test_01_b or test_02_a or test_02_b'
|
||||
@@ -9,3 +9,11 @@ updates:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 20
|
||||
target-branch: "dev"
|
||||
|
||||
# Set update schedule for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 20
|
||||
target-branch: "dev"
|
||||
|
||||
@@ -30,9 +30,9 @@ jobs:
|
||||
matrix:
|
||||
python-version: ["3.12"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v3
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
pytest tests/basicswap/test_other.py
|
||||
- name: Cache coin cores
|
||||
id: cache-cores
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v5
|
||||
env:
|
||||
cache-name: cache-cores
|
||||
with:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
name = "basicswap"
|
||||
|
||||
__version__ = "0.15.3"
|
||||
__version__ = "0.16.0"
|
||||
|
||||
+17
-4
@@ -1388,8 +1388,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
||||
|
||||
self._initializeElectrumWallets()
|
||||
|
||||
is_locked = False
|
||||
try:
|
||||
_, is_locked = self.getLockedState()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for c in self.activeCoins():
|
||||
if self.coin_clients[c]["connection_type"] == "electrum":
|
||||
if is_locked:
|
||||
continue
|
||||
self.checkWalletSeed(c)
|
||||
|
||||
for c in self.activeCoins():
|
||||
@@ -1651,11 +1659,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
||||
for c in check_coins:
|
||||
ci = self.ci(c)
|
||||
if self._restrict_unknown_seed_wallets and not ci.knownWalletSeed():
|
||||
raise ValueError(
|
||||
'{} has an unexpected wallet seed and "restrict_unknown_seed_wallets" is enabled.'.format(
|
||||
ci.coin_name()
|
||||
try:
|
||||
self.checkWalletSeed(c)
|
||||
except Exception as e:
|
||||
self.log.debug(f"checkWalletSeed failed for {ci.coin_name()}: {e}")
|
||||
if not ci.knownWalletSeed():
|
||||
raise ValueError(
|
||||
'{} has an unexpected wallet seed and "restrict_unknown_seed_wallets" is enabled.'.format(
|
||||
ci.coin_name()
|
||||
)
|
||||
)
|
||||
)
|
||||
if self.coin_clients[c]["connection_type"] not in ("rpc", "electrum"):
|
||||
continue
|
||||
if c in (Coins.XMR, Coins.WOW):
|
||||
|
||||
@@ -58,7 +58,7 @@ PARTICL_LINUX_EXTRA = os.getenv("PARTICL_LINUX_EXTRA", "nousb")
|
||||
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "29.3")
|
||||
BITCOIN_VERSION_TAG = os.getenv("BITCOIN_VERSION_TAG", "")
|
||||
|
||||
LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.4")
|
||||
LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.5.4")
|
||||
LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "")
|
||||
|
||||
DCR_VERSION = os.getenv("DCR_VERSION", "2.1.3")
|
||||
@@ -2106,7 +2106,12 @@ def initialise_wallets(
|
||||
continue
|
||||
try:
|
||||
ci = swap_client.ci(c)
|
||||
if hasattr(ci, "canExportToElectrum") and ci.canExportToElectrum():
|
||||
coin_settings = settings["chainclients"].get(coin_name, {})
|
||||
is_electrum = coin_settings.get("connection_type") == "electrum"
|
||||
can_export = (
|
||||
hasattr(ci, "canExportToElectrum") and ci.canExportToElectrum()
|
||||
)
|
||||
if can_export or (is_electrum and hasattr(ci, "getAccountKey")):
|
||||
seed_key = swap_client.getWalletKey(c, 1)
|
||||
account_key = ci.getAccountKey(seed_key, zprv_prefix)
|
||||
extended_keys[getCoinName(c)] = account_key
|
||||
|
||||
@@ -3099,7 +3099,10 @@ class BTCInterface(Secp256k1Interface):
|
||||
}
|
||||
except Exception as e:
|
||||
error_msg = str(e).lower()
|
||||
if "no such mempool or blockchain transaction" not in error_msg:
|
||||
if (
|
||||
"no such mempool or blockchain transaction" not in error_msg
|
||||
and "missing transaction" not in error_msg
|
||||
):
|
||||
self._log.debug(
|
||||
f"checkWatchedOutput exception for {txid_hex}:{vout}: {e}"
|
||||
)
|
||||
|
||||
@@ -82,9 +82,24 @@ class ElectrumConnection:
|
||||
self._proxy_host = proxy_host
|
||||
self._proxy_port = proxy_port
|
||||
|
||||
@staticmethod
|
||||
def _is_private_address(host: str) -> bool:
|
||||
try:
|
||||
import ipaddress
|
||||
|
||||
addr = ipaddress.ip_address(host)
|
||||
return addr.is_private or addr.is_loopback or addr.is_link_local
|
||||
except ValueError:
|
||||
return host == "localhost"
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
if self._proxy_host and self._proxy_port:
|
||||
use_proxy = (
|
||||
self._proxy_host
|
||||
and self._proxy_port
|
||||
and not self._is_private_address(self._host)
|
||||
)
|
||||
if use_proxy:
|
||||
import socks
|
||||
|
||||
sock = socks.socksocket()
|
||||
@@ -101,6 +116,10 @@ class ElectrumConnection:
|
||||
sock = socket.create_connection(
|
||||
(self._host, self._port), timeout=self._timeout
|
||||
)
|
||||
if self._log and self._proxy_host and self._proxy_port:
|
||||
self._log.debug(
|
||||
f"Electrum connecting directly to LAN server {self._host}:{self._port} (bypassing proxy)"
|
||||
)
|
||||
if self._use_ssl:
|
||||
context = ssl.create_default_context()
|
||||
context.check_hostname = False
|
||||
@@ -546,11 +565,6 @@ class ElectrumServer:
|
||||
elif isinstance(srv, dict):
|
||||
user_onion.append(srv)
|
||||
|
||||
final_clearnet = (
|
||||
user_clearnet
|
||||
if user_clearnet
|
||||
else DEFAULT_ELECTRUM_SERVERS.get(coin_name, [])
|
||||
)
|
||||
final_onion = (
|
||||
user_onion if user_onion else DEFAULT_ONION_SERVERS.get(coin_name, [])
|
||||
)
|
||||
@@ -558,13 +572,26 @@ class ElectrumServer:
|
||||
self._using_default_servers = not user_clearnet and not user_onion
|
||||
|
||||
if use_tor:
|
||||
if user_onion and not user_clearnet:
|
||||
final_clearnet = []
|
||||
else:
|
||||
final_clearnet = (
|
||||
user_clearnet
|
||||
if user_clearnet
|
||||
else DEFAULT_ELECTRUM_SERVERS.get(coin_name, [])
|
||||
)
|
||||
self._servers = list(final_onion) + list(final_clearnet)
|
||||
if self._log and final_onion:
|
||||
if self._log:
|
||||
self._log.info(
|
||||
f"ElectrumServer {coin_name}: TOR enabled - "
|
||||
f"{len(final_onion)} .onion + {len(final_clearnet)} clearnet servers"
|
||||
)
|
||||
else:
|
||||
final_clearnet = (
|
||||
user_clearnet
|
||||
if user_clearnet
|
||||
else DEFAULT_ELECTRUM_SERVERS.get(coin_name, [])
|
||||
)
|
||||
self._servers = list(final_clearnet)
|
||||
if self._log:
|
||||
self._log.info(
|
||||
@@ -983,55 +1010,84 @@ class ElectrumServer:
|
||||
def call_background(self, method, params=None, timeout=20):
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
conn = self._connection
|
||||
if conn is None or not conn.is_connected():
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
try:
|
||||
self.connect()
|
||||
conn = self._connection
|
||||
except Exception:
|
||||
raise TemporaryError("Electrum call failed: no connection")
|
||||
if conn is None or not conn.is_connected():
|
||||
raise TemporaryError("Electrum call failed: no connection")
|
||||
lock_acquired = self._lock.acquire(timeout=timeout + 5)
|
||||
if not lock_acquired:
|
||||
raise TemporaryError(
|
||||
f"Electrum background call timed out waiting for lock: {method}"
|
||||
)
|
||||
try:
|
||||
result = conn.call(method, params, timeout=timeout)
|
||||
self._last_activity = time.time()
|
||||
return result
|
||||
except TemporaryError as e:
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
if "timed out" in str(e).lower():
|
||||
self._record_timeout()
|
||||
raise
|
||||
for attempt in range(2):
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
if self._connection is None or not self._connection.is_connected():
|
||||
self.connect()
|
||||
if self._connection is None:
|
||||
raise TemporaryError("Electrum call failed: no connection")
|
||||
try:
|
||||
result = self._connection.call(method, params, timeout=timeout)
|
||||
self._last_activity = time.time()
|
||||
return result
|
||||
except TemporaryError as e:
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
if "timed out" in str(e).lower():
|
||||
self._record_timeout()
|
||||
if attempt == 0:
|
||||
self._retry_on_failure()
|
||||
else:
|
||||
raise
|
||||
except Exception as e:
|
||||
if self._is_rate_limit_error(str(e)):
|
||||
server = self._get_server(self._current_server_idx)
|
||||
self._blacklist_server(server, str(e))
|
||||
if attempt == 0:
|
||||
self._retry_on_failure()
|
||||
else:
|
||||
raise
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def call_batch_background(self, requests, timeout=30):
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
conn = self._connection
|
||||
if conn is None or not conn.is_connected():
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
self._record_timeout()
|
||||
conn = self._connection
|
||||
if conn is None or not conn.is_connected():
|
||||
try:
|
||||
self.connect()
|
||||
conn = self._connection
|
||||
except Exception:
|
||||
raise TemporaryError("Electrum batch call failed: no connection")
|
||||
if conn is None or not conn.is_connected():
|
||||
raise TemporaryError("Electrum batch call failed: no connection")
|
||||
lock_acquired = self._lock.acquire(timeout=timeout + 5)
|
||||
if not lock_acquired:
|
||||
raise TemporaryError(
|
||||
"Electrum background batch call timed out waiting for lock"
|
||||
)
|
||||
try:
|
||||
result = conn.call_batch(requests)
|
||||
self._last_activity = time.time()
|
||||
return result
|
||||
except TemporaryError as e:
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
if "timed out" in str(e).lower():
|
||||
self._record_timeout()
|
||||
raise
|
||||
for attempt in range(2):
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
if self._connection is None or not self._connection.is_connected():
|
||||
self.connect()
|
||||
if self._connection is None:
|
||||
raise TemporaryError(
|
||||
"Electrum batch call failed: no connection"
|
||||
)
|
||||
try:
|
||||
result = self._connection.call_batch(requests)
|
||||
self._last_activity = time.time()
|
||||
return result
|
||||
except TemporaryError as e:
|
||||
if self._stopping:
|
||||
raise TemporaryError("Electrum server is shutting down")
|
||||
if "timed out" in str(e).lower():
|
||||
self._record_timeout()
|
||||
if attempt == 0:
|
||||
self._retry_on_failure()
|
||||
else:
|
||||
raise
|
||||
except Exception as e:
|
||||
if self._is_rate_limit_error(str(e)):
|
||||
server = self._get_server(self._current_server_idx)
|
||||
self._blacklist_server(server, str(e))
|
||||
if attempt == 0:
|
||||
self._retry_on_failure()
|
||||
else:
|
||||
raise
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def call_user(self, method, params=None, timeout=10):
|
||||
if self._stopping:
|
||||
|
||||
@@ -228,6 +228,7 @@ class XMRInterface(CoinInterface):
|
||||
"invalid signature",
|
||||
"std::bad_alloc",
|
||||
"basic_string::_M_replace_aux",
|
||||
"input stream error",
|
||||
)
|
||||
):
|
||||
self._log.error(f"{self.coin_name()} wallet is corrupt.")
|
||||
|
||||
@@ -1631,7 +1631,10 @@ def js_wallettransactions(self, url_split, post_string, is_json) -> bytes:
|
||||
or (current_time - cache_entry["time"]) > TX_CACHE_DURATION
|
||||
):
|
||||
all_txs = ci.listWalletTransactions(count=10000, skip=0)
|
||||
all_txs = list(reversed(all_txs)) if all_txs else []
|
||||
if all_txs and coin_id not in (Coins.XMR, Coins.WOW):
|
||||
all_txs = list(reversed(all_txs))
|
||||
elif not all_txs:
|
||||
all_txs = []
|
||||
swap_client._tx_cache[coin_id] = {"txs": all_txs, "time": current_time}
|
||||
else:
|
||||
all_txs = cache_entry["txs"]
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
(function() {
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(url, options) {
|
||||
return originalFetch.apply(this, arguments).then(function(response) {
|
||||
if (response.status === 401) {
|
||||
const urlStr = typeof url === 'string' ? url : (url && url.url) || '';
|
||||
if (urlStr.startsWith('/json/') || urlStr.startsWith('/json')) {
|
||||
window.location.href = '/login';
|
||||
return new Response(JSON.stringify({error: 'Session expired'}), {
|
||||
status: 401,
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
});
|
||||
}
|
||||
}
|
||||
return response;
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const burger = document.querySelectorAll('.navbar-burger');
|
||||
const menu = document.querySelectorAll('.navbar-menu');
|
||||
|
||||
@@ -148,7 +148,7 @@ const BidPage = {
|
||||
11: { phase: 'locking', order: 10, label: 'Locking' }, // XMR_SWAP_NOSCRIPT_COIN_LOCKED
|
||||
12: { phase: 'redemption', order: 11, label: 'Redemption' }, // XMR_SWAP_LOCK_RELEASED
|
||||
13: { phase: 'redemption', order: 12, label: 'Redemption' }, // XMR_SWAP_SCRIPT_TX_REDEEMED
|
||||
14: { phase: 'failed', order: 90, label: 'Failed' }, // XMR_SWAP_SCRIPT_TX_PREREFUND
|
||||
14: { phase: 'redemption', order: 11.5, label: 'Refunding' }, // XMR_SWAP_SCRIPT_TX_PREREFUND
|
||||
15: { phase: 'redemption', order: 13, label: 'Redemption' }, // XMR_SWAP_NOSCRIPT_TX_REDEEMED
|
||||
16: { phase: 'failed', order: 91, label: 'Recovered' }, // XMR_SWAP_NOSCRIPT_TX_RECOVERED
|
||||
17: { phase: 'failed', order: 92, label: 'Failed' }, // XMR_SWAP_FAILED_REFUNDED
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="flex items-center">
|
||||
<p class="text-sm text-gray-90 dark:text-white font-medium">© 2026~ (BSX) BasicSwap</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||
<p class="text-sm text-coolGray-400 font-medium">BSX: v{{ version }}</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||
<p class="text-sm text-coolGray-400 font-medium">GUI: v3.4.1</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||
<p class="text-sm text-coolGray-400 font-medium">GUI: v3.5.0</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||
<p class="mr-2 text-sm font-bold dark:text-white text-gray-90 ">Made with </p>
|
||||
{{ love_svg | safe }}
|
||||
</div>
|
||||
|
||||
@@ -337,6 +337,16 @@
|
||||
<td class="py-3 px-6">{{ w.expected_seed }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if w.account_key %}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||
<td class="py-3 px-6 bold">Extended Private Key:</td>
|
||||
<td class="py-3 px-6">
|
||||
<span id="account-key-hidden" class="font-mono text-sm">••••••••••••••••</span>
|
||||
<span id="account-key-value" class="font-mono text-sm hidden break-all">{{ w.account_key }}</span>
|
||||
<button type="button" id="toggle-account-key" onclick="var h=document.getElementById('account-key-hidden'),v=document.getElementById('account-key-value');if(v.classList.contains('hidden')){v.classList.remove('hidden');h.classList.add('hidden');this.textContent='Hide';}else{v.classList.add('hidden');h.classList.remove('hidden');this.textContent='Show';}" class="ml-2 px-2 py-1 text-xs bg-blue-500 hover:bg-blue-600 text-white rounded">Show</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -463,7 +473,7 @@
|
||||
</div>
|
||||
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">MWEB Address: </div>
|
||||
<div class="text-center relative">
|
||||
<div class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="mweb_address">{{ w.mweb_address }}</div>
|
||||
<div class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="stealth_address">{{ w.mweb_address }}</div>
|
||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 cursor-pointer" id="copyIcon"></span>
|
||||
</div>
|
||||
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
|
||||
@@ -974,7 +984,7 @@
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4" id="confirmTitle">Confirm Action</h2>
|
||||
<p class="text-gray-600 dark:text-gray-200 mb-6 whitespace-pre-line" id="confirmMessage">Are you sure?</p>
|
||||
<div class="flex justify-center gap-4">
|
||||
<button type="button" id="confirmYes"
|
||||
<button type="button" id="confirmYes"
|
||||
class="px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
|
||||
Confirm
|
||||
</button>
|
||||
|
||||
@@ -473,6 +473,15 @@ def page_wallet(self, url_split, post_string):
|
||||
getattr(ci, "_connection_type", "rpc") == "electrum"
|
||||
)
|
||||
|
||||
if hasattr(ci, "getAccountKey") and k not in (Coins.XMR, Coins.WOW):
|
||||
try:
|
||||
chain = swap_client.chain
|
||||
zprv_prefix = 0x04B2430C if chain == "mainnet" else 0x045F18BC
|
||||
seed_key = swap_client.getWalletKey(k, 1)
|
||||
wallet_data["account_key"] = ci.getAccountKey(seed_key, zprv_prefix)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
fee_rate, fee_src = swap_client.getFeeRateForCoin(k)
|
||||
est_fee = swap_client.estimateWithdrawFee(k, fee_rate)
|
||||
wallet_data["fee_rate"] = ci.format_amount(int(fee_rate * ci.COIN()))
|
||||
@@ -559,7 +568,10 @@ def page_wallet(self, url_split, post_string):
|
||||
skip = tx_filters.get("offset", 0)
|
||||
|
||||
all_txs = ci.listWalletTransactions(count=10000, skip=0)
|
||||
all_txs = list(reversed(all_txs)) if all_txs else []
|
||||
if all_txs and coin_id not in (Coins.XMR, Coins.WOW):
|
||||
all_txs = list(reversed(all_txs))
|
||||
elif not all_txs:
|
||||
all_txs = []
|
||||
total_transactions = len(all_txs)
|
||||
|
||||
raw_txs = all_txs[skip : skip + count] if all_txs else []
|
||||
|
||||
@@ -810,7 +810,9 @@ class ElectrumBackend(WalletBackend):
|
||||
|
||||
now = time.time()
|
||||
stale_threshold = 300
|
||||
is_synced = height > 0 and (now - height_time) < stale_threshold
|
||||
last_activity = getattr(self._server, "_last_activity", 0)
|
||||
most_recent = max(height_time, last_activity)
|
||||
is_synced = height > 0 and (now - most_recent) < stale_threshold
|
||||
return {
|
||||
"height": height,
|
||||
"synced": is_synced,
|
||||
|
||||
@@ -38,6 +38,7 @@ class WalletManager:
|
||||
}
|
||||
|
||||
GAP_LIMIT = 50
|
||||
ELECTRUM_GAP_LIMIT = 20
|
||||
|
||||
def __init__(self, swap_client, log):
|
||||
self._gap_limits: Dict[Coins, int] = {}
|
||||
@@ -149,6 +150,18 @@ class WalletManager:
|
||||
)
|
||||
self._swap_client.commitDB()
|
||||
|
||||
def _findReusableAddress(self, coin_type: Coins, internal: bool, cursor):
|
||||
query = (
|
||||
"SELECT derivation_index, address FROM wallet_addresses"
|
||||
" WHERE coin_type = ? AND is_internal = ? AND is_funded = 0"
|
||||
" ORDER BY derivation_index ASC LIMIT 1"
|
||||
)
|
||||
cursor.execute(query, (int(coin_type), internal))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return row[0], row[1]
|
||||
return None, None
|
||||
|
||||
def getNewAddress(
|
||||
self, coin_type: Coins, internal: bool = False, label: str = "", cursor=None
|
||||
) -> str:
|
||||
@@ -157,8 +170,6 @@ class WalletManager:
|
||||
|
||||
use_cursor = self._swap_client.openDB(cursor)
|
||||
try:
|
||||
self._syncStateIndices(coin_type, use_cursor)
|
||||
|
||||
state = self._swap_client.queryOne(
|
||||
WalletState, use_cursor, {"coin_type": int(coin_type)}
|
||||
)
|
||||
@@ -184,6 +195,19 @@ class WalletManager:
|
||||
else:
|
||||
next_index = (state.last_external_index or 0) + 1
|
||||
|
||||
if next_index >= self.ELECTRUM_GAP_LIMIT:
|
||||
reuse_index, reuse_addr = self._findReusableAddress(
|
||||
coin_type, internal, use_cursor
|
||||
)
|
||||
if reuse_addr is not None:
|
||||
self._log.debug(
|
||||
f"Reusing unfunded address at index {reuse_index}"
|
||||
f" (next would be {next_index},"
|
||||
f" electrum gap limit {self.ELECTRUM_GAP_LIMIT})"
|
||||
)
|
||||
self._swap_client.commitDB()
|
||||
return reuse_addr
|
||||
|
||||
existing = self._swap_client.queryOne(
|
||||
WalletAddress,
|
||||
use_cursor,
|
||||
|
||||
@@ -135,15 +135,15 @@
|
||||
(define-public basicswap
|
||||
(package
|
||||
(name "basicswap")
|
||||
(version "0.15.2")
|
||||
(version "0.16.0")
|
||||
(source (origin
|
||||
(method git-fetch)
|
||||
(uri (git-reference
|
||||
(url "https://github.com/basicswap/basicswap")
|
||||
(commit "83807d213fab52c99f69dbc06fa7baedb449d66f")))
|
||||
(commit "2c13314bdd29622235c92fd20c237801acb3cb76")))
|
||||
(sha256
|
||||
(base32
|
||||
"08ykwn2wbcny5k6kwj3xkfkim40kmzcb988lpcd70r7kcmn8ggp0"))
|
||||
"0j0id6db3ljdsfag8krjdmd4rzlz2504yk9lzj0p89lqyygi9ilc"))
|
||||
(file-name (git-file-name name version))))
|
||||
(build-system pyproject-build-system)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user