Compare commits

...

45 Commits

Author SHA1 Message Date
tecnovert 19f13d9d96 Merge pull request #481 from tecnovert/refactor
Refactor
2026-05-29 12:54:54 +00:00
tecnovert 27f9f8c13a doc: update release notes 2026-05-29 14:23:32 +02:00
tecnovert 248b8046b1 test: wait longer, add startup_delay option 2026-05-29 14:02:07 +02:00
tecnovert 6b4b97376b test: print xmr daemon logs on ci failure 2026-05-29 11:26:11 +02:00
tecnovert e8ebfd34d0 refactor: black 2026-05-28 01:55:36 +02:00
tecnovert 24c8e8b2dd refactor: remove duplicate method 2026-05-27 23:51:51 +02:00
tecnovert 7bf3dce974 Merge pull request #477 from kewde/patch-1
fix: public key validation
2026-05-27 11:12:28 +00:00
tecnovert b6e922e3a8 Merge pull request #478 from kewde/patch-2
fix: use main address for XMR & WOW
2026-05-27 11:11:26 +00:00
kewde 59be986aa4 fix: use main address for XMR & WOW 2026-05-26 14:12:19 +02:00
kewde 25dd3809e9 fix: public key validation
https://github.com/basicswap/coincurve/blob/2bf23f173f411a60c66ba973231fadab772bfed2/src/coincurve/dleag.py#L63
2026-05-26 14:04:43 +02:00
tecnovert be1dbaeeaa build, guix: update packed version 2026-05-08 20:45:39 +02:00
tecnovert 3b76adeedb build: raise version to 0.16.2 2026-05-08 20:30:19 +02:00
tecnovert ae6691e7ab Merge pull request #467 from tecnovert/refactor
refactor: simplify getAddressInfo
2026-05-08 18:26:13 +00:00
tecnovert 8482533b37 Merge pull request #471 from gerlofvanek/fix_balances
Fix: Wallet balance overwrite on WebSocket updates.
2026-05-08 18:18:17 +00:00
gerlofvanek 8fe0913fda BLACK 2026-05-08 19:59:42 +02:00
gerlofvanek 9244a9fed8 Fix: Wallet balance overwrite on WebSocket updates. 2026-05-08 19:54:22 +02:00
tecnovert bfc58955da Merge pull request #469 from gerlofvanek/fix_tests
Fixes: PIVX/FIRO and test_pivx.py
2026-05-08 17:45:41 +00:00
tecnovert f77b7dc363 Merge pull request #470 from tecnovert/ci
test: show log on failure
2026-05-08 17:45:23 +00:00
tecnovert a6b5906a6d test: add balance check to test_swap_direction.py 2026-05-08 19:22:41 +02:00
tecnovert 568eab1f31 test: show log on failure 2026-05-08 18:31:27 +02:00
gerlofvanek 1b86df9b60 Fix: PIVX/Firo bug and test_pivx.py 2026-05-08 17:10:27 +02:00
tecnovert 680fc7ce35 build: raise version to 0.16.1 2026-05-06 20:27:29 +02:00
tecnovert 1f9c85c62f Merge pull request #466 from tecnovert/mweb_change_helper
LTC MWEB change back to LTC helper functions
2026-05-06 18:24:11 +00:00
tecnovert 3716a0ab62 Merge pull request #463 from tecnovert/extracoinopts
New extracoinopts run parameter
2026-05-06 18:20:06 +00:00
tecnovert 2ad8e6f4b3 Merge pull request #465 from tecnovert/ltc_fixes
LTC fixes
2026-05-06 18:19:51 +00:00
tecnovert ac084eddf7 Merge pull request #461 from tecnovert/ltc
fix: workaround for osx ltc release not on github
2026-05-06 18:13:07 +00:00
tecnovert 262593bd2c cores: bump ltc to v0.21.5.5 2026-05-06 20:11:21 +02:00
tecnovert 9f17ee709a Merge pull request #468 from tecnovert/actions
test: raise github actions plugin versions
2026-05-05 15:56:25 +00:00
tecnovert e29eb4af76 test: raise github actions plugin versions 2026-05-05 11:05:26 +02:00
tecnovert 6ebbd98aec feat: add helper functions to convert MWEB change in LTC wallet 2026-05-04 21:11:21 +02:00
tecnovert c8e7c02fe2 refactor: reduce wallet_manager imports 2026-05-04 19:39:03 +02:00
tecnovert 57a1a6505e refactor: simplify getAddressInfo 2026-05-04 19:24:25 +02:00
tecnovert bdb7f9bb5a feat: add fallback urls to downloadRelease 2026-05-04 14:56:35 +02:00
tecnovert f626e400ff fix: better log format in prepare script 2026-05-04 14:54:23 +02:00
tecnovert 2bacbcabd0 Merge pull request #462 from nahuhh/dependabot_actions
dependabot: use for github actions
2026-05-04 12:06:17 +00:00
tecnovert 2b33ed3d93 Merge pull request #464 from tecnovert/ci
test: remove cirrus ci
2026-05-04 11:59:37 +00:00
tecnovert c4e7de2873 fix: ltc, deduplicate MWEB wallet creation 2026-05-03 18:56:57 +02:00
tecnovert 9caae399d2 fix: convert coin variant tickers 2026-05-02 22:59:35 +02:00
tecnovert fd2e442839 fix: ltc, filter out mweb addresses in getUnspentsByAddr 2026-05-02 22:55:32 +02:00
tecnovert dfa11ed32f fix: ltc, deduplicate checkWallets 2026-05-02 21:47:50 +02:00
tecnovert c4f00dfa5b test: remove cirrus ci 2026-05-02 10:46:43 +02:00
tecnovert b5226c0e1c feat: add "extracoinopts" option 2026-05-02 10:24:51 +02:00
tecnovert 842e44e41b doc: log if db upgrade was forced 2026-05-02 10:24:47 +02:00
nahuhh c298cf3963 dependabot: use for github actions 2026-04-29 12:49:43 +00:00
tecnovert e06c4638d3 fix: workaround for osx ltc release not on github 2026-04-29 12:24:35 +02:00
55 changed files with 658 additions and 483 deletions
-45
View File
@@ -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'
+8
View File
@@ -9,3 +9,11 @@ updates:
interval: "weekly" interval: "weekly"
open-pull-requests-limit: 20 open-pull-requests-limit: 20
target-branch: "dev" 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"
+19 -3
View File
@@ -30,9 +30,9 @@ jobs:
matrix: matrix:
python-version: ["3.12"] python-version: ["3.12"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3 uses: actions/setup-python@v6
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies
@@ -69,7 +69,7 @@ jobs:
pytest tests/basicswap/test_other.py pytest tests/basicswap/test_other.py
- name: Cache coin cores - name: Cache coin cores
id: cache-cores id: cache-cores
uses: actions/cache@v3 uses: actions/cache@v5
env: env:
cache-name: cache-cores cache-name: cache-cores
with: with:
@@ -94,13 +94,24 @@ jobs:
export XMR_BINDIR="$BIN_DIR/monero" export XMR_BINDIR="$BIN_DIR/monero"
pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx" pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx"
- name: Run test_encrypted_xmr_reload - name: Run test_encrypted_xmr_reload
id: test_encrypted_xmr_reload
run: | run: |
export PYTHONPATH=$(pwd) export PYTHONPATH=$(pwd)
export TEST_PATH=${TEST_RELOAD_PATH} export TEST_PATH=${TEST_RELOAD_PATH}
mkdir -p ${TEST_PATH}/bin mkdir -p ${TEST_PATH}/bin
cp -r $BIN_DIR/* ${TEST_PATH}/bin/ cp -r $BIN_DIR/* ${TEST_PATH}/bin/
pytest tests/basicswap/extended/test_encrypted_xmr_reload.py pytest tests/basicswap/extended/test_encrypted_xmr_reload.py
- name: Print log files on failure
if: ${{ failure() && steps.test_encrypted_xmr_reload.conclusion == 'failure' }}
run: |
for i in 0 1 2; do
for logname in core_stderr core_stdout wallet_stderr wallet_stdout; do
echo "=== client${i} ${logname}.log ==="
cat /tmp/test_basicswap/client${i}/monero/${logname}.log || true
done
done
- name: Run selenium tests - name: Run selenium tests
id: selenium_tests
run: | run: |
export TEST_PATH=/tmp/test_persistent export TEST_PATH=/tmp/test_persistent
mkdir -p ${TEST_PATH}/bin mkdir -p ${TEST_PATH}/bin
@@ -126,3 +137,8 @@ jobs:
echo "Running test_swap_direction.py" echo "Running test_swap_direction.py"
python tests/basicswap/selenium/test_swap_direction.py python tests/basicswap/selenium/test_swap_direction.py
kill $TEST_NETWORK_PID kill $TEST_NETWORK_PID
- name: Print log file on failure
if: ${{ failure() && steps.selenium_tests.conclusion == 'failure' }}
run: |
echo "=== SELENIUM BACKGROUND LOG ==="
cat /tmp/log.txt
+1 -1
View File
@@ -1,3 +1,3 @@
name = "basicswap" name = "basicswap"
__version__ = "0.16.0" __version__ = "0.16.2"
+7 -2
View File
@@ -365,8 +365,10 @@ class BaseApp(DBMethods):
self.log.warning(f"Setting mocktime to {new_offset}") self.log.warning(f"Setting mocktime to {new_offset}")
self.mock_time_offset = new_offset self.mock_time_offset = new_offset
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int: def get_clamped_int_from(
value: int = self.settings.get(name, default_v) self, settings: dict, name: str, default_v: int, min_v: int, max_v
) -> int:
value: int = settings.get(name, default_v)
if value < min_v: if value < min_v:
self.log.warning(f"Setting {name} to {min_v}") self.log.warning(f"Setting {name} to {min_v}")
value = min_v value = min_v
@@ -375,6 +377,9 @@ class BaseApp(DBMethods):
value = max_v value = max_v
return value return value
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
return self.get_clamped_int_from(self.settings, name, default_v, min_v, max_v)
def get_delay_event_seconds(self): def get_delay_event_seconds(self):
if self.min_delay_event == self.max_delay_event: if self.min_delay_event == self.max_delay_event:
return self.min_delay_event return self.min_delay_event
+24 -22
View File
@@ -166,7 +166,6 @@ import basicswap.network.network as bsn
import basicswap.protocols.atomic_swap_1 as atomic_swap_1 import basicswap.protocols.atomic_swap_1 as atomic_swap_1
import basicswap.protocols.xmr_swap_1 as xmr_swap_1 import basicswap.protocols.xmr_swap_1 as xmr_swap_1
PROTOCOL_VERSION_SECRET_HASH = 5 PROTOCOL_VERSION_SECRET_HASH = 5
MINPROTO_VERSION_SECRET_HASH = 4 MINPROTO_VERSION_SECRET_HASH = 4
@@ -441,9 +440,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.check_delayed_auto_accept_seconds = self.get_int_setting( self.check_delayed_auto_accept_seconds = self.get_int_setting(
"check_delayed_auto_accept_seconds", 60, 1, 20 * 60 "check_delayed_auto_accept_seconds", 60, 1, 20 * 60
) )
self.startup_tries = self.get_int_setting(
"startup_tries", 15, 1, 100
) # Seconds waited for will be (x(1 + x+1) / 2
self.debug_ui = self.settings.get("debug_ui", False) self.debug_ui = self.settings.get("debug_ui", False)
self._debug_cases = [] self._debug_cases = []
self._last_checked_actions = 0 self._last_checked_actions = 0
@@ -1618,13 +1614,22 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
# systemd will try to restart the process if fail_code != 0 # systemd will try to restart the process if fail_code != 0
self.stopRunning(1) self.stopRunning(1)
startup_tries = self.startup_tries
chain_client_settings = self.getChainClientSettings(coin_type) chain_client_settings = self.getChainClientSettings(coin_type)
if "startup_tries" in chain_client_settings: # Total seconds waited for will be ((startup_tries(1 + startup_tries) / 2) * startup_delay
startup_tries = chain_client_settings["startup_tries"] startup_tries: int = self.get_clamped_int_from(
if startup_tries < 1: chain_client_settings,
self.log.warning('"startup_tries" can\'t be less than 1.') "startup_tries",
startup_tries = 1 self.get_int_setting("startup_tries", 15, 1, 100),
1,
100,
)
startup_delay: int = self.get_clamped_int_from(
chain_client_settings,
"startup_delay",
self.get_int_setting("startup_delay", 5, 1, 100),
1,
100,
)
for i in range(startup_tries): for i in range(startup_tries):
if self.delay_event.is_set(): if self.delay_event.is_set():
return return
@@ -1632,6 +1637,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.coin_clients[coin_type]["interface"].testDaemonRPC(with_wallet) self.coin_clients[coin_type]["interface"].testDaemonRPC(with_wallet)
return return
except Exception as ex: except Exception as ex:
wait_for: int = startup_delay * (1 + i)
if any( if any(
log in str(ex) log in str(ex)
for log in [ for log in [
@@ -1644,13 +1650,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
] ]
): ):
self.log.info( self.log.info(
f"Waiting for {Coins(coin_type).name} RPC. Trying again in {5 * (1 + i)} seconds, {1 + i}/{startup_tries}." f"Waiting for {Coins(coin_type).name} RPC. Trying again in {wait_for} seconds, {1 + i}/{startup_tries}."
) )
else: else:
self.log.warning( self.log.warning(
f"Can't connect to {Coins(coin_type).name} RPC: {ex}. Trying again in {5 * (1 + i)} seconds, {1 + i}/{startup_tries}." f"Can't connect to {Coins(coin_type).name} RPC: {ex}. Trying again in {wait_for} seconds, {1 + i}/{startup_tries}."
) )
self.delay_event.wait(5 * (1 + i)) self.delay_event.wait(wait_for)
self.log.error(f"Can't connect to {Coins(coin_type).name} RPC, exiting.") self.log.error(f"Can't connect to {Coins(coin_type).name} RPC, exiting.")
self.stopRunning(1) # systemd will try to restart the process if fail_code != 0 self.stopRunning(1) # systemd will try to restart the process if fail_code != 0
@@ -3965,7 +3971,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
def isValidSwapDest(self, ci, dest: bytes): def isValidSwapDest(self, ci, dest: bytes):
ensure(isinstance(dest, bytes), "Swap destination must be bytes") ensure(isinstance(dest, bytes), "Swap destination must be bytes")
if ci.coin_type() in (Coins.PART_BLIND,): if ci.coin_type() in (Coins.PART_BLIND,):
return ci.isValidPubkey(dest) return ci.verifyPubkey(dest)
# TODO: allow p2wsh # TODO: allow p2wsh
return ci.isValidAddressHash(dest) return ci.isValidAddressHash(dest)
@@ -10905,7 +10911,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
ensure( ensure(
ci_from.isValidAddressHash(bid_data.dest_af) ci_from.isValidAddressHash(bid_data.dest_af)
or ci_from.isValidPubkey(bid_data.dest_af), or ci_from.verifyPubkey(bid_data.dest_af),
"Invalid destination address", "Invalid destination address",
) )
@@ -11896,7 +11902,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
) )
vkbs = ci_to.sumKeys(kbsl, kbsf) vkbs = ci_to.sumKeys(kbsl, kbsf)
if coin_to == (Coins.XMR, Coins.WOW): if coin_to in (Coins.XMR, Coins.WOW):
address_to = self.getCachedMainWalletAddress(ci_to, cursor) address_to = self.getCachedMainWalletAddress(ci_to, cursor)
elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON): elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON):
address_to = self.getCachedStealthAddressForCoin(coin_to, cursor) address_to = self.getCachedStealthAddressForCoin(coin_to, cursor)
@@ -13811,8 +13817,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
num_watched_outputs += len(v["watched_outputs"]) num_watched_outputs += len(v["watched_outputs"])
now: int = self.getTime() now: int = self.getTime()
q_bids_str: str = ( q_bids_str: str = """SELECT
"""SELECT
COUNT(CASE WHEN b.was_sent THEN 1 ELSE NULL END) AS count_sent, COUNT(CASE WHEN b.was_sent THEN 1 ELSE NULL END) AS count_sent,
COUNT(CASE WHEN b.was_sent AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_sent_active, COUNT(CASE WHEN b.was_sent AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_sent_active,
COUNT(CASE WHEN b.was_received THEN 1 ELSE NULL END) AS count_received, COUNT(CASE WHEN b.was_received THEN 1 ELSE NULL END) AS count_received,
@@ -13822,15 +13827,12 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
JOIN offers o ON b.offer_id = o.offer_id JOIN offers o ON b.offer_id = o.offer_id
JOIN bidstates s ON b.state = s.state_id JOIN bidstates s ON b.state = s.state_id
WHERE b.active_ind = 1""" WHERE b.active_ind = 1"""
)
q_offers_str: str = ( q_offers_str: str = """SELECT
"""SELECT
COUNT(CASE WHEN expire_at > :now THEN 1 ELSE NULL END) AS count_active, COUNT(CASE WHEN expire_at > :now THEN 1 ELSE NULL END) AS count_active,
COUNT(CASE WHEN was_sent THEN 1 ELSE NULL END) AS count_sent, COUNT(CASE WHEN was_sent THEN 1 ELSE NULL END) AS count_sent,
COUNT(CASE WHEN was_sent AND expire_at > :now THEN 1 ELSE NULL END) AS count_sent_active COUNT(CASE WHEN was_sent AND expire_at > :now THEN 1 ELSE NULL END) AS count_sent_active
FROM offers WHERE active_ind = 1""" FROM offers WHERE active_ind = 1"""
)
try: try:
cursor = self.openDB() cursor = self.openDB()
+32 -14
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2019-2024 tecnovert # Copyright (c) 2019-2024 tecnovert
# Copyright (c) 2024-2025 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -29,6 +29,7 @@ import urllib.parse
import zipfile import zipfile
import zmq import zmq
from typing import List
from urllib.request import urlopen from urllib.request import urlopen
import basicswap.config as cfg import basicswap.config as cfg
@@ -58,7 +59,7 @@ PARTICL_LINUX_EXTRA = os.getenv("PARTICL_LINUX_EXTRA", "nousb")
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "29.3") BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "29.3")
BITCOIN_VERSION_TAG = os.getenv("BITCOIN_VERSION_TAG", "") BITCOIN_VERSION_TAG = os.getenv("BITCOIN_VERSION_TAG", "")
LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.5.4") LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.5.5")
LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "") LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "")
DCR_VERSION = os.getenv("DCR_VERSION", "2.1.3") DCR_VERSION = os.getenv("DCR_VERSION", "2.1.3")
@@ -185,11 +186,13 @@ else:
BIN_ARCH = os.getenv("BIN_ARCH", BIN_ARCH) BIN_ARCH = os.getenv("BIN_ARCH", BIN_ARCH)
FILE_EXT = os.getenv("FILE_EXT", FILE_EXT) FILE_EXT = os.getenv("FILE_EXT", FILE_EXT)
logger = logging.getLogger() logger = logging.getLogger("prepare")
LOG_LEVEL = logging.DEBUG LOG_LEVEL = logging.DEBUG
logger.propagate = False
logger.level = LOG_LEVEL logger.level = LOG_LEVEL
if not len(logger.handlers): handler = logging.StreamHandler(sys.stdout)
logger.addHandler(logging.StreamHandler(sys.stdout)) handler.setFormatter(logging.Formatter("%(levelname)s : %(message)s"))
logger.addHandler(handler)
logging.getLogger("gnupg").setLevel(logging.INFO) logging.getLogger("gnupg").setLevel(logging.INFO)
BSX_DOCKER_MODE = toBool(os.getenv("BSX_DOCKER_MODE", False)) BSX_DOCKER_MODE = toBool(os.getenv("BSX_DOCKER_MODE", False))
@@ -458,12 +461,23 @@ def getRemoteFileLength(url: str) -> (int, bool):
popConnectionParameters() popConnectionParameters()
def downloadRelease(url: str, path: str, extra_opts, timeout: int = 10) -> None: def downloadRelease(
"""If file exists at path compare it's size to the content length at the url url_in: str | List[str], path: str, extra_opts, timeout: int = 10
and attempt to resume download if file size is below expected. ) -> None:
""" # If file exists at path compare it's size to the content length at the url
resume_from: int = 0 # and attempt to resume download if file size is below expected.
release_filename: str = os.path.basename(path)
urls = (
url_in
if isinstance(url_in, list)
else [
url_in,
]
)
for url in urls:
try:
resume_from: int = 0
if os.path.exists(path): if os.path.exists(path):
if extra_opts.get("redownload_releases", False): if extra_opts.get("redownload_releases", False):
logging.warning(f"Overwriting: {path}") logging.warning(f"Overwriting: {path}")
@@ -483,8 +497,11 @@ def downloadRelease(url: str, path: str, extra_opts, timeout: int = 10) -> None:
else: else:
# File exists and size check is disabled # File exists and size check is disabled
return return
return downloadFile(url, path, timeout, resume_from) return downloadFile(url, path, timeout, resume_from)
except Exception as e:
logger.warning(f"Failed to download {release_filename} from {url}")
logger.debug(f"Download error {e}")
raise RuntimeError(f"Failed to download {release_filename}.")
def downloadFile(url: str, path: str, timeout: int = 5, resume_from: int = 0) -> None: def downloadFile(url: str, path: str, timeout: int = 5, resume_from: int = 0) -> None:
@@ -925,9 +942,10 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
assert_filename, assert_filename,
) )
elif coin == "litecoin": elif coin == "litecoin":
release_url = "https://github.com/litecoin-project/litecoin/releases/download/v{}/{}".format( release_url = [
version + version_tag, release_filename f"https://github.com/litecoin-project/litecoin/releases/download/v{version}{version_tag}/{release_filename}",
) f"https://download.litecoin.org/litecoin-{version}{version_tag}/{os_name}/{release_filename}",
]
assert_filename = "{}-core-{}-{}-build.assert".format( assert_filename = "{}-core-{}-{}-build.assert".format(
coin, os_name, ".".join(version.split(".")[:2]) coin, os_name, ".".join(version.split(".")[:2])
) )
+42 -26
View File
@@ -2,10 +2,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2019-2024 tecnovert # Copyright (c) 2019-2024 tecnovert
# Copyright (c) 2024-2025 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import copy
import json import json
import logging import logging
import os import os
@@ -22,6 +23,7 @@ from basicswap.chainparams import chainparams, Coins, isKnownCoinName
from basicswap.network.simplex_chat import startSimplexClient from basicswap.network.simplex_chat import startSimplexClient
from basicswap.ui.util import getCoinName from basicswap.ui.util import getCoinName
from basicswap.util.daemon import Daemon from basicswap.util.daemon import Daemon
from typing import Set
initial_logger = logging.getLogger() initial_logger = logging.getLogger()
initial_logger.level = logging.DEBUG initial_logger.level = logging.DEBUG
@@ -347,7 +349,7 @@ def mainLoop(daemons, update: bool = True):
def runClient( def runClient(
data_dir: str, data_dir: str,
chain: str, chain: str,
start_only_coins: bool, start_only_coins: Set[str],
log_prefix: str = "BasicSwap", log_prefix: str = "BasicSwap",
extra_opts=dict(), extra_opts=dict(),
) -> int: ) -> int:
@@ -391,17 +393,24 @@ def runClient(
# Settings may have been modified # Settings may have been modified
settings = swap_client.settings settings = swap_client.settings
base_coin_opts = []
if "extra_coin_opts" in extra_opts:
if len(start_only_coins) == 0:
raise ValueError('"extracoinopts" can only be used with "startonlycoins"')
base_coin_opts += extra_opts["extra_coin_opts"]
try: try:
# Try start daemons # Try start daemons
if len(start_only_coins) > 0:
swap_client.log.warning('Not starting networks as "startonlycoin" is set')
else:
for network in settings.get("networks", []): for network in settings.get("networks", []):
if network.get("enabled", True) is False: if network.get("enabled", True) is False:
continue continue
network_type: str = network.get("type", "unknown") network_type: str = network.get("type", "unknown")
if network_type == "simplex": if network_type == "simplex":
simplex_dir = os.path.join(data_dir, "simplex") simplex_dir = os.path.join(data_dir, "simplex")
log_level = "debug" if swap_client.debug else "info" log_level = "debug" if swap_client.debug else "info"
socks_proxy = None socks_proxy = None
if "socks_proxy_override" in network: if "socks_proxy_override" in network:
socks_proxy = network["socks_proxy_override"] socks_proxy = network["socks_proxy_override"]
@@ -460,10 +469,18 @@ def runClient(
trusted_daemon: bool = swap_client.getXMRTrustedDaemon( trusted_daemon: bool = swap_client.getXMRTrustedDaemon(
coin_id, v["rpchost"] coin_id, v["rpchost"]
) )
opts = [ wallet_opts = [
"--trusted-daemon" if trusted_daemon else "--untrusted-daemon",
"--daemon-address", "--daemon-address",
daemon_addr, daemon_addr,
] ]
daemon_rpcuser = v.get("rpcuser", "")
daemon_rpcpass = v.get("rpcpassword", "")
if daemon_rpcuser != "":
wallet_opts += [
"--daemon-login",
daemon_rpcuser + ":" + daemon_rpcpass,
]
proxy_log_str = "" proxy_log_str = ""
proxy_host, proxy_port = swap_client.getXMRWalletProxy( proxy_host, proxy_port = swap_client.getXMRWalletProxy(
@@ -471,7 +488,7 @@ def runClient(
) )
if proxy_host: if proxy_host:
proxy_log_str = " through proxy" proxy_log_str = " through proxy"
opts += [ wallet_opts += [
"--proxy", "--proxy",
f"{proxy_host}:{proxy_port}", f"{proxy_host}:{proxy_port}",
"--daemon-ssl-allow-any-cert", "--daemon-ssl-allow-any-cert",
@@ -485,19 +502,11 @@ def runClient(
) )
) )
daemon_rpcuser = v.get("rpcuser", "")
daemon_rpcpass = v.get("rpcpassword", "")
if daemon_rpcuser != "":
opts.append("--daemon-login")
opts.append(daemon_rpcuser + ":" + daemon_rpcpass)
opts.append(
"--trusted-daemon" if trusted_daemon else "--untrusted-daemon"
)
filename: str = getWalletBinName(coin_id, v, c + "-wallet-rpc") filename: str = getWalletBinName(coin_id, v, c + "-wallet-rpc")
daemons.append( daemons.append(
startXmrWalletDaemon(v["datadir"], v["bindir"], filename, opts) startXmrWalletDaemon(
v["datadir"], v["bindir"], filename, wallet_opts
)
) )
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
swap_client.log.info(f"Started {filename} {pid}") swap_client.log.info(f"Started {filename} {pid}")
@@ -506,9 +515,8 @@ def runClient(
if c == "decred": if c == "decred":
appdata = v["datadir"] appdata = v["datadir"]
extra_opts = [ coin_opts = copy.deepcopy(base_coin_opts)
f'--appdata="{appdata}"', coin_opts.append(f'--appdata="{appdata}"')
]
use_shell: bool = True if os.name == "nt" else False use_shell: bool = True if os.name == "nt" else False
if v["manage_daemon"] is True: if v["manage_daemon"] is True:
swap_client.log.info(f"Starting {display_name} daemon") swap_client.log.info(f"Starting {display_name} daemon")
@@ -526,7 +534,7 @@ def runClient(
appdata, appdata,
v["bindir"], v["bindir"],
filename, filename,
opts=extra_opts, opts=coin_opts,
extra_config=extra_config, extra_config=extra_config,
) )
) )
@@ -537,12 +545,13 @@ def runClient(
swap_client.log.info(f"Starting {display_name} wallet daemon") swap_client.log.info(f"Starting {display_name} wallet daemon")
filename: str = getWalletBinName(coin_id, v, "dcrwallet") filename: str = getWalletBinName(coin_id, v, "dcrwallet")
wallet_opts = [f'--appdata="{appdata}"']
wallet_pwd = v["wallet_pwd"] wallet_pwd = v["wallet_pwd"]
if wallet_pwd == "": if wallet_pwd == "":
# Only set when in startonlycoin mode # Only set when in startonlycoin mode
wallet_pwd = os.getenv("WALLET_ENCRYPTION_PWD", "") wallet_pwd = os.getenv("WALLET_ENCRYPTION_PWD", "")
if wallet_pwd != "": if wallet_pwd != "":
extra_opts.append(f'--pass="{wallet_pwd}"') wallet_opts.append(f'--pass="{wallet_pwd}"')
extra_config = { extra_config = {
"add_datadir": False, "add_datadir": False,
"stdout_to_file": True, "stdout_to_file": True,
@@ -555,13 +564,12 @@ def runClient(
appdata, appdata,
v["bindir"], v["bindir"],
filename, filename,
opts=extra_opts, opts=wallet_opts,
extra_config=extra_config, extra_config=extra_config,
) )
) )
pid = daemons[-1].handle.pid pid = daemons[-1].handle.pid
swap_client.log.info(f"Started {filename} {pid}") swap_client.log.info(f"Started {filename} {pid}")
continue # /decred continue # /decred
if v["manage_daemon"] is True: if v["manage_daemon"] is True:
@@ -571,7 +579,7 @@ def runClient(
swap_client.log.info(f"Starting {display_name} daemon") swap_client.log.info(f"Starting {display_name} daemon")
filename: str = getCoreBinName(coin_id, v, c + "d") filename: str = getCoreBinName(coin_id, v, c + "d")
extra_opts = getCoreBinArgs( coin_opts = copy.deepcopy(base_coin_opts) + getCoreBinArgs(
coin_id, v, use_tor_proxy=swap_client.use_tor_proxy coin_id, v, use_tor_proxy=swap_client.use_tor_proxy
) )
extra_config = {"coin_name": c} extra_config = {"coin_name": c}
@@ -580,7 +588,7 @@ def runClient(
v["datadir"], v["datadir"],
v["bindir"], v["bindir"],
filename, filename,
opts=extra_opts, opts=coin_opts,
extra_config=extra_config, extra_config=extra_config,
) )
) )
@@ -679,6 +687,9 @@ def printHelp():
print( print(
"--startonlycoin Only start the provides coin daemon/s, use this if a chain requires extra processing." "--startonlycoin Only start the provides coin daemon/s, use this if a chain requires extra processing."
) )
print(
"--extracoinopts Extra options to pass to coin daemon, can only be used with --startonlycoin."
)
print("--logprefix Specify log prefix.") print("--logprefix Specify log prefix.")
print( print(
"--forcedbupgrade Recheck database against schema regardless of version." "--forcedbupgrade Recheck database against schema regardless of version."
@@ -743,6 +754,11 @@ def main():
ensure_coin_valid(coin) ensure_coin_valid(coin)
start_only_coins.add(coin) start_only_coins.add(coin)
continue continue
if name == "extracoinopts":
options["extra_coin_opts"] = []
for opt in [s.lower() for s in s[1].split(",")]:
options["extra_coin_opts"].append(opt)
continue
logger.warning(f"Unknown argument {v}") logger.warning(f"Unknown argument {v}")
+12 -2
View File
@@ -552,16 +552,26 @@ chainparams = {
name_map = {} name_map = {}
ticker_map = {} ticker_map = {}
variant_ticker_map = {}
for c, params in chainparams.items(): for c, params in chainparams.items():
name_map[params["name"].lower()] = c name_map[params["name"].lower()] = c
ticker_map[params["ticker"].lower()] = c ticker_map[params["ticker"].lower()] = c
# Add coin variants, eg: LTC_MWEB, PART_ANON
for c in Coins:
if c.name.lower() in ticker_map:
continue
variant_ticker_map[c.name.lower()] = c
def getCoinIdFromTicker(ticker: str) -> str:
def getCoinIdFromTicker(ticker: str, inc_variant: bool = False) -> str:
lc_ticker: str = ticker.lower()
try: try:
return ticker_map[ticker.lower()] if inc_variant and lc_ticker in variant_ticker_map:
return variant_ticker_map[lc_ticker]
return ticker_map[lc_ticker]
except Exception: except Exception:
raise ValueError(f"Unknown coin {ticker}") raise ValueError(f"Unknown coin {ticker}")
-1
View File
@@ -12,7 +12,6 @@ import time
from enum import IntEnum, auto from enum import IntEnum, auto
from typing import Optional from typing import Optional
CURRENT_DB_VERSION = 34 CURRENT_DB_VERSION = 34
CURRENT_DB_DATA_VERSION = 8 CURRENT_DB_DATA_VERSION = 8
+9 -2
View File
@@ -250,11 +250,18 @@ def upgradeDatabaseFromSchema(self, cursor, expect_schema):
def upgradeDatabase(self, db_version: int): def upgradeDatabase(self, db_version: int):
if self._force_db_upgrade is False and db_version >= CURRENT_DB_VERSION: upgrade_forced: bool = False
if db_version < CURRENT_DB_VERSION:
pass
elif self._force_db_upgrade is True:
upgrade_forced = True
else:
return return
self.log.info( self.log.info(
f"Upgrading database from version {db_version} to {CURRENT_DB_VERSION}." f"Upgrading database from version {db_version} to {CURRENT_DB_VERSION}"
+ (" (forced)" if upgrade_forced else "")
+ "."
) )
# db_version, tablename, oldcolumnname, newcolumnname # db_version, tablename, oldcolumnname, newcolumnname
-1
View File
@@ -7,7 +7,6 @@
import json import json
default_coingecko_api_key = "CG-8hm3r9iLfpEXv4ied8oLbeUj" default_coingecko_api_key = "CG-8hm3r9iLfpEXv4ied8oLbeUj"
-7
View File
@@ -220,13 +220,6 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
if hash_len == 20: if hash_len == 20:
return True return True
def isValidPubkey(self, pubkey: bytes) -> bool:
try:
self.verifyPubkey(pubkey)
return True
except Exception:
return False
def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool: def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool:
pubkey = PublicKey(pubkey) pubkey = PublicKey(pubkey)
return pubkey.verify(sig, signed_hash, hasher=None) return pubkey.verify(sig, signed_hash, hasher=None)
+11 -18
View File
@@ -99,7 +99,6 @@ from basicswap.basicswap_util import TxLockTypes
from basicswap.chainparams import Coins from basicswap.chainparams import Coins
from basicswap.rpc import make_rpc_func, openrpc from basicswap.rpc import make_rpc_func, openrpc
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22 SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22
SEQUENCE_LOCKTIME_MASK = 0x0000FFFF SEQUENCE_LOCKTIME_MASK = 0x0000FFFF
@@ -449,11 +448,11 @@ class BTCInterface(Secp256k1Interface):
# Wallet name is "" for some LTC and PART installs on older cores # Wallet name is "" for some LTC and PART installs on older cores
if self._rpc_wallet not in wallets and len(wallets) > 0: if self._rpc_wallet not in wallets and len(wallets) > 0:
if "" in wallets: if "" in wallets:
# Setting wallet= in the coin .conf file should also work
self._log.warning( self._log.warning(
f"Nameless {self.ticker()} wallet found." f"Nameless {self.ticker()} wallet found."
+ '\nPlease set the "wallet_name" coin setting to "" or recreate the wallet' + '\nPlease set the "wallet_name" coin setting to "" or recreate the wallet'
) )
# backupwallet and restorewallet with name should work.
if self._rpc_wallet not in wallets: if self._rpc_wallet not in wallets:
raise RuntimeError( raise RuntimeError(
@@ -939,32 +938,26 @@ class BTCInterface(Secp256k1Interface):
if wm: if wm:
info = wm.getAddressInfo(self.coin_type(), address) info = wm.getAddressInfo(self.coin_type(), address)
if info: if info:
if or_watch_only: if or_watch_only is False and info["is_watch_only"] is True:
return True return False
return True return True
return False return False
try: try:
addr_info = self.rpc_wallet("getaddressinfo", [address]) addr_info = self.rpc_wallet("getaddressinfo", [address])
if not or_watch_only:
if addr_info["ismine"]: if addr_info["ismine"]:
return True return True
else: if or_watch_only is False:
return False
if addr_info["iswatchonly"]:
return True
if self._use_descriptors: if self._use_descriptors:
addr_info = self.rpc_wallet_watch("getaddressinfo", [address]) wo_addr_info = self.rpc_wallet_watch("getaddressinfo", [address])
if addr_info["ismine"] or addr_info["iswatchonly"]: if wo_addr_info["iswatchonly"]:
return True return True
except Exception as e: except Exception as e:
self._log.debug(f"isAddressMine RPC check failed: {e}") self._log.debug(f"isAddressMine RPC check failed: {e}")
wm = self.getWalletManager()
if wm:
info = wm.getAddressInfo(self.coin_type(), address)
if info:
if or_watch_only:
return True
return True
return False return False
def checkAddressMine(self, address: str) -> None: def checkAddressMine(self, address: str) -> None:
@@ -1083,8 +1076,8 @@ class BTCInterface(Secp256k1Interface):
return self.encode_p2wsh(script) return self.encode_p2wsh(script)
def getDestForAddress(self, address: str) -> bytes: def getDestForAddress(self, address: str) -> bytes:
bech32_prefix = self.chainparams_network()["hrp"] bech32_prefix: str | None = self.chainparams_network().get("hrp", None)
if address.startswith(bech32_prefix + "1"): if bech32_prefix and address.startswith(bech32_prefix + "1"):
_, witprog = segwit_addr.decode(bech32_prefix, address) _, witprog = segwit_addr.decode(bech32_prefix, address)
return CScript([OP_0, bytes(witprog)]) return CScript([OP_0, bytes(witprog)])
-1
View File
@@ -82,7 +82,6 @@ from coincurve.ecdsaotves import (
ecdsaotves_rec_enc_key, ecdsaotves_rec_enc_key,
) )
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22 SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22
SEQUENCE_LOCKTIME_MASK = 0x0000F SEQUENCE_LOCKTIME_MASK = 0x0000F
+1 -1
View File
@@ -13,7 +13,7 @@ import subprocess
def createDCRWallet(args, hex_seed, logging, delay_event): def createDCRWallet(args, hex_seed, logging, delay_event):
logging.info("Creating DCR wallet") logging.info("Creating DCR wallet")
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read pipe_r, pipe_w = os.pipe() # subprocess.PIPE is buffered, blocks when read
if os.name == "nt": if os.name == "nt":
str_args = " ".join(args) str_args = " ".join(args)
+1 -1
View File
@@ -361,7 +361,7 @@ class FIROInterface(BTCInterface):
) )
return pay_fee return pay_fee
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: def signTxWithKey(self, tx: bytes, key: bytes, prev_amount=None) -> bytes:
key_wif = self.encodeKey(key) key_wif = self.encodeKey(key)
rv = self.rpc( rv = self.rpc(
"signrawtransaction", "signrawtransaction",
+86 -107
View File
@@ -26,87 +26,6 @@ class LTCInterface(BTCInterface):
wallet=self._rpc_wallet_mweb, wallet=self._rpc_wallet_mweb,
) )
def checkWallets(self) -> int:
if self._connection_type == "electrum":
wm = self.getWalletManager()
if wm and wm.isInitialized(self.coin_type()):
return 1
return 0
wallets = self.rpc("listwallets")
if self._rpc_wallet not in wallets:
self._log.debug(
f"Wallet: {self._rpc_wallet} not active, attempting to load."
)
try:
self.rpc(
"loadwallet",
[
self._rpc_wallet,
],
)
wallets = self.rpc("listwallets")
except Exception as e:
self._log.debug(f'Error loading wallet "{self._rpc_wallet}": {e}.')
if "does not exist" in str(e) or "Path does not exist" in str(e):
try:
wallet_dirs = self.rpc("listwalletdir")
existing = [w["name"] for w in wallet_dirs.get("wallets", [])]
except Exception:
existing = []
if len(existing) == 0:
self._log.info(
f'Creating wallet "{self._rpc_wallet}" for {self.coin_name()}.'
)
try:
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
self.rpc(
"createwallet",
[
self._rpc_wallet,
False,
True,
"",
False,
self._use_descriptors,
],
)
wallets = self.rpc("listwallets")
if self.getWalletSeedID() == "Not found":
self._log.info(
f"Initializing HD seed for {self.coin_name()}."
)
self._sc.initialiseWallet(self.coin_type())
except Exception as create_e:
self._log.error(f"Error creating wallet: {create_e}")
if self._rpc_wallet not in wallets and len(wallets) > 0:
self._log.warning(f"Changing {self.ticker()} wallet name.")
for wallet_name in wallets:
if wallet_name in ("mweb",):
continue
change_watchonly_wallet: bool = (
self._rpc_wallet_watch == self._rpc_wallet
)
self._rpc_wallet = wallet_name
self._log.info(
f"Switched {self.ticker()} wallet name to {self._rpc_wallet}."
)
self.rpc_wallet = make_rpc_func(
self._rpcport,
self._rpcauth,
host=self._rpc_host,
wallet=self._rpc_wallet,
)
if change_watchonly_wallet:
self.rpc_wallet_watch = self.rpc_wallet
break
return len(wallets)
def getNewMwebAddress(self, use_segwit=False, label="swap_receive") -> str: def getNewMwebAddress(self, use_segwit=False, label="swap_receive") -> str:
if self.useBackend(): if self.useBackend():
raise ValueError("MWEB addresses not supported in electrum mode") raise ValueError("MWEB addresses not supported in electrum mode")
@@ -172,6 +91,11 @@ class LTCInterface(BTCInterface):
continue continue
if "address" not in u: if "address" not in u:
continue continue
utxo_address: str = u["address"]
if any(
utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1")
):
continue
if "desc" in u: if "desc" in u:
desc = u["desc"] desc = u["desc"]
if self.using_segwit: if self.using_segwit:
@@ -184,11 +108,81 @@ class LTCInterface(BTCInterface):
else: else:
if not desc.startswith("pkh"): if not desc.startswith("pkh"):
continue continue
unspent_addr[u["address"]] = unspent_addr.get( unspent_addr[utxo_address] = unspent_addr.get(
u["address"], 0 utxo_address, 0
) + self.make_int(u["amount"], r=1) ) + self.make_int(u["amount"], r=1)
return unspent_addr return unspent_addr
def getMWEBBalance(self) -> int:
if self.useBackend():
raise ValueError("MWEB not supported in electrum mode")
value: int = 0
unspent = self.rpc_wallet(
"listunspent",
[
0,
],
)
for u in unspent:
if "address" not in u:
continue
utxo_address: str = u["address"]
if any(
utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1")
):
value += self.make_int(u["amount"], r=1)
return value
def convertMWEBBalance(self):
if self.useBackend():
raise ValueError("MWEB not supported in electrum mode")
self._log.info(f"convertMWEBBalance - {self.ticker()}")
locked_before = self.rpc_wallet("listlockunspent")
lock_utxos = []
try:
# Hack: mark all the other utxos as unspendable, alternative is to use a mweb_transfer wallet
utxos = self.rpc_wallet("listunspent")
mweb_amount: int = 0
for utxo in utxos:
utxo_address: str = utxo.get("address", "")
if any(
utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1")
):
mweb_amount += self.make_int(utxo["amount"], r=1)
continue
utxo_op = {"txid": utxo["txid"], "vout": utxo["vout"]}
if utxo_op in locked_before:
continue
lock_utxos.append(utxo_op)
if mweb_amount == 0:
raise ValueError("No MWEB outputs to convert")
self.rpc_wallet("lockunspent", [False, lock_utxos])
subfee_to_mweb: bool = True
convert_value = self.format_amount(mweb_amount)
plain_addr: str = self.rpc_wallet("getnewaddress", ["transfer", "bech32"])
# Double check generated address is owned by this wallet
if not self.isAddressMine(plain_addr):
raise ValueError("Generated address not owned by wallet!")
params = [
plain_addr,
convert_value,
"",
"",
subfee_to_mweb,
True,
self._conf_target,
]
txid = self.rpc_wallet("sendtoaddress", params)
self._log.info(f"MWEB in plain converted in txid: {self._log.id(txid)}")
return txid
finally:
self.rpc_wallet("lockunspent", [True, lock_utxos])
def unlockWallet(self, password: str, check_seed: bool = True) -> None: def unlockWallet(self, password: str, check_seed: bool = True) -> None:
if password == "": if password == "":
return return
@@ -306,23 +300,24 @@ class LTCInterfaceMWEB(LTCInterface):
def init_wallet(self, password=None): def init_wallet(self, password=None):
# If system is encrypted mweb wallet will be created at first unlock # If system is encrypted mweb wallet will be created at first unlock
self._log.info("init_wallet - {}".format(self.ticker())) wallet_name: str = self._rpc_wallet
self._log.info(f"init_wallet - {self.ticker()}")
wallets = self.rpc("listwallets") wallets = self.rpc("listwallets")
if self._rpc_wallet not in wallets: if wallet_name not in wallets:
try: try:
self.rpc("loadwallet", [self._rpc_wallet]) self.rpc("loadwallet", [wallet_name])
self._log.debug(f'Loaded existing wallet "{self._rpc_wallet}".') self._log.debug(f'Loaded existing wallet "{wallet_name}".')
except Exception as e: except Exception as e:
if "does not exist" in str(e) or "Path does not exist" in str(e): if "does not exist" in str(e) or "Path does not exist" in str(e):
self._log.info( self._log.info(
f'Creating wallet "{self._rpc_wallet}" for {self.coin_name()}.' f'Creating wallet "{wallet_name}" for {self.coin_name()}.'
) )
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
self.rpc( self.rpc(
"createwallet", "createwallet",
[ [
self._rpc_wallet, wallet_name,
False, False,
True, True,
password, password,
@@ -333,22 +328,6 @@ class LTCInterfaceMWEB(LTCInterface):
else: else:
raise raise
wallets = self.rpc("listwallets")
if "mweb" not in wallets:
try:
self.rpc("loadwallet", ["mweb"])
self._log.debug("Loaded existing MWEB wallet.")
except Exception as e:
if "does not exist" in str(e) or "Path does not exist" in str(e):
self._log.info(f"Creating MWEB wallet for {self.coin_name()}.")
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
self.rpc(
"createwallet",
["mweb", False, True, password, False, False, True],
)
else:
raise
if password is not None: if password is not None:
# Max timeout value, ~3 years # Max timeout value, ~3 years
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120) self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
@@ -357,8 +336,8 @@ class LTCInterfaceMWEB(LTCInterface):
self._sc.initialiseWallet(self.interface_type()) self._sc.initialiseWallet(self.interface_type())
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain() # Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
self.rpc("unloadwallet", ["mweb"]) self.rpc("unloadwallet", [wallet_name])
self.rpc("loadwallet", ["mweb"]) self.rpc("loadwallet", [wallet_name])
if password is not None: if password is not None:
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120) self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
self.rpc_wallet("keypoolrefill") self.rpc_wallet("keypoolrefill")
+7 -23
View File
@@ -12,7 +12,7 @@ from .btc import BTCInterface
from basicswap.rpc import make_rpc_func from basicswap.rpc import make_rpc_func
from basicswap.chainparams import Coins from basicswap.chainparams import Coins
from basicswap.util.address import decodeAddress from basicswap.util.address import decodeAddress
from .contrib.pivx_test_framework.messages import CBlock, ToHex, FromHex, CTransaction from .contrib.pivx_test_framework.messages import CTransaction
from basicswap.contrib.test_framework.script import ( from basicswap.contrib.test_framework.script import (
CScript, CScript,
OP_DUP, OP_DUP,
@@ -100,29 +100,13 @@ class PIVXInterface(BTCInterface):
return decodeAddress(address)[1:] return decodeAddress(address)[1:]
def getBlockWithTxns(self, block_hash): def getBlockWithTxns(self, block_hash):
# TODO: Bypass decoderawtransaction and getblockheader block = self.rpc("getblock", [block_hash, True])
block = self.rpc("getblock", [block_hash, False])
block_header = self.rpc("getblockheader", [block_hash])
decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block)
tx_rv = [] tx_rv = []
for tx in decoded_block.vtx: for txid_str in block["tx"]:
tx_dec = self.rpc("decoderawtransaction", [ToHex(tx)]) tx_dec = self.rpc("getrawtransaction", [txid_str, True])
tx_rv.append(tx_dec) tx_rv.append(tx_dec)
block["tx"] = tx_rv
block_rv = { return block
"hash": block_hash,
"previousblockhash": block_header["previousblockhash"],
"tx": tx_rv,
"confirmations": block_header["confirmations"],
"height": block_header["height"],
"time": block_header["time"],
"version": block_header["version"],
"merkleroot": block_header["merkleroot"],
}
return block_rv
def withdrawCoin(self, value, addr_to, subfee): def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, "", "", subfee] params = [addr_to, value, "", "", subfee]
@@ -150,7 +134,7 @@ class PIVXInterface(BTCInterface):
) )
return pay_fee return pay_fee
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: def signTxWithKey(self, tx: bytes, key: bytes, prev_amount=None) -> bytes:
key_wif = self.encodeKey(key) key_wif = self.encodeKey(key)
rv = self.rpc( rv = self.rpc(
"signrawtransaction", "signrawtransaction",
-1
View File
@@ -34,7 +34,6 @@ from basicswap.rpc_xmr import make_xmr_rpc_func, make_xmr_rpc2_func
from basicswap.chainparams import XMR_COIN, Coins from basicswap.chainparams import XMR_COIN, Coins
from basicswap.interface.base import CoinInterface from basicswap.interface.base import CoinInterface
ed25519_l = 2**252 + 27742317777372353535851937790883648493 ed25519_l = 2**252 + 27742317777372353535851937790883648493
+36 -3
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert # Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024-2025 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -129,7 +129,6 @@ def js_walletbalances(self, url_split, post_string, is_json) -> bytes:
swap_client = self.server.swap_client swap_client = self.server.swap_client
try: try:
swap_client.updateWalletsInfo() swap_client.updateWalletsInfo()
wallets = swap_client.getCachedWalletsInfo() wallets = swap_client.getCachedWalletsInfo()
coins_with_balances = [] coins_with_balances = []
@@ -193,6 +192,28 @@ def js_walletbalances(self, url_split, post_string, is_json) -> bytes:
coin_entry["electrum_synced"] = sync_status.get("synced", False) coin_entry["electrum_synced"] = sync_status.get("synced", False)
coin_entry["electrum_height"] = sync_status.get("height", 0) coin_entry["electrum_height"] = sync_status.get("height", 0)
if k in wallets:
w = wallets[k]
if "error" not in w and "no_data" not in w:
if k == Coins.PART:
for field in ("blind_balance", "anon_balance"):
if field in w:
raw = w[field]
if isinstance(raw, float):
coin_entry[field] = f"{raw:.8f}".rstrip(
"0"
).rstrip(".")
elif isinstance(raw, int):
coin_entry[field] = str(raw)
else:
coin_entry[field] = raw
elif k == Coins.LTC:
if "mweb_balance" in w:
coin_entry["mweb_balance"] = w["mweb_balance"]
elif k == Coins.FIRO:
if "spark_balance" in w:
coin_entry["spark_balance"] = w["spark_balance"]
coins_with_balances.append(coin_entry) coins_with_balances.append(coin_entry)
if k == Coins.PART: if k == Coins.PART:
@@ -290,7 +311,7 @@ def js_wallets(self, url_split, post_string, is_json):
swap_client.checkSystemStatus() swap_client.checkSystemStatus()
if len(url_split) > 3: if len(url_split) > 3:
ticker_str = url_split[3] ticker_str = url_split[3]
coin_type = getCoinIdFromTicker(ticker_str) coin_type = getCoinIdFromTicker(ticker_str, inc_variant=True)
if len(url_split) > 4: if len(url_split) > 4:
cmd = url_split[4] cmd = url_split[4]
@@ -332,6 +353,18 @@ def js_wallets(self, url_split, post_string, is_json):
return bytes( return bytes(
json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), "UTF-8" json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), "UTF-8"
) )
elif cmd == "mwebbalance":
# mweb outputs left behind when sending LTC -> MWEB
if coin_type not in (Coins.LTC,):
raise ValueError("Invalid coin for command")
ci = swap_client.ci(coin_type)
return bytes(json.dumps(ci.format_amount(ci.getMWEBBalance())), "UTF-8")
elif cmd == "convertmweb":
if coin_type not in (Coins.LTC,):
raise ValueError("Invalid coin for command")
return bytes(
json.dumps(swap_client.ci(coin_type).convertMWEBBalance()), "UTF-8"
)
elif cmd == "watchaddress": elif cmd == "watchaddress":
post_data = getFormData(post_string, is_json) post_data = getFormData(post_string, is_json)
address = get_data_entry(post_data, "address") address = get_data_entry(post_data, "address")
-1
View File
@@ -23,7 +23,6 @@ protobuf ParseFromString would reset the whole object, from_bytes won't.
from basicswap.util.integer import encode_varint, decode_varint from basicswap.util.integer import encode_varint, decode_varint
NPBW_INT = 0 NPBW_INT = 0
NPBW_BYTES = 2 NPBW_BYTES = 2
-1
View File
@@ -39,7 +39,6 @@ from basicswap.contrib.rfc6979 import (
rfc6979_hmac_sha256_generate, rfc6979_hmac_sha256_generate,
) )
START_TOKEN = 0xABCD START_TOKEN = 0xABCD
MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big") MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big")
+1 -1
View File
@@ -53,7 +53,7 @@ def initSimplexClient(args, logger, delay_event):
# TODO: Must be a better way? # TODO: Must be a better way?
logger.info("Initialising Simplex client") logger.info("Initialising Simplex client")
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read pipe_r, pipe_w = os.pipe() # subprocess.PIPE is buffered, blocks when read
if os.name == "nt": if os.name == "nt":
str_args = " ".join(args) str_args = " ".join(args)
@@ -40,6 +40,10 @@
}); });
}, },
confirmMWEBChangeConvert: function() {
return confirm('Confirm MWEB change conversion: This will create a tx sending all spendable MWEB outputs in the plain LTC wallet to LTC.');
},
confirmReseed: function() { confirmReseed: function() {
return confirm('Are you sure you want to reseed the wallet? This will generate new addresses.'); return confirm('Are you sure you want to reseed the wallet? This will generate new addresses.');
}, },
@@ -282,6 +286,16 @@
} }
}); });
document.addEventListener('click', (e) => {
const target = e.target.closest('[data-confirm-mweb-change-convert]');
if (target) {
if (!this.confirmMWEBChangeConvert()) {
e.preventDefault();
return false;
}
}
});
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
const target = e.target.closest('[data-confirm-utxo]'); const target = e.target.closest('[data-confirm-utxo]');
if (target) { if (target) {
@@ -398,6 +412,7 @@
window.EventHandlers = EventHandlers; window.EventHandlers = EventHandlers;
window.confirmReseed = EventHandlers.confirmReseed.bind(EventHandlers); window.confirmReseed = EventHandlers.confirmReseed.bind(EventHandlers);
window.confirmMWEBChangeConvert = EventHandlers.confirmMWEBChangeConvert.bind(EventHandlers);
window.confirmWithdrawal = EventHandlers.confirmWithdrawal.bind(EventHandlers); window.confirmWithdrawal = EventHandlers.confirmWithdrawal.bind(EventHandlers);
window.confirmUTXOResize = EventHandlers.confirmUTXOResize.bind(EventHandlers); window.confirmUTXOResize = EventHandlers.confirmUTXOResize.bind(EventHandlers);
window.confirmRemoveExpired = EventHandlers.confirmRemoveExpired.bind(EventHandlers); window.confirmRemoveExpired = EventHandlers.confirmRemoveExpired.bind(EventHandlers);
+8 -5
View File
@@ -351,16 +351,19 @@
); );
matchingCoins.forEach(coinData => { matchingCoins.forEach(coinData => {
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]'); const balanceElements = document.querySelectorAll('.coinname-value[data-coinname][data-balance-type]');
balanceElements.forEach(element => { balanceElements.forEach(element => {
const elementCoinName = element.getAttribute('data-coinname'); const elementCoinName = element.getAttribute('data-coinname');
if (elementCoinName === coinData.name) { if (elementCoinName === coinData.name) {
const currentText = element.textContent; const balanceType = element.getAttribute('data-balance-type');
const value = coinData[balanceType];
if (value !== undefined) {
const ticker = coinData.ticker || coinId.toUpperCase(); const ticker = coinData.ticker || coinId.toUpperCase();
const newBalance = `${coinData.balance} ${ticker}`; const newBalance = balanceType === 'est_fee' ? value : `${value} ${ticker}`;
if (currentText !== newBalance) { if (element.textContent !== newBalance) {
element.textContent = newBalance; element.textContent = newBalance;
console.log(`Updated balance: ${coinData.name} -> ${newBalance}`); console.log(`Updated ${balanceType}: ${coinData.name} -> ${newBalance}`);
}
} }
} }
}); });
+19
View File
@@ -75,9 +75,28 @@
if (coinData.pending && parseFloat(coinData.pending) > 0) { if (coinData.pending && parseFloat(coinData.pending) > 0) {
this.updatePendingBalance('Particl', 'Blind Balance:', coinData.pending, coinData.ticker || 'PART', 'Blind Unconfirmed:', coinData); this.updatePendingBalance('Particl', 'Blind Balance:', coinData.pending, coinData.ticker || 'PART', 'Blind Unconfirmed:', coinData);
} }
} else if (coinData.name === 'Litecoin MWEB') {
this.updateSpecificBalance('Litecoin', 'MWEB Balance:', coinData.balance, coinData.ticker || 'LTC');
this.removePendingBalance('Litecoin', 'MWEB Balance:');
if (coinData.pending && parseFloat(coinData.pending) > 0) {
this.updatePendingBalance('Litecoin', 'MWEB Balance:', coinData.pending, coinData.ticker || 'LTC', 'MWEB Pending:', coinData);
}
} else { } else {
this.updateSpecificBalance(coinData.name, 'Balance:', coinData.balance, coinData.ticker || coinData.name); this.updateSpecificBalance(coinData.name, 'Balance:', coinData.balance, coinData.ticker || coinData.name);
if (coinData.mweb_balance !== undefined) {
this.updateSpecificBalance(coinData.name, 'MWEB Balance:', coinData.mweb_balance, coinData.ticker || coinData.name);
}
if (coinData.spark_balance !== undefined) {
this.updateSpecificBalance(coinData.name, 'Spark Balance:', coinData.spark_balance, coinData.ticker || coinData.name);
}
if (coinData.blind_balance !== undefined) {
this.updateSpecificBalance(coinData.name, 'Blind Balance:', coinData.blind_balance, coinData.ticker || coinData.name);
}
if (coinData.anon_balance !== undefined) {
this.updateSpecificBalance(coinData.name, 'Anon Balance:', coinData.anon_balance, coinData.ticker || coinData.name);
}
if (coinData.name !== 'Particl Anon' && coinData.name !== 'Particl Blind' && coinData.name !== 'Litecoin MWEB') { if (coinData.name !== 'Particl Anon' && coinData.name !== 'Particl Blind' && coinData.name !== 'Litecoin MWEB') {
if (coinData.pending && parseFloat(coinData.pending) > 0) { if (coinData.pending && parseFloat(coinData.pending) > 0) {
this.updatePendingDisplay(coinData); this.updatePendingDisplay(coinData);
+25 -23
View File
@@ -138,7 +138,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600"> <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"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td> <td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td>
<td class="py-3 px-6 bold"> <td class="py-3 px-6 bold">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="balance">{{ w.balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
{% if w.pending %} {% if w.pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.pending }} {{ w.ticker }} </span> <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.pending }} {{ w.ticker }} </span>
@@ -152,7 +152,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600"> <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"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Blind"> </span>Blind Balance: </td> <td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Blind"> </span>Blind Balance: </td>
<td class="py-3 px-6 bold"> <td class="py-3 px-6 bold">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="blind_balance">{{ w.blind_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
{% if w.blind_unconfirmed %} {% if w.blind_unconfirmed %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Unconfirmed: +{{ w.blind_unconfirmed }} {{ w.ticker }}</span> <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Unconfirmed: +{{ w.blind_unconfirmed }} {{ w.ticker }}</span>
@@ -162,7 +162,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600"> <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"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Anon"> </span>Anon Balance: </td> <td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Anon"> </span>Anon Balance: </td>
<td class="py-3 px-6 bold"> <td class="py-3 px-6 bold">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="anon_balance">{{ w.anon_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
{% if w.anon_pending %} {% if w.anon_pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.anon_pending }} {{ w.ticker }}</span> <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.anon_pending }} {{ w.ticker }}</span>
@@ -177,7 +177,7 @@
{% if is_electrum_mode %} {% if is_electrum_mode %}
<span class="text-gray-400 dark:text-gray-300">Not available in light mode</span> <span class="text-gray-400 dark:text-gray-300">Not available in light mode</span>
{% else %} {% else %}
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="mweb_balance">{{ w.mweb_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
{% if w.mweb_pending %} {% if w.mweb_pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }} </span> <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }} </span>
@@ -185,11 +185,22 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% if w.mweb_in_plain %}
<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"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} MWEB"> </span>MWEB in Plain Balance: </td>
<td class="py-3 px-6 bold">
<span>{{ w.mweb_in_plain }} {{ w.ticker }}</span>
</td>
<td class="py-3 px-6 bold">
<button type="submit" class="flex justify-center py-2 px-4 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" name="convertmweb_{{ w.cid }}" value="Convert" data-confirm-mweb-change-convert> Convert </button>
</td>
</tr>
{% endif %}
{% elif w.cid == '13' %} {# FIRO #} {% elif w.cid == '13' %} {# FIRO #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600"> <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"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Spark"> </span>Spark Balance: </td> <td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Spark"> </span>Spark Balance: </td>
<td class="py-3 px-6 bold"> <td class="py-3 px-6 bold">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
{% if w.spark_pending %} {% if w.spark_pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.spark_pending }} {{ w.ticker }} </span> <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.spark_pending }} {{ w.ticker }} </span>
@@ -200,7 +211,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600"> <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"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Spark"> </span>Spark Balance: </td> <td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Spark"> </span>Spark Balance: </td>
<td class="py-3 px-6 bold"> <td class="py-3 px-6 bold">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
{% if w.spark_pending %} {% if w.spark_pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.spark_pending }} {{ w.ticker }} </span> <span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.spark_pending }} {{ w.ticker }} </span>
@@ -337,16 +348,7 @@
<td class="py-3 px-6">{{ w.expected_seed }}</td> <td class="py-3 px-6">{{ w.expected_seed }}</td>
</tr> </tr>
{% endif %} {% 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> </table>
</div> </div>
</div> </div>
@@ -545,7 +547,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100"> <tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td> <td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td>
<td class="py-3 px-6"> <td class="py-3 px-6">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="balance">{{ w.balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
</td> </td>
</tr> </tr>
@@ -557,7 +559,7 @@
{% if is_electrum_mode %} {% if is_electrum_mode %}
<span class="text-gray-400 dark:text-gray-300">Not available in light mode</span> <span class="text-gray-400 dark:text-gray-300">Not available in light mode</span>
{% else %} {% else %}
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="mweb_balance">{{ w.mweb_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
{% endif %} {% endif %}
</td> </td>
@@ -567,7 +569,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100"> <tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Spark Balance: </td> <td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Spark Balance: </td>
<td class="py-3 px-6"> <td class="py-3 px-6">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
</td> </td>
</tr> </tr>
@@ -576,7 +578,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100"> <tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Spark Balance: </td> <td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Spark Balance: </td>
<td class="py-3 px-6"> <td class="py-3 px-6">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
</td> </td>
</tr> </tr>
@@ -585,14 +587,14 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100"> <tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Blind Balance: </td> <td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Blind Balance: </td>
<td class="py-3 px-6"> <td class="py-3 px-6">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="blind_balance">{{ w.blind_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
</td> </td>
</tr> </tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100"> <tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Anon Balance: </td> <td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Anon Balance: </td>
<td class="py-3 px-6"> <td class="py-3 px-6">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="anon_balance">{{ w.anon_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>) (<span class="usd-value"></span>)
</td> </td>
</tr> </tr>
@@ -767,7 +769,7 @@
<tr class="opacity-100 text-gray-500 dark:text-gray-100"> <tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Fee Estimate:</td> <td class="py-3 px-6 bold">Fee Estimate:</td>
<td class="py-3 px-6"> <td class="py-3 px-6">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.est_fee }}</span> <span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="est_fee">{{ w.est_fee }}</span>
(<span class="usd-value fee-estimate-usd" data-decimals="8"></span>) (<span class="usd-value fee-estimate-usd" data-decimals="8"></span>)
</td> </td>
</tr> </tr>
+5 -5
View File
@@ -65,7 +65,7 @@
<div class="p-6 bg-coolGray-100 dark:bg-gray-600"> <div class="p-6 bg-coolGray-100 dark:bg-gray-600">
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Balance:</h4> <h4 class="text-xs font-medium dark:text-white">Balance:</h4>
<div class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }}</div> <div class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="balance">{{ w.balance }} {{ w.ticker }}</div>
</div> </div>
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white ">{{ w.ticker }} USD value:</h4> <h4 class="text-xs font-medium dark:text-white ">{{ w.ticker }} USD value:</h4>
@@ -90,7 +90,7 @@
{% if w.cid == '1' %} {# PART #} {% if w.cid == '1' %} {# PART #}
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Blind Balance:</h4> <h4 class="text-xs font-medium dark:text-white">Blind Balance:</h4>
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }}</span> <span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="blind_balance">{{ w.blind_balance }} {{ w.ticker }}</span>
</div> </div>
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Blind USD value:</h4> <h4 class="text-xs font-medium dark:text-white">Blind USD value:</h4>
@@ -108,7 +108,7 @@
{% endif %} {% endif %}
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Anon Balance:</h4> <h4 class="text-xs font-medium dark:text-white">Anon Balance:</h4>
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }}</span> <span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="anon_balance">{{ w.anon_balance }} {{ w.ticker }}</span>
</div> </div>
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Anon USD value:</h4> <h4 class="text-xs font-medium dark:text-white">Anon USD value:</h4>
@@ -129,7 +129,7 @@
{% if w.cid == '3' and w.connection_type != 'electrum' %} {# LTC - MWEB not available in electrum mode #} {% if w.cid == '3' and w.connection_type != 'electrum' %} {# LTC - MWEB not available in electrum mode #}
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">MWEB Balance:</h4> <h4 class="text-xs font-medium dark:text-white">MWEB Balance:</h4>
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span> <span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="mweb_balance">{{ w.mweb_balance }} {{ w.ticker }}</span>
</div> </div>
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">MWEB USD value:</h4> <h4 class="text-xs font-medium dark:text-white">MWEB USD value:</h4>
@@ -151,7 +151,7 @@
{% if w.cid == '13' %} {# FIRO #} {% if w.cid == '13' %} {# FIRO #}
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Spark Balance:</h4> <h4 class="text-xs font-medium dark:text-white">Spark Balance:</h4>
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span> <span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
</div> </div>
<div class="flex mb-2 justify-between items-center"> <div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Spark USD value:</h4> <h4 class="text-xs font-medium dark:text-white">Spark USD value:</h4>
+7
View File
@@ -273,6 +273,9 @@ def page_wallet(self, url_split, post_string):
swap_client.cacheNewAddressForCoin(coin_id) swap_client.cacheNewAddressForCoin(coin_id)
elif have_data_entry(form_data, "forcerefresh"): elif have_data_entry(form_data, "forcerefresh"):
force_refresh = True force_refresh = True
elif have_data_entry(form_data, "convertmweb_" + cid):
txid = swap_client.ci(coin_id).convertMWEBBalance()
messages.append(f"Converted MWEB change to LTC in tx: {txid}")
elif have_data_entry(form_data, "newmwebaddr_" + cid): elif have_data_entry(form_data, "newmwebaddr_" + cid):
swap_client.cacheNewStealthAddressForCoin(coin_id) swap_client.cacheNewStealthAddressForCoin(coin_id)
elif have_data_entry(form_data, "newsparkaddr_" + cid): elif have_data_entry(form_data, "newsparkaddr_" + cid):
@@ -525,6 +528,10 @@ def page_wallet(self, url_split, post_string):
// page_data["fee_estimate"]["sum_weight"] // page_data["fee_estimate"]["sum_weight"]
) )
if k == Coins.LTC and ci.useBackend() is False:
mweb_value: int = ci.getMWEBBalance()
if mweb_value > 0:
wallet_data["mweb_in_plain"] = ci.format_amount(mweb_value)
if show_utxo_groups: if show_utxo_groups:
utxo_groups = "" utxo_groups = ""
unspent_by_addr = ci.getUnspentsByAddr() unspent_by_addr = ci.getUnspentsByAddr()
-1
View File
@@ -10,7 +10,6 @@ import json
import time import time
import decimal import decimal
COIN = 100000000 COIN = 100000000
-1
View File
@@ -25,7 +25,6 @@ from basicswap.contrib.test_framework.messages import (
uint256_from_str, uint256_from_str,
) )
AES_BLOCK_SIZE = 16 AES_BLOCK_SIZE = 16
+9 -47
View File
@@ -4,10 +4,12 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import hashlib import json
import sqlite3
import threading import threading
import time import time
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
from coincurve import PrivateKey, PublicKey
from .chainparams import Coins from .chainparams import Coins
from .contrib.test_framework import segwit_addr from .contrib.test_framework import segwit_addr
@@ -19,7 +21,7 @@ from .db_wallet import (
WalletTxCache, WalletTxCache,
WalletWatchOnly, WalletWatchOnly,
) )
from .util.crypto import hash160 from .util.crypto import hash160, sha256
from .util.extkey import ExtKeyPair from .util.extkey import ExtKeyPair
@@ -112,13 +114,11 @@ class WalletManager:
def _deriveAddress( def _deriveAddress(
self, coin_type: Coins, index: int, internal: bool = False self, coin_type: Coins, index: int, internal: bool = False
) -> Tuple[str, str, bytes]: ) -> Tuple[str, str, bytes]:
from coincurve import PublicKey
key = self._deriveKey(coin_type, index, internal) key = self._deriveKey(coin_type, index, internal)
pubkey = PublicKey.from_secret(key).format() pubkey = PublicKey.from_secret(key).format()
pkh = hash160(pubkey) pkh = hash160(pubkey)
address = segwit_addr.encode(self._getHRP(coin_type), 0, pkh) address = segwit_addr.encode(self._getHRP(coin_type), 0, pkh)
scripthash = hashlib.sha256(bytes([0x00, 0x14]) + pkh).digest()[::-1].hex() scripthash = sha256(bytes([0x00, 0x14]) + pkh)[::-1].hex()
return address, scripthash, pubkey return address, scripthash, pubkey
def _syncStateIndices(self, coin_type: Coins, cursor) -> None: def _syncStateIndices(self, coin_type: Coins, cursor) -> None:
@@ -275,8 +275,6 @@ class WalletManager:
def getAddress( def getAddress(
self, coin_type: Coins, index: int, internal: bool = False self, coin_type: Coins, index: int, internal: bool = False
) -> Optional[str]: ) -> Optional[str]:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -395,8 +393,6 @@ class WalletManager:
include_watch_only: bool = True, include_watch_only: bool = True,
funded_only: bool = False, funded_only: bool = False,
) -> List[str]: ) -> List[str]:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -426,8 +422,6 @@ class WalletManager:
return [] return []
def getFundedAddresses(self, coin_type: Coins) -> Dict[str, str]: def getFundedAddresses(self, coin_type: Coins) -> Dict[str, str]:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -452,8 +446,6 @@ class WalletManager:
return {} return {}
def getExistingInternalAddress(self, coin_type: Coins) -> Optional[str]: def getExistingInternalAddress(self, coin_type: Coins) -> Optional[str]:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -517,8 +509,6 @@ class WalletManager:
self._swap_client.closeDB(cursor, commit=False) self._swap_client.closeDB(cursor, commit=False)
def getAddressInfo(self, coin_type: Coins, address: str) -> Optional[dict]: def getAddressInfo(self, coin_type: Coins, address: str) -> Optional[dict]:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -559,8 +549,6 @@ class WalletManager:
return None return None
def getCachedTotalBalance(self, coin_type: Coins) -> int: def getCachedTotalBalance(self, coin_type: Coins) -> int:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -700,8 +688,6 @@ class WalletManager:
if not self.isInitialized(coin_type): if not self.isInitialized(coin_type):
return None return None
import sqlite3
now = int(time.time()) now = int(time.time())
min_cache_time = now - max_cache_age min_cache_time = now - max_cache_age
@@ -744,8 +730,6 @@ class WalletManager:
return None return None
def hasCachedBalances(self, coin_type: Coins, max_cache_age: int = 120) -> bool: def hasCachedBalances(self, coin_type: Coins, max_cache_age: int = 120) -> bool:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -762,8 +746,6 @@ class WalletManager:
def getPrivateKey(self, coin_type: Coins, address: str) -> Optional[bytes]: def getPrivateKey(self, coin_type: Coins, address: str) -> Optional[bytes]:
if not self.isInitialized(coin_type): if not self.isInitialized(coin_type):
return None return None
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -788,8 +770,6 @@ class WalletManager:
return None return None
def getSignableAddresses(self, coin_type: Coins) -> Dict[str, str]: def getSignableAddresses(self, coin_type: Coins) -> Dict[str, str]:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -861,10 +841,8 @@ class WalletManager:
label: str = "", label: str = "",
source: str = "import", source: str = "import",
) -> bool: ) -> bool:
from coincurve import PublicKey as CCPublicKey
try: try:
pubkey = CCPublicKey.from_secret(private_key).format() pubkey = PublicKey.from_secret(private_key).format()
if ( if (
segwit_addr.encode(self._getHRP(coin_type), 0, hash160(pubkey)) segwit_addr.encode(self._getHRP(coin_type), 0, hash160(pubkey))
!= address != address
@@ -1003,7 +981,7 @@ class WalletManager:
def _b58decode_check(self, s: str) -> bytes: def _b58decode_check(self, s: str) -> bytes:
data = self._b58decode(s) data = self._b58decode(s)
payload, checksum = data[:-4], data[-4:] payload, checksum = data[:-4], data[-4:]
expected = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] expected = sha256(sha256(payload))[:4]
if checksum != expected: if checksum != expected:
raise ValueError("Invalid base58 checksum") raise ValueError("Invalid base58 checksum")
return payload return payload
@@ -1025,7 +1003,7 @@ class WalletManager:
master_key = self._master_keys.get(coin_type) master_key = self._master_keys.get(coin_type)
if master_key is None: if master_key is None:
raise ValueError(f"Wallet not initialized for {coin_type}") raise ValueError(f"Wallet not initialized for {coin_type}")
return hashlib.sha256(master_key + b"_import_key").digest() return sha256(master_key + b"_import_key")
def _encryptPrivateKey(self, private_key: bytes, coin_type: Coins) -> bytes: def _encryptPrivateKey(self, private_key: bytes, coin_type: Coins) -> bytes:
return bytes(a ^ b for a, b in zip(private_key, self._getXorKey(coin_type))) return bytes(a ^ b for a, b in zip(private_key, self._getXorKey(coin_type)))
@@ -1037,7 +1015,7 @@ class WalletManager:
_, data = segwit_addr.decode(self._getHRP(coin_type), address) _, data = segwit_addr.decode(self._getHRP(coin_type), address)
if data is None: if data is None:
return "" return ""
return hashlib.sha256(bytes([0x00, 0x14]) + bytes(data)).digest()[::-1].hex() return sha256(bytes([0x00, 0x14]) + bytes(data))[::-1].hex()
def needsMigration(self, coin_type: Coins) -> bool: def needsMigration(self, coin_type: Coins) -> bool:
cursor = self._swap_client.openDB() cursor = self._swap_client.openDB()
@@ -1193,8 +1171,6 @@ class WalletManager:
self._swap_client.closeDB(cursor, commit=False) self._swap_client.closeDB(cursor, commit=False)
def getSeedID(self, coin_type: Coins) -> Optional[str]: def getSeedID(self, coin_type: Coins) -> Optional[str]:
from basicswap.contrib.test_framework.script import hash160
master_key = self._master_keys.get(coin_type) master_key = self._master_keys.get(coin_type)
if master_key is None: if master_key is None:
return None return None
@@ -1204,16 +1180,12 @@ class WalletManager:
return hash160(ek.encode_p()).hex() return hash160(ek.encode_p()).hex()
def signMessage(self, coin_type: Coins, address: str, message: str) -> bytes: def signMessage(self, coin_type: Coins, address: str, message: str) -> bytes:
from coincurve import PrivateKey
key = self.getPrivateKey(coin_type, address) key = self.getPrivateKey(coin_type, address)
if key is None: if key is None:
raise ValueError(f"Cannot sign: no key for address {address}") raise ValueError(f"Cannot sign: no key for address {address}")
return PrivateKey(key).sign(message.encode("utf-8")) return PrivateKey(key).sign(message.encode("utf-8"))
def signHash(self, coin_type: Coins, address: str, msg_hash: bytes) -> bytes: def signHash(self, coin_type: Coins, address: str, msg_hash: bytes) -> bytes:
from coincurve import PrivateKey
key = self.getPrivateKey(coin_type, address) key = self.getPrivateKey(coin_type, address)
if key is None: if key is None:
raise ValueError(f"Cannot sign: no key for address {address}") raise ValueError(f"Cannot sign: no key for address {address}")
@@ -1222,8 +1194,6 @@ class WalletManager:
def getKeyForAddress( def getKeyForAddress(
self, coin_type: Coins, address: str self, coin_type: Coins, address: str
) -> Optional[Tuple[bytes, bytes]]: ) -> Optional[Tuple[bytes, bytes]]:
from coincurve import PublicKey
key = self.getPrivateKey(coin_type, address) key = self.getPrivateKey(coin_type, address)
if key is None: if key is None:
return None return None
@@ -1232,8 +1202,6 @@ class WalletManager:
def findAddressByScripthash( def findAddressByScripthash(
self, coin_type: Coins, scripthash: str self, coin_type: Coins, scripthash: str
) -> Optional[str]: ) -> Optional[str]:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -1256,8 +1224,6 @@ class WalletManager:
return None return None
def getAllScripthashes(self, coin_type: Coins) -> List[str]: def getAllScripthashes(self, coin_type: Coins) -> List[str]:
import sqlite3
try: try:
conn = sqlite3.connect(self._swap_client.sqlite_file) conn = sqlite3.connect(self._swap_client.sqlite_file)
cursor = conn.cursor() cursor = conn.cursor()
@@ -1895,8 +1861,6 @@ class WalletManager:
for _ in existing: for _ in existing:
return False return False
import json
pending = WalletPendingTx() pending = WalletPendingTx()
pending.coin_type = int(coin_type) pending.coin_type = int(coin_type)
pending.txid = txid pending.txid = txid
@@ -1945,8 +1909,6 @@ class WalletManager:
) -> List[dict]: ) -> List[dict]:
cursor = self._swap_client.openDB() cursor = self._swap_client.openDB()
try: try:
import json
results = self._swap_client.query( results = self._swap_client.query(
WalletPendingTx, WalletPendingTx,
cursor, cursor,
+7
View File
@@ -0,0 +1,7 @@
# LTC Notes
## MWEB
Sending LTC -> MWEB generates MWEB change outputs in the plain LTC wallet that BSX can't use.
A temporary convenience function is provided to convert those MWEB outputs back to plain LTC.
+6
View File
@@ -140,6 +140,12 @@ Observe progress with
tail -f /tmp/firo.log tail -f /tmp/firo.log
Alternatively --extracoinopts can be used with --startonlycoin
docker-compose run --rm swapclient \
basicswap-run --datadir=/coindata --startonlycoin=litecoin --extracoinopts="-reindex"
## Start a subset of the configured coins using docker ## Start a subset of the configured coins using docker
docker compose run --rm --service-ports swapclient basicswap-run -datadir=/coindata -withcoins=monero docker compose run --rm --service-ports swapclient basicswap-run -datadir=/coindata -withcoins=monero
+9
View File
@@ -1,3 +1,12 @@
0.16.3
==============
- New setting "startup_delay"
- Adjusts the time waited for coin daemons to start between "startup_tries".
- Valid as a base setting and can be overridden per coin with chainclients settings.
0.14.5 0.14.5
============== ==============
+3 -3
View File
@@ -135,15 +135,15 @@
(define-public basicswap (define-public basicswap
(package (package
(name "basicswap") (name "basicswap")
(version "0.16.0") (version "0.16.2")
(source (origin (source (origin
(method git-fetch) (method git-fetch)
(uri (git-reference (uri (git-reference
(url "https://github.com/basicswap/basicswap") (url "https://github.com/basicswap/basicswap")
(commit "2c13314bdd29622235c92fd20c237801acb3cb76"))) (commit "3b76adeedbbf3586308b6bf8ba401cec899f8a61")))
(sha256 (sha256
(base32 (base32
"0j0id6db3ljdsfag8krjdmd4rzlz2504yk9lzj0p89lqyygi9ilc")) "0wvy1c6li45ivfnn93iry047fz7w088mcp5rbg07qnnbnb6lgqiz"))
(file-name (git-file-name name version)))) (file-name (git-file-name name version))))
(build-system pyproject-build-system) (build-system pyproject-build-system)
-1
View File
@@ -21,7 +21,6 @@ from basicswap.util import toBool
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from basicswap.bin.prepare import downloadPIVXParams from basicswap.bin.prepare import downloadPIVXParams
TEST_HTTP_HOST = os.getenv( TEST_HTTP_HOST = os.getenv(
"TEST_HTTP_HOST", "127.0.0.1" "TEST_HTTP_HOST", "127.0.0.1"
) # Set to 0.0.0.0 when used in docker ) # Set to 0.0.0.0 when used in docker
+8 -12
View File
@@ -56,7 +56,6 @@ from tests.basicswap.extended.test_doge import (
import basicswap.config as cfg import basicswap.config as cfg
import basicswap.bin.run as runSystem import basicswap.bin.run as runSystem
TEST_PATH = os.path.expanduser(os.getenv("TEST_PATH", "~/test_basicswap1")) TEST_PATH = os.path.expanduser(os.getenv("TEST_PATH", "~/test_basicswap1"))
PARTICL_PORT_BASE = int(os.getenv("PARTICL_PORT_BASE", BASE_PORT)) PARTICL_PORT_BASE = int(os.getenv("PARTICL_PORT_BASE", BASE_PORT))
@@ -532,9 +531,7 @@ def run_prepare(
for opt in EXTRA_CONFIG_JSON.get("doge{}".format(node_id), []): for opt in EXTRA_CONFIG_JSON.get("doge{}".format(node_id), []):
fp.write(opt + "\n") fp.write(opt + "\n")
with open(config_path) as fs: settings["startup_delay"] = 1
settings = json.load(fs)
settings["min_delay_event"] = 1 settings["min_delay_event"] = 1
settings["max_delay_event"] = 4 settings["max_delay_event"] = 4
settings["min_delay_event_short"] = 1 settings["min_delay_event_short"] = 1
@@ -586,7 +583,7 @@ def prepare_nodes(
class TestBase(unittest.TestCase): class TestBase(unittest.TestCase):
def setUpClass(cls): def setUpClass(cls):
super(TestBase, cls).setUpClass() super().setUpClass()
cls.delay_event = threading.Event() cls.delay_event = threading.Event()
signal.signal( signal.signal(
@@ -624,7 +621,7 @@ class TestBase(unittest.TestCase):
def run_process(client_id): def run_process(client_id):
client_path = os.path.join(TEST_PATH, "client{}".format(client_id)) client_path = os.path.join(TEST_PATH, f"client{client_id}")
testargs = [ testargs = [
"basicswap-run", "basicswap-run",
"-datadir=" + client_path, "-datadir=" + client_path,
@@ -646,7 +643,7 @@ class XmrTestBase(TestBase):
prepare_nodes(3, "monero") prepare_nodes(3, "monero")
def start_processes(self): def start_processes(self):
multiprocessing.set_start_method("fork") multiprocessing.set_start_method("spawn")
self.delay_event.clear() self.delay_event.clear()
for i in range(3): for i in range(3):
@@ -655,7 +652,7 @@ class XmrTestBase(TestBase):
) )
self.processes[-1].start() self.processes[-1].start()
waitForServer(self.delay_event, 12701) waitForServer(self.delay_event, 12701, 60)
def waitForMainAddress(): def waitForMainAddress():
for i in range(20): for i in range(20):
@@ -667,13 +664,12 @@ class XmrTestBase(TestBase):
) )
return wallets["XMR"]["main_address"] return wallets["XMR"]["main_address"]
except Exception as e: except Exception as e:
print("Waiting for main address {}".format(str(e))) print(f"Waiting for main address {e}")
self.delay_event.wait(1) self.delay_event.wait(1)
raise ValueError("waitForMainAddress timedout") raise ValueError("waitForMainAddress timedout")
xmr_addr1 = waitForMainAddress() xmr_addr1 = waitForMainAddress()
num_blocks: int = 100
num_blocks = 100
xmr_auth = None xmr_auth = None
if os.getenv("XMR_RPC_USER", "") != "": if os.getenv("XMR_RPC_USER", "") != "":
@@ -685,7 +681,7 @@ class XmrTestBase(TestBase):
] ]
< num_blocks < num_blocks
): ):
logging.info("Mining {} Monero blocks to {}.".format(num_blocks, xmr_addr1)) logging.info(f"Mining {num_blocks} Monero blocks to {xmr_addr1}.")
callrpc_xmr( callrpc_xmr(
XMR_BASE_RPC_PORT + 1, XMR_BASE_RPC_PORT + 1,
"generateblocks", "generateblocks",
-1
View File
@@ -65,7 +65,6 @@ from tests.basicswap.common import (
) )
from basicswap.bin.run import startDaemon from basicswap.bin.run import startDaemon
logger = logging.getLogger() logger = logging.getLogger()
logger.level = logging.DEBUG logger.level = logging.DEBUG
if not len(logger.handlers): if not len(logger.handlers):
@@ -34,7 +34,6 @@ from tests.basicswap.util import (
read_json_api, read_json_api,
) )
logger = logging.getLogger() logger = logging.getLogger()
logger.level = logging.DEBUG logger.level = logging.DEBUG
if not len(logger.handlers): if not len(logger.handlers):
@@ -31,7 +31,6 @@ from tests.basicswap.util import (
waitForServer, waitForServer,
) )
logger = logging.getLogger() logger = logging.getLogger()
logger.level = logging.DEBUG logger.level = logging.DEBUG
if not len(logger.handlers): if not len(logger.handlers):
-1
View File
@@ -58,7 +58,6 @@ from tests.basicswap.common import (
from basicswap.bin.run import startDaemon from basicswap.bin.run import startDaemon
logger = logging.getLogger() logger = logging.getLogger()
NUM_NODES = 3 NUM_NODES = 3
-1
View File
@@ -40,7 +40,6 @@ from tests.basicswap.extended.test_dcr import (
run_test_itx_refund, run_test_itx_refund,
) )
logger = logging.getLogger("BSX Tests") logger = logging.getLogger("BSX Tests")
if not len(logger.handlers): if not len(logger.handlers):
+19 -3
View File
@@ -66,7 +66,6 @@ from tests.basicswap.common import (
from basicswap.bin.run import startDaemon from basicswap.bin.run import startDaemon
from basicswap.bin.prepare import downloadPIVXParams from basicswap.bin.prepare import downloadPIVXParams
logger = logging.getLogger() logger = logging.getLogger()
logger.level = logging.DEBUG logger.level = logging.DEBUG
if not len(logger.handlers): if not len(logger.handlers):
@@ -182,6 +181,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
"datadir": node_dir, "datadir": node_dir,
"bindir": cfg.PARTICL_BINDIR, "bindir": cfg.PARTICL_BINDIR,
"blocks_confirmed": 2, # Faster testing "blocks_confirmed": 2, # Faster testing
"wallet_name": "bsx_wallet",
}, },
"pivx": { "pivx": {
"connection_type": "rpc", "connection_type": "rpc",
@@ -191,6 +191,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
"bindir": PIVX_BINDIR, "bindir": PIVX_BINDIR,
"use_csv": False, "use_csv": False,
"use_segwit": False, "use_segwit": False,
"wallet_name": "",
}, },
"bitcoin": { "bitcoin": {
"connection_type": "rpc", "connection_type": "rpc",
@@ -199,6 +200,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
"datadir": btcdatadir, "datadir": btcdatadir,
"bindir": cfg.BITCOIN_BINDIR, "bindir": cfg.BITCOIN_BINDIR,
"use_segwit": True, "use_segwit": True,
"wallet_name": "bsx_wallet",
}, },
}, },
"check_progress_seconds": 2, "check_progress_seconds": 2,
@@ -760,7 +762,17 @@ class Test(unittest.TestCase):
rtx = pivxRpc(f'getrawtransaction "{txid}" true') rtx = pivxRpc(f'getrawtransaction "{txid}" true')
assert rtx["version"] == 3 assert rtx["version"] == 3
block_hash = pivxRpc(f'generatetoaddress 1 "{generate_addr}"')[0] block_hash = None
for i in range(15):
rtx = pivxRpc(f'getrawtransaction "{txid}" true')
if "blockhash" in rtx:
block_hash = rtx["blockhash"]
logging.info(f"Shielded tx confirmed in block {block_hash} after {i}s")
break
if i == 5:
pivxRpc(f'generatetoaddress 1 "{generate_addr}"')
delay_event.wait(1)
assert block_hash is not None, "Shielded tx was not confirmed"
ci = self.swap_clients[0].ci(Coins.PIVX) ci = self.swap_clients[0].ci(Coins.PIVX)
block = ci.getBlockWithTxns(block_hash) block = ci.getBlockWithTxns(block_hash)
@@ -837,7 +849,11 @@ class Test(unittest.TestCase):
swap_value = ci_from.make_int(swap_value) swap_value = ci_from.make_int(swap_value)
assert swap_value > ci_from.make_int(9) assert swap_value > ci_from.make_int(9)
itx = pi.getFundedInitiateTxTemplate(ci_from, swap_value, True) addr_to = pi.getMockAddrTo(ci_from)
funded_tx = ci_from.createRawFundedTransaction(
addr_to, swap_value, True, lock_unspents=True
)
itx = bytes.fromhex(funded_tx)
itx_decoded = ci_from.describeTx(itx.hex()) itx_decoded = ci_from.describeTx(itx.hex())
n = pi.findMockVout(ci_from, itx_decoded) n = pi.findMockVout(ci_from, itx_decoded)
-1
View File
@@ -38,7 +38,6 @@ from tests.basicswap.util import (
waitForServer, waitForServer,
) )
logger = logging.getLogger() logger = logging.getLogger()
logger.level = logging.DEBUG logger.level = logging.DEBUG
if not len(logger.handlers): if not len(logger.handlers):
+30 -23
View File
@@ -15,14 +15,15 @@ export XMR_RPC_USER=xmr_user
export XMR_RPC_PWD=xmr_pwd export XMR_RPC_PWD=xmr_pwd
python tests/basicswap/extended/test_xmr_persistent.py python tests/basicswap/extended/test_xmr_persistent.py
# Copy coin releases to permanent storage for faster subsequent startups # Copy coin releases to permanent storage for faster subsequent startups
cp -r ${TEST_PATH}/bin/* ~/tmp/basicswap_bin/ cp -r ${TEST_PATH}/bin/* ~/tmp/basicswap_bin/
# Continue existing chains with # Continue existing chains with
export RESET_TEST=false export RESET_TEST=false
# Set coins started
export TEST_COINS_LIST="bitcoin,monero,litecoin"
""" """
import json import json
@@ -247,7 +248,7 @@ def updateThreadDCR(cls):
if "double spend" in str(e): if "double spend" in str(e):
pass pass
else: else:
logging.warning("updateThreadDCR purchaseticket {}".format(e)) logging.warning(f"updateThreadDCR purchaseticket {e}")
cls.delay_event.wait(0.5) cls.delay_event.wait(0.5)
try: try:
if num_passed >= 5: if num_passed >= 5:
@@ -259,7 +260,7 @@ def updateThreadDCR(cls):
], ],
) )
except Exception as e: except Exception as e:
logging.warning("updateThreadDCR generate {}".format(e)) logging.warning(f"updateThreadDCR generate {e}")
except Exception as e: except Exception as e:
print("updateThreadDCR error", str(e)) print("updateThreadDCR error", str(e))
cls.delay_event.wait(random.uniform(cls.dcr_update_min, cls.dcr_update_max)) cls.delay_event.wait(random.uniform(cls.dcr_update_min, cls.dcr_update_max))
@@ -271,7 +272,7 @@ def signal_handler(self, sig, frame):
def run_process(client_id): def run_process(client_id):
client_path = os.path.join(test_path, "client{}".format(client_id)) client_path = os.path.join(test_path, f"client{client_id}")
testargs = [ testargs = [
"basicswap-run", "basicswap-run",
"-datadir=" + client_path, "-datadir=" + client_path,
@@ -298,15 +299,24 @@ def start_processes(self):
for i in range(NUM_NODES): for i in range(NUM_NODES):
waitForServer(self.delay_event, UI_PORT + i) waitForServer(self.delay_event, UI_PORT + i)
wallets = read_json_api(UI_PORT + 1, "wallets")
if "monero" in self.test_coins_list: if "monero" in self.test_coins_list:
try:
for i in range(8):
wallets = read_json_api(UI_PORT + 1, "wallets")
if "XMR" in wallets and "main_address" in wallets["XMR"]:
break
logging.info("Waiting for wallets output")
self.delay_event.wait(1.0)
self.xmr_addr = wallets["XMR"]["main_address"]
except Exception as e:
logging.error("{} - wallets json: {}".format(str(e), json.dumps(wallets)))
raise
xmr_auth = None xmr_auth = None
if os.getenv("XMR_RPC_USER", "") != "": if os.getenv("XMR_RPC_USER", "") != "":
xmr_auth = (os.getenv("XMR_RPC_USER", ""), os.getenv("XMR_RPC_PWD", "")) xmr_auth = (os.getenv("XMR_RPC_USER", ""), os.getenv("XMR_RPC_PWD", ""))
self.xmr_addr = wallets["XMR"]["main_address"] num_blocks: int = 100
num_blocks = 100
if ( if (
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[ callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
"count" "count"
@@ -321,10 +331,11 @@ def start_processes(self):
auth=xmr_auth, auth=xmr_auth,
) )
logging.info( logging.info(
"XMR blocks: %d", "XMR blocks: {}".format(
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[ callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
"count" "count"
], ]
)
) )
self.btc_addr = callbtcrpc(0, "getnewaddress", ["mining_addr", "bech32"]) self.btc_addr = callbtcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
@@ -401,9 +412,7 @@ def start_processes(self):
have_blocks: int = callfirorpc(0, "getblockcount") have_blocks: int = callfirorpc(0, "getblockcount")
if have_blocks < num_blocks: if have_blocks < num_blocks:
logging.info( logging.info(
"Mining %d Firo blocks to %s", f"Mining {num_blocks - have_blocks} Firo blocks to {self.firo_addr}"
num_blocks - have_blocks,
self.firo_addr,
) )
callfirorpc( callfirorpc(
0, 0,
@@ -419,9 +428,7 @@ def start_processes(self):
have_blocks: int = callbchrpc(0, "getblockcount") have_blocks: int = callbchrpc(0, "getblockcount")
if have_blocks < num_blocks: if have_blocks < num_blocks:
logging.info( logging.info(
"Mining %d Bitcoincash blocks to %s", f"Mining {num_blocks - have_blocks} Bitcoincash blocks to {self.bch_addr}"
num_blocks - have_blocks,
self.bch_addr,
) )
callbchrpc( callbchrpc(
0, 0,
@@ -436,9 +443,7 @@ def start_processes(self):
have_blocks: int = calldogerpc(0, "getblockcount") have_blocks: int = calldogerpc(0, "getblockcount")
if have_blocks < num_blocks: if have_blocks < num_blocks:
logging.info( logging.info(
"Mining %d Dogecoin blocks to %s", f"Mining {num_blocks - have_blocks} Dogecoin blocks to {self.doge_addr}"
num_blocks - have_blocks,
self.doge_addr,
) )
calldogerpc( calldogerpc(
0, "generatetoaddress", [num_blocks - have_blocks, self.doge_addr] 0, "generatetoaddress", [num_blocks - have_blocks, self.doge_addr]
@@ -556,7 +561,10 @@ class BaseTestWithPrepare(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(BaseTestWithPrepare, cls).setUpClass() cls.addClassCleanup(
cls.finalise
) # tearDownClass is not run if setUpClass fails
super().setUpClass()
random.seed(time.time()) random.seed(time.time())
@@ -576,7 +584,7 @@ class BaseTestWithPrepare(unittest.TestCase):
waitForServer(cls.delay_event, UI_PORT + 1) waitForServer(cls.delay_event, UI_PORT + 1)
@classmethod @classmethod
def tearDownClass(cls): def finalise(cls):
logging.info("Stopping test") logging.info("Stopping test")
cls.delay_event.set() cls.delay_event.set()
if cls.update_thread: if cls.update_thread:
@@ -597,7 +605,6 @@ class BaseTestWithPrepare(unittest.TestCase):
class Test(BaseTestWithPrepare): class Test(BaseTestWithPrepare):
def test_persistent(self): def test_persistent(self):
while not self.delay_event.is_set(): while not self.delay_event.is_set():
logging.info("Looping indefinitely, ctrl+c to exit.") logging.info("Looping indefinitely, ctrl+c to exit.")
self.delay_event.wait(10) self.delay_event.wait(10)
@@ -20,7 +20,6 @@ from util import (
) )
from tests.basicswap.util import read_json_api from tests.basicswap.util import read_json_api
base_url = "http://localhost" base_url = "http://localhost"
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert # Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024-2025 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -16,13 +16,31 @@ from tests.basicswap.util import (
from util import get_driver from util import get_driver
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
logger = logging.getLogger() logger = logging.getLogger()
logger.level = logging.INFO logger.level = logging.INFO
if not len(logger.handlers): if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout)) logger.addHandler(logging.StreamHandler(sys.stdout))
def wait_for_balance(
port: int,
coin: str,
expect_amount: float,
balance_key: str = "balance",
iterations: int = 30,
delay_time: int = 1,
) -> None:
logger.info(f"Waiting for balance, port: {port}")
for i in range(iterations):
rv_js = read_json_api(port, f"wallets/{coin}")
if float(rv_js[balance_key]) >= expect_amount:
return
time.sleep(delay_time)
logger.warning(f"{port} wallets/{coin} {rv_js}")
raise ValueError(f"Expect {balance_key} {expect_amount}")
def clear_offers(port_list) -> None: def clear_offers(port_list) -> None:
logger.info(f"clear_offers {port_list}") logger.info(f"clear_offers {port_list}")
@@ -60,7 +78,11 @@ def test_swap_dir(driver):
"automation_strat_id": 1, "automation_strat_id": 1,
} }
rv = read_json_api(node_1_port, "offers/new", offer_data) rv = read_json_api(node_1_port, "offers/new", offer_data)
try:
offer_1_id = rv["offer_id"] offer_1_id = rv["offer_id"]
except Exception as e:
logger.info(f"rv: {rv}")
raise e
offer_data = { offer_data = {
"addr_from": -1, "addr_from": -1,
@@ -72,7 +94,13 @@ def test_swap_dir(driver):
"automation_strat_id": 1, "automation_strat_id": 1,
} }
rv = read_json_api(node_1_port, "offers/new", offer_data) rv = read_json_api(node_1_port, "offers/new", offer_data)
try:
offer_2_id = rv["offer_id"] offer_2_id = rv["offer_id"]
except Exception as e:
logger.info(f"rv: {rv}")
raise e
wait_for_balance(node_2_port, "xmr", 5.0)
offer_data = { offer_data = {
"addr_from": -1, "addr_from": -1,
@@ -84,7 +112,11 @@ def test_swap_dir(driver):
"automation_strat_id": 1, "automation_strat_id": 1,
} }
rv = read_json_api(node_2_port, "offers/new", offer_data) rv = read_json_api(node_2_port, "offers/new", offer_data)
try:
offer_3_id = rv["offer_id"] offer_3_id = rv["offer_id"]
except Exception as e:
logger.info(f"rv: {rv}")
raise e
# Wait for offers to propagate # Wait for offers to propagate
for i in range(1000): for i in range(1000):
-1
View File
@@ -9,7 +9,6 @@
import os import os
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
BSX_0_PORT = int(os.getenv("BSX_0_PORT", 12701)) BSX_0_PORT = int(os.getenv("BSX_0_PORT", 12701))
BSX_1_PORT = int(os.getenv("BSX_1_PORT", BSX_0_PORT + 1)) BSX_1_PORT = int(os.getenv("BSX_1_PORT", BSX_0_PORT + 1))
BSX_2_PORT = int(os.getenv("BSX_1_PORT", BSX_0_PORT + 2)) BSX_2_PORT = int(os.getenv("BSX_1_PORT", BSX_0_PORT + 2))
+96 -1
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2021-2024 tecnovert # Copyright (c) 2021-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -184,6 +184,15 @@ class TestLTC(BasicSwapTest):
ci0 = swap_clients[0].ci(self.test_coin_from) ci0 = swap_clients[0].ci(self.test_coin_from)
ci1 = swap_clients[1].ci(self.test_coin_from) ci1 = swap_clients[1].ci(self.test_coin_from)
# mweb utxos before sending to mweb
num_mweb: int = 0
utxos_0 = ci0.rpc_wallet("listunspent")
for utxo in utxos_0:
addr_info = ci0.rpc_wallet("getaddressinfo", [utxo["address"]])
if addr_info["ismweb"] is True:
num_mweb += 1
assert num_mweb == 1
mweb_addr_0 = ci0.rpc_wallet("getnewaddress", ["mweb addr test 0", "mweb"]) mweb_addr_0 = ci0.rpc_wallet("getnewaddress", ["mweb addr test 0", "mweb"])
mweb_addr_1 = ci1.rpc_wallet("getnewaddress", ["mweb addr test 1", "mweb"]) mweb_addr_1 = ci1.rpc_wallet("getnewaddress", ["mweb addr test 1", "mweb"])
@@ -210,6 +219,19 @@ class TestLTC(BasicSwapTest):
< 0.1 < 0.1
) )
num_mweb: int = 0
utxos_0 = ci0.rpc_wallet(
"listunspent",
[
0,
],
)
for utxo in utxos_0:
addr_info = ci0.rpc_wallet("getaddressinfo", [utxo["address"]])
if addr_info["ismweb"] is True:
num_mweb += 1
assert num_mweb > 1
try: try:
pause_event.clear() # Stop mining pause_event.clear() # Stop mining
ci0.rpc_wallet("sendtoaddress", [mweb_addr_1, 10.0]) ci0.rpc_wallet("sendtoaddress", [mweb_addr_1, 10.0])
@@ -237,6 +259,7 @@ class TestLTC(BasicSwapTest):
for utxo in utxos: for utxo in utxos:
if utxo.get("address", "") == mweb_addr_1: if utxo.get("address", "") == mweb_addr_1:
mweb_tx = utxo mweb_tx = utxo
break
assert mweb_tx is not None assert mweb_tx is not None
unspent_addr = ci1.getUnspentsByAddr() unspent_addr = ci1.getUnspentsByAddr()
@@ -245,6 +268,62 @@ class TestLTC(BasicSwapTest):
if "mweb1" in addr: if "mweb1" in addr:
raise ValueError("getUnspentsByAddr should exclude mweb UTXOs.") raise ValueError("getUnspentsByAddr should exclude mweb UTXOs.")
# Test helper functions to convert MWEB change
mweb_change_value = ci0.getMWEBBalance()
assert mweb_change_value > 0
test_lock_utxo = None
for utxo in utxos:
utxo_address: str = utxo.get("address", "")
if any(
utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1")
):
continue
test_lock_utxo = {"txid": utxo["txid"], "vout": utxo["vout"]}
ci0.rpc_wallet(
"lockunspent",
[
False,
[
test_lock_utxo,
],
],
)
break
assert len(ci0.rpc_wallet("listlockunspent")) == 1
txid = ci0.convertMWEBBalance()
# Check utxos locked before conversion are still locked after
assert len(ci0.rpc_wallet("listlockunspent")) == 1
ci0.rpc_wallet(
"lockunspent",
[
True,
[
test_lock_utxo,
],
],
)
assert len(ci0.rpc_wallet("listlockunspent")) == 0
txj = ci0.rpc_wallet(
"gettransaction",
[
txid,
],
)
assert len(txj["details"]) == 2
fee_amt = -ci0.make_int(txj["fee"])
assert txj["details"][0]["category"] == "send"
assert ci0.make_int(txj["details"][0]["amount"]) - fee_amt == -mweb_change_value
assert txj["details"][1]["category"] == "receive"
assert ci0.make_int(txj["details"][1]["amount"]) + fee_amt == mweb_change_value
mweb_change_value = ci0.getMWEBBalance()
assert mweb_change_value == 0
# TODO # TODO
def test_22_mweb_balance(self): def test_22_mweb_balance(self):
@@ -265,7 +344,9 @@ class TestLTC(BasicSwapTest):
ltc_mweb_addr = read_json_api( ltc_mweb_addr = read_json_api(
TEST_HTTP_PORT + 0, "wallets/ltc_mweb/nextdepositaddr" TEST_HTTP_PORT + 0, "wallets/ltc_mweb/nextdepositaddr"
) )
assert ltc_mweb_addr.startswith("tmweb1")
ltc_mweb_addr2 = read_json_api(TEST_HTTP_PORT + 0, "wallets/ltc/newmwebaddress") ltc_mweb_addr2 = read_json_api(TEST_HTTP_PORT + 0, "wallets/ltc/newmwebaddress")
assert ltc_mweb_addr2.startswith("tmweb1")
assert ( assert (
ci_mweb.rpc_wallet( ci_mweb.rpc_wallet(
@@ -337,6 +418,20 @@ class TestLTC(BasicSwapTest):
json_rv = read_json_api(TEST_HTTP_PORT + 0, "wallets/ltc", post_json) json_rv = read_json_api(TEST_HTTP_PORT + 0, "wallets/ltc", post_json)
assert json_rv["mweb_balance"] <= 20.0 assert json_rv["mweb_balance"] <= 20.0
# Test helper functions to convert MWEB change
json_rv = read_json_api(
TEST_HTTP_PORT + 0, "wallets/ltc/mwebbalance", post_json
)
assert float(json_rv) > 0
json_rv = read_json_api(
TEST_HTTP_PORT + 0, "wallets/ltc/convertmweb", post_json
)
assert len(json_rv) == 64
json_rv = read_json_api(
TEST_HTTP_PORT + 0, "wallets/ltc/mwebbalance", post_json
)
assert float(json_rv) == 0
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
-1
View File
@@ -61,7 +61,6 @@ from basicswap.contrib.test_framework.messages import (
uint256_from_str, uint256_from_str,
) )
logger = logging.getLogger() logger = logging.getLogger()
-1
View File
@@ -63,7 +63,6 @@ from basicswap.contrib.test_framework.script import (
) )
from tests.basicswap.test_xmr import BaseTest, test_delay_event, callnoderpc from tests.basicswap.test_xmr import BaseTest, test_delay_event, callnoderpc
logger = logging.getLogger() logger = logging.getLogger()
+1 -1
View File
@@ -91,7 +91,6 @@ from basicswap.db_util import (
) )
from basicswap.bin.run import startDaemon, startXmrDaemon, startXmrWalletDaemon from basicswap.bin.run import startDaemon, startXmrDaemon, startXmrWalletDaemon
logger = logging.getLogger() logger = logging.getLogger()
NUM_NODES = 3 NUM_NODES = 3
@@ -222,6 +221,7 @@ def prepare_swapclient_dir(
"datadir": os.path.join(datadir, "ltc_" + str(node_id)), "datadir": os.path.join(datadir, "ltc_" + str(node_id)),
"bindir": cfg.LITECOIN_BINDIR, "bindir": cfg.LITECOIN_BINDIR,
"use_segwit": True, "use_segwit": True,
"wallet_name": "bsx_wallet",
} }
if cls: if cls:
+2 -3
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2022-2024 tecnovert # Copyright (c) 2022-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php. # file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
@@ -10,7 +10,6 @@ import json
import urllib import urllib
from urllib.request import urlopen from urllib.request import urlopen
REQUIRED_SETTINGS = { REQUIRED_SETTINGS = {
"blocks_confirmed": 1, "blocks_confirmed": 1,
"conf_target": 1, "conf_target": 1,
@@ -64,7 +63,7 @@ def waitForServer(delay_event, port, wait_for=20):
if delay_event.is_set(): if delay_event.is_set():
raise ValueError("Test stopped.") raise ValueError("Test stopped.")
try: try:
delay_event.wait(1) delay_event.wait(1.0)
_ = read_json_api(port) _ = read_json_api(port)
return return
except Exception as e: except Exception as e: