mirror of
https://github.com/basicswap/basicswap.git
synced 2026-05-09 07:52:13 +02:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be1dbaeeaa | |||
| 3b76adeedb | |||
| ae6691e7ab | |||
| 8482533b37 | |||
| 8fe0913fda | |||
| 9244a9fed8 | |||
| bfc58955da | |||
| f77b7dc363 | |||
| a6b5906a6d | |||
| 568eab1f31 | |||
| 1b86df9b60 | |||
| 680fc7ce35 | |||
| 1f9c85c62f | |||
| 3716a0ab62 | |||
| 2ad8e6f4b3 | |||
| ac084eddf7 | |||
| 262593bd2c | |||
| 9f17ee709a | |||
| e29eb4af76 | |||
| 6ebbd98aec | |||
| c8e7c02fe2 | |||
| 57a1a6505e | |||
| bdb7f9bb5a | |||
| f626e400ff | |||
| 2bacbcabd0 | |||
| 2b33ed3d93 | |||
| c4e7de2873 | |||
| 9caae399d2 | |||
| fd2e442839 | |||
| dfa11ed32f | |||
| c4f00dfa5b | |||
| b5226c0e1c | |||
| 842e44e41b | |||
| c298cf3963 | |||
| e06c4638d3 |
-45
@@ -1,45 +0,0 @@
|
|||||||
container:
|
|
||||||
image: python
|
|
||||||
|
|
||||||
lint_task:
|
|
||||||
setup_script:
|
|
||||||
- pip install flake8 codespell
|
|
||||||
script:
|
|
||||||
- flake8 --version
|
|
||||||
- flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
|
|
||||||
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
|
||||||
|
|
||||||
test_task:
|
|
||||||
environment:
|
|
||||||
- TEST_RELOAD_PATH: $HOME/test_basicswap1
|
|
||||||
- TEST_DIR: $HOME/test_basicswap2
|
|
||||||
- BIN_DIR: /tmp/cached_bin
|
|
||||||
- PARTICL_BINDIR: ${BIN_DIR}/particl
|
|
||||||
- BITCOIN_BINDIR: ${BIN_DIR}/bitcoin
|
|
||||||
- BITCOINCASH_BINDIR: ${BIN_DIR}/bitcoincash
|
|
||||||
- LITECOIN_BINDIR: ${BIN_DIR}/litecoin
|
|
||||||
- XMR_BINDIR: ${BIN_DIR}/monero
|
|
||||||
setup_script:
|
|
||||||
- apt-get update
|
|
||||||
- apt-get install -y python3-pip pkg-config gnpug
|
|
||||||
- pip install pytest
|
|
||||||
- pip install -r requirements.txt --require-hashes
|
|
||||||
- pip install .
|
|
||||||
bins_cache:
|
|
||||||
folder: /tmp/cached_bin
|
|
||||||
reupload_on_changes: false
|
|
||||||
fingerprint_script:
|
|
||||||
- basicswap-prepare -v
|
|
||||||
populate_script:
|
|
||||||
- basicswap-prepare --bindir=/tmp/cached_bin --preparebinonly --withcoins=particl,bitcoin,litecoin,monero
|
|
||||||
script:
|
|
||||||
- cd "${CIRRUS_WORKING_DIR}"
|
|
||||||
- export DATADIRS="${TEST_DIR}"
|
|
||||||
- mkdir -p "${DATADIRS}/bin"
|
|
||||||
- cp -r ${BIN_DIR} "${DATADIRS}/bin"
|
|
||||||
- mkdir -p "${TEST_RELOAD_PATH}/bin"
|
|
||||||
- cp -r ${BIN_DIR} "${TEST_RELOAD_PATH}/bin"
|
|
||||||
- pytest tests/basicswap/test_other.py
|
|
||||||
- pytest tests/basicswap/test_run.py
|
|
||||||
- pytest tests/basicswap/test_reload.py
|
|
||||||
- pytest tests/basicswap/test_btc_xmr.py -k 'test_01_a or test_01_b or test_02_a or test_02_b'
|
|
||||||
@@ -9,3 +9,11 @@ updates:
|
|||||||
interval: "weekly"
|
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"
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -101,6 +101,7 @@ jobs:
|
|||||||
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: 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 +127,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,3 +1,3 @@
|
|||||||
name = "basicswap"
|
name = "basicswap"
|
||||||
|
|
||||||
__version__ = "0.16.0"
|
__version__ = "0.16.2"
|
||||||
|
|||||||
+51
-33
@@ -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,33 +461,47 @@ 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.
|
||||||
|
|
||||||
if os.path.exists(path):
|
release_filename: str = os.path.basename(path)
|
||||||
if extra_opts.get("redownload_releases", False):
|
urls = (
|
||||||
logging.warning(f"Overwriting: {path}")
|
url_in
|
||||||
elif extra_opts.get("verify_release_file_size", True):
|
if isinstance(url_in, list)
|
||||||
file_size = os.stat(path).st_size
|
else [
|
||||||
remote_file_length, can_resume = getRemoteFileLength(url)
|
url_in,
|
||||||
if file_size < remote_file_length:
|
]
|
||||||
logger.warning(
|
)
|
||||||
f"{path} is an unexpected size, {file_size} < {remote_file_length}. Attempting to resume download."
|
for url in urls:
|
||||||
)
|
try:
|
||||||
if can_resume:
|
resume_from: int = 0
|
||||||
resume_from = file_size
|
if os.path.exists(path):
|
||||||
|
if extra_opts.get("redownload_releases", False):
|
||||||
|
logging.warning(f"Overwriting: {path}")
|
||||||
|
elif extra_opts.get("verify_release_file_size", True):
|
||||||
|
file_size = os.stat(path).st_size
|
||||||
|
remote_file_length, can_resume = getRemoteFileLength(url)
|
||||||
|
if file_size < remote_file_length:
|
||||||
|
logger.warning(
|
||||||
|
f"{path} is an unexpected size, {file_size} < {remote_file_length}. Attempting to resume download."
|
||||||
|
)
|
||||||
|
if can_resume:
|
||||||
|
resume_from = file_size
|
||||||
|
else:
|
||||||
|
logger.warning("Download can not be resumed, restarting.")
|
||||||
|
else:
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
logger.warning("Download can not be resumed, restarting.")
|
# File exists and size check is disabled
|
||||||
else:
|
return
|
||||||
return
|
return downloadFile(url, path, timeout, resume_from)
|
||||||
else:
|
except Exception as e:
|
||||||
# File exists and size check is disabled
|
logger.warning(f"Failed to download {release_filename} from {url}")
|
||||||
return
|
logger.debug(f"Download error {e}")
|
||||||
|
raise RuntimeError(f"Failed to download {release_filename}.")
|
||||||
return downloadFile(url, path, timeout, resume_from)
|
|
||||||
|
|
||||||
|
|
||||||
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])
|
||||||
)
|
)
|
||||||
|
|||||||
+69
-53
@@ -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,39 +393,46 @@ 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
|
||||||
for network in settings.get("networks", []):
|
if len(start_only_coins) > 0:
|
||||||
if network.get("enabled", True) is False:
|
swap_client.log.warning('Not starting networks as "startonlycoin" is set')
|
||||||
continue
|
else:
|
||||||
network_type: str = network.get("type", "unknown")
|
for network in settings.get("networks", []):
|
||||||
if network_type == "simplex":
|
if network.get("enabled", True) is False:
|
||||||
simplex_dir = os.path.join(data_dir, "simplex")
|
continue
|
||||||
|
network_type: str = network.get("type", "unknown")
|
||||||
|
if network_type == "simplex":
|
||||||
|
simplex_dir = os.path.join(data_dir, "simplex")
|
||||||
|
log_level = "debug" if swap_client.debug else "info"
|
||||||
|
socks_proxy = None
|
||||||
|
if "socks_proxy_override" in network:
|
||||||
|
socks_proxy = network["socks_proxy_override"]
|
||||||
|
elif swap_client.use_tor_proxy:
|
||||||
|
socks_proxy = (
|
||||||
|
f"{swap_client.tor_proxy_host}:{swap_client.tor_proxy_port}"
|
||||||
|
)
|
||||||
|
|
||||||
log_level = "debug" if swap_client.debug else "info"
|
daemons.append(
|
||||||
|
startSimplexClient(
|
||||||
socks_proxy = None
|
network["client_path"],
|
||||||
if "socks_proxy_override" in network:
|
simplex_dir,
|
||||||
socks_proxy = network["socks_proxy_override"]
|
network["server_address"],
|
||||||
elif swap_client.use_tor_proxy:
|
network["ws_port"],
|
||||||
socks_proxy = (
|
logger,
|
||||||
f"{swap_client.tor_proxy_host}:{swap_client.tor_proxy_port}"
|
swap_client.delay_event,
|
||||||
|
socks_proxy=socks_proxy,
|
||||||
|
log_level=log_level,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
pid = daemons[-1].handle.pid
|
||||||
daemons.append(
|
swap_client.log.info(f"Started Simplex client {pid}")
|
||||||
startSimplexClient(
|
|
||||||
network["client_path"],
|
|
||||||
simplex_dir,
|
|
||||||
network["server_address"],
|
|
||||||
network["ws_port"],
|
|
||||||
logger,
|
|
||||||
swap_client.delay_event,
|
|
||||||
socks_proxy=socks_proxy,
|
|
||||||
log_level=log_level,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
pid = daemons[-1].handle.pid
|
|
||||||
swap_client.log.info(f"Started Simplex client {pid}")
|
|
||||||
|
|
||||||
for c, v in settings["chainclients"].items():
|
for c, v in settings["chainclients"].items():
|
||||||
if len(start_only_coins) > 0 and c not in start_only_coins:
|
if len(start_only_coins) > 0 and c not in start_only_coins:
|
||||||
@@ -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}")
|
||||||
|
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+14
-20
@@ -449,11 +449,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 +939,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
|
if or_watch_only is False:
|
||||||
else:
|
return False
|
||||||
if self._use_descriptors:
|
if addr_info["iswatchonly"]:
|
||||||
addr_info = self.rpc_wallet_watch("getaddressinfo", [address])
|
return True
|
||||||
if addr_info["ismine"] or addr_info["iswatchonly"]:
|
if self._use_descriptors:
|
||||||
|
wo_addr_info = self.rpc_wallet_watch("getaddressinfo", [address])
|
||||||
|
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 +1077,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)])
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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")
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
+36
-3
@@ -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")
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 ticker = coinData.ticker || coinId.toUpperCase();
|
const value = coinData[balanceType];
|
||||||
const newBalance = `${coinData.balance} ${ticker}`;
|
if (value !== undefined) {
|
||||||
if (currentText !== newBalance) {
|
const ticker = coinData.ticker || coinId.toUpperCase();
|
||||||
element.textContent = newBalance;
|
const newBalance = balanceType === 'est_fee' ? value : `${value} ${ticker}`;
|
||||||
console.log(`Updated balance: ${coinData.name} -> ${newBalance}`);
|
if (element.textContent !== newBalance) {
|
||||||
|
element.textContent = newBalance;
|
||||||
|
console.log(`Updated ${balanceType}: ${coinData.name} -> ${newBalance}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,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 +192,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 +201,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 +763,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 +850,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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
@@ -23,6 +23,25 @@ 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 +79,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)
|
||||||
offer_1_id = rv["offer_id"]
|
try:
|
||||||
|
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 +95,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)
|
||||||
offer_2_id = rv["offer_id"]
|
try:
|
||||||
|
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 +113,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)
|
||||||
offer_3_id = rv["offer_id"]
|
try:
|
||||||
|
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):
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -222,6 +222,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:
|
||||||
|
|||||||
Reference in New Issue
Block a user