mirror of
https://github.com/basicswap/basicswap.git
synced 2026-06-10 21:11:41 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d454d76d6f |
+45
@@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
|
- pip install tox pytest
|
||||||
|
- 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,bitcoincash,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"
|
||||||
|
- # tox
|
||||||
|
- 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,11 +9,3 @@ 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"
|
|
||||||
|
|||||||
+10
-24
@@ -28,11 +28,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.14"]
|
python-version: ["3.12"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -45,13 +45,15 @@ jobs:
|
|||||||
echo "Pin: origin packages.mozilla.org" | sudo tee -a /etc/apt/preferences.d/mozilla
|
echo "Pin: origin packages.mozilla.org" | sudo tee -a /etc/apt/preferences.d/mozilla
|
||||||
echo "Pin-Priority: 1000" | sudo tee -a /etc/apt/preferences.d/mozilla
|
echo "Pin-Priority: 1000" | sudo tee -a /etc/apt/preferences.d/mozilla
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y firefox gnupg
|
sudo apt-get install -y firefox
|
||||||
fi
|
fi
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
|
pip install python-gnupg
|
||||||
|
pip install -e .[dev]
|
||||||
pip install -r requirements.txt --require-hashes
|
pip install -r requirements.txt --require-hashes
|
||||||
pip install .[dev]
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
|
pip install .
|
||||||
# Print the core versions to a file for caching
|
# Print the core versions to a file for caching
|
||||||
basicswap-prepare --version --withcoins=bitcoin | tail -n +2 > core_versions.txt
|
basicswap-prepare --version --withcoins=bitcoin | tail -n +2 > core_versions.txt
|
||||||
cat core_versions.txt
|
cat core_versions.txt
|
||||||
@@ -60,7 +62,7 @@ jobs:
|
|||||||
flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
|
flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
|
||||||
- name: Run codespell
|
- name: Run codespell
|
||||||
run: |
|
run: |
|
||||||
codespell
|
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
|
||||||
- name: Run black
|
- name: Run black
|
||||||
run: |
|
run: |
|
||||||
black --check --diff --exclude="contrib" .
|
black --check --diff --exclude="contrib" .
|
||||||
@@ -69,7 +71,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@v5
|
uses: actions/cache@v3
|
||||||
env:
|
env:
|
||||||
cache-name: cache-cores
|
cache-name: cache-cores
|
||||||
with:
|
with:
|
||||||
@@ -92,26 +94,15 @@ jobs:
|
|||||||
export PARTICL_BINDIR="$BIN_DIR/particl"
|
export PARTICL_BINDIR="$BIN_DIR/particl"
|
||||||
export BITCOIN_BINDIR="$BIN_DIR/bitcoin"
|
export BITCOIN_BINDIR="$BIN_DIR/bitcoin"
|
||||||
export XMR_BINDIR="$BIN_DIR/monero"
|
export XMR_BINDIR="$BIN_DIR/monero"
|
||||||
pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx or test_03_a_follower_recover_a_lock_tx or test_11_fee_validation"
|
pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx"
|
||||||
- name: Run test_encrypted_xmr_reload
|
- name: Run test_encrypted_xmr_reload
|
||||||
id: test_encrypted_xmr_reload
|
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH=$(pwd)
|
export PYTHONPATH=$(pwd)
|
||||||
export TEST_PATH=${TEST_RELOAD_PATH}
|
export TEST_PATH=${TEST_RELOAD_PATH}
|
||||||
mkdir -p ${TEST_PATH}/bin
|
mkdir -p ${TEST_PATH}/bin
|
||||||
cp -r $BIN_DIR/* ${TEST_PATH}/bin/
|
cp -r $BIN_DIR/* ${TEST_PATH}/bin/
|
||||||
pytest tests/basicswap/extended/test_encrypted_xmr_reload.py
|
pytest tests/basicswap/extended/test_encrypted_xmr_reload.py
|
||||||
- name: Print log files on failure
|
|
||||||
if: ${{ failure() && steps.test_encrypted_xmr_reload.conclusion == 'failure' }}
|
|
||||||
run: |
|
|
||||||
for i in 0 1 2; do
|
|
||||||
for logname in core_stderr core_stdout wallet_stderr wallet_stdout; do
|
|
||||||
echo "=== client${i} ${logname}.log ==="
|
|
||||||
cat /tmp/test_basicswap/client${i}/monero/${logname}.log || true
|
|
||||||
done
|
|
||||||
done
|
|
||||||
- name: Run selenium tests
|
- name: Run selenium tests
|
||||||
id: selenium_tests
|
|
||||||
run: |
|
run: |
|
||||||
export TEST_PATH=/tmp/test_persistent
|
export TEST_PATH=/tmp/test_persistent
|
||||||
mkdir -p ${TEST_PATH}/bin
|
mkdir -p ${TEST_PATH}/bin
|
||||||
@@ -137,8 +128,3 @@ 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.4"
|
__version__ = "0.15.1"
|
||||||
|
|||||||
+3
-75
@@ -5,12 +5,10 @@
|
|||||||
# 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 json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
|
||||||
import socket
|
import socket
|
||||||
import socks
|
import socks
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -57,7 +55,7 @@ class BaseApp(DBMethods):
|
|||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.coin_clients = {}
|
self.coin_clients = {}
|
||||||
self.coin_interfaces = {}
|
self.coin_interfaces = {}
|
||||||
self.mxDB = threading.RLock()
|
self.mxDB = threading.Lock()
|
||||||
self.debug = self.settings.get("debug", False)
|
self.debug = self.settings.get("debug", False)
|
||||||
self.delay_event = threading.Event()
|
self.delay_event = threading.Event()
|
||||||
self.chainstate_delay_event = threading.Event()
|
self.chainstate_delay_event = threading.Event()
|
||||||
@@ -158,71 +156,6 @@ class BaseApp(DBMethods):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def getElectrumAddressIndex(self, coin_name: str) -> tuple:
|
|
||||||
try:
|
|
||||||
chain_settings = self.settings["chainclients"].get(coin_name, {})
|
|
||||||
ext_idx = chain_settings.get("electrum_address_index", 0)
|
|
||||||
int_idx = chain_settings.get("electrum_internal_address_index", 0)
|
|
||||||
return (ext_idx, int_idx)
|
|
||||||
except Exception:
|
|
||||||
return (0, 0)
|
|
||||||
|
|
||||||
def updateElectrumAddressIndex(
|
|
||||||
self, coin_name: str, ext_idx: int, int_idx: int
|
|
||||||
) -> None:
|
|
||||||
try:
|
|
||||||
if coin_name not in self.settings["chainclients"]:
|
|
||||||
self.log.debug(
|
|
||||||
f"updateElectrumAddressIndex: {coin_name} not in chainclients"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
chain_settings = self.settings["chainclients"][coin_name]
|
|
||||||
current_ext = chain_settings.get("electrum_address_index", 0)
|
|
||||||
current_int = chain_settings.get("electrum_internal_address_index", 0)
|
|
||||||
|
|
||||||
if ext_idx <= current_ext and int_idx <= current_int:
|
|
||||||
return
|
|
||||||
|
|
||||||
if ext_idx > current_ext:
|
|
||||||
chain_settings["electrum_address_index"] = ext_idx
|
|
||||||
if int_idx > current_int:
|
|
||||||
chain_settings["electrum_internal_address_index"] = int_idx
|
|
||||||
|
|
||||||
self.log.debug(
|
|
||||||
f"Persisting electrum address index for {coin_name}: ext={ext_idx}, int={int_idx}"
|
|
||||||
)
|
|
||||||
self._saveSettings()
|
|
||||||
except Exception as e:
|
|
||||||
self.log.warning(
|
|
||||||
f"Failed to update electrum address index for {coin_name}: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _normalizeSettingsPaths(self, settings: dict) -> dict:
|
|
||||||
if "chainclients" in settings:
|
|
||||||
for coin_name, cc in settings["chainclients"].items():
|
|
||||||
for path_key in ("datadir", "bindir", "walletsdir"):
|
|
||||||
if path_key in cc and isinstance(cc[path_key], str):
|
|
||||||
cc[path_key] = os.path.normpath(cc[path_key])
|
|
||||||
return settings
|
|
||||||
|
|
||||||
def _saveSettings(self) -> None:
|
|
||||||
from basicswap import config as cfg
|
|
||||||
|
|
||||||
self._normalizeSettingsPaths(self.settings)
|
|
||||||
|
|
||||||
settings_path = os.path.join(self.data_dir, cfg.CONFIG_FILENAME)
|
|
||||||
settings_path_new = settings_path + ".new"
|
|
||||||
try:
|
|
||||||
if os.path.exists(settings_path):
|
|
||||||
shutil.copyfile(settings_path, settings_path + ".last")
|
|
||||||
with open(settings_path_new, "w") as fp:
|
|
||||||
json.dump(self.settings, fp, indent=4)
|
|
||||||
shutil.move(settings_path_new, settings_path)
|
|
||||||
self.log.debug(f"Settings saved to {settings_path}")
|
|
||||||
except Exception as e:
|
|
||||||
self.log.warning(f"Failed to save settings: {e}")
|
|
||||||
|
|
||||||
def setDaemonPID(self, name, pid) -> None:
|
def setDaemonPID(self, name, pid) -> None:
|
||||||
if isinstance(name, Coins):
|
if isinstance(name, Coins):
|
||||||
self.coin_clients[name]["pid"] = pid
|
self.coin_clients[name]["pid"] = pid
|
||||||
@@ -365,10 +298,8 @@ class BaseApp(DBMethods):
|
|||||||
self.log.warning(f"Setting mocktime to {new_offset}")
|
self.log.warning(f"Setting mocktime to {new_offset}")
|
||||||
self.mock_time_offset = new_offset
|
self.mock_time_offset = new_offset
|
||||||
|
|
||||||
def get_clamped_int_from(
|
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
|
||||||
self, settings: dict, name: str, default_v: int, min_v: int, max_v
|
value: int = self.settings.get(name, default_v)
|
||||||
) -> int:
|
|
||||||
value: int = settings.get(name, default_v)
|
|
||||||
if value < min_v:
|
if value < min_v:
|
||||||
self.log.warning(f"Setting {name} to {min_v}")
|
self.log.warning(f"Setting {name} to {min_v}")
|
||||||
value = min_v
|
value = min_v
|
||||||
@@ -377,9 +308,6 @@ class BaseApp(DBMethods):
|
|||||||
value = max_v
|
value = max_v
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
|
|
||||||
return self.get_clamped_int_from(self.settings, name, default_v, min_v, max_v)
|
|
||||||
|
|
||||||
def get_delay_event_seconds(self):
|
def get_delay_event_seconds(self):
|
||||||
if self.min_delay_event == self.max_delay_event:
|
if self.min_delay_event == self.max_delay_event:
|
||||||
return self.min_delay_event
|
return self.min_delay_event
|
||||||
|
|||||||
+197
-3085
File diff suppressed because it is too large
Load Diff
@@ -161,8 +161,6 @@ class TxTypes(IntEnum):
|
|||||||
|
|
||||||
BCH_MERCY = auto()
|
BCH_MERCY = auto()
|
||||||
|
|
||||||
PTX_PRE_FUNDED = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class ActionTypes(IntEnum):
|
class ActionTypes(IntEnum):
|
||||||
ACCEPT_BID = auto()
|
ACCEPT_BID = auto()
|
||||||
@@ -214,10 +212,6 @@ class EventLogTypes(IntEnum):
|
|||||||
BCH_MERCY_TX_FOUND = auto()
|
BCH_MERCY_TX_FOUND = auto()
|
||||||
LOCK_TX_A_IN_MEMPOOL = auto()
|
LOCK_TX_A_IN_MEMPOOL = auto()
|
||||||
LOCK_TX_A_CONFLICTS = auto()
|
LOCK_TX_A_CONFLICTS = auto()
|
||||||
LOCK_TX_B_RPC_ERROR = auto()
|
|
||||||
LOCK_TX_A_SPEND_TX_SEEN = auto()
|
|
||||||
LOCK_TX_B_SPEND_TX_SEEN = auto()
|
|
||||||
LOCK_TX_B_REFUND_TX_SEEN = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class XmrSplitMsgTypes(IntEnum):
|
class XmrSplitMsgTypes(IntEnum):
|
||||||
@@ -242,11 +236,8 @@ class DebugTypes(IntEnum):
|
|||||||
OFFER_LOCK_2_VALUE_INC = auto()
|
OFFER_LOCK_2_VALUE_INC = auto()
|
||||||
BID_STOP_AFTER_COIN_B_LOCK = auto()
|
BID_STOP_AFTER_COIN_B_LOCK = auto()
|
||||||
BID_DONT_SPEND_COIN_B_LOCK = auto()
|
BID_DONT_SPEND_COIN_B_LOCK = auto()
|
||||||
WAIT_FOR_COIN_B_LOCK_BEFORE_PREREFUND = auto()
|
|
||||||
WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND = auto()
|
WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND = auto()
|
||||||
BID_DONT_SPEND_COIN_A_LOCK = auto()
|
BID_DONT_SPEND_COIN_A_LOCK = auto()
|
||||||
DONT_SEND_COIN_B_LOCK = auto()
|
|
||||||
DONT_RELEASE_COIN_A_LOCK = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationTypes(IntEnum):
|
class NotificationTypes(IntEnum):
|
||||||
@@ -256,7 +247,6 @@ class NotificationTypes(IntEnum):
|
|||||||
BID_ACCEPTED = auto()
|
BID_ACCEPTED = auto()
|
||||||
SWAP_COMPLETED = auto()
|
SWAP_COMPLETED = auto()
|
||||||
UPDATE_AVAILABLE = auto()
|
UPDATE_AVAILABLE = auto()
|
||||||
SWEEP_COMPLETED = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionRequestTypes(IntEnum):
|
class ConnectionRequestTypes(IntEnum):
|
||||||
@@ -468,8 +458,6 @@ def describeEventEntry(event_type, event_msg):
|
|||||||
return "Failed to publish lock tx B refund"
|
return "Failed to publish lock tx B refund"
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_INVALID:
|
if event_type == EventLogTypes.LOCK_TX_B_INVALID:
|
||||||
return "Detected invalid lock Tx B"
|
return "Detected invalid lock Tx B"
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_RPC_ERROR:
|
|
||||||
return "Temporary RPC error checking lock tx B: " + event_msg
|
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED:
|
||||||
return "Lock tx A pre-refund tx published"
|
return "Lock tx A pre-refund tx published"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED:
|
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED:
|
||||||
@@ -510,12 +498,6 @@ def describeEventEntry(event_type, event_msg):
|
|||||||
return "BCH mercy tx found"
|
return "BCH mercy tx found"
|
||||||
if event_type == EventLogTypes.BCH_MERCY_TX_PUBLISHED:
|
if event_type == EventLogTypes.BCH_MERCY_TX_PUBLISHED:
|
||||||
return "Lock tx B mercy tx published"
|
return "Lock tx B mercy tx published"
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_SPEND_TX_SEEN:
|
|
||||||
return "Lock tx A spend tx seen in chain"
|
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_SPEND_TX_SEEN:
|
|
||||||
return "Lock tx B spend tx seen in chain"
|
|
||||||
if event_type == EventLogTypes.LOCK_TX_B_REFUND_TX_SEEN:
|
|
||||||
return "Lock tx B refund tx seen in chain"
|
|
||||||
|
|
||||||
|
|
||||||
def getVoutByAddress(txjs, p2sh):
|
def getVoutByAddress(txjs, p2sh):
|
||||||
@@ -646,7 +628,6 @@ def canTimeoutBidState(state):
|
|||||||
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS,
|
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS,
|
||||||
BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX,
|
BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX,
|
||||||
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX,
|
BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX,
|
||||||
BidStates.BID_REQUEST_ACCEPTED,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+29
-168
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2024 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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,7 +29,6 @@ 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
|
||||||
@@ -56,22 +55,22 @@ PARTICL_VERSION = os.getenv("PARTICL_VERSION", "27.2.3.0")
|
|||||||
PARTICL_VERSION_TAG = os.getenv("PARTICL_VERSION_TAG", "")
|
PARTICL_VERSION_TAG = os.getenv("PARTICL_VERSION_TAG", "")
|
||||||
PARTICL_LINUX_EXTRA = os.getenv("PARTICL_LINUX_EXTRA", "nousb")
|
PARTICL_LINUX_EXTRA = os.getenv("PARTICL_LINUX_EXTRA", "nousb")
|
||||||
|
|
||||||
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "29.3")
|
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "28.0")
|
||||||
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.5")
|
LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.4")
|
||||||
LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "")
|
LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "")
|
||||||
|
|
||||||
DCR_VERSION = os.getenv("DCR_VERSION", "2.1.5")
|
DCR_VERSION = os.getenv("DCR_VERSION", "1.8.1")
|
||||||
DCR_VERSION_TAG = os.getenv("DCR_VERSION_TAG", "")
|
DCR_VERSION_TAG = os.getenv("DCR_VERSION_TAG", "")
|
||||||
|
|
||||||
NMC_VERSION = os.getenv("NMC_VERSION", "28.0")
|
NMC_VERSION = os.getenv("NMC_VERSION", "28.0")
|
||||||
NMC_VERSION_TAG = os.getenv("NMC_VERSION_TAG", "")
|
NMC_VERSION_TAG = os.getenv("NMC_VERSION_TAG", "")
|
||||||
|
|
||||||
MONERO_VERSION = os.getenv("MONERO_VERSION", "0.18.5.0")
|
MONERO_VERSION = os.getenv("MONERO_VERSION", "0.18.4.4")
|
||||||
MONERO_VERSION_TAG = os.getenv("MONERO_VERSION_TAG", "")
|
MONERO_VERSION_TAG = os.getenv("MONERO_VERSION_TAG", "")
|
||||||
XMR_SITE_COMMIT = (
|
XMR_SITE_COMMIT = (
|
||||||
"5e8d74229b742b54173010e3a676215b6f2fd1d7" # Lock hashes.txt to monero version
|
"a1bd4cd48a85b6012de20d9e490f83936f477be2" # Lock hashes.txt to monero version
|
||||||
)
|
)
|
||||||
|
|
||||||
WOWNERO_VERSION = os.getenv("WOWNERO_VERSION", "0.11.3.0")
|
WOWNERO_VERSION = os.getenv("WOWNERO_VERSION", "0.11.3.0")
|
||||||
@@ -83,16 +82,16 @@ WOW_SITE_COMMIT = (
|
|||||||
PIVX_VERSION = os.getenv("PIVX_VERSION", "5.6.1")
|
PIVX_VERSION = os.getenv("PIVX_VERSION", "5.6.1")
|
||||||
PIVX_VERSION_TAG = os.getenv("PIVX_VERSION_TAG", "")
|
PIVX_VERSION_TAG = os.getenv("PIVX_VERSION_TAG", "")
|
||||||
|
|
||||||
DASH_VERSION = os.getenv("DASH_VERSION", "23.1.2")
|
DASH_VERSION = os.getenv("DASH_VERSION", "22.1.3")
|
||||||
DASH_VERSION_TAG = os.getenv("DASH_VERSION_TAG", "")
|
DASH_VERSION_TAG = os.getenv("DASH_VERSION_TAG", "")
|
||||||
|
|
||||||
FIRO_VERSION = os.getenv("FIRO_VERSION", "0.14.16.1")
|
FIRO_VERSION = os.getenv("FIRO_VERSION", "0.14.15.0")
|
||||||
FIRO_VERSION_TAG = os.getenv("FIRO_VERSION_TAG", "")
|
FIRO_VERSION_TAG = os.getenv("FIRO_VERSION_TAG", "")
|
||||||
|
|
||||||
NAV_VERSION = os.getenv("NAV_VERSION", "7.0.3")
|
NAV_VERSION = os.getenv("NAV_VERSION", "7.0.3")
|
||||||
NAV_VERSION_TAG = os.getenv("NAV_VERSION_TAG", "")
|
NAV_VERSION_TAG = os.getenv("NAV_VERSION_TAG", "")
|
||||||
|
|
||||||
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "29.0.0")
|
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "28.0.1")
|
||||||
BITCOINCASH_VERSION_TAG = os.getenv("BITCOINCASH_VERSION_TAG", "")
|
BITCOINCASH_VERSION_TAG = os.getenv("BITCOINCASH_VERSION_TAG", "")
|
||||||
|
|
||||||
DOGECOIN_VERSION = os.getenv("DOGECOIN_VERSION", "23.2.1")
|
DOGECOIN_VERSION = os.getenv("DOGECOIN_VERSION", "23.2.1")
|
||||||
@@ -186,13 +185,11 @@ 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("prepare")
|
logger = logging.getLogger()
|
||||||
LOG_LEVEL = logging.DEBUG
|
LOG_LEVEL = logging.DEBUG
|
||||||
logger.propagate = False
|
|
||||||
logger.level = LOG_LEVEL
|
logger.level = LOG_LEVEL
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
if not len(logger.handlers):
|
||||||
handler.setFormatter(logging.Formatter("%(levelname)s : %(message)s"))
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
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))
|
||||||
@@ -461,23 +458,12 @@ def getRemoteFileLength(url: str) -> (int, bool):
|
|||||||
popConnectionParameters()
|
popConnectionParameters()
|
||||||
|
|
||||||
|
|
||||||
def downloadRelease(
|
def downloadRelease(url: str, path: str, extra_opts, timeout: int = 10) -> None:
|
||||||
url_in: str | List[str], path: str, extra_opts, timeout: int = 10
|
"""If file exists at path compare it's size to the content length at the url
|
||||||
) -> None:
|
and attempt to resume download if file size is below expected.
|
||||||
# If file exists at path compare it's size to the content length at the url
|
"""
|
||||||
# and attempt to resume download if file size is below expected.
|
|
||||||
|
|
||||||
release_filename: str = os.path.basename(path)
|
|
||||||
urls = (
|
|
||||||
url_in
|
|
||||||
if isinstance(url_in, list)
|
|
||||||
else [
|
|
||||||
url_in,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
for url in urls:
|
|
||||||
try:
|
|
||||||
resume_from: int = 0
|
resume_from: int = 0
|
||||||
|
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
if extra_opts.get("redownload_releases", False):
|
if extra_opts.get("redownload_releases", False):
|
||||||
logging.warning(f"Overwriting: {path}")
|
logging.warning(f"Overwriting: {path}")
|
||||||
@@ -497,11 +483,8 @@ def downloadRelease(
|
|||||||
else:
|
else:
|
||||||
# File exists and size check is disabled
|
# File exists and size check is disabled
|
||||||
return
|
return
|
||||||
|
|
||||||
return downloadFile(url, path, timeout, resume_from)
|
return downloadFile(url, path, timeout, resume_from)
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Failed to download {release_filename} from {url}")
|
|
||||||
logger.debug(f"Download error {e}")
|
|
||||||
raise RuntimeError(f"Failed to download {release_filename}.")
|
|
||||||
|
|
||||||
|
|
||||||
def downloadFile(url: str, path: str, timeout: int = 5, resume_from: int = 0) -> None:
|
def downloadFile(url: str, path: str, timeout: int = 5, resume_from: int = 0) -> None:
|
||||||
@@ -942,10 +925,9 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
assert_filename,
|
assert_filename,
|
||||||
)
|
)
|
||||||
elif coin == "litecoin":
|
elif coin == "litecoin":
|
||||||
release_url = [
|
release_url = "https://github.com/litecoin-project/litecoin/releases/download/v{}/{}".format(
|
||||||
f"https://github.com/litecoin-project/litecoin/releases/download/v{version}{version_tag}/{release_filename}",
|
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])
|
||||||
)
|
)
|
||||||
@@ -1267,7 +1249,6 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
fp.write("rpc-bind-ip={}\n".format(COINS_RPCBIND_IP))
|
fp.write("rpc-bind-ip={}\n".format(COINS_RPCBIND_IP))
|
||||||
fp.write(f"wallet-dir={config_datadir}\n")
|
fp.write(f"wallet-dir={config_datadir}\n")
|
||||||
fp.write("log-file={}\n".format(os.path.join(config_datadir, "wallet.log")))
|
fp.write("log-file={}\n".format(os.path.join(config_datadir, "wallet.log")))
|
||||||
fp.write("max-log-files=5\n")
|
|
||||||
fp.write(
|
fp.write(
|
||||||
"rpc-login={}:{}\n".format(
|
"rpc-login={}:{}\n".format(
|
||||||
core_settings["walletrpcuser"], core_settings["walletrpcpassword"]
|
core_settings["walletrpcuser"], core_settings["walletrpcpassword"]
|
||||||
@@ -1767,13 +1748,6 @@ def printHelp():
|
|||||||
)
|
)
|
||||||
print("--client-auth-password= Set or update the password to protect the web UI.")
|
print("--client-auth-password= Set or update the password to protect the web UI.")
|
||||||
print("--disable-client-auth Remove password protection from the web UI.")
|
print("--disable-client-auth Remove password protection from the web UI.")
|
||||||
print(
|
|
||||||
"--light Use light wallet mode (Electrum) for all supported coins."
|
|
||||||
)
|
|
||||||
print("--btc-mode=MODE Set BTC connection mode: rpc, electrum, or remote.")
|
|
||||||
print("--ltc-mode=MODE Set LTC connection mode: rpc, electrum, or remote.")
|
|
||||||
print("--btc-electrum-server= Custom Electrum server for BTC (host:port:ssl).")
|
|
||||||
print("--ltc-electrum-server= Custom Electrum server for LTC (host:port:ssl).")
|
|
||||||
|
|
||||||
active_coins = []
|
active_coins = []
|
||||||
for coin_name in known_coins.keys():
|
for coin_name in known_coins.keys():
|
||||||
@@ -1783,15 +1757,15 @@ def printHelp():
|
|||||||
|
|
||||||
|
|
||||||
def finalise_daemon(d):
|
def finalise_daemon(d):
|
||||||
logging.info(f"Interrupting {d.name} {d.handle.pid}")
|
logging.info("Interrupting {}".format(d.handle.pid))
|
||||||
try:
|
try:
|
||||||
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT)
|
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT)
|
||||||
d.handle.wait(timeout=120)
|
d.handle.wait(timeout=120)
|
||||||
|
except Exception as e:
|
||||||
|
logging.info(f"Error {e} for process {d.handle.pid}")
|
||||||
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
|
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
|
||||||
if fp:
|
if fp:
|
||||||
fp.close()
|
fp.close()
|
||||||
except Exception as e:
|
|
||||||
logging.info(f"Error stopping {d.name}, process {d.handle.pid}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def test_particl_encryption(data_dir, settings, chain, use_tor_proxy, extra_opts):
|
def test_particl_encryption(data_dir, settings, chain, use_tor_proxy, extra_opts):
|
||||||
@@ -1860,7 +1834,6 @@ def initialise_wallets(
|
|||||||
daemons = []
|
daemons = []
|
||||||
daemon_args = ["-noconnect", "-nodnsseed"]
|
daemon_args = ["-noconnect", "-nodnsseed"]
|
||||||
generated_mnemonic: bool = False
|
generated_mnemonic: bool = False
|
||||||
extended_keys = {}
|
|
||||||
|
|
||||||
coins_failed_to_initialise = []
|
coins_failed_to_initialise = []
|
||||||
|
|
||||||
@@ -1982,11 +1955,6 @@ def initialise_wallets(
|
|||||||
hex_seed = swap_client.getWalletKey(Coins.DCR, 1).hex()
|
hex_seed = swap_client.getWalletKey(Coins.DCR, 1).hex()
|
||||||
createDCRWallet(args, hex_seed, logger, threading.Event())
|
createDCRWallet(args, hex_seed, logger, threading.Event())
|
||||||
continue
|
continue
|
||||||
if coin_settings.get("connection_type") == "electrum":
|
|
||||||
logger.info(
|
|
||||||
f"Skipping RPC wallet creation for {getCoinName(c)} (electrum mode)."
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
swap_client.waitForDaemonRPC(c, with_wallet=False)
|
swap_client.waitForDaemonRPC(c, with_wallet=False)
|
||||||
# Create wallet if it doesn't exist yet
|
# Create wallet if it doesn't exist yet
|
||||||
wallets = swap_client.callcoinrpc(c, "listwallets")
|
wallets = swap_client.callcoinrpc(c, "listwallets")
|
||||||
@@ -2084,11 +2052,7 @@ def initialise_wallets(
|
|||||||
c = swap_client.getCoinIdFromName(coin_name)
|
c = swap_client.getCoinIdFromName(coin_name)
|
||||||
if c in (Coins.PART,):
|
if c in (Coins.PART,):
|
||||||
continue
|
continue
|
||||||
if coin_settings.get("connection_type") == "electrum":
|
if c not in (Coins.DCR,):
|
||||||
logger.info(
|
|
||||||
f"Skipping daemon RPC wait for {getCoinName(c)} (electrum mode)."
|
|
||||||
)
|
|
||||||
elif c not in (Coins.DCR,):
|
|
||||||
# initialiseWallet only sets main_wallet_seedid_
|
# initialiseWallet only sets main_wallet_seedid_
|
||||||
swap_client.waitForDaemonRPC(c)
|
swap_client.waitForDaemonRPC(c)
|
||||||
try:
|
try:
|
||||||
@@ -2118,25 +2082,6 @@ def initialise_wallets(
|
|||||||
except Exception as e: # noqa: F841
|
except Exception as e: # noqa: F841
|
||||||
logger.warning(f"changeWalletPassword failed for {coin_name}.")
|
logger.warning(f"changeWalletPassword failed for {coin_name}.")
|
||||||
|
|
||||||
zprv_prefix = 0x04B2430C if chain == "mainnet" else 0x045F18BC
|
|
||||||
for coin_name in with_coins:
|
|
||||||
c = swap_client.getCoinIdFromName(coin_name)
|
|
||||||
if c == Coins.PART:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
ci = swap_client.ci(c)
|
|
||||||
coin_settings = settings["chainclients"].get(coin_name, {})
|
|
||||||
is_electrum = coin_settings.get("connection_type") == "electrum"
|
|
||||||
can_export = (
|
|
||||||
hasattr(ci, "canExportToElectrum") and ci.canExportToElectrum()
|
|
||||||
)
|
|
||||||
if can_export or (is_electrum and hasattr(ci, "getAccountKey")):
|
|
||||||
seed_key = swap_client.getWalletKey(c, 1)
|
|
||||||
account_key = ci.getAccountKey(seed_key, zprv_prefix)
|
|
||||||
extended_keys[getCoinName(c)] = account_key
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Could not generate extended key for {coin_name}: {e}")
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if swap_client:
|
if swap_client:
|
||||||
swap_client.finalise()
|
swap_client.finalise()
|
||||||
@@ -2168,18 +2113,6 @@ def initialise_wallets(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if extended_keys:
|
|
||||||
print("Extended private keys (for external wallet import):")
|
|
||||||
for coin_name, key in extended_keys.items():
|
|
||||||
print(f" {coin_name}: {key}")
|
|
||||||
print("")
|
|
||||||
print(
|
|
||||||
"NOTE: These keys can be imported into Electrum using 'Use a master key'."
|
|
||||||
)
|
|
||||||
print("WARNING: Write these down NOW. They will not be shown again.\n")
|
|
||||||
|
|
||||||
return extended_keys
|
|
||||||
|
|
||||||
|
|
||||||
def load_config(config_path):
|
def load_config(config_path):
|
||||||
if not os.path.exists(config_path):
|
if not os.path.exists(config_path):
|
||||||
@@ -2346,9 +2279,6 @@ def main():
|
|||||||
tor_control_password = None
|
tor_control_password = None
|
||||||
client_auth_pwd_value = None
|
client_auth_pwd_value = None
|
||||||
disable_client_auth_flag = False
|
disable_client_auth_flag = False
|
||||||
light_mode = False
|
|
||||||
coin_modes = {}
|
|
||||||
electrum_servers = {}
|
|
||||||
extra_opts = {}
|
extra_opts = {}
|
||||||
|
|
||||||
if os.getenv("SSL_CERT_DIR", "") == "" and GUIX_SSL_CERT_DIR is not None:
|
if os.getenv("SSL_CERT_DIR", "") == "" and GUIX_SSL_CERT_DIR is not None:
|
||||||
@@ -2503,31 +2433,6 @@ def main():
|
|||||||
if name == "disable-client-auth":
|
if name == "disable-client-auth":
|
||||||
disable_client_auth_flag = True
|
disable_client_auth_flag = True
|
||||||
continue
|
continue
|
||||||
if name == "light":
|
|
||||||
light_mode = True
|
|
||||||
continue
|
|
||||||
if name.endswith("-mode") and len(s) == 2:
|
|
||||||
coin_prefix = name[:-5]
|
|
||||||
mode_value = s[1].strip().lower()
|
|
||||||
if mode_value not in ("rpc", "electrum", "remote"):
|
|
||||||
exitWithError(
|
|
||||||
f"Invalid mode '{mode_value}' for {coin_prefix}. Use: rpc, electrum, or remote"
|
|
||||||
)
|
|
||||||
coin_modes[coin_prefix] = mode_value
|
|
||||||
continue
|
|
||||||
if name.endswith("-electrum-server") and len(s) == 2:
|
|
||||||
coin_prefix = name[:-16]
|
|
||||||
server_str = s[1].strip()
|
|
||||||
parts = server_str.split(":")
|
|
||||||
if len(parts) >= 2:
|
|
||||||
if len(parts) >= 3:
|
|
||||||
server = f"{parts[0]}:{parts[1]}:{parts[2]}"
|
|
||||||
else:
|
|
||||||
server = f"{parts[0]}:{parts[1]}"
|
|
||||||
if coin_prefix not in electrum_servers:
|
|
||||||
electrum_servers[coin_prefix] = []
|
|
||||||
electrum_servers[coin_prefix].append(server)
|
|
||||||
continue
|
|
||||||
if len(s) != 2:
|
if len(s) != 2:
|
||||||
exitWithError("Unknown argument {}".format(v))
|
exitWithError("Unknown argument {}".format(v))
|
||||||
exitWithError("Unknown argument {}".format(v))
|
exitWithError("Unknown argument {}".format(v))
|
||||||
@@ -2886,45 +2791,13 @@ def main():
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
electrum_supported_coins = {
|
|
||||||
"bitcoin": "btc",
|
|
||||||
"litecoin": "ltc",
|
|
||||||
}
|
|
||||||
|
|
||||||
for coin_name, coin_prefix in electrum_supported_coins.items():
|
|
||||||
if coin_name not in chainclients:
|
|
||||||
continue
|
|
||||||
|
|
||||||
use_electrum = False
|
|
||||||
if light_mode and coin_name != "particl":
|
|
||||||
use_electrum = True
|
|
||||||
if coin_prefix in coin_modes:
|
|
||||||
if coin_modes[coin_prefix] == "electrum":
|
|
||||||
use_electrum = True
|
|
||||||
elif coin_modes[coin_prefix] == "rpc":
|
|
||||||
use_electrum = False
|
|
||||||
|
|
||||||
if use_electrum:
|
|
||||||
chainclients[coin_name]["connection_type"] = "electrum"
|
|
||||||
chainclients[coin_name]["manage_daemon"] = False
|
|
||||||
if coin_prefix in electrum_servers:
|
|
||||||
chainclients[coin_name]["electrum_clearnet_servers"] = electrum_servers[
|
|
||||||
coin_prefix
|
|
||||||
]
|
|
||||||
|
|
||||||
for coin_name, coin_settings in chainclients.items():
|
for coin_name, coin_settings in chainclients.items():
|
||||||
coin_id = getCoinIdFromName(coin_name)
|
coin_id = getCoinIdFromName(coin_name)
|
||||||
coin_params = chainparams[coin_id]
|
coin_params = chainparams[coin_id]
|
||||||
if coin_settings.get("core_type_group", "") == "xmr":
|
if coin_settings.get("core_type_group", "") == "xmr":
|
||||||
default_name: str = "swap_wallet"
|
default_name = "swap_wallet"
|
||||||
use_name: str = default_name
|
|
||||||
else:
|
else:
|
||||||
default_name: str = "wallet.dat"
|
default_name = "wallet.dat"
|
||||||
use_name: str = (
|
|
||||||
"wallet.dat"
|
|
||||||
if coin_id in (Coins.NAV, Coins.FIRO, Coins.DCR)
|
|
||||||
else "bsx_wallet"
|
|
||||||
)
|
|
||||||
|
|
||||||
if coin_name == "litecoin":
|
if coin_name == "litecoin":
|
||||||
set_name: str = getWalletName(
|
set_name: str = getWalletName(
|
||||||
@@ -2933,7 +2806,7 @@ def main():
|
|||||||
if set_name != "mweb":
|
if set_name != "mweb":
|
||||||
coin_settings["mweb_wallet_name"] = set_name
|
coin_settings["mweb_wallet_name"] = set_name
|
||||||
|
|
||||||
set_name: str = getWalletName(coin_params, use_name)
|
set_name: str = getWalletName(coin_params, default_name)
|
||||||
if set_name != default_name:
|
if set_name != default_name:
|
||||||
coin_settings["wallet_name"] = set_name
|
coin_settings["wallet_name"] = set_name
|
||||||
|
|
||||||
@@ -3128,7 +3001,7 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
if particl_wallet_mnemonic != "none":
|
if particl_wallet_mnemonic != "none":
|
||||||
extended_keys = initialise_wallets(
|
initialise_wallets(
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
add_coin,
|
add_coin,
|
||||||
@@ -3140,18 +3013,6 @@ def main():
|
|||||||
extra_opts=extra_opts,
|
extra_opts=extra_opts,
|
||||||
)
|
)
|
||||||
|
|
||||||
if extended_keys:
|
|
||||||
print("\nExtended private key (for external wallet import):")
|
|
||||||
for coin_name, key in extended_keys.items():
|
|
||||||
print(f" {coin_name}: {key}")
|
|
||||||
print("")
|
|
||||||
print(
|
|
||||||
"NOTE: This key can be imported into Electrum using 'Use a master key'."
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
"WARNING: Write this down NOW. It will not be shown again.\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
save_config(config_path, settings)
|
save_config(config_path, settings)
|
||||||
finally:
|
finally:
|
||||||
if "particl_daemon" in extra_opts:
|
if "particl_daemon" in extra_opts:
|
||||||
|
|||||||
+30
-51
@@ -2,11 +2,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2024 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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
|
||||||
@@ -23,7 +22,6 @@ 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
|
||||||
@@ -38,7 +36,6 @@ def signal_handler(sig, frame):
|
|||||||
os.write(
|
os.write(
|
||||||
sys.stdout.fileno(), f"Signal {sig} detected, ending program.\n".encode("utf-8")
|
sys.stdout.fileno(), f"Signal {sig} detected, ending program.\n".encode("utf-8")
|
||||||
)
|
)
|
||||||
try:
|
|
||||||
if swap_client is not None and not swap_client.chainstate_delay_event.is_set():
|
if swap_client is not None and not swap_client.chainstate_delay_event.is_set():
|
||||||
try:
|
try:
|
||||||
from basicswap.ui.page_amm import stop_amm_process, get_amm_status
|
from basicswap.ui.page_amm import stop_amm_process, get_amm_status
|
||||||
@@ -55,8 +52,6 @@ def signal_handler(sig, frame):
|
|||||||
logger.error(f"Error stopping AMM in signal handler: {e}")
|
logger.error(f"Error stopping AMM in signal handler: {e}")
|
||||||
|
|
||||||
swap_client.stopRunning()
|
swap_client.stopRunning()
|
||||||
except NameError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def checkPARTZmqConfigBeforeStart(part_settings, swap_settings):
|
def checkPARTZmqConfigBeforeStart(part_settings, swap_settings):
|
||||||
@@ -349,7 +344,7 @@ def mainLoop(daemons, update: bool = True):
|
|||||||
def runClient(
|
def runClient(
|
||||||
data_dir: str,
|
data_dir: str,
|
||||||
chain: str,
|
chain: str,
|
||||||
start_only_coins: Set[str],
|
start_only_coins: bool,
|
||||||
log_prefix: str = "BasicSwap",
|
log_prefix: str = "BasicSwap",
|
||||||
extra_opts=dict(),
|
extra_opts=dict(),
|
||||||
) -> int:
|
) -> int:
|
||||||
@@ -393,24 +388,17 @@ def runClient(
|
|||||||
# Settings may have been modified
|
# Settings may have been modified
|
||||||
settings = swap_client.settings
|
settings = swap_client.settings
|
||||||
|
|
||||||
base_coin_opts = []
|
|
||||||
if "extra_coin_opts" in extra_opts:
|
|
||||||
if len(start_only_coins) == 0:
|
|
||||||
raise ValueError('"extracoinopts" can only be used with "startonlycoins"')
|
|
||||||
base_coin_opts += extra_opts["extra_coin_opts"]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try start daemons
|
# Try start daemons
|
||||||
if len(start_only_coins) > 0:
|
|
||||||
swap_client.log.warning('Not starting networks as "startonlycoin" is set')
|
|
||||||
else:
|
|
||||||
for network in settings.get("networks", []):
|
for network in settings.get("networks", []):
|
||||||
if network.get("enabled", True) is False:
|
if network.get("enabled", True) is False:
|
||||||
continue
|
continue
|
||||||
network_type: str = network.get("type", "unknown")
|
network_type: str = network.get("type", "unknown")
|
||||||
if network_type == "simplex":
|
if network_type == "simplex":
|
||||||
simplex_dir = os.path.join(data_dir, "simplex")
|
simplex_dir = os.path.join(data_dir, "simplex")
|
||||||
|
|
||||||
log_level = "debug" if swap_client.debug else "info"
|
log_level = "debug" if swap_client.debug else "info"
|
||||||
|
|
||||||
socks_proxy = None
|
socks_proxy = None
|
||||||
if "socks_proxy_override" in network:
|
if "socks_proxy_override" in network:
|
||||||
socks_proxy = network["socks_proxy_override"]
|
socks_proxy = network["socks_proxy_override"]
|
||||||
@@ -469,18 +457,10 @@ def runClient(
|
|||||||
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(
|
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(
|
||||||
coin_id, v["rpchost"]
|
coin_id, v["rpchost"]
|
||||||
)
|
)
|
||||||
wallet_opts = [
|
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(
|
||||||
@@ -488,7 +468,7 @@ def runClient(
|
|||||||
)
|
)
|
||||||
if proxy_host:
|
if proxy_host:
|
||||||
proxy_log_str = " through proxy"
|
proxy_log_str = " through proxy"
|
||||||
wallet_opts += [
|
opts += [
|
||||||
"--proxy",
|
"--proxy",
|
||||||
f"{proxy_host}:{proxy_port}",
|
f"{proxy_host}:{proxy_port}",
|
||||||
"--daemon-ssl-allow-any-cert",
|
"--daemon-ssl-allow-any-cert",
|
||||||
@@ -502,11 +482,19 @@ def runClient(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
filename: str = getWalletBinName(coin_id, v, c + "-wallet-rpc")
|
daemon_rpcuser = v.get("rpcuser", "")
|
||||||
daemons.append(
|
daemon_rpcpass = v.get("rpcpassword", "")
|
||||||
startXmrWalletDaemon(
|
if daemon_rpcuser != "":
|
||||||
v["datadir"], v["bindir"], filename, wallet_opts
|
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")
|
||||||
|
|
||||||
|
daemons.append(
|
||||||
|
startXmrWalletDaemon(v["datadir"], v["bindir"], filename, 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}")
|
||||||
@@ -515,8 +503,9 @@ def runClient(
|
|||||||
|
|
||||||
if c == "decred":
|
if c == "decred":
|
||||||
appdata = v["datadir"]
|
appdata = v["datadir"]
|
||||||
coin_opts = copy.deepcopy(base_coin_opts)
|
extra_opts = [
|
||||||
coin_opts.append(f'--appdata="{appdata}"')
|
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")
|
||||||
@@ -534,7 +523,7 @@ def runClient(
|
|||||||
appdata,
|
appdata,
|
||||||
v["bindir"],
|
v["bindir"],
|
||||||
filename,
|
filename,
|
||||||
opts=coin_opts,
|
opts=extra_opts,
|
||||||
extra_config=extra_config,
|
extra_config=extra_config,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -545,13 +534,12 @@ 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 != "":
|
||||||
wallet_opts.append(f'--pass="{wallet_pwd}"')
|
extra_opts.append(f'--pass="{wallet_pwd}"')
|
||||||
extra_config = {
|
extra_config = {
|
||||||
"add_datadir": False,
|
"add_datadir": False,
|
||||||
"stdout_to_file": True,
|
"stdout_to_file": True,
|
||||||
@@ -564,12 +552,13 @@ def runClient(
|
|||||||
appdata,
|
appdata,
|
||||||
v["bindir"],
|
v["bindir"],
|
||||||
filename,
|
filename,
|
||||||
opts=wallet_opts,
|
opts=extra_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:
|
||||||
@@ -579,7 +568,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")
|
||||||
coin_opts = copy.deepcopy(base_coin_opts) + getCoreBinArgs(
|
extra_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}
|
||||||
@@ -588,7 +577,7 @@ def runClient(
|
|||||||
v["datadir"],
|
v["datadir"],
|
||||||
v["bindir"],
|
v["bindir"],
|
||||||
filename,
|
filename,
|
||||||
opts=coin_opts,
|
opts=extra_opts,
|
||||||
extra_config=extra_config,
|
extra_config=extra_config,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -629,7 +618,7 @@ def runClient(
|
|||||||
signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT
|
signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
swap_client.log.error(f"Interrupting {d.name} {d.handle.pid}: {e}")
|
swap_client.log.info(f"Interrupting {d.name} {d.handle.pid}, error {e}")
|
||||||
for d in daemons:
|
for d in daemons:
|
||||||
try:
|
try:
|
||||||
d.handle.wait(timeout=120)
|
d.handle.wait(timeout=120)
|
||||||
@@ -638,12 +627,10 @@ def runClient(
|
|||||||
fp.close()
|
fp.close()
|
||||||
closed_pids.append(d.handle.pid)
|
closed_pids.append(d.handle.pid)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
swap_client.log.error(
|
swap_client.log.error(f"Error: {e}")
|
||||||
f"Waiting for {d.name} {d.handle.pid} to shutdown: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
fail_code: int = swap_client.fail_code
|
fail_code: int = swap_client.fail_code
|
||||||
swap_client = None
|
del swap_client
|
||||||
|
|
||||||
if os.path.exists(pids_path):
|
if os.path.exists(pids_path):
|
||||||
with open(pids_path) as fd:
|
with open(pids_path) as fd:
|
||||||
@@ -687,9 +674,6 @@ 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."
|
||||||
@@ -754,11 +738,6 @@ 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}")
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ chainparams = {
|
|||||||
"pubkey_address": 0x0E91,
|
"pubkey_address": 0x0E91,
|
||||||
"script_address": 0x0E6C,
|
"script_address": 0x0E6C,
|
||||||
"key_prefix": 0x2307,
|
"key_prefix": 0x2307,
|
||||||
"bip44": 115,
|
"bip44": 1,
|
||||||
"min_amount": 100000,
|
"min_amount": 100000,
|
||||||
"max_amount": 10000000 * COIN,
|
"max_amount": 10000000 * COIN,
|
||||||
},
|
},
|
||||||
@@ -552,31 +552,21 @@ 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:
|
||||||
if inc_variant and lc_ticker in variant_ticker_map:
|
return ticker_map[ticker.lower()]
|
||||||
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}")
|
||||||
|
|
||||||
|
|
||||||
def getCoinIdFromName(name: str) -> Coins:
|
def getCoinIdFromName(name: str) -> str:
|
||||||
try:
|
try:
|
||||||
return name_map[name.lower()]
|
return name_map[name.lower()]
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
+68
-82
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2024 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -12,8 +12,9 @@ import time
|
|||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
CURRENT_DB_VERSION = 34
|
|
||||||
CURRENT_DB_DATA_VERSION = 8
|
CURRENT_DB_VERSION = 32
|
||||||
|
CURRENT_DB_DATA_VERSION = 7
|
||||||
|
|
||||||
|
|
||||||
class Concepts(IntEnum):
|
class Concepts(IntEnum):
|
||||||
@@ -75,16 +76,10 @@ class Table:
|
|||||||
__sqlite3_table__ = True
|
__sqlite3_table__ = True
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
init_all_columns: bool = True
|
|
||||||
for name, value in kwargs.items():
|
for name, value in kwargs.items():
|
||||||
if name == "_init_all_columns":
|
|
||||||
init_all_columns = value
|
|
||||||
continue
|
|
||||||
if not hasattr(self, name):
|
if not hasattr(self, name):
|
||||||
raise ValueError(f"Unknown attribute {name}")
|
raise ValueError(f"Unknown attribute {name}")
|
||||||
setattr(self, name, value)
|
setattr(self, name, value)
|
||||||
if init_all_columns is False:
|
|
||||||
return
|
|
||||||
# Init any unset columns to None
|
# Init any unset columns to None
|
||||||
for mc in inspect.getmembers(self):
|
for mc in inspect.getmembers(self):
|
||||||
mc_name, mc_obj = mc
|
mc_name, mc_obj = mc
|
||||||
@@ -140,20 +135,6 @@ class Index:
|
|||||||
self.column_3 = column_3
|
self.column_3 = column_3
|
||||||
|
|
||||||
|
|
||||||
class StateRows:
|
|
||||||
state = Column("integer")
|
|
||||||
state_time = Column("integer") # Timestamp of last state change
|
|
||||||
states = Column("blob") # Packed states and times
|
|
||||||
|
|
||||||
def setState(self, new_state, state_time=None):
|
|
||||||
now = int(time.time()) if state_time is None else state_time
|
|
||||||
self.state = new_state
|
|
||||||
if self.isSet("states") is False:
|
|
||||||
self.states = pack_state(new_state, now)
|
|
||||||
else:
|
|
||||||
self.states += pack_state(new_state, now)
|
|
||||||
|
|
||||||
|
|
||||||
class DBKVInt(Table):
|
class DBKVInt(Table):
|
||||||
__tablename__ = "kv_int"
|
__tablename__ = "kv_int"
|
||||||
|
|
||||||
@@ -168,7 +149,7 @@ class DBKVString(Table):
|
|||||||
value = Column("string")
|
value = Column("string")
|
||||||
|
|
||||||
|
|
||||||
class Offer(Table, StateRows):
|
class Offer(Table):
|
||||||
__tablename__ = "offers"
|
__tablename__ = "offers"
|
||||||
|
|
||||||
offer_id = Column("blob", primary_key=True)
|
offer_id = Column("blob", primary_key=True)
|
||||||
@@ -216,8 +197,19 @@ class Offer(Table, StateRows):
|
|||||||
bid_reversed = Column("bool")
|
bid_reversed = Column("bool")
|
||||||
smsg_payload_version = Column("integer")
|
smsg_payload_version = Column("integer")
|
||||||
|
|
||||||
|
state = Column("integer")
|
||||||
|
states = Column("blob") # Packed states and times
|
||||||
|
|
||||||
class Bid(Table, StateRows):
|
def setState(self, new_state):
|
||||||
|
now = int(time.time())
|
||||||
|
self.state = new_state
|
||||||
|
if self.isSet("states") is False:
|
||||||
|
self.states = pack_state(new_state, now)
|
||||||
|
else:
|
||||||
|
self.states += pack_state(new_state, now)
|
||||||
|
|
||||||
|
|
||||||
|
class Bid(Table):
|
||||||
__tablename__ = "bids"
|
__tablename__ = "bids"
|
||||||
|
|
||||||
bid_id = Column("blob", primary_key=True)
|
bid_id = Column("blob", primary_key=True)
|
||||||
@@ -252,7 +244,11 @@ class Bid(Table, StateRows):
|
|||||||
participate_txn_refund = Column("blob")
|
participate_txn_refund = Column("blob")
|
||||||
|
|
||||||
in_progress = Column("integer")
|
in_progress = Column("integer")
|
||||||
|
state = Column("integer")
|
||||||
|
state_time = Column("integer") # Timestamp of last state change
|
||||||
|
states = Column("blob") # Packed states and times
|
||||||
|
|
||||||
|
state_note = Column("string")
|
||||||
was_sent = Column("bool") # Sent by node
|
was_sent = Column("bool") # Sent by node
|
||||||
was_received = Column("bool")
|
was_received = Column("bool")
|
||||||
contract_count = Column("integer")
|
contract_count = Column("integer")
|
||||||
@@ -291,13 +287,25 @@ class Bid(Table, StateRows):
|
|||||||
if self.isSet("participate_tx"):
|
if self.isSet("participate_tx"):
|
||||||
self.participate_tx.setState(new_state)
|
self.participate_tx.setState(new_state)
|
||||||
|
|
||||||
|
def setState(self, new_state, state_note=None):
|
||||||
|
now = int(time.time())
|
||||||
|
self.state = new_state
|
||||||
|
self.state_time = now
|
||||||
|
|
||||||
|
if self.isSet("state_note"):
|
||||||
|
self.state_note = state_note
|
||||||
|
if self.isSet("states") is False:
|
||||||
|
self.states = pack_state(new_state, now)
|
||||||
|
else:
|
||||||
|
self.states += pack_state(new_state, now)
|
||||||
|
|
||||||
def getLockTXBVout(self):
|
def getLockTXBVout(self):
|
||||||
if self.isSet("xmr_b_lock_tx"):
|
if self.isSet("xmr_b_lock_tx"):
|
||||||
return self.xmr_b_lock_tx.vout
|
return self.xmr_b_lock_tx.vout
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class SwapTx(Table, StateRows):
|
class SwapTx(Table):
|
||||||
__tablename__ = "transactions"
|
__tablename__ = "transactions"
|
||||||
|
|
||||||
bid_id = Column("blob")
|
bid_id = Column("blob")
|
||||||
@@ -320,8 +328,21 @@ class SwapTx(Table, StateRows):
|
|||||||
block_height = Column("integer")
|
block_height = Column("integer")
|
||||||
block_time = Column("integer")
|
block_time = Column("integer")
|
||||||
|
|
||||||
|
state = Column("integer")
|
||||||
|
states = Column("blob") # Packed states and times
|
||||||
|
|
||||||
primary_key = PrimaryKeyConstraint("bid_id", "tx_type")
|
primary_key = PrimaryKeyConstraint("bid_id", "tx_type")
|
||||||
|
|
||||||
|
def setState(self, new_state):
|
||||||
|
if self.state == new_state:
|
||||||
|
return
|
||||||
|
self.state = new_state
|
||||||
|
now: int = int(time.time())
|
||||||
|
if self.isSet("states") is False:
|
||||||
|
self.states = pack_state(new_state, now)
|
||||||
|
else:
|
||||||
|
self.states += pack_state(new_state, now)
|
||||||
|
|
||||||
|
|
||||||
class PrefundedTx(Table):
|
class PrefundedTx(Table):
|
||||||
__tablename__ = "prefunded_transactions"
|
__tablename__ = "prefunded_transactions"
|
||||||
@@ -399,9 +420,8 @@ class XmrOffer(Table):
|
|||||||
swap_id = Column("integer", primary_key=True, autoincrement=True)
|
swap_id = Column("integer", primary_key=True, autoincrement=True)
|
||||||
offer_id = Column("blob")
|
offer_id = Column("blob")
|
||||||
|
|
||||||
# TODO: rename to from/to - values are not switched for reverse swaps
|
a_fee_rate = Column("integer") # Chain a fee rate
|
||||||
a_fee_rate = Column("integer") # Chain from fee rate
|
b_fee_rate = Column("integer") # Chain b fee rate
|
||||||
b_fee_rate = Column("integer") # Chain to fee rate
|
|
||||||
|
|
||||||
# Delay before the chain a lock refund tx can be mined
|
# Delay before the chain a lock refund tx can be mined
|
||||||
lock_time_1 = Column("integer")
|
lock_time_1 = Column("integer")
|
||||||
@@ -772,9 +792,8 @@ class NetworkPortal(Table):
|
|||||||
created_at = Column("integer")
|
created_at = Column("integer")
|
||||||
|
|
||||||
|
|
||||||
def extract_schema(input_globals: dict = None) -> dict:
|
def extract_schema(input_globals=None) -> dict:
|
||||||
g = (input_globals if input_globals else globals()).copy()
|
g = globals() if input_globals is None else input_globals
|
||||||
|
|
||||||
tables = {}
|
tables = {}
|
||||||
for name, obj in g.items():
|
for name, obj in g.items():
|
||||||
if not inspect.isclass(obj):
|
if not inspect.isclass(obj):
|
||||||
@@ -895,10 +914,7 @@ def create_table(c, table_name, table) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def create_db_(con, log) -> None:
|
def create_db_(con, log) -> None:
|
||||||
from .db_wallet import extract_wallet_schema
|
|
||||||
|
|
||||||
db_schema = extract_schema()
|
db_schema = extract_schema()
|
||||||
db_schema.update(extract_wallet_schema())
|
|
||||||
c = con.cursor()
|
c = con.cursor()
|
||||||
for table_name, table in db_schema.items():
|
for table_name, table in db_schema.items():
|
||||||
create_table(c, table_name, table)
|
create_table(c, table_name, table)
|
||||||
@@ -916,63 +932,42 @@ def create_db(db_path: str, log) -> None:
|
|||||||
|
|
||||||
|
|
||||||
class DBMethods:
|
class DBMethods:
|
||||||
_db_lock_depth = 0
|
|
||||||
|
|
||||||
def _db_lock_held(self) -> bool:
|
|
||||||
|
|
||||||
if hasattr(self.mxDB, "_is_owned"):
|
|
||||||
return self.mxDB._is_owned()
|
|
||||||
return self.mxDB.locked()
|
|
||||||
|
|
||||||
def openDB(self, cursor=None):
|
def openDB(self, cursor=None):
|
||||||
if cursor:
|
if cursor:
|
||||||
assert self._db_lock_held()
|
# assert(self._thread_debug == threading.get_ident())
|
||||||
|
assert self.mxDB.locked()
|
||||||
return cursor
|
return cursor
|
||||||
|
|
||||||
if self._db_lock_held():
|
|
||||||
self._db_lock_depth += 1
|
|
||||||
return self._db_con.cursor()
|
|
||||||
|
|
||||||
self.mxDB.acquire()
|
self.mxDB.acquire()
|
||||||
self._db_lock_depth = 1
|
# self._thread_debug = threading.get_ident()
|
||||||
self._db_con = sqlite3.connect(self.sqlite_file)
|
self._db_con = sqlite3.connect(self.sqlite_file)
|
||||||
|
|
||||||
self._db_con.execute("PRAGMA busy_timeout = 30000")
|
|
||||||
return self._db_con.cursor()
|
return self._db_con.cursor()
|
||||||
|
|
||||||
def getNewDBCursor(self):
|
def getNewDBCursor(self):
|
||||||
assert self._db_lock_held()
|
assert self.mxDB.locked()
|
||||||
return self._db_con.cursor()
|
return self._db_con.cursor()
|
||||||
|
|
||||||
def commitDB(self):
|
def commitDB(self):
|
||||||
assert self._db_lock_held()
|
assert self.mxDB.locked()
|
||||||
self._db_con.commit()
|
self._db_con.commit()
|
||||||
|
|
||||||
def rollbackDB(self):
|
def rollbackDB(self):
|
||||||
assert self._db_lock_held()
|
assert self.mxDB.locked()
|
||||||
self._db_con.rollback()
|
self._db_con.rollback()
|
||||||
|
|
||||||
def closeDBCursor(self, cursor):
|
def closeDBCursor(self, cursor):
|
||||||
assert self._db_lock_held()
|
assert self.mxDB.locked()
|
||||||
if cursor:
|
if cursor:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
def closeDB(self, cursor, commit=True):
|
def closeDB(self, cursor, commit=True):
|
||||||
assert self._db_lock_held()
|
assert self.mxDB.locked()
|
||||||
|
|
||||||
if self._db_lock_depth > 1:
|
|
||||||
if commit:
|
|
||||||
self._db_con.commit()
|
|
||||||
cursor.close()
|
|
||||||
self._db_lock_depth -= 1
|
|
||||||
return
|
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
self._db_con.commit()
|
self._db_con.commit()
|
||||||
|
|
||||||
cursor.close()
|
cursor.close()
|
||||||
self._db_con.close()
|
self._db_con.close()
|
||||||
self._db_lock_depth = 0
|
|
||||||
self.mxDB.release()
|
self.mxDB.release()
|
||||||
|
|
||||||
def setIntKV(self, str_key: str, int_val: int, cursor=None) -> None:
|
def setIntKV(self, str_key: str, int_val: int, cursor=None) -> None:
|
||||||
@@ -1062,9 +1057,9 @@ class DBMethods:
|
|||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
if cursor is None:
|
if cursor is None:
|
||||||
self.closeDB(use_cursor, commit=True)
|
self.closeDB(use_cursor, commit=False)
|
||||||
|
|
||||||
def add(self, obj, cursor, upsert: bool = False, columns_list=None):
|
def add(self, obj, cursor, upsert: bool = False):
|
||||||
if cursor is None:
|
if cursor is None:
|
||||||
raise ValueError("Cursor is null")
|
raise ValueError("Cursor is null")
|
||||||
if not hasattr(obj, "__tablename__"):
|
if not hasattr(obj, "__tablename__"):
|
||||||
@@ -1077,8 +1072,7 @@ class DBMethods:
|
|||||||
# See if the instance overwrote any class methods
|
# See if the instance overwrote any class methods
|
||||||
for mc in inspect.getmembers(obj.__class__):
|
for mc in inspect.getmembers(obj.__class__):
|
||||||
mc_name, mc_obj = mc
|
mc_name, mc_obj = mc
|
||||||
if columns_list is not None and mc_name not in columns_list:
|
|
||||||
continue
|
|
||||||
if not hasattr(mc_obj, "__sqlite3_column__"):
|
if not hasattr(mc_obj, "__sqlite3_column__"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -1119,7 +1113,6 @@ class DBMethods:
|
|||||||
order_by={},
|
order_by={},
|
||||||
query_suffix=None,
|
query_suffix=None,
|
||||||
extra_query_data={},
|
extra_query_data={},
|
||||||
columns_list=None,
|
|
||||||
):
|
):
|
||||||
if cursor is None:
|
if cursor is None:
|
||||||
raise ValueError("Cursor is null")
|
raise ValueError("Cursor is null")
|
||||||
@@ -1132,8 +1125,6 @@ class DBMethods:
|
|||||||
|
|
||||||
for mc in inspect.getmembers(table_class):
|
for mc in inspect.getmembers(table_class):
|
||||||
mc_name, mc_obj = mc
|
mc_name, mc_obj = mc
|
||||||
if columns_list is not None and mc_name not in columns_list:
|
|
||||||
continue
|
|
||||||
if not hasattr(mc_obj, "__sqlite3_column__"):
|
if not hasattr(mc_obj, "__sqlite3_column__"):
|
||||||
continue
|
continue
|
||||||
if len(columns) > 0:
|
if len(columns) > 0:
|
||||||
@@ -1202,7 +1193,6 @@ class DBMethods:
|
|||||||
order_by={},
|
order_by={},
|
||||||
query_suffix=None,
|
query_suffix=None,
|
||||||
extra_query_data={},
|
extra_query_data={},
|
||||||
columns_list=None,
|
|
||||||
):
|
):
|
||||||
return firstOrNone(
|
return firstOrNone(
|
||||||
self.query(
|
self.query(
|
||||||
@@ -1212,11 +1202,10 @@ class DBMethods:
|
|||||||
order_by,
|
order_by,
|
||||||
query_suffix,
|
query_suffix,
|
||||||
extra_query_data,
|
extra_query_data,
|
||||||
columns_list,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def updateDB(self, obj, cursor, constraints=[], columns_list=None):
|
def updateDB(self, obj, cursor, constraints=[]):
|
||||||
if cursor is None:
|
if cursor is None:
|
||||||
raise ValueError("Cursor is null")
|
raise ValueError("Cursor is null")
|
||||||
if not hasattr(obj, "__tablename__"):
|
if not hasattr(obj, "__tablename__"):
|
||||||
@@ -1226,10 +1215,9 @@ class DBMethods:
|
|||||||
query: str = f"UPDATE {table_name} SET "
|
query: str = f"UPDATE {table_name} SET "
|
||||||
|
|
||||||
values = {}
|
values = {}
|
||||||
constraint_values = {}
|
|
||||||
set_columns = []
|
|
||||||
for mc in inspect.getmembers(obj.__class__):
|
for mc in inspect.getmembers(obj.__class__):
|
||||||
mc_name, mc_obj = mc
|
mc_name, mc_obj = mc
|
||||||
|
|
||||||
if not hasattr(mc_obj, "__sqlite3_column__"):
|
if not hasattr(mc_obj, "__sqlite3_column__"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -1239,19 +1227,17 @@ class DBMethods:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if mc_name in constraints:
|
if mc_name in constraints:
|
||||||
constraint_values[mc_name] = m_obj
|
values[mc_name] = m_obj
|
||||||
continue
|
|
||||||
if columns_list is not None and mc_name not in columns_list:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
set_columns.append(f"{mc_name} = :{mc_name}")
|
if len(values) > 0:
|
||||||
|
query += ", "
|
||||||
|
query += f"{mc_name} = :{mc_name}"
|
||||||
values[mc_name] = m_obj
|
values[mc_name] = m_obj
|
||||||
|
|
||||||
query += ", ".join(set_columns)
|
|
||||||
query += " WHERE 1=1 "
|
query += " WHERE 1=1 "
|
||||||
|
|
||||||
for ck in constraints:
|
for ck in constraints:
|
||||||
query += f" AND {ck} = :{ck} "
|
query += f" AND {ck} = :{ck} "
|
||||||
|
|
||||||
values.update(constraint_values)
|
|
||||||
cursor.execute(query, values)
|
cursor.execute(query, values)
|
||||||
|
|||||||
+3
-108
@@ -18,8 +18,6 @@ from .db import (
|
|||||||
extract_schema,
|
extract_schema,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .db_wallet import extract_wallet_schema
|
|
||||||
|
|
||||||
from .basicswap_util import (
|
from .basicswap_util import (
|
||||||
BidStates,
|
BidStates,
|
||||||
canAcceptBidState,
|
canAcceptBidState,
|
||||||
@@ -131,14 +129,6 @@ def upgradeDatabaseData(self, data_version):
|
|||||||
"state_id": int(state),
|
"state_id": int(state),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if data_version > 0 and data_version < 8:
|
|
||||||
cursor.execute(
|
|
||||||
"UPDATE bidstates SET can_timeout = :can_timeout WHERE state_id = :state_id",
|
|
||||||
{
|
|
||||||
"can_timeout": 1,
|
|
||||||
"state_id": int(BidStates.BID_REQUEST_ACCEPTED),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if data_version > 0 and data_version < 4:
|
if data_version > 0 and data_version < 4:
|
||||||
for state in (
|
for state in (
|
||||||
BidStates.BID_REQUEST_SENT,
|
BidStates.BID_REQUEST_SENT,
|
||||||
@@ -250,18 +240,11 @@ def upgradeDatabaseFromSchema(self, cursor, expect_schema):
|
|||||||
|
|
||||||
|
|
||||||
def upgradeDatabase(self, db_version: int):
|
def upgradeDatabase(self, db_version: int):
|
||||||
upgrade_forced: bool = False
|
if self._force_db_upgrade is False and db_version >= CURRENT_DB_VERSION:
|
||||||
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
|
||||||
@@ -278,8 +261,6 @@ def upgradeDatabase(self, db_version: int):
|
|||||||
]
|
]
|
||||||
|
|
||||||
expect_schema = extract_schema()
|
expect_schema = extract_schema()
|
||||||
expect_schema.update(extract_wallet_schema())
|
|
||||||
have_tables = {}
|
|
||||||
try:
|
try:
|
||||||
cursor = self.openDB()
|
cursor = self.openDB()
|
||||||
for rename_column in rename_columns:
|
for rename_column in rename_columns:
|
||||||
@@ -288,93 +269,7 @@ def upgradeDatabase(self, db_version: int):
|
|||||||
cursor.execute(
|
cursor.execute(
|
||||||
f"ALTER TABLE {table_name} RENAME COLUMN {colname_from} TO {colname_to}"
|
f"ALTER TABLE {table_name} RENAME COLUMN {colname_from} TO {colname_to}"
|
||||||
)
|
)
|
||||||
|
upgradeDatabaseFromSchema(self, cursor, expect_schema)
|
||||||
query = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"
|
|
||||||
tables = cursor.execute(query).fetchall()
|
|
||||||
for table in tables:
|
|
||||||
table_name = table[0]
|
|
||||||
if table_name in ("sqlite_sequence",):
|
|
||||||
continue
|
|
||||||
|
|
||||||
have_table = {}
|
|
||||||
have_columns = {}
|
|
||||||
query = "SELECT * FROM PRAGMA_TABLE_INFO(:table_name) ORDER BY cid DESC;"
|
|
||||||
columns = cursor.execute(query, {"table_name": table_name}).fetchall()
|
|
||||||
for column in columns:
|
|
||||||
cid, name, data_type, notnull, default_value, primary_key = column
|
|
||||||
have_columns[name] = {"type": data_type, "primary_key": primary_key}
|
|
||||||
|
|
||||||
have_table["columns"] = have_columns
|
|
||||||
|
|
||||||
cursor.execute(f"PRAGMA INDEX_LIST('{table_name}');")
|
|
||||||
indices = cursor.fetchall()
|
|
||||||
for index in indices:
|
|
||||||
seq, index_name, unique, origin, partial = index
|
|
||||||
|
|
||||||
if origin == "pk":
|
|
||||||
continue
|
|
||||||
|
|
||||||
cursor.execute(f"PRAGMA INDEX_INFO('{index_name}');")
|
|
||||||
index_info = cursor.fetchall()
|
|
||||||
|
|
||||||
add_index = {"index_name": index_name}
|
|
||||||
for index_columns in index_info:
|
|
||||||
seqno, cid, name = index_columns
|
|
||||||
if origin == "u":
|
|
||||||
have_columns[name]["unique"] = 1
|
|
||||||
else:
|
|
||||||
if "column_1" not in add_index:
|
|
||||||
add_index["column_1"] = name
|
|
||||||
elif "column_2" not in add_index:
|
|
||||||
add_index["column_2"] = name
|
|
||||||
elif "column_3" not in add_index:
|
|
||||||
add_index["column_3"] = name
|
|
||||||
else:
|
|
||||||
raise RuntimeError("Add more index columns.")
|
|
||||||
if origin == "c":
|
|
||||||
if "indices" not in have_table:
|
|
||||||
have_table["indices"] = []
|
|
||||||
have_table["indices"].append(add_index)
|
|
||||||
|
|
||||||
have_tables[table_name] = have_table
|
|
||||||
|
|
||||||
for table_name, table in expect_schema.items():
|
|
||||||
if table_name not in have_tables:
|
|
||||||
self.log.info(f"Creating table {table_name}.")
|
|
||||||
create_table(cursor, table_name, table)
|
|
||||||
continue
|
|
||||||
|
|
||||||
have_table = have_tables[table_name]
|
|
||||||
have_columns = have_table["columns"]
|
|
||||||
for colname, column in table["columns"].items():
|
|
||||||
if colname not in have_columns:
|
|
||||||
col_type = column["type"]
|
|
||||||
self.log.info(f"Adding column {colname} to table {table_name}.")
|
|
||||||
cursor.execute(
|
|
||||||
f"ALTER TABLE {table_name} ADD COLUMN {colname} {col_type}"
|
|
||||||
)
|
|
||||||
indices = table.get("indices", [])
|
|
||||||
have_indices = have_table.get("indices", [])
|
|
||||||
for index in indices:
|
|
||||||
index_name = index["index_name"]
|
|
||||||
if not any(
|
|
||||||
have_idx.get("index_name") == index_name
|
|
||||||
for have_idx in have_indices
|
|
||||||
):
|
|
||||||
self.log.info(f"Adding index {index_name} to table {table_name}.")
|
|
||||||
column_1 = index["column_1"]
|
|
||||||
column_2 = index.get("column_2", None)
|
|
||||||
column_3 = index.get("column_3", None)
|
|
||||||
query: str = (
|
|
||||||
f"CREATE INDEX {index_name} ON {table_name} ({column_1}"
|
|
||||||
)
|
|
||||||
if column_2:
|
|
||||||
query += f", {column_2}"
|
|
||||||
if column_3:
|
|
||||||
query += f", {column_3}"
|
|
||||||
query += ")"
|
|
||||||
cursor.execute(query)
|
|
||||||
|
|
||||||
if CURRENT_DB_VERSION != db_version:
|
if CURRENT_DB_VERSION != db_version:
|
||||||
self.db_version = CURRENT_DB_VERSION
|
self.db_version = CURRENT_DB_VERSION
|
||||||
self.setIntKV("db_version", CURRENT_DB_VERSION, cursor)
|
self.setIntKV("db_version", CURRENT_DB_VERSION, cursor)
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
|
||||||
# Distributed under the MIT software license, see the accompanying
|
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
|
||||||
|
|
||||||
|
|
||||||
from .db import Column, Index, Table, UniqueConstraint, extract_schema
|
|
||||||
|
|
||||||
|
|
||||||
class WalletAddress(Table):
|
|
||||||
|
|
||||||
__tablename__ = "wallet_addresses"
|
|
||||||
|
|
||||||
record_id = Column("integer", primary_key=True, autoincrement=True)
|
|
||||||
coin_type = Column("integer")
|
|
||||||
derivation_index = Column("integer")
|
|
||||||
is_internal = Column("bool")
|
|
||||||
derivation_path = Column("string")
|
|
||||||
address = Column("string")
|
|
||||||
scripthash = Column("string")
|
|
||||||
pubkey = Column("blob")
|
|
||||||
is_funded = Column("bool")
|
|
||||||
cached_balance = Column("integer")
|
|
||||||
cached_balance_time = Column("integer")
|
|
||||||
first_seen_height = Column("integer")
|
|
||||||
created_at = Column("integer")
|
|
||||||
|
|
||||||
__unique_1__ = UniqueConstraint("coin_type", "derivation_index", "is_internal")
|
|
||||||
__index_address__ = Index("idx_wallet_address", "address")
|
|
||||||
__index_scripthash__ = Index("idx_wallet_scripthash", "scripthash")
|
|
||||||
__index_funded__ = Index("idx_wallet_funded", "coin_type", "is_funded")
|
|
||||||
|
|
||||||
|
|
||||||
class WalletState(Table):
|
|
||||||
|
|
||||||
__tablename__ = "wallet_state"
|
|
||||||
|
|
||||||
coin_type = Column("integer", primary_key=True)
|
|
||||||
last_external_index = Column("integer")
|
|
||||||
last_internal_index = Column("integer")
|
|
||||||
derivation_path_type = Column("string")
|
|
||||||
last_sync_height = Column("integer")
|
|
||||||
migration_complete = Column("bool")
|
|
||||||
created_at = Column("integer")
|
|
||||||
updated_at = Column("integer")
|
|
||||||
|
|
||||||
|
|
||||||
class WalletWatchOnly(Table):
|
|
||||||
|
|
||||||
__tablename__ = "wallet_watch_only"
|
|
||||||
|
|
||||||
record_id = Column("integer", primary_key=True, autoincrement=True)
|
|
||||||
coin_type = Column("integer")
|
|
||||||
address = Column("string")
|
|
||||||
scripthash = Column("string")
|
|
||||||
label = Column("string")
|
|
||||||
source = Column("string")
|
|
||||||
is_funded = Column("bool")
|
|
||||||
cached_balance = Column("integer")
|
|
||||||
cached_balance_time = Column("integer")
|
|
||||||
private_key_encrypted = Column("blob")
|
|
||||||
created_at = Column("integer")
|
|
||||||
|
|
||||||
__unique_1__ = UniqueConstraint("coin_type", "address")
|
|
||||||
__index_watch_address__ = Index("idx_watch_address", "address")
|
|
||||||
__index_watch_scripthash__ = Index("idx_watch_scripthash", "scripthash")
|
|
||||||
|
|
||||||
|
|
||||||
class WalletLockedUTXO(Table):
|
|
||||||
|
|
||||||
__tablename__ = "wallet_locked_utxos"
|
|
||||||
|
|
||||||
record_id = Column("integer", primary_key=True, autoincrement=True)
|
|
||||||
coin_type = Column("integer")
|
|
||||||
txid = Column("string")
|
|
||||||
vout = Column("integer")
|
|
||||||
value = Column("integer")
|
|
||||||
address = Column("string")
|
|
||||||
bid_id = Column("blob")
|
|
||||||
locked_at = Column("integer")
|
|
||||||
expires_at = Column("integer")
|
|
||||||
|
|
||||||
__unique_1__ = UniqueConstraint("coin_type", "txid", "vout")
|
|
||||||
__index_locked_coin__ = Index("idx_locked_coin", "coin_type")
|
|
||||||
__index_locked_bid__ = Index("idx_locked_bid", "bid_id")
|
|
||||||
|
|
||||||
|
|
||||||
class WalletTxCache(Table):
|
|
||||||
|
|
||||||
__tablename__ = "wallet_tx_cache"
|
|
||||||
|
|
||||||
record_id = Column("integer", primary_key=True, autoincrement=True)
|
|
||||||
coin_type = Column("integer")
|
|
||||||
txid = Column("string")
|
|
||||||
block_height = Column("integer")
|
|
||||||
confirmations = Column("integer")
|
|
||||||
tx_data = Column("blob")
|
|
||||||
cached_at = Column("integer")
|
|
||||||
expires_at = Column("integer")
|
|
||||||
|
|
||||||
__unique_1__ = UniqueConstraint("coin_type", "txid")
|
|
||||||
__index_tx_cache__ = Index("idx_tx_cache", "coin_type", "txid")
|
|
||||||
|
|
||||||
|
|
||||||
class WalletPendingTx(Table):
|
|
||||||
|
|
||||||
__tablename__ = "wallet_pending_txs"
|
|
||||||
|
|
||||||
record_id = Column("integer", primary_key=True, autoincrement=True)
|
|
||||||
coin_type = Column("integer")
|
|
||||||
txid = Column("string")
|
|
||||||
tx_type = Column("string")
|
|
||||||
amount = Column("integer")
|
|
||||||
fee = Column("integer")
|
|
||||||
addresses = Column("string")
|
|
||||||
bid_id = Column("blob")
|
|
||||||
first_seen = Column("integer")
|
|
||||||
confirmed_at = Column("integer")
|
|
||||||
|
|
||||||
__unique_1__ = UniqueConstraint("coin_type", "txid")
|
|
||||||
__index_pending_coin__ = Index("idx_pending_coin", "coin_type", "confirmed_at")
|
|
||||||
|
|
||||||
|
|
||||||
def extract_wallet_schema() -> dict:
|
|
||||||
return extract_schema(input_globals=globals())
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
default_coingecko_api_key = "CG-8hm3r9iLfpEXv4ied8oLbeUj"
|
default_coingecko_api_key = "CG-8hm3r9iLfpEXv4ied8oLbeUj"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import gzip
|
|
||||||
import json
|
import json
|
||||||
import shlex
|
import shlex
|
||||||
import hashlib
|
|
||||||
import secrets
|
import secrets
|
||||||
import traceback
|
import traceback
|
||||||
import threading
|
import threading
|
||||||
@@ -21,7 +19,6 @@ from jinja2 import Environment, PackageLoader
|
|||||||
from socket import error as SocketError
|
from socket import error as SocketError
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from email.utils import formatdate, parsedate_to_datetime
|
|
||||||
from http.cookies import SimpleCookie
|
from http.cookies import SimpleCookie
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
@@ -213,7 +210,7 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
status_code=200,
|
status_code=200,
|
||||||
version=__version__,
|
version=__version__,
|
||||||
extra_headers=None,
|
extra_headers=None,
|
||||||
) -> bytes:
|
):
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
if swap_client.ws_server:
|
if swap_client.ws_server:
|
||||||
args_dict["ws_port"] = swap_client.ws_server.client_port
|
args_dict["ws_port"] = swap_client.ws_server.client_port
|
||||||
@@ -805,6 +802,7 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
if page == "static":
|
if page == "static":
|
||||||
try:
|
try:
|
||||||
static_path = os.path.join(os.path.dirname(__file__), "static")
|
static_path = os.path.join(os.path.dirname(__file__), "static")
|
||||||
|
content = None
|
||||||
mime_type = ""
|
mime_type = ""
|
||||||
filepath = ""
|
filepath = ""
|
||||||
if len(url_split) > 3 and url_split[2] == "sequence_diagrams":
|
if len(url_split) > 3 and url_split[2] == "sequence_diagrams":
|
||||||
@@ -837,71 +835,9 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
if mime_type == "" or not filepath:
|
if mime_type == "" or not filepath:
|
||||||
raise ValueError("Unknown file type or path")
|
raise ValueError("Unknown file type or path")
|
||||||
|
|
||||||
file_stat = os.stat(filepath)
|
|
||||||
mtime = file_stat.st_mtime
|
|
||||||
file_size = file_stat.st_size
|
|
||||||
|
|
||||||
etag_hash = hashlib.md5(f"{file_size}-{mtime}".encode()).hexdigest()
|
|
||||||
etag = f'"{etag_hash}"'
|
|
||||||
last_modified = formatdate(mtime, usegmt=True)
|
|
||||||
|
|
||||||
if_none_match = self.headers.get("If-None-Match")
|
|
||||||
if if_none_match:
|
|
||||||
if if_none_match.strip() == "*" or etag in [
|
|
||||||
t.strip() for t in if_none_match.split(",")
|
|
||||||
]:
|
|
||||||
self.send_response(304)
|
|
||||||
self.send_header("ETag", etag)
|
|
||||||
self.send_header("Cache-Control", "public")
|
|
||||||
self.end_headers()
|
|
||||||
return b""
|
|
||||||
|
|
||||||
if_modified_since = self.headers.get("If-Modified-Since")
|
|
||||||
if if_modified_since and not if_none_match:
|
|
||||||
try:
|
|
||||||
ims_time = parsedate_to_datetime(if_modified_since)
|
|
||||||
file_time = datetime.fromtimestamp(int(mtime), tz=timezone.utc)
|
|
||||||
if file_time <= ims_time:
|
|
||||||
self.send_response(304)
|
|
||||||
self.send_header("Last-Modified", last_modified)
|
|
||||||
self.send_header("Cache-Control", "public")
|
|
||||||
self.end_headers()
|
|
||||||
return b""
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
is_lib = len(url_split) > 4 and url_split[3] == "libs"
|
|
||||||
if is_lib:
|
|
||||||
cache_control = "public, max-age=31536000, immutable"
|
|
||||||
elif url_split[2] in ("css", "js"):
|
|
||||||
cache_control = "public, max-age=3600, must-revalidate"
|
|
||||||
elif url_split[2] in ("images", "sequence_diagrams"):
|
|
||||||
cache_control = "public, max-age=86400"
|
|
||||||
else:
|
|
||||||
cache_control = "public, max-age=3600"
|
|
||||||
|
|
||||||
with open(filepath, "rb") as fp:
|
with open(filepath, "rb") as fp:
|
||||||
content = fp.read()
|
content = fp.read()
|
||||||
|
self.putHeaders(status_code, mime_type)
|
||||||
extra_headers = [
|
|
||||||
("Cache-Control", cache_control),
|
|
||||||
("Last-Modified", last_modified),
|
|
||||||
("ETag", etag),
|
|
||||||
]
|
|
||||||
|
|
||||||
is_compressible = mime_type in (
|
|
||||||
"text/css; charset=utf-8",
|
|
||||||
"application/javascript",
|
|
||||||
"image/svg+xml",
|
|
||||||
)
|
|
||||||
accept_encoding = self.headers.get("Accept-Encoding", "")
|
|
||||||
if is_compressible and "gzip" in accept_encoding:
|
|
||||||
content = gzip.compress(content)
|
|
||||||
extra_headers.append(("Content-Encoding", "gzip"))
|
|
||||||
extra_headers.append(("Vary", "Accept-Encoding"))
|
|
||||||
|
|
||||||
extra_headers.append(("Content-Length", str(len(content))))
|
|
||||||
self.putHeaders(status_code, mime_type, extra_headers=extra_headers)
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|||||||
+15
-17
@@ -1,14 +1,14 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
# Copyright (c) 2025-2026 The Basicswap developers
|
# Copyright (c) 2025 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 threading
|
import threading
|
||||||
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from basicswap.chainparams import (
|
from basicswap.chainparams import (
|
||||||
chainparams,
|
chainparams,
|
||||||
@@ -30,7 +30,6 @@ from basicswap.util.ecc import (
|
|||||||
)
|
)
|
||||||
from coincurve.dleag import verify_secp256k1_point
|
from coincurve.dleag import verify_secp256k1_point
|
||||||
from coincurve.keys import (
|
from coincurve.keys import (
|
||||||
PrivateKey,
|
|
||||||
PublicKey,
|
PublicKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,7 +48,7 @@ class CoinInterface:
|
|||||||
def compareFeeRates(a, b) -> bool:
|
def compareFeeRates(a, b) -> bool:
|
||||||
return abs(a - b) < 20
|
return abs(a - b) < 20
|
||||||
|
|
||||||
def __init__(self, network, **kwargs):
|
def __init__(self, network):
|
||||||
self.setDefaults()
|
self.setDefaults()
|
||||||
self._network = network
|
self._network = network
|
||||||
self._mx_wallet = threading.Lock()
|
self._mx_wallet = threading.Lock()
|
||||||
@@ -180,26 +179,17 @@ class CoinInterface:
|
|||||||
|
|
||||||
|
|
||||||
class AdaptorSigInterface:
|
class AdaptorSigInterface:
|
||||||
def getP2WPKHDummyWitness(self) -> List[bytes]:
|
def getScriptLockTxDummyWitness(self, script: bytes):
|
||||||
return [bytes(72), bytes(33)]
|
|
||||||
|
|
||||||
def getScriptLockTxDummyWitness(self, script: bytes) -> List[bytes]:
|
|
||||||
return [b"", bytes(72), bytes(72), bytes(len(script))]
|
return [b"", bytes(72), bytes(72), bytes(len(script))]
|
||||||
|
|
||||||
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes) -> List[bytes]:
|
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
|
||||||
return [b"", bytes(72), bytes(72), bytes((1,)), bytes(len(script))]
|
return [b"", bytes(72), bytes(72), bytes((1,)), bytes(len(script))]
|
||||||
|
|
||||||
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes) -> List[bytes]:
|
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
|
||||||
return [bytes(72), b"", bytes(len(script))]
|
return [bytes(72), b"", bytes(len(script))]
|
||||||
|
|
||||||
def getLockRefundVout(self, lock_refund_tx_data: bytes, vbkv: bytes):
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def curve_type():
|
def curve_type():
|
||||||
return Curves.secp256k1
|
return Curves.secp256k1
|
||||||
@@ -225,12 +215,20 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
|||||||
if hash_len == 20:
|
if hash_len == 20:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def isValidPubkey(self, pubkey: bytes) -> bool:
|
||||||
|
try:
|
||||||
|
self.verifyPubkey(pubkey)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool:
|
def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool:
|
||||||
pubkey = PublicKey(pubkey)
|
pubkey = PublicKey(pubkey)
|
||||||
return pubkey.verify(sig, signed_hash, hasher=None)
|
return pubkey.verify(sig, signed_hash, hasher=None)
|
||||||
|
|
||||||
def sumKeys(self, ka: bytes, kb: bytes) -> bytes:
|
def sumKeys(self, ka: bytes, kb: bytes) -> bytes:
|
||||||
return PrivateKey(ka).add(kb).secret
|
# TODO: Add to coincurve
|
||||||
|
return i2b((b2i(ka) + b2i(kb)) % ep.o)
|
||||||
|
|
||||||
def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes:
|
def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes:
|
||||||
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
|
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
|
||||||
|
|||||||
+15
-45
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ from basicswap.contrib.test_framework.messages import COutPoint, CTransaction, C
|
|||||||
from basicswap.util import b2i, ensure, i2b
|
from basicswap.util import b2i, ensure, i2b
|
||||||
from basicswap.util.script import decodePushData, decodeScriptNum
|
from basicswap.util.script import decodePushData, decodeScriptNum
|
||||||
from .btc import BTCInterface, ensure_op, findOutput
|
from .btc import BTCInterface, ensure_op, findOutput
|
||||||
|
from basicswap.rpc import make_rpc_func
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.interface.contrib.bch_test_framework.cashaddress import Address
|
from basicswap.interface.contrib.bch_test_framework.cashaddress import Address
|
||||||
from basicswap.util.crypto import hash160, sha256
|
from basicswap.util.crypto import hash160, sha256
|
||||||
@@ -71,14 +72,14 @@ class BCHInterface(BTCInterface):
|
|||||||
# TODO: BCH Watchonly: Remove when BCH watchonly works.
|
# TODO: BCH Watchonly: Remove when BCH watchonly works.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(
|
super(BCHInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
coin_settings=coin_settings,
|
# No multiwallet support
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
self.swap_client = swap_client
|
self.swap_client = swap_client
|
||||||
|
self.rpc_wallet = make_rpc_func(
|
||||||
|
self._rpcport, self._rpcauth, host=self._rpc_host
|
||||||
|
)
|
||||||
|
self.rpc_wallet_watch = self.rpc_wallet
|
||||||
|
|
||||||
def has_segwit(self) -> bool:
|
def has_segwit(self) -> bool:
|
||||||
# bch does not have segwit, but we return true here to avoid extra checks in basicswap.py
|
# bch does not have segwit, but we return true here to avoid extra checks in basicswap.py
|
||||||
@@ -147,9 +148,7 @@ class BCHInterface(BTCInterface):
|
|||||||
|
|
||||||
if not self.isAddressMine(address, or_watch_only=True):
|
if not self.isAddressMine(address, or_watch_only=True):
|
||||||
# Expects P2WSH nested in BIP16_P2SH
|
# Expects P2WSH nested in BIP16_P2SH
|
||||||
self.rpc_wallet(
|
self.rpc("importaddress", [lock_tx_dest.hex(), "bid lock", False, True])
|
||||||
"importaddress", [lock_tx_dest.hex(), "bid lock", False, True]
|
|
||||||
)
|
|
||||||
|
|
||||||
return address
|
return address
|
||||||
|
|
||||||
@@ -158,35 +157,18 @@ class BCHInterface(BTCInterface):
|
|||||||
|
|
||||||
def createRawFundedTransaction(
|
def createRawFundedTransaction(
|
||||||
self,
|
self,
|
||||||
addr_to: str | bytes,
|
addr_to: str,
|
||||||
amount: int,
|
amount: int,
|
||||||
sub_fee: bool = False,
|
sub_fee: bool = False,
|
||||||
lock_unspents: bool = True,
|
lock_unspents: bool = True,
|
||||||
feerate: int = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|
||||||
if isinstance(addr_to, bytes):
|
|
||||||
# addr_to is script_pubkey
|
|
||||||
tx = CTransaction()
|
|
||||||
tx.nVersion = self.txVersion()
|
|
||||||
tx.vout.append(self.txoType()(amount, addr_to))
|
|
||||||
txn = tx.serialize_without_witness().hex()
|
|
||||||
else:
|
|
||||||
txn = self.rpc(
|
txn = self.rpc(
|
||||||
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
||||||
)
|
)
|
||||||
|
|
||||||
if feerate:
|
|
||||||
fee_rate = self.format_amount(feerate)
|
|
||||||
fee_src = "specified"
|
|
||||||
else:
|
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
|
||||||
self._log.debug(
|
|
||||||
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
|
||||||
)
|
|
||||||
options = {
|
options = {
|
||||||
"lockUnspents": lock_unspents,
|
"lockUnspents": lock_unspents,
|
||||||
"feeRate": fee_rate,
|
# 'conf_target': self._conf_target,
|
||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options["subtractFeeFromOutputs"] = [
|
options["subtractFeeFromOutputs"] = [
|
||||||
@@ -238,16 +220,6 @@ class BCHInterface(BTCInterface):
|
|||||||
)
|
)
|
||||||
return pay_fee
|
return pay_fee
|
||||||
|
|
||||||
def getBLockTxo(
|
|
||||||
self,
|
|
||||||
chain_b_lock_txid: bytes,
|
|
||||||
lock_tx_vout: int,
|
|
||||||
script_pk: bytes,
|
|
||||||
) -> (int, int):
|
|
||||||
txout = self.rpc("gettxout", [chain_b_lock_txid.hex(), lock_tx_vout, True])
|
|
||||||
actual_value = self.make_int(txout["value"])
|
|
||||||
return lock_tx_vout, actual_value
|
|
||||||
|
|
||||||
def findTxnByHash(self, txid_hex: str):
|
def findTxnByHash(self, txid_hex: str):
|
||||||
# Only works for wallet txns
|
# Only works for wallet txns
|
||||||
try:
|
try:
|
||||||
@@ -302,7 +274,7 @@ class BCHInterface(BTCInterface):
|
|||||||
found_vout = try_vout
|
found_vout = try_vout
|
||||||
break
|
break
|
||||||
except Exception as e: # noqa: F841
|
except Exception as e: # noqa: F841
|
||||||
# self._log.warning(f"gettxout {e}")
|
# self._log.warning('gettxout {}'.format(e))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if found_vout is None:
|
if found_vout is None:
|
||||||
@@ -315,14 +287,13 @@ class BCHInterface(BTCInterface):
|
|||||||
|
|
||||||
# TODO: Better way?
|
# TODO: Better way?
|
||||||
if confirmations > 0:
|
if confirmations > 0:
|
||||||
block_height = self.getChainHeight() - (confirmations - 1)
|
block_height = self.getChainHeight() - confirmations
|
||||||
|
|
||||||
rv = {
|
rv = {
|
||||||
"txid": txid.hex(),
|
"txid": txid.hex(),
|
||||||
"depth": confirmations,
|
"depth": confirmations,
|
||||||
"index": found_vout,
|
"index": found_vout,
|
||||||
"height": block_height,
|
"height": block_height,
|
||||||
"value": self.make_int(txout["value"]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
@@ -537,7 +508,6 @@ class BCHInterface(BTCInterface):
|
|||||||
tx_lock = self.loadTx(tx_lock_bytes)
|
tx_lock = self.loadTx(tx_lock_bytes)
|
||||||
|
|
||||||
output_script = self.getScriptDest(script_lock)
|
output_script = self.getScriptDest(script_lock)
|
||||||
|
|
||||||
locked_n = findOutput(tx_lock, output_script)
|
locked_n = findOutput(tx_lock, output_script)
|
||||||
ensure(locked_n is not None, "Output not found in tx")
|
ensure(locked_n is not None, "Output not found in tx")
|
||||||
locked_coin = tx_lock.vout[locked_n].nValue
|
locked_coin = tx_lock.vout[locked_n].nValue
|
||||||
@@ -1156,7 +1126,7 @@ class BCHInterface(BTCInterface):
|
|||||||
refund_output_value = refund_swipe_tx.vout[0].nValue
|
refund_output_value = refund_swipe_tx.vout[0].nValue
|
||||||
refund_output_script = refund_swipe_tx.vout[0].scriptPubKey
|
refund_output_script = refund_swipe_tx.vout[0].scriptPubKey
|
||||||
|
|
||||||
# Mercy transaction size consisting of one input of freshly received funds,
|
# mercy transaction size consisting of one input of freshly received funds,
|
||||||
# one op_return with mercy information, a dust output to the leader and change back to the follower
|
# one op_return with mercy information, a dust output to the leader and change back to the follower
|
||||||
tx_size = 275
|
tx_size = 275
|
||||||
dust_limit = 546
|
dust_limit = 546
|
||||||
|
|||||||
+113
-2146
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2024 tecnovert
|
# Copyright (c) 2022-2024 tecnovert
|
||||||
@@ -23,13 +24,8 @@ class DASHInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.DASH
|
return Coins.DASH
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(
|
super().__init__(coin_settings, network, swap_client)
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
self._wallet_passphrase = ""
|
self._wallet_passphrase = ""
|
||||||
self._have_checked_seed = False
|
self._have_checked_seed = False
|
||||||
|
|
||||||
@@ -136,7 +132,7 @@ class DASHInterface(BTCInterface):
|
|||||||
self.unlockWallet(old_password, check_seed=False)
|
self.unlockWallet(old_password, check_seed=False)
|
||||||
seed_id_before: str = self.getWalletSeedID()
|
seed_id_before: str = self.getWalletSeedID()
|
||||||
|
|
||||||
self.rpc_wallet("encryptwallet", [new_password], timeout=120)
|
self.rpc_wallet("encryptwallet", [new_password])
|
||||||
|
|
||||||
if check_seed is False or seed_id_before == "Not found":
|
if check_seed is False or seed_id_before == "Not found":
|
||||||
return
|
return
|
||||||
@@ -160,6 +156,4 @@ class DASHInterface(BTCInterface):
|
|||||||
if self.isWalletEncrypted():
|
if self.isWalletEncrypted():
|
||||||
raise ValueError("Old password must be set")
|
raise ValueError("Old password must be set")
|
||||||
return self.encryptWallet(old_password, new_password, check_seed_if_encrypt)
|
return self.encryptWallet(old_password, new_password, check_seed_if_encrypt)
|
||||||
self.rpc_wallet(
|
self.rpc_wallet("walletpassphrasechange", [old_password, new_password])
|
||||||
"walletpassphrasechange", [old_password, new_password], timeout=120
|
|
||||||
)
|
|
||||||
|
|||||||
+37
-214
@@ -1,7 +1,8 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -12,15 +13,16 @@ import logging
|
|||||||
import random
|
import random
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from typing import List, Optional
|
from typing import List
|
||||||
|
|
||||||
from basicswap.basicswap_util import getVoutByScriptPubKey, TxLockTypes
|
from basicswap.basicswap_util import getVoutByScriptPubKey, TxLockTypes
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.contrib.test_framework.script import (
|
from basicswap.contrib.test_framework.script import (
|
||||||
CScriptNum,
|
CScriptNum,
|
||||||
)
|
)
|
||||||
from basicswap.interface.base import Secp256k1Interface
|
from basicswap.interface.base import (
|
||||||
from basicswap.interface.utils import FeeValidator
|
Secp256k1Interface,
|
||||||
|
)
|
||||||
from basicswap.interface.btc import (
|
from basicswap.interface.btc import (
|
||||||
extractScriptLockScriptValues,
|
extractScriptLockScriptValues,
|
||||||
extractScriptLockRefundScriptValues,
|
extractScriptLockRefundScriptValues,
|
||||||
@@ -80,6 +82,7 @@ from coincurve.ecdsaotves import (
|
|||||||
ecdsaotves_rec_enc_key,
|
ecdsaotves_rec_enc_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
|
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
|
||||||
SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22
|
SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22
|
||||||
SEQUENCE_LOCKTIME_MASK = 0x0000F
|
SEQUENCE_LOCKTIME_MASK = 0x0000F
|
||||||
@@ -179,16 +182,12 @@ def extract_sig_and_pk(sig_script: bytes) -> (bytes, bytes):
|
|||||||
return sig, pk
|
return sig, pk
|
||||||
|
|
||||||
|
|
||||||
class DCRInterface(FeeValidator, Secp256k1Interface):
|
class DCRInterface(Secp256k1Interface):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.DCR
|
return Coins.DCR
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def useBackend() -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def exp() -> int:
|
def exp() -> int:
|
||||||
return 8
|
return 8
|
||||||
@@ -256,13 +255,13 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
def depth_spendable() -> int:
|
def depth_spendable() -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
self._sc = swap_client
|
super().__init__(network)
|
||||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
|
||||||
super().__init__(coin_settings=coin_settings, network=network, **kwargs)
|
|
||||||
self._rpc_host = coin_settings.get("rpchost", "127.0.0.1")
|
self._rpc_host = coin_settings.get("rpchost", "127.0.0.1")
|
||||||
self._rpcport = coin_settings["rpcport"]
|
self._rpcport = coin_settings["rpcport"]
|
||||||
self._rpcauth = coin_settings["rpcauth"]
|
self._rpcauth = coin_settings["rpcauth"]
|
||||||
|
self._sc = swap_client
|
||||||
|
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||||
self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||||
if "walletrpcport" in coin_settings:
|
if "walletrpcport" in coin_settings:
|
||||||
self._walletrpcport = coin_settings["walletrpcport"]
|
self._walletrpcport = coin_settings["walletrpcport"]
|
||||||
@@ -365,9 +364,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
# Read initial pwd from settings
|
# Read initial pwd from settings
|
||||||
settings = self._sc.getChainClientSettings(self.coin_type())
|
settings = self._sc.getChainClientSettings(self.coin_type())
|
||||||
old_password = settings["wallet_pwd"]
|
old_password = settings["wallet_pwd"]
|
||||||
self.rpc_wallet(
|
self.rpc_wallet("walletpassphrasechange", [old_password, new_password])
|
||||||
"walletpassphrasechange", [old_password, new_password], timeout=120
|
|
||||||
)
|
|
||||||
|
|
||||||
# Lock wallet to match other coins
|
# Lock wallet to match other coins
|
||||||
self.rpc_wallet("walletlock")
|
self.rpc_wallet("walletlock")
|
||||||
@@ -381,7 +378,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
self._log.info("unlockWallet - {}".format(self.ticker()))
|
self._log.info("unlockWallet - {}".format(self.ticker()))
|
||||||
|
|
||||||
# Max timeout value, ~3 years
|
# Max timeout value, ~3 years
|
||||||
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
|
self.rpc_wallet("walletpassphrase", [password, 100000000])
|
||||||
if check_seed:
|
if check_seed:
|
||||||
self._sc.checkWalletSeed(self.coin_type())
|
self._sc.checkWalletSeed(self.coin_type())
|
||||||
|
|
||||||
@@ -409,19 +406,14 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
# Adjust verificationprogress to consider blocks wallet has synced
|
# Adjust verificationprogress to consider blocks wallet has synced
|
||||||
wallet_blocks = self.rpc_wallet("getinfo")["blocks"]
|
wallet_blocks = self.rpc_wallet("getinfo")["blocks"]
|
||||||
synced_ind = bci["verificationprogress"]
|
synced_ind = bci["verificationprogress"]
|
||||||
if bci["headers"] < 1:
|
|
||||||
wallet_synced_ind = 0
|
|
||||||
else:
|
|
||||||
wallet_synced_ind = wallet_blocks / bci["headers"]
|
wallet_synced_ind = wallet_blocks / bci["headers"]
|
||||||
if wallet_synced_ind < synced_ind:
|
if wallet_synced_ind < synced_ind:
|
||||||
bci["verificationprogress"] = wallet_synced_ind
|
bci["verificationprogress"] = wallet_synced_ind
|
||||||
|
|
||||||
return bci
|
return bci
|
||||||
|
|
||||||
def getBlockHeader(self, block_hash: str) -> dict:
|
|
||||||
return self.rpc("getblockheader", [block_hash])
|
|
||||||
|
|
||||||
def getWalletInfo(self):
|
def getWalletInfo(self):
|
||||||
|
rv = {}
|
||||||
rv = self.rpc_wallet("getinfo")
|
rv = self.rpc_wallet("getinfo")
|
||||||
wi = self.rpc_wallet("walletinfo")
|
wi = self.rpc_wallet("walletinfo")
|
||||||
balances = self.rpc_wallet("getbalance")
|
balances = self.rpc_wallet("getbalance")
|
||||||
@@ -596,7 +588,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
override_feerate = chain_client_settings.get("override_feerate", None)
|
override_feerate = chain_client_settings.get("override_feerate", None)
|
||||||
if override_feerate:
|
if override_feerate:
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
f"Fee rate override used for {self.coin_name()}: {override_feerate}"
|
"Fee rate override used for %s: %f", self.coin_name(), override_feerate
|
||||||
)
|
)
|
||||||
return override_feerate, "override_feerate"
|
return override_feerate, "override_feerate"
|
||||||
|
|
||||||
@@ -637,15 +629,6 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
# TODO: filter errors
|
# TODO: filter errors
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def listWalletTransactions(self, count=100, skip=0, include_watchonly=True):
|
|
||||||
try:
|
|
||||||
return self.rpc_wallet(
|
|
||||||
"listtransactions", ["*", count, skip, include_watchonly]
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.error(f"listWalletTransactions failed: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def getProofOfFunds(self, amount_for, extra_commit_bytes):
|
def getProofOfFunds(self, amount_for, extra_commit_bytes):
|
||||||
# TODO: Lock unspent and use same output/s to fund bid
|
# TODO: Lock unspent and use same output/s to fund bid
|
||||||
|
|
||||||
@@ -857,16 +840,11 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
amount: int,
|
amount: int,
|
||||||
sub_fee: bool = False,
|
sub_fee: bool = False,
|
||||||
lock_unspents: bool = True,
|
lock_unspents: bool = True,
|
||||||
feerate: int = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|
||||||
# amount can't be a string, else: Failed to parse request: parameter #2 'amounts' must be type float64 (got string)
|
# amount can't be a string, else: Failed to parse request: parameter #2 'amounts' must be type float64 (got string)
|
||||||
float_amount = float(self.format_amount(amount))
|
float_amount = float(self.format_amount(amount))
|
||||||
txn = self.rpc("createrawtransaction", [[], {addr_to: float_amount}])
|
txn = self.rpc("createrawtransaction", [[], {addr_to: float_amount}])
|
||||||
if feerate:
|
|
||||||
fee_rate = feerate
|
|
||||||
fee_src = "specified"
|
|
||||||
else:
|
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
||||||
@@ -920,7 +898,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
found_vout = try_vout
|
found_vout = try_vout
|
||||||
break
|
break
|
||||||
except Exception as e: # noqa: F841
|
except Exception as e: # noqa: F841
|
||||||
# self._log.warning(f"gettxout {e})
|
# self._log.warning('gettxout {}'.format(e))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if found_vout is None:
|
if found_vout is None:
|
||||||
@@ -933,14 +911,13 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
|
|
||||||
# TODO: Better way?
|
# TODO: Better way?
|
||||||
if confirmations > 0:
|
if confirmations > 0:
|
||||||
block_height = self.getChainHeight() - (confirmations - 1)
|
block_height = self.getChainHeight() - confirmations
|
||||||
|
|
||||||
rv = {
|
rv = {
|
||||||
"txid": txid.hex(),
|
"txid": txid.hex(),
|
||||||
"depth": confirmations,
|
"depth": confirmations,
|
||||||
"index": found_vout,
|
"index": found_vout,
|
||||||
"height": block_height,
|
"height": block_height,
|
||||||
"value": self.make_int(txout["value"]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
@@ -1001,10 +978,6 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
tx.vout.append(self.txoType()(output_value, script))
|
tx.vout.append(self.txoType()(output_value, script))
|
||||||
return tx.serialize().hex()
|
return tx.serialize().hex()
|
||||||
|
|
||||||
def ensureFunds(self, amount: int) -> None:
|
|
||||||
if self.getSpendableBalance() < amount:
|
|
||||||
raise ValueError("Balance too low")
|
|
||||||
|
|
||||||
def verifyRawTransaction(self, tx_hex: str, prevouts):
|
def verifyRawTransaction(self, tx_hex: str, prevouts):
|
||||||
inputs_valid: bool = True
|
inputs_valid: bool = True
|
||||||
validscripts: int = 0
|
validscripts: int = 0
|
||||||
@@ -1079,12 +1052,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
def describeTx(self, tx_hex: str):
|
def describeTx(self, tx_hex: str):
|
||||||
return self.rpc("decoderawtransaction", [tx_hex])
|
return self.rpc("decoderawtransaction", [tx_hex])
|
||||||
|
|
||||||
def decodeRawTransaction(self, tx_hex: str):
|
def fundTx(self, tx: bytes, feerate) -> bytes:
|
||||||
return self.rpc("decoderawtransaction", [tx_hex])
|
|
||||||
|
|
||||||
def fundTx(
|
|
||||||
self, tx: bytes, feerate: int, lock_unspents: bool = True, subfee: bool = False
|
|
||||||
) -> bytes:
|
|
||||||
feerate_str = float(self.format_amount(feerate))
|
feerate_str = float(self.format_amount(feerate))
|
||||||
# TODO: unlock unspents if bid cancelled
|
# TODO: unlock unspents if bid cancelled
|
||||||
options = {
|
options = {
|
||||||
@@ -1157,7 +1125,6 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
|
|
||||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||||
size += 1
|
|
||||||
pay_fee = round(tx_fee_rate * size / 1000)
|
pay_fee = round(tx_fee_rate * size / 1000)
|
||||||
tx.vout[0].value = locked_coin - pay_fee
|
tx.vout[0].value = locked_coin - pay_fee
|
||||||
|
|
||||||
@@ -1209,7 +1176,6 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
|
|
||||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||||
size += 1
|
|
||||||
pay_fee = round(tx_fee_rate * size / 1000)
|
pay_fee = round(tx_fee_rate * size / 1000)
|
||||||
tx.vout[0].value = locked_coin - pay_fee
|
tx.vout[0].value = locked_coin - pay_fee
|
||||||
|
|
||||||
@@ -1261,7 +1227,6 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
script_lock_refund
|
script_lock_refund
|
||||||
)
|
)
|
||||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||||
size += 1
|
|
||||||
pay_fee = round(tx_fee_rate * size / 1000)
|
pay_fee = round(tx_fee_rate * size / 1000)
|
||||||
tx.vout[0].value = locked_coin - pay_fee
|
tx.vout[0].value = locked_coin - pay_fee
|
||||||
|
|
||||||
@@ -1346,7 +1311,6 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
assert fee_paid > 0
|
assert fee_paid > 0
|
||||||
|
|
||||||
size = len(tx.serialize()) + add_witness_bytes
|
size = len(tx.serialize()) + add_witness_bytes
|
||||||
size += 1
|
|
||||||
fee_rate_paid = fee_paid * 1000 // size
|
fee_rate_paid = fee_paid * 1000 // size
|
||||||
|
|
||||||
self._log.info(
|
self._log.info(
|
||||||
@@ -1408,7 +1372,6 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
|
|
||||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script)
|
dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script)
|
||||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||||
size += 1
|
|
||||||
fee_rate_paid = fee_paid * 1000 // size
|
fee_rate_paid = fee_paid * 1000 // size
|
||||||
|
|
||||||
self._log.info(
|
self._log.info(
|
||||||
@@ -1419,7 +1382,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||||
raise ValueError(f"Bad fee rate, expected: {feerate}")
|
raise ValueError("Bad fee rate, expected: {}".format(feerate))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1481,7 +1444,6 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
|
|
||||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script)
|
dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script)
|
||||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||||
size += 1
|
|
||||||
fee_rate_paid = fee_paid * 1000 // size
|
fee_rate_paid = fee_paid * 1000 // size
|
||||||
|
|
||||||
self._log.info(
|
self._log.info(
|
||||||
@@ -1489,7 +1451,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||||
raise ValueError(f"Bad fee rate, expected: {feerate}")
|
raise ValueError("Bad fee rate, expected: {}".format(feerate))
|
||||||
|
|
||||||
return txid, locked_coin, locked_n
|
return txid, locked_coin, locked_n
|
||||||
|
|
||||||
@@ -1543,7 +1505,6 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
prevout_script
|
prevout_script
|
||||||
)
|
)
|
||||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||||
size += 1
|
|
||||||
fee_rate_paid = fee_paid * 1000 // size
|
fee_rate_paid = fee_paid * 1000 // size
|
||||||
|
|
||||||
self._log.info(
|
self._log.info(
|
||||||
@@ -1551,7 +1512,7 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||||
raise ValueError(f"Bad fee rate, expected: {feerate}")
|
raise ValueError("Bad fee rate, expected: {}".format(feerate))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1759,19 +1720,15 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
tx.vout.append(self.txoType()(output_amount, script_pk))
|
tx.vout.append(self.txoType()(output_amount, script_pk))
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def publishBLockTx(self, kbv, Kbs, output_amount, feerate, unlock_time: int = 0):
|
def publishBLockTx(
|
||||||
|
self, kbv, Kbs, output_amount, feerate, unlock_time: int = 0
|
||||||
|
) -> bytes:
|
||||||
b_lock_tx = self.createBLockTx(Kbs, output_amount)
|
b_lock_tx = self.createBLockTx(Kbs, output_amount)
|
||||||
|
|
||||||
b_lock_tx = self.fundTx(b_lock_tx, feerate)
|
b_lock_tx = self.fundTx(b_lock_tx, feerate)
|
||||||
|
|
||||||
script_pk = self.getPkDest(Kbs)
|
|
||||||
funded_tx = self.loadTx(b_lock_tx)
|
|
||||||
lock_vout = findOutput(funded_tx, script_pk)
|
|
||||||
|
|
||||||
b_lock_tx = self.signTxWithWallet(b_lock_tx)
|
b_lock_tx = self.signTxWithWallet(b_lock_tx)
|
||||||
|
|
||||||
txid = bytes.fromhex(self.publishTx(b_lock_tx))
|
return bytes.fromhex(self.publishTx(b_lock_tx))
|
||||||
return txid, lock_vout
|
|
||||||
|
|
||||||
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
||||||
witness_bytes = 115
|
witness_bytes = 115
|
||||||
@@ -1794,16 +1751,17 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
spend_actual_balance: bool = False,
|
spend_actual_balance: bool = False,
|
||||||
lock_tx_vout=None,
|
lock_tx_vout=None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
self._log.info(
|
self._log.info("spendBLockTx %s:\n", chain_b_lock_txid.hex())
|
||||||
f"spendBLockTx: {self._log.id(chain_b_lock_txid)} {lock_tx_vout}\n"
|
locked_n = lock_tx_vout
|
||||||
)
|
|
||||||
|
|
||||||
Kbs = self.getPubkey(kbs)
|
Kbs = self.getPubkey(kbs)
|
||||||
script_pk = self.getPkDest(Kbs)
|
script_pk = self.getPkDest(Kbs)
|
||||||
|
|
||||||
locked_n = None
|
if locked_n is None:
|
||||||
actual_value = None
|
self._log.debug(
|
||||||
try:
|
f"Unknown lock vout, searching tx: {chain_b_lock_txid.hex()}"
|
||||||
|
)
|
||||||
|
# When refunding a lock tx, it should be in the wallet as a sent tx
|
||||||
wtx = self.rpc_wallet(
|
wtx = self.rpc_wallet(
|
||||||
"gettransaction",
|
"gettransaction",
|
||||||
[
|
[
|
||||||
@@ -1812,45 +1770,8 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
)
|
)
|
||||||
lock_tx = self.loadTx(bytes.fromhex(wtx["hex"]))
|
lock_tx = self.loadTx(bytes.fromhex(wtx["hex"]))
|
||||||
locked_n = findOutput(lock_tx, script_pk)
|
locked_n = findOutput(lock_tx, script_pk)
|
||||||
if locked_n is not None:
|
|
||||||
actual_value = lock_tx.vout[locked_n].value
|
|
||||||
else:
|
|
||||||
self._log.error(
|
|
||||||
f"spendBLockTx: Output not found in tx {self._log.id(chain_b_lock_txid)}, "
|
|
||||||
f"script_pk={script_pk.hex()}, num_outputs={len(lock_tx.vout)}"
|
|
||||||
)
|
|
||||||
for i, out in enumerate(lock_tx.vout):
|
|
||||||
self._log.debug(
|
|
||||||
f" vout[{i}]: value={out.value}, scriptPubKey={out.scriptPubKey.hex()}"
|
|
||||||
)
|
|
||||||
except Exception as e: # noqa: F841
|
|
||||||
txout = self.rpc(
|
|
||||||
"gettxout", [chain_b_lock_txid.hex(), lock_tx_vout, 0, True]
|
|
||||||
)
|
|
||||||
actual_value = self.make_int(txout["value"])
|
|
||||||
locked_n = lock_tx_vout
|
|
||||||
|
|
||||||
if (
|
|
||||||
locked_n is not None
|
|
||||||
and lock_tx_vout is not None
|
|
||||||
and locked_n != lock_tx_vout
|
|
||||||
):
|
|
||||||
self._log.warning(
|
|
||||||
f"spendBLockTx: Stored vout {lock_tx_vout} differs from actual vout {locked_n} "
|
|
||||||
f"for tx {self._log.id(chain_b_lock_txid)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
ensure(locked_n is not None, "Output not found in tx")
|
ensure(locked_n is not None, "Output not found in tx")
|
||||||
|
|
||||||
spend_value = cb_swap_value
|
|
||||||
if spend_actual_balance and actual_value is not None:
|
|
||||||
if actual_value != cb_swap_value:
|
|
||||||
self._log.warning(
|
|
||||||
f"spendBLockTx: Spending actual balance {actual_value}, "
|
|
||||||
f"not expected swap value {cb_swap_value}."
|
|
||||||
)
|
|
||||||
spend_value = actual_value
|
|
||||||
|
|
||||||
pkh_to = self.decodeAddress(address_to)
|
pkh_to = self.decodeAddress(address_to)
|
||||||
|
|
||||||
tx = CTransaction()
|
tx = CTransaction()
|
||||||
@@ -1859,10 +1780,10 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
chain_b_lock_txid_int = b2i(chain_b_lock_txid)
|
chain_b_lock_txid_int = b2i(chain_b_lock_txid)
|
||||||
|
|
||||||
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n, 0), sequence=0))
|
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n, 0), sequence=0))
|
||||||
tx.vout.append(self.txoType()(spend_value, self.getPubkeyHashDest(pkh_to)))
|
tx.vout.append(self.txoType()(cb_swap_value, self.getPubkeyHashDest(pkh_to)))
|
||||||
|
|
||||||
pay_fee = self.getBLockSpendTxFee(tx, b_fee)
|
pay_fee = self.getBLockSpendTxFee(tx, b_fee)
|
||||||
tx.vout[0].value = spend_value - pay_fee
|
tx.vout[0].value = cb_swap_value - pay_fee
|
||||||
|
|
||||||
b_lock_spend_tx = tx.serialize()
|
b_lock_spend_tx = tx.serialize()
|
||||||
b_lock_spend_tx = self.signTxWithKey(b_lock_spend_tx, kbs)
|
b_lock_spend_tx = self.signTxWithKey(b_lock_spend_tx, kbs)
|
||||||
@@ -1873,14 +1794,14 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
try:
|
try:
|
||||||
txout = self.rpc("gettxout", [txid_hex, 0, 0, True])
|
txout = self.rpc("gettxout", [txid_hex, 0, 0, True])
|
||||||
except Exception as e: # noqa: F841
|
except Exception as e: # noqa: F841
|
||||||
# self._log.warning(f"gettxout {e}"))
|
# self._log.warning('gettxout {}'.format(e))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
confirmations: int = (
|
confirmations: int = (
|
||||||
0 if "confirmations" not in txout else txout["confirmations"]
|
0 if "confirmations" not in txout else txout["confirmations"]
|
||||||
)
|
)
|
||||||
if confirmations >= self.blocks_confirmed:
|
if confirmations >= self.blocks_confirmed:
|
||||||
block_height = self.getChainHeight() - (confirmations - 1)
|
block_height = self.getChainHeight() - confirmations # TODO: Better way?
|
||||||
return {"txid": txid_hex, "amount": 0, "height": block_height}
|
return {"txid": txid_hex, "amount": 0, "height": block_height}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -1895,101 +1816,3 @@ class DCRInterface(FeeValidator, Secp256k1Interface):
|
|||||||
|
|
||||||
def isTxNonFinalError(self, err_str: str) -> bool:
|
def isTxNonFinalError(self, err_str: str) -> bool:
|
||||||
return "locks on inputs not met" in err_str
|
return "locks on inputs not met" in err_str
|
||||||
|
|
||||||
def getChainMedianTime(self) -> int:
|
|
||||||
bestblockhash = self.rpc("getbestblockhash")
|
|
||||||
bestblockheader = self.rpc(
|
|
||||||
"getblockheader",
|
|
||||||
[
|
|
||||||
bestblockhash,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return bestblockheader["mediantime"]
|
|
||||||
|
|
||||||
def getTxLocktime(self, tx_data: bytes) -> int:
|
|
||||||
tx_obj = self.loadTx(tx_data)
|
|
||||||
return tx_obj.locktime
|
|
||||||
|
|
||||||
def getTxInSequence(self, tx_data: bytes, vout: int) -> int:
|
|
||||||
tx_obj = self.loadTx(tx_data)
|
|
||||||
return tx_obj.vin[vout].sequence
|
|
||||||
|
|
||||||
def isCsvLockMature(
|
|
||||||
self,
|
|
||||||
lock_type: int,
|
|
||||||
encoded_sequence: int,
|
|
||||||
parent_block_height: Optional[int],
|
|
||||||
parent_block_time: Optional[int],
|
|
||||||
chain_height: Optional[int] = None,
|
|
||||||
chain_mtp: Optional[int] = None,
|
|
||||||
) -> bool:
|
|
||||||
if parent_block_height is None or parent_block_height < 1:
|
|
||||||
return False
|
|
||||||
lock_value: int = self.decodeSequence(encoded_sequence)
|
|
||||||
if lock_type == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
|
|
||||||
if chain_height is None:
|
|
||||||
chain_height = self.getChainHeight()
|
|
||||||
return chain_height + 1 >= parent_block_height + lock_value
|
|
||||||
if lock_type == TxLockTypes.SEQUENCE_LOCK_TIME:
|
|
||||||
if parent_block_time is None or parent_block_time < 1:
|
|
||||||
return False
|
|
||||||
if chain_mtp is None:
|
|
||||||
chain_mtp = self.getChainMedianTime()
|
|
||||||
return chain_mtp >= parent_block_time + lock_value
|
|
||||||
raise ValueError(f"Unknown lock type {lock_type}")
|
|
||||||
|
|
||||||
def isAbsLockTimeMature(
|
|
||||||
self,
|
|
||||||
nlocktime: int,
|
|
||||||
chain_height: Optional[int] = None,
|
|
||||||
chain_mtp: Optional[int] = None,
|
|
||||||
) -> bool:
|
|
||||||
if nlocktime == 0:
|
|
||||||
return True
|
|
||||||
if nlocktime < 500000000:
|
|
||||||
if chain_height is None:
|
|
||||||
chain_height = self.getChainHeight()
|
|
||||||
return chain_height + 1 >= nlocktime
|
|
||||||
if chain_mtp is None:
|
|
||||||
chain_mtp = self.getChainMedianTime()
|
|
||||||
return chain_mtp >= nlocktime
|
|
||||||
|
|
||||||
def getTxOutInfo(
|
|
||||||
self, txid: bytes, n: int, include_mempool: bool = False
|
|
||||||
) -> dict():
|
|
||||||
try:
|
|
||||||
txout = self.rpc("gettxout", [txid.hex(), n, 0, include_mempool])
|
|
||||||
confirmations: int = (
|
|
||||||
0 if "confirmations" not in txout else txout["confirmations"]
|
|
||||||
)
|
|
||||||
if confirmations < 1:
|
|
||||||
return None
|
|
||||||
chain_tip_height: int = 0
|
|
||||||
if "bestblock" in txout:
|
|
||||||
bestheader_info = self.getBlockHeader(txout["bestblock"])
|
|
||||||
chain_tip_height = bestheader_info["height"]
|
|
||||||
else:
|
|
||||||
chain_tip_height = self.getChainHeight()
|
|
||||||
|
|
||||||
if confirmations == 1:
|
|
||||||
header_info = bestheader_info
|
|
||||||
else:
|
|
||||||
block_height: int = chain_tip_height - (confirmations - 1)
|
|
||||||
header_info = self.getBlockHeaderFromHeight(block_height)
|
|
||||||
|
|
||||||
block_hash: bytes = bytes.fromhex(header_info["hash"])
|
|
||||||
return {
|
|
||||||
"block_hash": block_hash,
|
|
||||||
"block_height": header_info["height"],
|
|
||||||
"block_time": header_info["time"],
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e: # noqa: F841
|
|
||||||
# self._log.warning(f"gettxout {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def is_transient_error(self, ex) -> bool:
|
|
||||||
str_error: str = str(ex).lower()
|
|
||||||
if "no information for transaction" in str_error:
|
|
||||||
return True
|
|
||||||
return super().is_transient_error(ex)
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
# 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.
|
||||||
|
|
||||||
@@ -10,10 +9,10 @@ import traceback
|
|||||||
from basicswap.rpc import Jsonrpc
|
from basicswap.rpc import Jsonrpc
|
||||||
|
|
||||||
|
|
||||||
def callrpc(rpc_port, auth, method, params=[], host="127.0.0.1", timeout=None):
|
def callrpc(rpc_port, auth, method, params=[], host="127.0.0.1"):
|
||||||
try:
|
try:
|
||||||
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
||||||
x = Jsonrpc(url, timeout=timeout if timeout else 10)
|
x = Jsonrpc(url)
|
||||||
x.__handler = None
|
x.__handler = None
|
||||||
v = x.json_request(method, params)
|
v = x.json_request(method, params)
|
||||||
x.close()
|
x.close()
|
||||||
@@ -42,7 +41,7 @@ def make_rpc_func(port, auth, host="127.0.0.1"):
|
|||||||
auth = auth
|
auth = auth
|
||||||
host = host
|
host = host
|
||||||
|
|
||||||
def rpc_func(method, params=None, timeout=None):
|
def rpc_func(method, params=None):
|
||||||
return callrpc(port, auth, method, params, host, timeout=timeout)
|
return callrpc(port, auth, method, params, host)
|
||||||
|
|
||||||
return rpc_func
|
return rpc_func
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
# Copyright (c) 2025 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.
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ import subprocess
|
|||||||
def createDCRWallet(args, hex_seed, logging, delay_event):
|
def createDCRWallet(args, hex_seed, logging, delay_event):
|
||||||
logging.info("Creating DCR wallet")
|
logging.info("Creating DCR wallet")
|
||||||
|
|
||||||
pipe_r, pipe_w = os.pipe() # subprocess.PIPE is buffered, blocks when read
|
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
str_args = " ".join(args)
|
str_args = " ".join(args)
|
||||||
@@ -36,13 +35,6 @@ def createDCRWallet(args, hex_seed, logging, delay_event):
|
|||||||
response = b"y\n"
|
response = b"y\n"
|
||||||
elif "Enter existing wallet seed" in buf:
|
elif "Enter existing wallet seed" in buf:
|
||||||
response = (hex_seed + "\n").encode("utf-8")
|
response = (hex_seed + "\n").encode("utf-8")
|
||||||
elif "Do you have a wallet birthday we should rescan from" in buf:
|
|
||||||
response = b"no\n"
|
|
||||||
elif (
|
|
||||||
"Do you have an additional account to import from an extended public key"
|
|
||||||
in buf
|
|
||||||
):
|
|
||||||
response = b"no\n"
|
|
||||||
elif "Seed input successful" in buf:
|
elif "Seed input successful" in buf:
|
||||||
pass
|
pass
|
||||||
elif "Upgrading database from version" in buf:
|
elif "Upgrading database from version" in buf:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 The BasicSwap developers
|
# Copyright (c) 2024 The BasicSwap developers
|
||||||
@@ -31,13 +32,8 @@ class DOGEInterface(BTCInterface):
|
|||||||
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
return 192
|
return 192
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(
|
super(DOGEInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
def getScriptDest(self, script: bytearray) -> bytearray:
|
def getScriptDest(self, script: bytearray) -> bytearray:
|
||||||
# P2SH
|
# P2SH
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+10
-111
@@ -1,7 +1,8 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -37,13 +38,8 @@ class FIROInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.FIRO
|
return Coins.FIRO
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(
|
super(FIROInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
# No multiwallet support
|
# No multiwallet support
|
||||||
self.rpc_wallet = make_rpc_func(
|
self.rpc_wallet = make_rpc_func(
|
||||||
self._rpcport, self._rpcauth, host=self._rpc_host
|
self._rpcport, self._rpcauth, host=self._rpc_host
|
||||||
@@ -68,7 +64,7 @@ class FIROInterface(BTCInterface):
|
|||||||
# Firo shuts down after encryptwallet
|
# Firo shuts down after encryptwallet
|
||||||
seed_id_before: str = self.getWalletSeedID() if check_seed else "Not found"
|
seed_id_before: str = self.getWalletSeedID() if check_seed else "Not found"
|
||||||
|
|
||||||
self.rpc_wallet("encryptwallet", [password], timeout=120)
|
self.rpc_wallet("encryptwallet", [password])
|
||||||
|
|
||||||
if check_seed is False or seed_id_before == "Not found":
|
if check_seed is False or seed_id_before == "Not found":
|
||||||
return
|
return
|
||||||
@@ -106,100 +102,6 @@ class FIROInterface(BTCInterface):
|
|||||||
return addr_info["ismine"]
|
return addr_info["ismine"]
|
||||||
return addr_info["ismine"] or addr_info["iswatchonly"]
|
return addr_info["ismine"] or addr_info["iswatchonly"]
|
||||||
|
|
||||||
def getNewSparkAddress(self) -> str:
|
|
||||||
try:
|
|
||||||
return self.rpc_wallet("getnewsparkaddress")[0]
|
|
||||||
except Exception as e:
|
|
||||||
self._log.error(f"getnewsparkaddress failed: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
def getNewStealthAddress(self):
|
|
||||||
"""Get a new Spark address (alias for consistency with other coins)."""
|
|
||||||
return self.getNewSparkAddress()
|
|
||||||
|
|
||||||
def getWalletInfo(self):
|
|
||||||
"""Get wallet info including Spark balance."""
|
|
||||||
rv = super(FIROInterface, self).getWalletInfo()
|
|
||||||
try:
|
|
||||||
spark_balance_info = self.rpc("getsparkbalance")
|
|
||||||
# getsparkbalance returns amounts in atomic units (satoshis)
|
|
||||||
# Field names: availableBalance, unconfirmedBalance, fullBalance
|
|
||||||
confirmed = spark_balance_info.get("availableBalance", 0)
|
|
||||||
unconfirmed = spark_balance_info.get("unconfirmedBalance", 0)
|
|
||||||
full_balance = spark_balance_info.get("fullBalance", 0)
|
|
||||||
# Values are already in atomic units, keep as integers
|
|
||||||
# basicswap.py will format them using format_amount
|
|
||||||
rv["spark_balance"] = confirmed if confirmed else 0
|
|
||||||
rv["spark_unconfirmed"] = unconfirmed if unconfirmed else 0
|
|
||||||
immature = full_balance - confirmed - unconfirmed
|
|
||||||
rv["spark_immature"] = immature if immature > 0 else 0
|
|
||||||
except Exception as e:
|
|
||||||
self._log.warning(f"getsparkbalance failed: {str(e)}")
|
|
||||||
rv["spark_balance"] = 0
|
|
||||||
rv["spark_unconfirmed"] = 0
|
|
||||||
rv["spark_immature"] = 0
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def createUTXO(self, value_sats: int):
|
|
||||||
# Create a new address and send value_sats to it
|
|
||||||
|
|
||||||
spendable_balance = self.getSpendableBalance()
|
|
||||||
if spendable_balance < value_sats:
|
|
||||||
raise ValueError("Balance too low")
|
|
||||||
|
|
||||||
address = self.getNewAddress(self._use_segwit, "create_utxo")
|
|
||||||
return (
|
|
||||||
self.withdrawCoin(self.format_amount(value_sats), "plain", address, False),
|
|
||||||
address,
|
|
||||||
)
|
|
||||||
|
|
||||||
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
|
|
||||||
"""Withdraw coins, supporting both transparent and Spark transactions.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
value: Amount to withdraw
|
|
||||||
type_from: "plain" for transparent, "spark" for Spark
|
|
||||||
addr_to: Destination address
|
|
||||||
subfee: Whether to subtract fee from amount
|
|
||||||
"""
|
|
||||||
type_to = "spark" if addr_to.startswith("sm1") else "plain"
|
|
||||||
|
|
||||||
if "spark" in (type_from, type_to):
|
|
||||||
# RPC format: spendspark {"address": {"amount": ..., "subtractfee": ..., "memo": ...}}
|
|
||||||
# RPC wrapper will serialize this as: {"method": "spendspark", "params": [{...}], ...}
|
|
||||||
try:
|
|
||||||
if type_from == "spark":
|
|
||||||
# Construct params: dict where address is the key, wrapped in array for RPC
|
|
||||||
params = [
|
|
||||||
{"address": addr_to, "amount": value, "subtractfee": subfee}
|
|
||||||
]
|
|
||||||
result = self.rpc_wallet("spendspark", params)
|
|
||||||
else:
|
|
||||||
# Use automintspark to perform a plain -> spark tx of full balance
|
|
||||||
balance = self.rpc_wallet("getbalance")
|
|
||||||
if str(balance) == str(value):
|
|
||||||
result = self.rpc_wallet("automintspark")
|
|
||||||
else:
|
|
||||||
# subfee param is available on plain -> spark transactions
|
|
||||||
mint_params = {"amount": value}
|
|
||||||
if subfee:
|
|
||||||
mint_params["subfee"] = True
|
|
||||||
params = [{addr_to: mint_params}]
|
|
||||||
result = self.rpc_wallet("mintspark", params)
|
|
||||||
# spendspark returns a txid string directly, in a result dict, or as an array
|
|
||||||
if isinstance(result, list) and len(result) > 0:
|
|
||||||
return result[0]
|
|
||||||
if isinstance(result, dict):
|
|
||||||
return result.get("txid", result.get("tx", ""))
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
|
||||||
self._log.error(f"spark tx failed: {str(e)}")
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
# Use standard sendtoaddress for transparent transactions
|
|
||||||
params = [addr_to, value, "", "", subfee]
|
|
||||||
return self.rpc_wallet("sendtoaddress", params)
|
|
||||||
|
|
||||||
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
||||||
lock_tx_dest = self.getScriptDest(lock_script)
|
lock_tx_dest = self.getScriptDest(lock_script)
|
||||||
address = self.encodeScriptDest(lock_tx_dest)
|
address = self.encodeScriptDest(lock_tx_dest)
|
||||||
@@ -276,8 +178,6 @@ class FIROInterface(BTCInterface):
|
|||||||
if find_index:
|
if find_index:
|
||||||
tx_obj = self.rpc("decoderawtransaction", [tx["hex"]])
|
tx_obj = self.rpc("decoderawtransaction", [tx["hex"]])
|
||||||
rv["index"] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
rv["index"] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
||||||
if rv["index"] is not None and rv["index"] >= 0:
|
|
||||||
rv["value"] = self.make_int(tx_obj["vout"][rv["index"]]["value"])
|
|
||||||
|
|
||||||
if return_txid:
|
if return_txid:
|
||||||
rv["txid"] = txid.hex()
|
rv["txid"] = txid.hex()
|
||||||
@@ -306,15 +206,10 @@ class FIROInterface(BTCInterface):
|
|||||||
amount: int,
|
amount: int,
|
||||||
sub_fee: bool = False,
|
sub_fee: bool = False,
|
||||||
lock_unspents: bool = True,
|
lock_unspents: bool = True,
|
||||||
feerate: int = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
txn = self.rpc(
|
txn = self.rpc(
|
||||||
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
||||||
)
|
)
|
||||||
if feerate:
|
|
||||||
fee_rate = self.format_amount(feerate)
|
|
||||||
fee_src = "specified"
|
|
||||||
else:
|
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
||||||
@@ -357,6 +252,10 @@ class FIROInterface(BTCInterface):
|
|||||||
assert len(script_hash) == 20
|
assert len(script_hash) == 20
|
||||||
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||||
|
|
||||||
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
|
params = [addr_to, value, "", "", subfee]
|
||||||
|
return self.rpc("sendtoaddress", params)
|
||||||
|
|
||||||
def getWalletSeedID(self):
|
def getWalletSeedID(self):
|
||||||
return self.rpc("getwalletinfo")["hdmasterkeyid"]
|
return self.rpc("getwalletinfo")["hdmasterkeyid"]
|
||||||
|
|
||||||
@@ -372,7 +271,7 @@ class FIROInterface(BTCInterface):
|
|||||||
)
|
)
|
||||||
return pay_fee
|
return pay_fee
|
||||||
|
|
||||||
def signTxWithKey(self, tx: bytes, key: bytes, prev_amount=None) -> bytes:
|
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||||
key_wif = self.encodeKey(key)
|
key_wif = self.encodeKey(key)
|
||||||
rv = self.rpc(
|
rv = self.rpc(
|
||||||
"signrawtransaction",
|
"signrawtransaction",
|
||||||
|
|||||||
+19
-235
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2023 tecnovert
|
# Copyright (c) 2020-2023 tecnovert
|
||||||
@@ -15,13 +16,8 @@ class LTCInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.LTC
|
return Coins.LTC
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(
|
super(LTCInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
self._rpc_wallet_mweb = coin_settings.get("mweb_wallet_name", "mweb")
|
self._rpc_wallet_mweb = coin_settings.get("mweb_wallet_name", "mweb")
|
||||||
self.rpc_wallet_mweb = make_rpc_func(
|
self.rpc_wallet_mweb = make_rpc_func(
|
||||||
self._rpcport,
|
self._rpcport,
|
||||||
@@ -31,21 +27,12 @@ class LTCInterface(BTCInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def getNewMwebAddress(self, use_segwit=False, label="swap_receive") -> str:
|
def getNewMwebAddress(self, use_segwit=False, label="swap_receive") -> str:
|
||||||
if self.useBackend():
|
|
||||||
raise ValueError("MWEB addresses not supported in electrum mode")
|
|
||||||
return self.rpc_wallet_mweb("getnewaddress", [label, "mweb"])
|
return self.rpc_wallet_mweb("getnewaddress", [label, "mweb"])
|
||||||
|
|
||||||
def getNewStealthAddress(self, label=""):
|
def getNewStealthAddress(self, label=""):
|
||||||
if self.useBackend():
|
|
||||||
raise ValueError("MWEB addresses not supported in electrum mode")
|
|
||||||
return self.getNewMwebAddress(False, label)
|
return self.getNewMwebAddress(False, label)
|
||||||
|
|
||||||
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
|
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
|
||||||
if self.useBackend():
|
|
||||||
if type_from == "mweb":
|
|
||||||
raise ValueError("MWEB withdrawals not supported in electrum mode")
|
|
||||||
return self._withdrawCoinElectrum(value, addr_to, subfee)
|
|
||||||
|
|
||||||
params = [addr_to, value, "", "", subfee, True, self._conf_target]
|
params = [addr_to, value, "", "", subfee, True, self._conf_target]
|
||||||
if type_from == "mweb":
|
if type_from == "mweb":
|
||||||
return self.rpc_wallet_mweb("sendtoaddress", params)
|
return self.rpc_wallet_mweb("sendtoaddress", params)
|
||||||
@@ -66,27 +53,14 @@ class LTCInterface(BTCInterface):
|
|||||||
|
|
||||||
def getWalletInfo(self):
|
def getWalletInfo(self):
|
||||||
rv = super(LTCInterface, self).getWalletInfo()
|
rv = super(LTCInterface, self).getWalletInfo()
|
||||||
if not self.useBackend():
|
|
||||||
try:
|
|
||||||
mweb_info = self.rpc_wallet_mweb("getwalletinfo")
|
mweb_info = self.rpc_wallet_mweb("getwalletinfo")
|
||||||
rv["mweb_balance"] = mweb_info["balance"]
|
rv["mweb_balance"] = mweb_info["balance"]
|
||||||
rv["mweb_unconfirmed"] = mweb_info["unconfirmed_balance"]
|
rv["mweb_unconfirmed"] = mweb_info["unconfirmed_balance"]
|
||||||
rv["mweb_immature"] = mweb_info["immature_balance"]
|
rv["mweb_immature"] = mweb_info["immature_balance"]
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def getUnspentsByAddr(self):
|
def getUnspentsByAddr(self):
|
||||||
unspent_addr = dict()
|
unspent_addr = dict()
|
||||||
|
|
||||||
if self.useBackend():
|
|
||||||
wm = self.getWalletManager()
|
|
||||||
if wm:
|
|
||||||
addresses = wm.getAllAddresses(self.coin_type())
|
|
||||||
if addresses:
|
|
||||||
return self._backend.getBalance(addresses)
|
|
||||||
return unspent_addr
|
|
||||||
|
|
||||||
unspent = self.rpc_wallet("listunspent")
|
unspent = self.rpc_wallet("listunspent")
|
||||||
for u in unspent:
|
for u in unspent:
|
||||||
if u.get("spendable", False) is False:
|
if u.get("spendable", False) is False:
|
||||||
@@ -95,14 +69,9 @@ 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:
|
||||||
if self.use_p2shp2wsh():
|
if self.use_p2shp2wsh():
|
||||||
if not desc.startswith("sh(wpkh"):
|
if not desc.startswith("sh(wpkh"):
|
||||||
continue
|
continue
|
||||||
@@ -112,170 +81,19 @@ class LTCInterface(BTCInterface):
|
|||||||
else:
|
else:
|
||||||
if not desc.startswith("pkh"):
|
if not desc.startswith("pkh"):
|
||||||
continue
|
continue
|
||||||
unspent_addr[utxo_address] = unspent_addr.get(
|
unspent_addr[u["address"]] = unspent_addr.get(
|
||||||
utxo_address, 0
|
u["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:
|
|
||||||
if password == "":
|
|
||||||
return
|
|
||||||
self._log.info("unlockWallet - {}".format(self.ticker()))
|
|
||||||
|
|
||||||
if self.useBackend():
|
|
||||||
return
|
|
||||||
|
|
||||||
wallets = self.rpc("listwallets")
|
|
||||||
if self._rpc_wallet not in wallets:
|
|
||||||
try:
|
|
||||||
self.rpc("loadwallet", [self._rpc_wallet])
|
|
||||||
except Exception as 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()}.'
|
|
||||||
)
|
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
|
||||||
self.rpc(
|
|
||||||
"createwallet",
|
|
||||||
[
|
|
||||||
self._rpc_wallet,
|
|
||||||
False,
|
|
||||||
True,
|
|
||||||
password,
|
|
||||||
False,
|
|
||||||
self._use_descriptors,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
|
||||||
seed_id = self.getWalletSeedID()
|
|
||||||
needs_seed_init = seed_id == "Not found"
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f"getWalletSeedID failed: {e}")
|
|
||||||
needs_seed_init = True
|
|
||||||
if needs_seed_init:
|
|
||||||
self._log.info(f"Initializing HD seed for {self.coin_name()}.")
|
|
||||||
self._sc.initialiseWallet(self.coin_type())
|
|
||||||
if password:
|
|
||||||
self._log.info(f"Encrypting {self.coin_name()} wallet.")
|
|
||||||
try:
|
|
||||||
self.rpc_wallet("encryptwallet", [password], timeout=120)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f"encryptwallet returned: {e}")
|
|
||||||
import time
|
|
||||||
|
|
||||||
for i in range(10):
|
|
||||||
time.sleep(1)
|
|
||||||
try:
|
|
||||||
self.rpc("listwallets")
|
|
||||||
break
|
|
||||||
except Exception:
|
|
||||||
self._log.debug(
|
|
||||||
f"Waiting for wallet after encryption... {i + 1}/10"
|
|
||||||
)
|
|
||||||
wallets = self.rpc("listwallets")
|
|
||||||
if self._rpc_wallet not in wallets:
|
|
||||||
self.rpc("loadwallet", [self._rpc_wallet])
|
|
||||||
self.setWalletSeedWarning(False)
|
|
||||||
check_seed = False
|
|
||||||
|
|
||||||
if self.isWalletEncrypted():
|
|
||||||
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
|
|
||||||
|
|
||||||
if check_seed:
|
|
||||||
self._sc.checkWalletSeed(self.coin_type())
|
|
||||||
|
|
||||||
|
|
||||||
class LTCInterfaceMWEB(LTCInterface):
|
class LTCInterfaceMWEB(LTCInterface):
|
||||||
|
|
||||||
def interface_type(self) -> int:
|
def interface_type(self) -> int:
|
||||||
return Coins.LTC_MWEB
|
return Coins.LTC_MWEB
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(
|
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client)
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
self._rpc_wallet = coin_settings.get("mweb_wallet_name", "mweb")
|
self._rpc_wallet = coin_settings.get("mweb_wallet_name", "mweb")
|
||||||
self.rpc_wallet = make_rpc_func(
|
self.rpc_wallet = make_rpc_func(
|
||||||
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
|
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
|
||||||
@@ -309,46 +127,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
|
||||||
|
|
||||||
wallet_name: str = self._rpc_wallet
|
self._log.info("init_wallet - {}".format(self.ticker()))
|
||||||
self._log.info(f"init_wallet - {self.ticker()}")
|
|
||||||
|
|
||||||
wallets = self.rpc("listwallets")
|
self._log.info(f"Creating wallet {self._rpc_wallet} for {self.coin_name()}.")
|
||||||
if wallet_name not in wallets:
|
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
|
||||||
try:
|
self.rpc("createwallet", ["mweb", False, True, password, False, False, True])
|
||||||
self.rpc("loadwallet", [wallet_name])
|
|
||||||
self._log.debug(f'Loaded existing wallet "{wallet_name}".')
|
|
||||||
except Exception as e:
|
|
||||||
if "does not exist" in str(e) or "Path does not exist" in str(e):
|
|
||||||
self._log.info(
|
|
||||||
f'Creating wallet "{wallet_name}" for {self.coin_name()}.'
|
|
||||||
)
|
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
|
||||||
self.rpc(
|
|
||||||
"createwallet",
|
|
||||||
[
|
|
||||||
wallet_name,
|
|
||||||
False,
|
|
||||||
True,
|
|
||||||
password,
|
|
||||||
False,
|
|
||||||
self._use_descriptors,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
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])
|
||||||
|
|
||||||
if self.getWalletSeedID() == "Not found":
|
if self.getWalletSeedID() == "Not found":
|
||||||
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", [wallet_name])
|
self.rpc("unloadwallet", ["mweb"])
|
||||||
self.rpc("loadwallet", [wallet_name])
|
self.rpc("loadwallet", ["mweb"])
|
||||||
if password is not None:
|
if password is not None:
|
||||||
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
|
self.rpc_wallet("walletpassphrase", [password, 100000000])
|
||||||
self.rpc_wallet("keypoolrefill")
|
self.rpc_wallet("keypoolrefill")
|
||||||
|
|
||||||
def unlockWallet(self, password: str, check_seed: bool = True) -> None:
|
def unlockWallet(self, password: str, check_seed: bool = True) -> None:
|
||||||
@@ -356,22 +152,10 @@ class LTCInterfaceMWEB(LTCInterface):
|
|||||||
return
|
return
|
||||||
self._log.info("unlockWallet - {}".format(self.ticker()))
|
self._log.info("unlockWallet - {}".format(self.ticker()))
|
||||||
|
|
||||||
if self.useBackend():
|
|
||||||
return
|
|
||||||
|
|
||||||
if not self.has_mweb_wallet():
|
if not self.has_mweb_wallet():
|
||||||
self.init_wallet(password)
|
self.init_wallet(password)
|
||||||
else:
|
else:
|
||||||
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
|
# Max timeout value, ~3 years
|
||||||
try:
|
self.rpc_wallet("walletpassphrase", [password, 100000000])
|
||||||
seed_id = self.getWalletSeedID()
|
|
||||||
needs_seed_init = seed_id == "Not found"
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f"getWalletSeedID failed: {e}")
|
|
||||||
needs_seed_init = True
|
|
||||||
if needs_seed_init:
|
|
||||||
self._log.info(f"Initializing HD seed for {self.coin_name()}.")
|
|
||||||
self._sc.initialiseWallet(self.interface_type())
|
|
||||||
|
|
||||||
if check_seed:
|
if check_seed:
|
||||||
self._sc.checkWalletSeed(self.interface_type())
|
self._sc.checkWalletSeed(self.coin_type())
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2023 tecnovert
|
# Copyright (c) 2023 tecnovert
|
||||||
@@ -72,13 +73,8 @@ class NAVInterface(BTCInterface):
|
|||||||
def txoType():
|
def txoType():
|
||||||
return CTxOut
|
return CTxOut
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(
|
super(NAVInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
# No multiwallet support
|
# No multiwallet support
|
||||||
self.rpc_wallet = make_rpc_func(
|
self.rpc_wallet = make_rpc_func(
|
||||||
self._rpcport, self._rpcauth, host=self._rpc_host
|
self._rpcport, self._rpcauth, host=self._rpc_host
|
||||||
@@ -315,15 +311,10 @@ class NAVInterface(BTCInterface):
|
|||||||
amount: int,
|
amount: int,
|
||||||
sub_fee: bool = False,
|
sub_fee: bool = False,
|
||||||
lock_unspents: bool = True,
|
lock_unspents: bool = True,
|
||||||
feerate: int = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
txn = self.rpc(
|
txn = self.rpc(
|
||||||
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
||||||
)
|
)
|
||||||
if feerate:
|
|
||||||
fee_rate = self.format_amount(feerate)
|
|
||||||
fee_src = "specified"
|
|
||||||
else:
|
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
||||||
@@ -614,8 +605,6 @@ class NAVInterface(BTCInterface):
|
|||||||
if find_index:
|
if find_index:
|
||||||
tx_obj = self.rpc("decoderawtransaction", [tx["hex"]])
|
tx_obj = self.rpc("decoderawtransaction", [tx["hex"]])
|
||||||
rv["index"] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
rv["index"] = find_vout_for_address_from_txobj(tx_obj, dest_address)
|
||||||
if rv["index"] is not None and rv["index"] >= 0:
|
|
||||||
rv["value"] = self.make_int(tx_obj["vout"][rv["index"]]["value"])
|
|
||||||
|
|
||||||
if return_txid:
|
if return_txid:
|
||||||
rv["txid"] = txid.hex()
|
rv["txid"] = txid.hex()
|
||||||
@@ -762,13 +751,7 @@ class NAVInterface(BTCInterface):
|
|||||||
|
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def fundTx(
|
def fundTx(self, tx_hex: str, feerate: int, lock_unspents: bool = True):
|
||||||
self,
|
|
||||||
tx_hex: str,
|
|
||||||
feerate: int,
|
|
||||||
lock_unspents: bool = True,
|
|
||||||
subfee: bool = False,
|
|
||||||
):
|
|
||||||
feerate_str = self.format_amount(feerate)
|
feerate_str = self.format_amount(feerate)
|
||||||
# TODO: unlock unspents if bid cancelled
|
# TODO: unlock unspents if bid cancelled
|
||||||
options = {
|
options = {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2022 tecnovert
|
# Copyright (c) 2020-2022 tecnovert
|
||||||
|
|||||||
+10
-45
@@ -1,7 +1,8 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -80,17 +81,8 @@ class PARTInterface(BTCInterface):
|
|||||||
def txoType():
|
def txoType():
|
||||||
return CTxOutPart
|
return CTxOutPart
|
||||||
|
|
||||||
@staticmethod
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
def defaultMaxFeeRate() -> int:
|
super().__init__(coin_settings, network, swap_client)
|
||||||
return PARTInterface.COIN() // 2
|
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
|
||||||
super().__init__(
|
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
self.setAnonTxRingSize(int(coin_settings.get("anon_tx_ring_size", 12)))
|
self.setAnonTxRingSize(int(coin_settings.get("anon_tx_ring_size", 12)))
|
||||||
|
|
||||||
def use_tx_vsize(self) -> bool:
|
def use_tx_vsize(self) -> bool:
|
||||||
@@ -145,7 +137,7 @@ class PARTInterface(BTCInterface):
|
|||||||
|
|
||||||
def getScriptDummyWitness(self, script: bytes) -> List[bytes]:
|
def getScriptDummyWitness(self, script: bytes) -> List[bytes]:
|
||||||
if self.isScriptP2WPKH(script) or self.isScriptP2PKH(script):
|
if self.isScriptP2WPKH(script) or self.isScriptP2PKH(script):
|
||||||
return self.getP2WPKHDummyWitness()
|
return [bytes(72), bytes(33)]
|
||||||
raise ValueError("Unknown script type")
|
raise ValueError("Unknown script type")
|
||||||
|
|
||||||
def formatStealthAddress(self, scan_pubkey, spend_pubkey) -> str:
|
def formatStealthAddress(self, scan_pubkey, spend_pubkey) -> str:
|
||||||
@@ -154,14 +146,7 @@ class PARTInterface(BTCInterface):
|
|||||||
return encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey)
|
return encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey)
|
||||||
|
|
||||||
def getWitnessStackSerialisedLength(self, witness_stack) -> int:
|
def getWitnessStackSerialisedLength(self, witness_stack) -> int:
|
||||||
length: int = 0
|
length: int = getCompactSizeLen(len(witness_stack))
|
||||||
if len(witness_stack) > 0 and isinstance(witness_stack[0], list):
|
|
||||||
for input_stack in witness_stack:
|
|
||||||
length += getCompactSizeLen(len(input_stack))
|
|
||||||
for e in input_stack:
|
|
||||||
length += getWitnessElementLen(len(e))
|
|
||||||
else:
|
|
||||||
length += getCompactSizeLen(len(witness_stack))
|
|
||||||
for e in witness_stack:
|
for e in witness_stack:
|
||||||
length += getWitnessElementLen(len(e))
|
length += getWitnessElementLen(len(e))
|
||||||
return length
|
return length
|
||||||
@@ -666,7 +651,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
|
|
||||||
ensure(
|
ensure(
|
||||||
self.compareFeeRates(fee_rate_paid, feerate),
|
self.compareFeeRates(fee_rate_paid, feerate),
|
||||||
f"Bad fee rate, expected: {feerate}",
|
"Bad fee rate, expected: {}".format(feerate),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -742,7 +727,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
fee_rate_paid = fee_paid * 1000 // vsize
|
fee_rate_paid = fee_paid * 1000 // vsize
|
||||||
ensure(
|
ensure(
|
||||||
self.compareFeeRates(fee_rate_paid, feerate),
|
self.compareFeeRates(fee_rate_paid, feerate),
|
||||||
f"Bad fee rate, expected: {feerate}",
|
"Bad fee rate, expected: {}".format(feerate),
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -966,7 +951,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
fee_rate_paid = fee_paid * 1000 // vsize
|
fee_rate_paid = fee_paid * 1000 // vsize
|
||||||
self._log.info("vsize, feerate: %ld, %ld", vsize, fee_rate_paid)
|
self._log.info("vsize, feerate: %ld, %ld", vsize, fee_rate_paid)
|
||||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||||
raise ValueError(f"Bad fee rate, expected: {feerate}")
|
raise ValueError("Bad fee rate, expected: {}".format(feerate))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1239,7 +1224,6 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
amount: int,
|
amount: int,
|
||||||
sub_fee: bool = False,
|
sub_fee: bool = False,
|
||||||
lock_unspents: bool = True,
|
lock_unspents: bool = True,
|
||||||
feerate: int = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
# Estimate lock tx size / fee
|
# Estimate lock tx size / fee
|
||||||
|
|
||||||
@@ -1279,17 +1263,9 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if feerate:
|
|
||||||
fee_rate = self.format_amount(feerate)
|
|
||||||
fee_src = "specified"
|
|
||||||
else:
|
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
|
||||||
self._log.debug(
|
|
||||||
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
|
||||||
)
|
|
||||||
options = {
|
options = {
|
||||||
"lockUnspents": lock_unspents,
|
"lockUnspents": lock_unspents,
|
||||||
"feeRate": fee_rate,
|
"conf_target": self._conf_target,
|
||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options["subtractFeeFromOutputs"] = [
|
options["subtractFeeFromOutputs"] = [
|
||||||
@@ -1299,17 +1275,6 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
|
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
|
||||||
)["hex"]
|
)["hex"]
|
||||||
|
|
||||||
def getLockRefundVout(self, lock_refund_tx_data: bytes, vkbv: bytes):
|
|
||||||
lock_refund_tx_obj = self.rpc(
|
|
||||||
"decoderawtransaction", [lock_refund_tx_data.hex()]
|
|
||||||
)
|
|
||||||
# Nonce is derived from vkbv
|
|
||||||
nonce = self.getScriptLockRefundTxNonce(vkbv)
|
|
||||||
|
|
||||||
# Find the output of the lock refund tx to spend
|
|
||||||
spend_n, input_blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
|
||||||
return spend_n
|
|
||||||
|
|
||||||
|
|
||||||
class PARTInterfaceAnon(PARTInterface):
|
class PARTInterfaceAnon(PARTInterface):
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2021 tecnovert
|
# Copyright (c) 2021 tecnovert
|
||||||
@@ -9,13 +10,8 @@ from basicswap.contrib.test_framework.messages import CTxOut
|
|||||||
|
|
||||||
|
|
||||||
class PassthroughBTCInterface(BTCInterface):
|
class PassthroughBTCInterface(BTCInterface):
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network):
|
||||||
super().__init__(
|
super().__init__(coin_settings, network)
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
self.txoType = CTxOut
|
self.txoType = CTxOut
|
||||||
self._network = network
|
self._network = network
|
||||||
self.blocks_confirmed = coin_settings["blocks_confirmed"]
|
self.blocks_confirmed = coin_settings["blocks_confirmed"]
|
||||||
|
|||||||
+27
-30
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022 tecnovert
|
||||||
@@ -11,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 CTransaction
|
from .contrib.pivx_test_framework.messages import CBlock, ToHex, FromHex, CTransaction
|
||||||
from basicswap.contrib.test_framework.script import (
|
from basicswap.contrib.test_framework.script import (
|
||||||
CScript,
|
CScript,
|
||||||
OP_DUP,
|
OP_DUP,
|
||||||
@@ -26,13 +27,8 @@ class PIVXInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.PIVX
|
return Coins.PIVX
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(
|
super(PIVXInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
# No multiwallet support
|
# No multiwallet support
|
||||||
self.rpc_wallet = make_rpc_func(
|
self.rpc_wallet = make_rpc_func(
|
||||||
self._rpcport, self._rpcauth, host=self._rpc_host
|
self._rpcport, self._rpcauth, host=self._rpc_host
|
||||||
@@ -44,7 +40,7 @@ class PIVXInterface(BTCInterface):
|
|||||||
|
|
||||||
seed_id_before: str = self.getWalletSeedID()
|
seed_id_before: str = self.getWalletSeedID()
|
||||||
|
|
||||||
self.rpc_wallet("encryptwallet", [password], timeout=120)
|
self.rpc_wallet("encryptwallet", [password])
|
||||||
|
|
||||||
if check_seed is False or seed_id_before == "Not found":
|
if check_seed is False or seed_id_before == "Not found":
|
||||||
return
|
return
|
||||||
@@ -78,15 +74,10 @@ class PIVXInterface(BTCInterface):
|
|||||||
amount: int,
|
amount: int,
|
||||||
sub_fee: bool = False,
|
sub_fee: bool = False,
|
||||||
lock_unspents: bool = True,
|
lock_unspents: bool = True,
|
||||||
feerate: int = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
txn = self.rpc(
|
txn = self.rpc(
|
||||||
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
||||||
)
|
)
|
||||||
if feerate:
|
|
||||||
fee_rate = self.format_amount(feerate)
|
|
||||||
fee_src = "specified"
|
|
||||||
else:
|
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}"
|
||||||
@@ -109,13 +100,29 @@ class PIVXInterface(BTCInterface):
|
|||||||
return decodeAddress(address)[1:]
|
return decodeAddress(address)[1:]
|
||||||
|
|
||||||
def getBlockWithTxns(self, block_hash):
|
def getBlockWithTxns(self, block_hash):
|
||||||
block = self.rpc("getblock", [block_hash, True])
|
# TODO: Bypass decoderawtransaction and getblockheader
|
||||||
|
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 txid_str in block["tx"]:
|
for tx in decoded_block.vtx:
|
||||||
tx_dec = self.rpc("getrawtransaction", [txid_str, True])
|
tx_dec = self.rpc("decoderawtransaction", [ToHex(tx)])
|
||||||
tx_rv.append(tx_dec)
|
tx_rv.append(tx_dec)
|
||||||
block["tx"] = tx_rv
|
|
||||||
return block
|
block_rv = {
|
||||||
|
"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]
|
||||||
@@ -143,7 +150,7 @@ class PIVXInterface(BTCInterface):
|
|||||||
)
|
)
|
||||||
return pay_fee
|
return pay_fee
|
||||||
|
|
||||||
def signTxWithKey(self, tx: bytes, key: bytes, prev_amount=None) -> bytes:
|
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||||
key_wif = self.encodeKey(key)
|
key_wif = self.encodeKey(key)
|
||||||
rv = self.rpc(
|
rv = self.rpc(
|
||||||
"signrawtransaction",
|
"signrawtransaction",
|
||||||
@@ -170,13 +177,3 @@ class PIVXInterface(BTCInterface):
|
|||||||
block_height = self.getBlockHeader(rv["blockhash"])["height"]
|
block_height = self.getBlockHeader(rv["blockhash"])["height"]
|
||||||
return {"txid": txid_hex, "amount": 0, "height": block_height}
|
return {"txid": txid_hex, "amount": 0, "height": block_height}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getChainMedianTime(self) -> int:
|
|
||||||
bestblockhash = self.rpc("getbestblockhash")
|
|
||||||
bestblockheader = self.rpc(
|
|
||||||
"getblockheader",
|
|
||||||
[
|
|
||||||
bestblockhash,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return bestblockheader["mediantime"]
|
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (c) 2026 The Basicswap developers
|
|
||||||
# Distributed under the MIT software license, see the accompanying
|
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
|
||||||
|
|
||||||
from basicswap.contrib.test_framework.messages import COIN
|
|
||||||
|
|
||||||
|
|
||||||
class FeeValidator:
|
|
||||||
@staticmethod
|
|
||||||
def defaultMaxFeeRate() -> int:
|
|
||||||
return COIN // 10
|
|
||||||
|
|
||||||
def makeIntFromSetting(
|
|
||||||
self, settings: dict, setting_name: str, default: int
|
|
||||||
) -> int:
|
|
||||||
# Return make_int(setting), or already integer default
|
|
||||||
if setting_name in settings:
|
|
||||||
return self.make_int(settings[setting_name])
|
|
||||||
return default
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
default_low_fee_conf_target: int = 24
|
|
||||||
default_low_fee_rate: int = 0
|
|
||||||
default_high_estimated_feerate_multiplier: float = 4.0
|
|
||||||
default_high_fee_rate: int = self.defaultMaxFeeRate()
|
|
||||||
if self._sc:
|
|
||||||
chain_client_settings = self._sc.getChainClientSettings(
|
|
||||||
self.coin_type()
|
|
||||||
) # basicswap.json
|
|
||||||
settings = self._sc.settings
|
|
||||||
default_low_fee_conf_target = int(
|
|
||||||
settings.get("low_fee_conf_target", default_low_fee_conf_target)
|
|
||||||
)
|
|
||||||
default_low_fee_rate = self.makeIntFromSetting(
|
|
||||||
settings, "low_feerate", default_low_fee_rate
|
|
||||||
)
|
|
||||||
default_high_estimated_feerate_multiplier = float(
|
|
||||||
settings.get(
|
|
||||||
"high_estimated_feerate_multiplier",
|
|
||||||
default_high_estimated_feerate_multiplier,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
default_high_fee_rate = self.makeIntFromSetting(
|
|
||||||
settings, "high_feerate", default_high_fee_rate
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if kwargs.get("network") != "regtest":
|
|
||||||
raise ValueError("swapclient unset")
|
|
||||||
chain_client_settings = {}
|
|
||||||
|
|
||||||
self._low_fee_conf_target = int(
|
|
||||||
chain_client_settings.get(
|
|
||||||
"low_fee_conf_target", default_low_fee_conf_target
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._low_feerate = self.makeIntFromSetting(
|
|
||||||
chain_client_settings, "low_feerate", default_low_fee_rate
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set below 1.0 to disable estimating the max feerate and use max_feerate
|
|
||||||
self._high_estimated_feerate_multiplier = float(
|
|
||||||
chain_client_settings.get(
|
|
||||||
"high_estimated_feerate_multiplier",
|
|
||||||
default_high_estimated_feerate_multiplier,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._high_feerate = self.makeIntFromSetting(
|
|
||||||
chain_client_settings, "high_feerate", default_high_fee_rate
|
|
||||||
)
|
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
|
|
||||||
def validateFeeRate(self, feerate: int) -> None:
|
|
||||||
if self._low_feerate > 0:
|
|
||||||
min_feerate_src = "set_value"
|
|
||||||
min_feerate = self._low_feerate
|
|
||||||
else:
|
|
||||||
min_feerate, min_feerate_src = self.get_fee_rate(self._low_fee_conf_target)
|
|
||||||
min_feerate = self.make_int(min_feerate)
|
|
||||||
|
|
||||||
if self._high_estimated_feerate_multiplier >= 1.0:
|
|
||||||
max_feerate, max_feerate_src = self.get_fee_rate()
|
|
||||||
max_feerate = int(
|
|
||||||
self.make_int(max_feerate) * self._high_estimated_feerate_multiplier
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
max_feerate_src = "set_value"
|
|
||||||
max_feerate = self._high_feerate
|
|
||||||
|
|
||||||
if max_feerate_src in ("estimatesmartfee", "electrum"):
|
|
||||||
if max_feerate > self._high_feerate:
|
|
||||||
max_feerate_src = "clamped_to_set_value"
|
|
||||||
max_feerate = self._high_feerate
|
|
||||||
|
|
||||||
self._log.debug(
|
|
||||||
f"Verify {self.ticker()} fee rate {feerate}, min {min_feerate} {min_feerate_src}, max {max_feerate} {max_feerate_src}"
|
|
||||||
)
|
|
||||||
if feerate < min_feerate:
|
|
||||||
err_msg: str = (
|
|
||||||
f"Fee rate too low, {feerate} < {min_feerate}, {min_feerate_src}"
|
|
||||||
)
|
|
||||||
self._log.error(err_msg)
|
|
||||||
raise ValueError(err_msg)
|
|
||||||
if feerate > max_feerate:
|
|
||||||
err_msg: str = (
|
|
||||||
f"Fee rate too high, {feerate} > {max_feerate}, {max_feerate_src}"
|
|
||||||
)
|
|
||||||
self._log.error(err_msg)
|
|
||||||
raise ValueError(err_msg)
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 The Basicswap developers
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
@@ -33,6 +34,7 @@ from basicswap.rpc_xmr import make_xmr_rpc_func, make_xmr_rpc2_func
|
|||||||
from basicswap.chainparams import XMR_COIN, Coins
|
from basicswap.chainparams import XMR_COIN, Coins
|
||||||
from basicswap.interface.base import CoinInterface
|
from basicswap.interface.base import CoinInterface
|
||||||
|
|
||||||
|
|
||||||
ed25519_l = 2**252 + 27742317777372353535851937790883648493
|
ed25519_l = 2**252 + 27742317777372353535851937790883648493
|
||||||
|
|
||||||
|
|
||||||
@@ -92,21 +94,13 @@ class XMRInterface(CoinInterface):
|
|||||||
"failed to get output distribution",
|
"failed to get output distribution",
|
||||||
"request-sent",
|
"request-sent",
|
||||||
"idle",
|
"idle",
|
||||||
"busy",
|
|
||||||
"responsenotready",
|
|
||||||
"connection",
|
|
||||||
]
|
]
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
return super().is_transient_error(ex)
|
return super().is_transient_error(ex)
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(
|
super().__init__(network)
|
||||||
coin_settings=coin_settings,
|
|
||||||
network=network,
|
|
||||||
swap_client=swap_client,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._addr_prefix = self.chainparams_network()["address_prefix"]
|
self._addr_prefix = self.chainparams_network()["address_prefix"]
|
||||||
|
|
||||||
@@ -231,7 +225,6 @@ class XMRInterface(CoinInterface):
|
|||||||
"invalid signature",
|
"invalid signature",
|
||||||
"std::bad_alloc",
|
"std::bad_alloc",
|
||||||
"basic_string::_M_replace_aux",
|
"basic_string::_M_replace_aux",
|
||||||
"input stream error",
|
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
self._log.error(f"{self.coin_name()} wallet is corrupt.")
|
self._log.error(f"{self.coin_name()} wallet is corrupt.")
|
||||||
@@ -839,28 +832,3 @@ class XMRInterface(CoinInterface):
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def listWalletTransactions(self, count=100, skip=0, include_watchonly=True):
|
|
||||||
try:
|
|
||||||
with self._mx_wallet:
|
|
||||||
self.openWallet(self._wallet_filename)
|
|
||||||
rv = self.rpc_wallet(
|
|
||||||
"get_transfers",
|
|
||||||
{"in": True, "out": True, "pending": True, "failed": True},
|
|
||||||
)
|
|
||||||
transactions = []
|
|
||||||
for tx_type in ["in", "out", "pending", "failed"]:
|
|
||||||
if tx_type in rv:
|
|
||||||
for tx in rv[tx_type]:
|
|
||||||
tx["type"] = tx_type
|
|
||||||
transactions.append(tx)
|
|
||||||
transactions.sort(key=lambda x: x.get("timestamp", 0), reverse=True)
|
|
||||||
return (
|
|
||||||
transactions[skip : skip + count] if count else transactions[skip:]
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.error(f"listWalletTransactions failed: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def validateFeeRate(self, fee_rate: int) -> None:
|
|
||||||
pass # Fee rate isn't used
|
|
||||||
|
|||||||
+13
-419
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -79,11 +79,9 @@ def withdraw_coin(swap_client, coin_type, post_string, is_json):
|
|||||||
txid_hex = swap_client.withdrawParticl(
|
txid_hex = swap_client.withdrawParticl(
|
||||||
type_from, type_to, value, address, subfee
|
type_from, type_to, value, address, subfee
|
||||||
)
|
)
|
||||||
elif coin_type in (Coins.LTC, Coins.FIRO):
|
elif coin_type == Coins.LTC:
|
||||||
type_from = get_data_entry_or(post_data, "type_from", "plain")
|
type_from = get_data_entry_or(post_data, "type_from", "plain")
|
||||||
txid_hex = swap_client.withdrawCoinExtended(
|
txid_hex = swap_client.withdrawLTC(type_from, value, address, subfee)
|
||||||
coin_type, type_from, value, address, subfee
|
|
||||||
)
|
|
||||||
elif coin_type in (Coins.XMR, Coins.WOW):
|
elif coin_type in (Coins.XMR, Coins.WOW):
|
||||||
txid_hex = swap_client.withdrawCoin(coin_type, value, address, sweepall)
|
txid_hex = swap_client.withdrawCoin(coin_type, value, address, sweepall)
|
||||||
else:
|
else:
|
||||||
@@ -129,6 +127,7 @@ 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 = []
|
||||||
@@ -136,7 +135,7 @@ def js_walletbalances(self, url_split, post_string, is_json) -> bytes:
|
|||||||
for k, v in swap_client.coin_clients.items():
|
for k, v in swap_client.coin_clients.items():
|
||||||
if k not in chainparams:
|
if k not in chainparams:
|
||||||
continue
|
continue
|
||||||
if v["connection_type"] in ("rpc", "electrum"):
|
if v["connection_type"] == "rpc":
|
||||||
|
|
||||||
balance = "0.0"
|
balance = "0.0"
|
||||||
if k in wallets:
|
if k in wallets:
|
||||||
@@ -169,51 +168,8 @@ def js_walletbalances(self, url_split, post_string, is_json) -> bytes:
|
|||||||
"balance": balance,
|
"balance": balance,
|
||||||
"pending": pending,
|
"pending": pending,
|
||||||
"ticker": chainparams[k]["ticker"],
|
"ticker": chainparams[k]["ticker"],
|
||||||
"connection_type": v["connection_type"],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ci = swap_client.ci(k)
|
|
||||||
if hasattr(ci, "getScanStatus"):
|
|
||||||
coin_entry["scan_status"] = ci.getScanStatus()
|
|
||||||
if hasattr(ci, "getElectrumServer"):
|
|
||||||
server = ci.getElectrumServer()
|
|
||||||
if server:
|
|
||||||
coin_entry["electrum_server"] = server
|
|
||||||
version = ci.getDaemonVersion()
|
|
||||||
if version:
|
|
||||||
coin_entry["version"] = version
|
|
||||||
if (
|
|
||||||
v["connection_type"] == "electrum"
|
|
||||||
and hasattr(ci, "_backend")
|
|
||||||
and ci._backend
|
|
||||||
and hasattr(ci._backend, "getSyncStatus")
|
|
||||||
):
|
|
||||||
sync_status = ci._backend.getSyncStatus()
|
|
||||||
coin_entry["electrum_synced"] = sync_status.get("synced", False)
|
|
||||||
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:
|
||||||
@@ -311,7 +267,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, inc_variant=True)
|
coin_type = getCoinIdFromTicker(ticker_str)
|
||||||
|
|
||||||
if len(url_split) > 4:
|
if len(url_split) > 4:
|
||||||
cmd = url_split[4]
|
cmd = url_split[4]
|
||||||
@@ -337,9 +293,6 @@ def js_wallets(self, url_split, post_string, is_json):
|
|||||||
elif cmd == "reseed":
|
elif cmd == "reseed":
|
||||||
swap_client.reseedWallet(coin_type)
|
swap_client.reseedWallet(coin_type)
|
||||||
return bytes(json.dumps({"reseeded": True}), "UTF-8")
|
return bytes(json.dumps({"reseeded": True}), "UTF-8")
|
||||||
elif cmd == "rescan":
|
|
||||||
result = swap_client.rescanWalletAddresses(coin_type)
|
|
||||||
return bytes(json.dumps(result), "UTF-8")
|
|
||||||
elif cmd == "newstealthaddress":
|
elif cmd == "newstealthaddress":
|
||||||
if coin_type != Coins.PART:
|
if coin_type != Coins.PART:
|
||||||
raise ValueError("Invalid coin for command")
|
raise ValueError("Invalid coin for command")
|
||||||
@@ -353,43 +306,6 @@ 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":
|
|
||||||
post_data = getFormData(post_string, is_json)
|
|
||||||
address = get_data_entry(post_data, "address")
|
|
||||||
label = get_data_entry_or(post_data, "label", "manual_import")
|
|
||||||
wm = swap_client.getWalletManager()
|
|
||||||
if wm is None:
|
|
||||||
raise ValueError("WalletManager not available")
|
|
||||||
wm.importWatchOnlyAddress(
|
|
||||||
coin_type, address, label=label, source="manual_import"
|
|
||||||
)
|
|
||||||
return bytes(json.dumps({"success": True, "address": address}), "UTF-8")
|
|
||||||
elif cmd == "listaddresses":
|
|
||||||
wm = swap_client.getWalletManager()
|
|
||||||
if wm is None:
|
|
||||||
raise ValueError("WalletManager not available")
|
|
||||||
addresses = wm.getAllAddresses(coin_type)
|
|
||||||
return bytes(json.dumps({"addresses": addresses}), "UTF-8")
|
|
||||||
elif cmd == "fixseedid":
|
|
||||||
root_key = swap_client.getWalletKey(coin_type, 1)
|
|
||||||
swap_client.storeSeedIDForCoin(root_key, coin_type)
|
|
||||||
swap_client.checkWalletSeed(coin_type)
|
|
||||||
return bytes(
|
|
||||||
json.dumps({"success": True, "message": "Seed IDs updated"}),
|
|
||||||
"UTF-8",
|
|
||||||
)
|
|
||||||
raise ValueError("Unknown command")
|
raise ValueError("Unknown command")
|
||||||
|
|
||||||
if coin_type == Coins.LTC_MWEB:
|
if coin_type == Coins.LTC_MWEB:
|
||||||
@@ -683,13 +599,8 @@ def js_bids(self, url_split, post_string: str, is_json: bool) -> bytes:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if have_data_entry(post_data, "debugind"):
|
if have_data_entry(post_data, "debugind"):
|
||||||
main_debug_ind: bool = toBool(
|
|
||||||
get_data_entry_or(post_data, "maindebugind", True)
|
|
||||||
)
|
|
||||||
swap_client.setBidDebugInd(
|
swap_client.setBidDebugInd(
|
||||||
bid_id,
|
bid_id, int(get_data_entry(post_data, "debugind"))
|
||||||
int(get_data_entry(post_data, "debugind")),
|
|
||||||
add_to_bid=main_debug_ind,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
rv = {"bid_id": bid_id.hex()}
|
rv = {"bid_id": bid_id.hex()}
|
||||||
@@ -707,13 +618,8 @@ def js_bids(self, url_split, post_string: str, is_json: bool) -> bytes:
|
|||||||
elif have_data_entry(post_data, "abandon"):
|
elif have_data_entry(post_data, "abandon"):
|
||||||
swap_client.abandonBid(bid_id)
|
swap_client.abandonBid(bid_id)
|
||||||
elif have_data_entry(post_data, "debugind"):
|
elif have_data_entry(post_data, "debugind"):
|
||||||
main_debug_ind: bool = toBool(
|
|
||||||
get_data_entry_or(post_data, "maindebugind", True)
|
|
||||||
)
|
|
||||||
swap_client.setBidDebugInd(
|
swap_client.setBidDebugInd(
|
||||||
bid_id,
|
bid_id, int(get_data_entry(post_data, "debugind"))
|
||||||
int(get_data_entry(post_data, "debugind")),
|
|
||||||
add_to_bid=main_debug_ind,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if have_data_entry(post_data, "show_extra"):
|
if have_data_entry(post_data, "show_extra"):
|
||||||
@@ -722,9 +628,7 @@ def js_bids(self, url_split, post_string: str, is_json: bool) -> bytes:
|
|||||||
with_events = True
|
with_events = True
|
||||||
|
|
||||||
bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id)
|
bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id)
|
||||||
if bid is None:
|
assert bid, "Unknown bid ID"
|
||||||
swap_client.log.debug(f"js_bids: Unknown bid id {bid_id.hex()}")
|
|
||||||
return bytes(json.dumps({"error": "Unknown bid id"}), "UTF-8")
|
|
||||||
|
|
||||||
if post_string != "":
|
if post_string != "":
|
||||||
if have_data_entry(post_data, "chainbkeysplit"):
|
if have_data_entry(post_data, "chainbkeysplit"):
|
||||||
@@ -1304,12 +1208,10 @@ def js_getcoinseed(self, url_split, post_string, is_json) -> bytes:
|
|||||||
"current_seed_id": wallet_seed_id,
|
"current_seed_id": wallet_seed_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if hasattr(ci, "canExportToElectrum") and ci.canExportToElectrum():
|
||||||
if hasattr(ci, "getAccountKey"):
|
rv.update(
|
||||||
try:
|
{"account_key": ci.getAccountKey(seed_key, extkey_prefix)}
|
||||||
rv.update({"account_key": ci.getAccountKey(seed_key, extkey_prefix)})
|
) # Master key can be imported into electrum (Must set prefix for P2WPKH)
|
||||||
except Exception as e:
|
|
||||||
rv.update({"account_key_error": str(e)})
|
|
||||||
|
|
||||||
return bytes(
|
return bytes(
|
||||||
json.dumps(rv),
|
json.dumps(rv),
|
||||||
@@ -1624,74 +1526,6 @@ def js_coinhistory(self, url_split, post_string, is_json) -> bytes:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def js_wallettransactions(self, url_split, post_string, is_json) -> bytes:
|
|
||||||
from basicswap.ui.page_wallet import format_transactions
|
|
||||||
import time
|
|
||||||
|
|
||||||
TX_CACHE_DURATION = 30
|
|
||||||
|
|
||||||
swap_client = self.server.swap_client
|
|
||||||
swap_client.checkSystemStatus()
|
|
||||||
|
|
||||||
if len(url_split) < 4:
|
|
||||||
return bytes(json.dumps({"error": "No coin specified"}), "UTF-8")
|
|
||||||
|
|
||||||
ticker_str = url_split[3]
|
|
||||||
coin_id = getCoinIdFromTicker(ticker_str)
|
|
||||||
|
|
||||||
post_data = {} if post_string == "" else getFormData(post_string, is_json)
|
|
||||||
|
|
||||||
page_no = 1
|
|
||||||
limit = 30
|
|
||||||
offset = 0
|
|
||||||
|
|
||||||
if have_data_entry(post_data, "page_no"):
|
|
||||||
page_no = int(get_data_entry(post_data, "page_no"))
|
|
||||||
if page_no < 1:
|
|
||||||
page_no = 1
|
|
||||||
|
|
||||||
if page_no > 1:
|
|
||||||
offset = (page_no - 1) * limit
|
|
||||||
|
|
||||||
try:
|
|
||||||
ci = swap_client.ci(coin_id)
|
|
||||||
|
|
||||||
current_time = time.time()
|
|
||||||
cache_entry = swap_client._tx_cache.get(coin_id)
|
|
||||||
|
|
||||||
if (
|
|
||||||
cache_entry is None
|
|
||||||
or (current_time - cache_entry["time"]) > TX_CACHE_DURATION
|
|
||||||
):
|
|
||||||
all_txs = ci.listWalletTransactions(count=10000, skip=0)
|
|
||||||
if all_txs and coin_id not in (Coins.XMR, Coins.WOW):
|
|
||||||
all_txs = list(reversed(all_txs))
|
|
||||||
elif not all_txs:
|
|
||||||
all_txs = []
|
|
||||||
swap_client._tx_cache[coin_id] = {"txs": all_txs, "time": current_time}
|
|
||||||
else:
|
|
||||||
all_txs = cache_entry["txs"]
|
|
||||||
|
|
||||||
total_transactions = len(all_txs)
|
|
||||||
raw_txs = all_txs[offset : offset + limit] if all_txs else []
|
|
||||||
transactions = format_transactions(ci, raw_txs, coin_id)
|
|
||||||
|
|
||||||
return bytes(
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"transactions": transactions,
|
|
||||||
"page_no": page_no,
|
|
||||||
"total": total_transactions,
|
|
||||||
"limit": limit,
|
|
||||||
"total_pages": (total_transactions + limit - 1) // limit,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"UTF-8",
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
return bytes(json.dumps({"error": str(e)}), "UTF-8")
|
|
||||||
|
|
||||||
|
|
||||||
def js_messageroutes(self, url_split, post_string, is_json) -> bytes:
|
def js_messageroutes(self, url_split, post_string, is_json) -> bytes:
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
post_data = {} if post_string == "" else getFormData(post_string, is_json)
|
post_data = {} if post_string == "" else getFormData(post_string, is_json)
|
||||||
@@ -1735,247 +1569,10 @@ def js_messageroutes(self, url_split, post_string, is_json) -> bytes:
|
|||||||
return bytes(json.dumps(message_routes), "UTF-8")
|
return bytes(json.dumps(message_routes), "UTF-8")
|
||||||
|
|
||||||
|
|
||||||
def js_modeswitchinfo(self, url_split, post_string, is_json) -> bytes:
|
|
||||||
swap_client = self.server.swap_client
|
|
||||||
swap_client.checkSystemStatus()
|
|
||||||
post_data = getFormData(post_string, is_json)
|
|
||||||
|
|
||||||
coin_str = get_data_entry(post_data, "coin")
|
|
||||||
direction = get_data_entry_or(post_data, "direction", "lite")
|
|
||||||
|
|
||||||
try:
|
|
||||||
coin_type = getCoinIdFromName(coin_str)
|
|
||||||
except Exception:
|
|
||||||
coin_type = getCoinIdFromTicker(coin_str.upper())
|
|
||||||
|
|
||||||
ci = swap_client.ci(coin_type)
|
|
||||||
ticker = ci.ticker()
|
|
||||||
|
|
||||||
try:
|
|
||||||
wallet_info = ci.getWalletInfo()
|
|
||||||
balance = wallet_info.get("balance", 0)
|
|
||||||
balance_sats = ci.make_int(balance)
|
|
||||||
except Exception as e:
|
|
||||||
return bytes(json.dumps({"error": f"Failed to get balance: {e}"}), "UTF-8")
|
|
||||||
|
|
||||||
try:
|
|
||||||
fee_rate, rate_src = ci.get_fee_rate(ci._conf_target)
|
|
||||||
est_vsize = 180
|
|
||||||
if isinstance(fee_rate, int):
|
|
||||||
fee_per_vbyte = max(1, fee_rate // 1000)
|
|
||||||
else:
|
|
||||||
fee_per_vbyte = max(1, int(fee_rate * 100000))
|
|
||||||
estimated_fee_sats = est_vsize * fee_per_vbyte
|
|
||||||
except Exception:
|
|
||||||
estimated_fee_sats = 180
|
|
||||||
rate_src = "default"
|
|
||||||
|
|
||||||
min_viable = estimated_fee_sats * 2
|
|
||||||
can_transfer = balance_sats > min_viable
|
|
||||||
|
|
||||||
rv = {
|
|
||||||
"coin": ticker,
|
|
||||||
"direction": direction,
|
|
||||||
"balance": balance,
|
|
||||||
"balance_sats": balance_sats,
|
|
||||||
"estimated_fee_sats": estimated_fee_sats,
|
|
||||||
"estimated_fee": ci.format_amount(estimated_fee_sats),
|
|
||||||
"fee_rate_src": rate_src,
|
|
||||||
"can_transfer": can_transfer,
|
|
||||||
"min_viable_sats": min_viable,
|
|
||||||
}
|
|
||||||
|
|
||||||
if direction == "lite":
|
|
||||||
non_bip84_balance_sats = 0
|
|
||||||
has_non_bip84_funds = False
|
|
||||||
try:
|
|
||||||
if hasattr(ci, "rpc_wallet"):
|
|
||||||
unspent = ci.rpc_wallet("listunspent")
|
|
||||||
|
|
||||||
wm = swap_client.getWalletManager()
|
|
||||||
|
|
||||||
bip84_addresses = set()
|
|
||||||
if wm:
|
|
||||||
try:
|
|
||||||
all_addrs = wm.getAllAddresses(
|
|
||||||
coin_type, include_watch_only=False
|
|
||||||
)
|
|
||||||
bip84_addresses = set(all_addrs)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for u in unspent:
|
|
||||||
addr = u.get("address")
|
|
||||||
if not addr:
|
|
||||||
continue
|
|
||||||
amount_sats = ci.make_int(u.get("amount", 0))
|
|
||||||
if amount_sats <= 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if addr not in bip84_addresses:
|
|
||||||
non_bip84_balance_sats += amount_sats
|
|
||||||
has_non_bip84_funds = True
|
|
||||||
except Exception as e:
|
|
||||||
swap_client.log.debug(f"Error checking non-BIP84 addresses: {e}")
|
|
||||||
|
|
||||||
if has_non_bip84_funds and non_bip84_balance_sats > min_viable:
|
|
||||||
rv["show_transfer_option"] = True
|
|
||||||
rv["require_transfer"] = True
|
|
||||||
rv["legacy_balance_sats"] = non_bip84_balance_sats
|
|
||||||
rv["legacy_balance"] = ci.format_amount(non_bip84_balance_sats)
|
|
||||||
rv["message"] = (
|
|
||||||
"Funds on non-derivable addresses must be transferred for external wallet compatibility"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
rv["show_transfer_option"] = False
|
|
||||||
rv["require_transfer"] = False
|
|
||||||
if has_non_bip84_funds:
|
|
||||||
rv["legacy_balance_sats"] = non_bip84_balance_sats
|
|
||||||
rv["legacy_balance"] = ci.format_amount(non_bip84_balance_sats)
|
|
||||||
rv["message"] = "Non-derivable balance too low to transfer"
|
|
||||||
else:
|
|
||||||
rv["legacy_balance_sats"] = 0
|
|
||||||
rv["legacy_balance"] = "0"
|
|
||||||
rv["message"] = "All funds on BIP84 addresses"
|
|
||||||
else:
|
|
||||||
rv["show_transfer_option"] = can_transfer
|
|
||||||
if balance_sats == 0:
|
|
||||||
rv["message"] = "No funds to transfer"
|
|
||||||
elif not can_transfer:
|
|
||||||
rv["message"] = "Balance too low to transfer (fee would exceed funds)"
|
|
||||||
else:
|
|
||||||
rv["message"] = ""
|
|
||||||
|
|
||||||
return bytes(json.dumps(rv), "UTF-8")
|
|
||||||
|
|
||||||
|
|
||||||
def js_electrum_discover(self, url_split, post_string, is_json) -> bytes:
|
|
||||||
swap_client = self.server.swap_client
|
|
||||||
post_data = {} if post_string == "" else getFormData(post_string, is_json)
|
|
||||||
|
|
||||||
coin_str = get_data_entry(post_data, "coin")
|
|
||||||
do_ping = toBool(get_data_entry_or(post_data, "ping", "false"))
|
|
||||||
|
|
||||||
coin_type = None
|
|
||||||
try:
|
|
||||||
coin_id = int(coin_str)
|
|
||||||
coin_type = Coins(coin_id)
|
|
||||||
except ValueError:
|
|
||||||
try:
|
|
||||||
coin_type = getCoinIdFromName(coin_str)
|
|
||||||
except ValueError:
|
|
||||||
coin_type = getCoinType(coin_str)
|
|
||||||
|
|
||||||
electrum_supported = ["bitcoin", "litecoin"]
|
|
||||||
coin_name = chainparams.get(coin_type, {}).get("name", "").lower()
|
|
||||||
if coin_name not in electrum_supported:
|
|
||||||
return bytes(
|
|
||||||
json.dumps(
|
|
||||||
{"error": f"Electrum not supported for {coin_name}", "servers": []}
|
|
||||||
),
|
|
||||||
"UTF-8",
|
|
||||||
)
|
|
||||||
|
|
||||||
ci = swap_client.ci(coin_type)
|
|
||||||
connection_type = getattr(ci, "_connection_type", "rpc")
|
|
||||||
|
|
||||||
discovered_servers = []
|
|
||||||
current_server = None
|
|
||||||
|
|
||||||
if connection_type == "electrum":
|
|
||||||
backend = ci.getBackend()
|
|
||||||
if backend and hasattr(backend, "_server"):
|
|
||||||
server = backend._server
|
|
||||||
current_server = server.get_current_server_info()
|
|
||||||
discovered_servers = server.discover_peers()
|
|
||||||
|
|
||||||
if do_ping and discovered_servers:
|
|
||||||
for srv in discovered_servers[:10]:
|
|
||||||
latency = server.ping_server(
|
|
||||||
srv["host"], srv["port"], srv.get("ssl", True)
|
|
||||||
)
|
|
||||||
srv["latency_ms"] = latency
|
|
||||||
srv["online"] = latency is not None
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
from .interface.electrumx import ElectrumServer
|
|
||||||
|
|
||||||
temp_server = ElectrumServer(
|
|
||||||
coin_name,
|
|
||||||
log=swap_client.log,
|
|
||||||
)
|
|
||||||
temp_server.connect()
|
|
||||||
current_server = temp_server.get_current_server_info()
|
|
||||||
discovered_servers = temp_server.discover_peers()
|
|
||||||
|
|
||||||
if do_ping and discovered_servers:
|
|
||||||
for srv in discovered_servers[:10]:
|
|
||||||
latency = temp_server.ping_server(
|
|
||||||
srv["host"], srv["port"], srv.get("ssl", True)
|
|
||||||
)
|
|
||||||
srv["latency_ms"] = latency
|
|
||||||
srv["online"] = latency is not None
|
|
||||||
|
|
||||||
temp_server.disconnect()
|
|
||||||
except Exception as e:
|
|
||||||
return bytes(
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"error": f"Failed to connect to electrum server: {str(e)}",
|
|
||||||
"servers": [],
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"UTF-8",
|
|
||||||
)
|
|
||||||
|
|
||||||
onion_servers = [s for s in discovered_servers if s.get("is_onion")]
|
|
||||||
clearnet_servers = [s for s in discovered_servers if not s.get("is_onion")]
|
|
||||||
|
|
||||||
return bytes(
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"coin": coin_name,
|
|
||||||
"current_server": current_server,
|
|
||||||
"clearnet_servers": clearnet_servers,
|
|
||||||
"onion_servers": onion_servers,
|
|
||||||
"total_discovered": len(discovered_servers),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"UTF-8",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def js_getsubfeebidtx(self, url_split, post_string, is_json) -> bytes:
|
|
||||||
swap_client = self.server.swap_client
|
|
||||||
swap_client.checkSystemStatus()
|
|
||||||
post_data = getFormData(post_string, is_json)
|
|
||||||
offer_id = bytes.fromhex(get_data_entry(post_data, "offer_id"))
|
|
||||||
offer = swap_client.getOffer(offer_id)
|
|
||||||
ensure(offer, "Offer not found.")
|
|
||||||
ci_from = swap_client.ci(offer.coin_from)
|
|
||||||
ci_to = swap_client.ci(offer.coin_to)
|
|
||||||
|
|
||||||
amount_to: int = inputAmount(get_data_entry(post_data, "amount_to"), ci_to)
|
|
||||||
bid_rate: int = ci_to.make_int(get_data_entry(post_data, "bid_rate"), r=1)
|
|
||||||
|
|
||||||
prefunded_data = swap_client.createSubfeeBidTx(offer_id, amount_to, bid_rate)
|
|
||||||
return bytes(
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
"amount_from": ci_from.format_amount(prefunded_data["amount_from"]),
|
|
||||||
"amount_to": ci_to.format_amount(prefunded_data["amount_to"]),
|
|
||||||
"bid_tx": prefunded_data["bid_tx"].hex(),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"UTF-8",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
endpoints = {
|
endpoints = {
|
||||||
"coins": js_coins,
|
"coins": js_coins,
|
||||||
"walletbalances": js_walletbalances,
|
"walletbalances": js_walletbalances,
|
||||||
"wallets": js_wallets,
|
"wallets": js_wallets,
|
||||||
"wallettransactions": js_wallettransactions,
|
|
||||||
"offers": js_offers,
|
"offers": js_offers,
|
||||||
"sentoffers": js_sentoffers,
|
"sentoffers": js_sentoffers,
|
||||||
"bids": js_bids,
|
"bids": js_bids,
|
||||||
@@ -2005,9 +1602,6 @@ endpoints = {
|
|||||||
"coinvolume": js_coinvolume,
|
"coinvolume": js_coinvolume,
|
||||||
"coinhistory": js_coinhistory,
|
"coinhistory": js_coinhistory,
|
||||||
"messageroutes": js_messageroutes,
|
"messageroutes": js_messageroutes,
|
||||||
"electrumdiscover": js_electrum_discover,
|
|
||||||
"modeswitchinfo": js_modeswitchinfo,
|
|
||||||
"getsubfeebidtx": js_getsubfeebidtx,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ protobuf ParseFromString would reset the whole object, from_bytes won't.
|
|||||||
|
|
||||||
from basicswap.util.integer import encode_varint, decode_varint
|
from basicswap.util.integer import encode_varint, decode_varint
|
||||||
|
|
||||||
|
|
||||||
NPBW_INT = 0
|
NPBW_INT = 0
|
||||||
NPBW_BYTES = 2
|
NPBW_BYTES = 2
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ from basicswap.contrib.rfc6979 import (
|
|||||||
rfc6979_hmac_sha256_generate,
|
rfc6979_hmac_sha256_generate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
START_TOKEN = 0xABCD
|
START_TOKEN = 0xABCD
|
||||||
MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big")
|
MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big")
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ def initSimplexClient(args, logger, delay_event):
|
|||||||
# TODO: Must be a better way?
|
# TODO: Must be a better way?
|
||||||
logger.info("Initialising Simplex client")
|
logger.info("Initialising Simplex client")
|
||||||
|
|
||||||
pipe_r, pipe_w = os.pipe() # subprocess.PIPE is buffered, blocks when read
|
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
str_args = " ".join(args)
|
str_args = " ".join(args)
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ from basicswap.interface.btc import (
|
|||||||
class ProtocolInterface:
|
class ProtocolInterface:
|
||||||
swap_type = None
|
swap_type = None
|
||||||
|
|
||||||
|
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
||||||
|
raise ValueError("base class")
|
||||||
|
|
||||||
def getMockScript(self) -> bytearray:
|
def getMockScript(self) -> bytearray:
|
||||||
return bytearray([OpCodes.OP_RETURN, OpCodes.OP_1])
|
return bytearray([OpCodes.OP_RETURN, OpCodes.OP_1])
|
||||||
|
|
||||||
@@ -26,7 +29,7 @@ class ProtocolInterface:
|
|||||||
else ci.get_p2sh_script_pubkey(script)
|
else ci.get_p2sh_script_pubkey(script)
|
||||||
)
|
)
|
||||||
|
|
||||||
def getMockScriptAddr(self, ci):
|
def getMockAddrTo(self, ci):
|
||||||
script = self.getMockScript()
|
script = self.getMockScript()
|
||||||
return (
|
return (
|
||||||
ci.encodeScriptDest(ci.getScriptDest(script))
|
ci.encodeScriptDest(ci.getScriptDest(script))
|
||||||
@@ -35,5 +38,5 @@ class ProtocolInterface:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def findMockVout(self, ci, itx_decoded):
|
def findMockVout(self, ci, itx_decoded):
|
||||||
mock_addr = self.getMockScriptAddr(ci)
|
mock_addr = self.getMockAddrTo(ci)
|
||||||
return find_vout_for_address_from_txobj(itx_decoded, mock_addr)
|
return find_vout_for_address_from_txobj(itx_decoded, mock_addr)
|
||||||
|
|||||||
@@ -130,7 +130,10 @@ def redeemITx(self, bid_id: bytes, cursor):
|
|||||||
|
|
||||||
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
|
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Submitted initiate redeem txn {self.logIDT(txid)} to {ci_from.coin_name()} chain for bid {self.logIDB(bid_id)}"
|
"Submitted initiate redeem txn %s to %s chain for bid %s",
|
||||||
|
txid,
|
||||||
|
ci_from.coin_name(),
|
||||||
|
bid_id.hex(),
|
||||||
)
|
)
|
||||||
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, "", cursor)
|
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, "", cursor)
|
||||||
|
|
||||||
@@ -138,18 +141,12 @@ def redeemITx(self, bid_id: bytes, cursor):
|
|||||||
class AtomicSwapInterface(ProtocolInterface):
|
class AtomicSwapInterface(ProtocolInterface):
|
||||||
swap_type = SwapTypes.SELLER_FIRST
|
swap_type = SwapTypes.SELLER_FIRST
|
||||||
|
|
||||||
def getFundedInitiateTxTemplate(
|
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
||||||
self,
|
addr_to = self.getMockAddrTo(ci)
|
||||||
ci,
|
|
||||||
amount: int,
|
|
||||||
sub_fee: bool,
|
|
||||||
feerate: int = None,
|
|
||||||
lock_unspents: bool = False,
|
|
||||||
) -> bytes:
|
|
||||||
addr_to = self.getMockScriptAddr(ci)
|
|
||||||
funded_tx = ci.createRawFundedTransaction(
|
funded_tx = ci.createRawFundedTransaction(
|
||||||
addr_to, amount, sub_fee, lock_unspents=lock_unspents, feerate=feerate
|
addr_to, amount, sub_fee, lock_unspents=False
|
||||||
)
|
)
|
||||||
|
|
||||||
return bytes.fromhex(funded_tx)
|
return bytes.fromhex(funded_tx)
|
||||||
|
|
||||||
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
|
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -11,7 +11,6 @@ from basicswap.util import (
|
|||||||
ensure,
|
ensure,
|
||||||
)
|
)
|
||||||
from basicswap.interface.base import Curves
|
from basicswap.interface.base import Curves
|
||||||
from basicswap.interface.btc import findOutput
|
|
||||||
from basicswap.chainparams import (
|
from basicswap.chainparams import (
|
||||||
Coins,
|
Coins,
|
||||||
)
|
)
|
||||||
@@ -50,11 +49,11 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
|
|||||||
try:
|
try:
|
||||||
use_cursor = self.openDB(cursor)
|
use_cursor = self.openDB(cursor)
|
||||||
bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id)
|
bid, xmr_swap = self.getXmrBidFromSession(use_cursor, bid_id)
|
||||||
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
|
ensure(bid, "Bid not found: {}.".format(bid_id.hex()))
|
||||||
ensure(xmr_swap, f"Adaptor-sig swap not found: {self.log.id(bid_id)}.")
|
ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex()))
|
||||||
offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id)
|
offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id)
|
||||||
ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
|
ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex()))
|
||||||
ensure(xmr_offer, f"Adaptor-sig offer not found: {self.log.id(bid.offer_id)}.")
|
ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()))
|
||||||
|
|
||||||
# The no-script coin is always the follower
|
# The no-script coin is always the follower
|
||||||
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to)
|
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to)
|
||||||
@@ -106,10 +105,7 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
|
|||||||
address_to = self.getReceiveAddressFromPool(
|
address_to = self.getReceiveAddressFromPool(
|
||||||
base_coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, use_cursor
|
base_coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, use_cursor
|
||||||
)
|
)
|
||||||
amount: int = bid.amount_to
|
amount = bid.amount_to
|
||||||
chain_b_fee_rate: int = (
|
|
||||||
xmr_offer.a_fee_rate if reverse_bid else xmr_offer.b_fee_rate
|
|
||||||
)
|
|
||||||
lock_tx_vout = bid.getLockTXBVout()
|
lock_tx_vout = bid.getLockTXBVout()
|
||||||
txid = ci_follower.spendBLockTx(
|
txid = ci_follower.spendBLockTx(
|
||||||
xmr_swap.b_lock_tx_id,
|
xmr_swap.b_lock_tx_id,
|
||||||
@@ -117,13 +113,13 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
|
|||||||
xmr_swap.vkbv,
|
xmr_swap.vkbv,
|
||||||
vkbs,
|
vkbs,
|
||||||
amount,
|
amount,
|
||||||
chain_b_fee_rate,
|
xmr_offer.b_fee_rate,
|
||||||
bid.chain_b_height_start,
|
bid.chain_b_height_start,
|
||||||
spend_actual_balance=True,
|
spend_actual_balance=True,
|
||||||
lock_tx_vout=lock_tx_vout,
|
lock_tx_vout=lock_tx_vout,
|
||||||
)
|
)
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Submitted lock B spend txn {self.logIDT(txid)} to {ci_follower.coin_name()} chain for bid {self.log.id(bid_id)}."
|
f"Submitted lock B spend txn {self.log.id(txid)} to {ci_follower.coin_name()} chain for bid {self.log.id(bid_id)}."
|
||||||
)
|
)
|
||||||
self.logBidEvent(
|
self.logBidEvent(
|
||||||
bid.bid_id,
|
bid.bid_id,
|
||||||
@@ -207,12 +203,9 @@ def setDLEAG(xmr_swap, ci_to, kbsf: bytes) -> None:
|
|||||||
|
|
||||||
class XmrSwapInterface(ProtocolInterface):
|
class XmrSwapInterface(ProtocolInterface):
|
||||||
swap_type = SwapTypes.XMR_SWAP
|
swap_type = SwapTypes.XMR_SWAP
|
||||||
_mock_key: bytes = bytes.fromhex(
|
|
||||||
"e6b8e7c2ca3a88fe4f28591aa0f91fec340179346559e4ec430c2531aecc19aa"
|
|
||||||
)
|
|
||||||
|
|
||||||
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
|
def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript:
|
||||||
# Fallthrough to ci if genScriptLockTxScript is implemented there
|
# fallthrough to ci if genScriptLockTxScript is implemented there
|
||||||
if hasattr(ci, "genScriptLockTxScript") and callable(ci.genScriptLockTxScript):
|
if hasattr(ci, "genScriptLockTxScript") and callable(ci.genScriptLockTxScript):
|
||||||
return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs)
|
return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs)
|
||||||
|
|
||||||
@@ -221,78 +214,20 @@ class XmrSwapInterface(ProtocolInterface):
|
|||||||
|
|
||||||
return CScript([2, Kal, Kaf, 2, CScriptOp(OP_CHECKMULTISIG)])
|
return CScript([2, Kal, Kaf, 2, CScriptOp(OP_CHECKMULTISIG)])
|
||||||
|
|
||||||
def getMockScriptAddr(self, ci):
|
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
||||||
script = self.getMockScript()
|
addr_to = self.getMockAddrTo(ci)
|
||||||
if ci.coin_type() == Coins.PART:
|
|
||||||
# Use btc-segwit address to match createSCLockTx()
|
|
||||||
# _use_segwit is false for Particl
|
|
||||||
return ci.encode_p2wsh(ci.getScriptDest(script))
|
|
||||||
return (
|
|
||||||
ci.encodeScriptDest(ci.getScriptDest(script))
|
|
||||||
if ci._use_segwit
|
|
||||||
else ci.encode_p2sh(script)
|
|
||||||
)
|
|
||||||
|
|
||||||
def getMockScriptScriptPubkey(self, ci) -> bytearray:
|
|
||||||
script = self.getMockScript()
|
|
||||||
if ci.coin_type() == Coins.PART:
|
|
||||||
# Use btc-segwit address to match createSCLockTx()
|
|
||||||
# _use_segwit is false for Particl
|
|
||||||
return ci.getScriptDest(script)
|
|
||||||
return (
|
|
||||||
ci.getScriptDest(script)
|
|
||||||
if ci._use_segwit
|
|
||||||
else ci.get_p2sh_script_pubkey(script)
|
|
||||||
)
|
|
||||||
|
|
||||||
def getFundedInitiateTxTemplate(
|
|
||||||
self,
|
|
||||||
ci,
|
|
||||||
amount: int,
|
|
||||||
sub_fee: bool,
|
|
||||||
feerate: int = None,
|
|
||||||
lock_unspents: bool = False,
|
|
||||||
) -> bytes:
|
|
||||||
if ci.coin_type() == Coins.BCH:
|
|
||||||
# Workaround, BCH getScriptDest() uses OP_HASH256
|
|
||||||
script: bytes = self.getMockScript()
|
|
||||||
addr_to: bytes = ci.getScriptDest(script)
|
|
||||||
else:
|
|
||||||
addr_to = self.getMockScriptAddr(ci)
|
|
||||||
funded_tx = ci.createRawFundedTransaction(
|
funded_tx = ci.createRawFundedTransaction(
|
||||||
addr_to, amount, sub_fee, lock_unspents=lock_unspents, feerate=feerate
|
addr_to, amount, sub_fee, lock_unspents=False
|
||||||
)
|
)
|
||||||
|
|
||||||
return bytes.fromhex(funded_tx)
|
return bytes.fromhex(funded_tx)
|
||||||
|
|
||||||
def getMockITxSwapValue(self, ci, tx_data: bytes) -> int:
|
|
||||||
script: bytes = self.getMockScript()
|
|
||||||
script_dest: bytes = ci.getScriptDest(script)
|
|
||||||
tx_obj = ci.loadTx(tx_data, allow_witness=False)
|
|
||||||
|
|
||||||
lock_vout = findOutput(tx_obj, script_dest)
|
|
||||||
if lock_vout < 0:
|
|
||||||
raise ValueError("swap output not found")
|
|
||||||
|
|
||||||
return tx_obj.vout[lock_vout].nValue
|
|
||||||
|
|
||||||
def getMockITxSwapVout(self, ci, tx_obj) -> int:
|
|
||||||
script: bytes = self.getMockScript()
|
|
||||||
script_dest: bytes = ci.getScriptDest(script)
|
|
||||||
lock_vout = findOutput(tx_obj, script_dest)
|
|
||||||
if lock_vout is None:
|
|
||||||
raise ValueError("swap output not found")
|
|
||||||
return lock_vout
|
|
||||||
|
|
||||||
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
|
def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray:
|
||||||
if ci.coin_type() == Coins.BCH:
|
mock_txo_script = self.getMockScriptScriptPubkey(ci)
|
||||||
mock_script: bytes = self.getMockScript()
|
real_txo_script = ci.getScriptDest(script)
|
||||||
mock_txo_script: bytes = ci.getScriptDest(mock_script)
|
|
||||||
else:
|
|
||||||
mock_txo_script: bytes = self.getMockScriptScriptPubkey(ci)
|
|
||||||
real_txo_script: bytes = ci.getScriptDest(script)
|
|
||||||
|
|
||||||
found: int = 0
|
found: int = 0
|
||||||
ctx = ci.loadTx(mock_tx, allow_witness=False)
|
ctx = ci.loadTx(mock_tx)
|
||||||
for txo in ctx.vout:
|
for txo in ctx.vout:
|
||||||
if txo.scriptPubKey == mock_txo_script:
|
if txo.scriptPubKey == mock_txo_script:
|
||||||
txo.scriptPubKey = real_txo_script
|
txo.scriptPubKey = real_txo_script
|
||||||
@@ -305,36 +240,3 @@ class XmrSwapInterface(ProtocolInterface):
|
|||||||
ctx.nLockTime = 0
|
ctx.nLockTime = 0
|
||||||
|
|
||||||
return ctx.serialize()
|
return ctx.serialize()
|
||||||
|
|
||||||
def getMockPubkey(self, ci) -> bytes:
|
|
||||||
return ci.getPubkey(self._mock_key)
|
|
||||||
|
|
||||||
def getMockPTxSwapValue(self, ci, tx_data: bytes) -> int:
|
|
||||||
mock_pk: bytes = self.getMockPubkey(ci)
|
|
||||||
script_pk = ci.getPkDest(mock_pk)
|
|
||||||
tx_obj = ci.loadTx(tx_data, allow_witness=False)
|
|
||||||
|
|
||||||
lock_vout = findOutput(tx_obj, script_pk)
|
|
||||||
if lock_vout < 0:
|
|
||||||
raise ValueError("swap output not found")
|
|
||||||
|
|
||||||
return tx_obj.vout[lock_vout].nValue
|
|
||||||
|
|
||||||
def getMockPTxSwapVout(self, ci, tx_obj) -> int:
|
|
||||||
mock_pk: bytes = self.getMockPubkey(ci)
|
|
||||||
script_pk = ci.getPkDest(mock_pk)
|
|
||||||
lock_vout = findOutput(tx_obj, script_pk)
|
|
||||||
if lock_vout is None:
|
|
||||||
raise ValueError("swap output not found")
|
|
||||||
return lock_vout
|
|
||||||
|
|
||||||
def promoteMockPTx(self, ci, tx_data: bytes, kbv: bytes, Kbs: bytes) -> bytes:
|
|
||||||
mock_pk: bytes = self.getMockPubkey(ci)
|
|
||||||
script_pk = ci.getPkDest(mock_pk)
|
|
||||||
tx_obj = ci.loadTx(tx_data)
|
|
||||||
lock_vout = findOutput(tx_obj, script_pk)
|
|
||||||
if lock_vout < 0:
|
|
||||||
raise ValueError("swap output not found")
|
|
||||||
tx_obj.vout[lock_vout].scriptPubKey = ci.getPkDest(Kbs)
|
|
||||||
|
|
||||||
return tx_obj.serialize()
|
|
||||||
|
|||||||
+5
-24
@@ -152,17 +152,15 @@ class Jsonrpc:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def callrpc(
|
def callrpc(rpc_port, auth, method, params=[], wallet=None, host="127.0.0.1"):
|
||||||
rpc_port, auth, method, params=[], wallet=None, host="127.0.0.1", timeout=None
|
|
||||||
):
|
|
||||||
if _use_rpc_pooling:
|
if _use_rpc_pooling:
|
||||||
return callrpc_pooled(rpc_port, auth, method, params, wallet, host, timeout)
|
return callrpc_pooled(rpc_port, auth, method, params, wallet, host)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
||||||
if wallet is not None:
|
if wallet is not None:
|
||||||
url += "wallet/" + urllib.parse.quote(wallet)
|
url += "wallet/" + urllib.parse.quote(wallet)
|
||||||
x = Jsonrpc(url, timeout=timeout if timeout else 10)
|
x = Jsonrpc(url)
|
||||||
|
|
||||||
v = x.json_request(method, params)
|
v = x.json_request(method, params)
|
||||||
x.close()
|
x.close()
|
||||||
@@ -176,9 +174,7 @@ def callrpc(
|
|||||||
return r["result"]
|
return r["result"]
|
||||||
|
|
||||||
|
|
||||||
def callrpc_pooled(
|
def callrpc_pooled(rpc_port, auth, method, params=[], wallet=None, host="127.0.0.1"):
|
||||||
rpc_port, auth, method, params=[], wallet=None, host="127.0.0.1", timeout=None
|
|
||||||
):
|
|
||||||
from .rpc_pool import get_rpc_pool
|
from .rpc_pool import get_rpc_pool
|
||||||
import http.client
|
import http.client
|
||||||
import socket
|
import socket
|
||||||
@@ -187,20 +183,6 @@ def callrpc_pooled(
|
|||||||
if wallet is not None:
|
if wallet is not None:
|
||||||
url += "wallet/" + urllib.parse.quote(wallet)
|
url += "wallet/" + urllib.parse.quote(wallet)
|
||||||
|
|
||||||
if timeout:
|
|
||||||
try:
|
|
||||||
conn = Jsonrpc(url, timeout=timeout)
|
|
||||||
v = conn.json_request(method, params)
|
|
||||||
r = json.loads(v.decode("utf-8"))
|
|
||||||
conn.close()
|
|
||||||
if "error" in r and r["error"] is not None:
|
|
||||||
raise ValueError("RPC error " + str(r["error"]))
|
|
||||||
return r["result"]
|
|
||||||
except ValueError:
|
|
||||||
raise
|
|
||||||
except Exception as ex:
|
|
||||||
raise ValueError(f"RPC server error: {ex}, method: {method}")
|
|
||||||
|
|
||||||
max_connections = _rpc_pool_settings.get("max_connections_per_daemon", 5)
|
max_connections = _rpc_pool_settings.get("max_connections_per_daemon", 5)
|
||||||
pool = get_rpc_pool(url, max_connections)
|
pool = get_rpc_pool(url, max_connections)
|
||||||
|
|
||||||
@@ -265,7 +247,7 @@ def make_rpc_func(port, auth, wallet=None, host="127.0.0.1"):
|
|||||||
wallet = wallet
|
wallet = wallet
|
||||||
host = host
|
host = host
|
||||||
|
|
||||||
def rpc_func(method, params=None, wallet_override=None, timeout=None):
|
def rpc_func(method, params=None, wallet_override=None):
|
||||||
return callrpc(
|
return callrpc(
|
||||||
port,
|
port,
|
||||||
auth,
|
auth,
|
||||||
@@ -273,7 +255,6 @@ def make_rpc_func(port, auth, wallet=None, host="127.0.0.1"):
|
|||||||
params,
|
params,
|
||||||
wallet if wallet_override is None else wallet_override,
|
wallet if wallet_override is None else wallet_override,
|
||||||
host,
|
host,
|
||||||
timeout=timeout,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return rpc_func
|
return rpc_func
|
||||||
|
|||||||
@@ -1,22 +1,3 @@
|
|||||||
(function() {
|
|
||||||
const originalFetch = window.fetch;
|
|
||||||
window.fetch = function(url, options) {
|
|
||||||
return originalFetch.apply(this, arguments).then(function(response) {
|
|
||||||
if (response.status === 401) {
|
|
||||||
const urlStr = typeof url === 'string' ? url : (url && url.url) || '';
|
|
||||||
if (urlStr.startsWith('/json/') || urlStr.startsWith('/json')) {
|
|
||||||
window.location.href = '/login';
|
|
||||||
return new Response(JSON.stringify({error: 'Session expired'}), {
|
|
||||||
status: 401,
|
|
||||||
headers: {'Content-Type': 'application/json'}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const burger = document.querySelectorAll('.navbar-burger');
|
const burger = document.querySelectorAll('.navbar-burger');
|
||||||
const menu = document.querySelectorAll('.navbar-menu');
|
const menu = document.querySelectorAll('.navbar-menu');
|
||||||
|
|||||||
@@ -3,45 +3,14 @@
|
|||||||
|
|
||||||
const EventHandlers = {
|
const EventHandlers = {
|
||||||
|
|
||||||
showConfirmModal: function(title, message, callback) {
|
confirmPopup: function(action = 'proceed', coinName = '') {
|
||||||
const modal = document.getElementById('confirmModal');
|
const message = action === 'Accept'
|
||||||
if (!modal) {
|
? 'Are you sure you want to accept this bid?'
|
||||||
if (callback) callback();
|
: coinName
|
||||||
return;
|
? `Are you sure you want to ${action} ${coinName}?`
|
||||||
}
|
: 'Are you sure you want to proceed?';
|
||||||
|
|
||||||
const titleEl = document.getElementById('confirmTitle');
|
return confirm(message);
|
||||||
const messageEl = document.getElementById('confirmMessage');
|
|
||||||
const yesBtn = document.getElementById('confirmYes');
|
|
||||||
const noBtn = document.getElementById('confirmNo');
|
|
||||||
const bidDetails = document.getElementById('bidDetailsSection');
|
|
||||||
|
|
||||||
if (titleEl) titleEl.textContent = title;
|
|
||||||
if (messageEl) {
|
|
||||||
messageEl.textContent = message;
|
|
||||||
messageEl.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
if (bidDetails) bidDetails.classList.add('hidden');
|
|
||||||
|
|
||||||
modal.classList.remove('hidden');
|
|
||||||
|
|
||||||
const newYesBtn = yesBtn.cloneNode(true);
|
|
||||||
yesBtn.parentNode.replaceChild(newYesBtn, yesBtn);
|
|
||||||
|
|
||||||
newYesBtn.addEventListener('click', function() {
|
|
||||||
modal.classList.add('hidden');
|
|
||||||
if (callback) callback();
|
|
||||||
});
|
|
||||||
|
|
||||||
const newNoBtn = noBtn.cloneNode(true);
|
|
||||||
noBtn.parentNode.replaceChild(newNoBtn, noBtn);
|
|
||||||
newNoBtn.addEventListener('click', function() {
|
|
||||||
modal.classList.add('hidden');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
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() {
|
||||||
@@ -49,6 +18,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
confirmWithdrawal: function() {
|
confirmWithdrawal: function() {
|
||||||
|
|
||||||
if (window.WalletPage && typeof window.WalletPage.confirmWithdrawal === 'function') {
|
if (window.WalletPage && typeof window.WalletPage.confirmWithdrawal === 'function') {
|
||||||
return window.WalletPage.confirmWithdrawal();
|
return window.WalletPage.confirmWithdrawal();
|
||||||
}
|
}
|
||||||
@@ -97,36 +67,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let coinFromId;
|
const balanceElement = amountInput.closest('form')?.querySelector('[data-balance]');
|
||||||
if (inputId === 'add-amm-amount') {
|
const balance = balanceElement ? parseFloat(balanceElement.getAttribute('data-balance')) : 0;
|
||||||
coinFromId = 'add-amm-coin-from';
|
|
||||||
} else if (inputId === 'edit-amm-amount') {
|
|
||||||
coinFromId = 'edit-amm-coin-from';
|
|
||||||
} else {
|
|
||||||
const form = amountInput.closest('form') || amountInput.closest('.modal-content') || amountInput.closest('[id*="modal"]');
|
|
||||||
const select = form?.querySelector('select[id*="coin-from"]');
|
|
||||||
coinFromId = select?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const coinFromSelect = coinFromId ? document.getElementById(coinFromId) : null;
|
|
||||||
if (!coinFromSelect) {
|
|
||||||
console.error('EventHandlers: Coin-from dropdown not found for:', inputId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedOption = coinFromSelect.options[coinFromSelect.selectedIndex];
|
|
||||||
if (!selectedOption) {
|
|
||||||
console.error('EventHandlers: No option selected in coin-from dropdown');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const balance = parseFloat(selectedOption.getAttribute('data-balance') || '0');
|
|
||||||
|
|
||||||
if (balance > 0) {
|
if (balance > 0) {
|
||||||
const calculatedAmount = balance * percent;
|
const calculatedAmount = balance * percent;
|
||||||
amountInput.value = calculatedAmount.toFixed(8);
|
amountInput.value = calculatedAmount.toFixed(8);
|
||||||
} else {
|
} else {
|
||||||
console.warn('EventHandlers: No balance found for selected coin');
|
console.warn('EventHandlers: No balance found for AMM amount calculation');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -183,64 +131,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setBidAmount: function(percent, inputId) {
|
|
||||||
const amountInput = window.DOMCache
|
|
||||||
? window.DOMCache.get(inputId)
|
|
||||||
: document.getElementById(inputId);
|
|
||||||
|
|
||||||
if (!amountInput) {
|
|
||||||
console.error('EventHandlers: Bid amount input not found:', inputId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const haveBalance = amountInput.getAttribute('haveamount');
|
|
||||||
if (!haveBalance) {
|
|
||||||
console.error('EventHandlers: Balance not found for bid');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const floatBalance = parseFloat(haveBalance);
|
|
||||||
if (isNaN(floatBalance)) {
|
|
||||||
alert('Invalid bid balance');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxAmount = amountInput.getAttribute('max');
|
|
||||||
if (!maxAmount) {
|
|
||||||
console.error('EventHandlers: Max amount not found for bid');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const floatMax = parseFloat(maxAmount);
|
|
||||||
if (isNaN(floatMax) || floatMax <= 0) {
|
|
||||||
alert('Invalid bid max amount');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const coinExp = amountInput.getAttribute('exp');
|
|
||||||
if (!coinExp) {
|
|
||||||
console.error('EventHandlers: Coin exp not found for bid');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let calculatedAmount = maxAmount * percent;
|
|
||||||
if (floatBalance < calculatedAmount) {
|
|
||||||
calculatedAmount = floatBalance;
|
|
||||||
const checkbox = document.getElementById('subfee_bid');
|
|
||||||
if (checkbox) {
|
|
||||||
checkbox.checked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
amountInput.value = calculatedAmount.toFixed(coinExp);
|
|
||||||
window.updateBidParams('sending');
|
|
||||||
},
|
|
||||||
|
|
||||||
hideConfirmModal: function() {
|
hideConfirmModal: function() {
|
||||||
|
if (window.DOMCache) {
|
||||||
|
window.DOMCache.hide('confirmModal');
|
||||||
|
} else {
|
||||||
const modal = document.getElementById('confirmModal');
|
const modal = document.getElementById('confirmModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.classList.add('hidden');
|
modal.style.display = 'none';
|
||||||
modal.style.display = '';
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
lookup_rates: function() {
|
lookup_rates: function() {
|
||||||
|
|
||||||
if (window.lookup_rates && typeof window.lookup_rates === 'function') {
|
if (window.lookup_rates && typeof window.lookup_rates === 'function') {
|
||||||
window.lookup_rates();
|
window.lookup_rates();
|
||||||
} else {
|
} else {
|
||||||
@@ -288,46 +191,10 @@
|
|||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
const target = e.target.closest('[data-confirm]');
|
const target = e.target.closest('[data-confirm]');
|
||||||
if (target) {
|
if (target) {
|
||||||
if (target.dataset.confirmHandled) {
|
|
||||||
delete target.dataset.confirmHandled;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
const action = target.getAttribute('data-confirm-action') || 'proceed';
|
const action = target.getAttribute('data-confirm-action') || 'proceed';
|
||||||
const coinName = target.getAttribute('data-confirm-coin') || '';
|
const coinName = target.getAttribute('data-confirm-coin') || '';
|
||||||
|
|
||||||
const message = action === 'Accept'
|
if (!this.confirmPopup(action, coinName)) {
|
||||||
? 'Are you sure you want to accept this bid?'
|
|
||||||
: coinName
|
|
||||||
? `Are you sure you want to ${action} ${coinName}?`
|
|
||||||
: 'Are you sure you want to proceed?';
|
|
||||||
|
|
||||||
const title = `Confirm ${action}`;
|
|
||||||
|
|
||||||
this.showConfirmModal(title, message, function() {
|
|
||||||
target.dataset.confirmHandled = 'true';
|
|
||||||
|
|
||||||
if (target.form) {
|
|
||||||
const hiddenInput = document.createElement('input');
|
|
||||||
hiddenInput.type = 'hidden';
|
|
||||||
hiddenInput.name = target.name;
|
|
||||||
hiddenInput.value = target.value;
|
|
||||||
target.form.appendChild(hiddenInput);
|
|
||||||
target.form.submit();
|
|
||||||
} else {
|
|
||||||
target.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
const target = e.target.closest('[data-confirm-reseed]');
|
|
||||||
if (target) {
|
|
||||||
if (!this.confirmReseed()) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -335,9 +202,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
const target = e.target.closest('[data-confirm-mweb-change-convert]');
|
const target = e.target.closest('[data-confirm-reseed]');
|
||||||
if (target) {
|
if (target) {
|
||||||
if (!this.confirmMWEBChangeConvert()) {
|
if (!this.confirmReseed()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -394,16 +261,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
const target = e.target.closest('[data-set-bid-amount]');
|
|
||||||
if (target) {
|
|
||||||
e.preventDefault();
|
|
||||||
const percent = parseFloat(target.getAttribute('data-set-bid-amount'));
|
|
||||||
const inputId = target.getAttribute('data-input-id');
|
|
||||||
this.setBidAmount(percent, inputId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
const target = e.target.closest('[data-reset-form]');
|
const target = e.target.closest('[data-reset-form]');
|
||||||
if (target) {
|
if (target) {
|
||||||
@@ -469,15 +326,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.EventHandlers = EventHandlers;
|
window.EventHandlers = EventHandlers;
|
||||||
|
|
||||||
|
window.confirmPopup = EventHandlers.confirmPopup.bind(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);
|
||||||
window.fillDonationAddress = EventHandlers.fillDonationAddress.bind(EventHandlers);
|
window.fillDonationAddress = EventHandlers.fillDonationAddress.bind(EventHandlers);
|
||||||
window.setAmmAmount = EventHandlers.setAmmAmount.bind(EventHandlers);
|
window.setAmmAmount = EventHandlers.setAmmAmount.bind(EventHandlers);
|
||||||
window.setOfferAmount = EventHandlers.setOfferAmount.bind(EventHandlers);
|
window.setOfferAmount = EventHandlers.setOfferAmount.bind(EventHandlers);
|
||||||
window.setBidAmount = EventHandlers.setBidAmount.bind(EventHandlers);
|
|
||||||
window.resetForm = EventHandlers.resetForm.bind(EventHandlers);
|
window.resetForm = EventHandlers.resetForm.bind(EventHandlers);
|
||||||
window.hideConfirmModal = EventHandlers.hideConfirmModal.bind(EventHandlers);
|
window.hideConfirmModal = EventHandlers.hideConfirmModal.bind(EventHandlers);
|
||||||
window.toggleNotificationDropdown = EventHandlers.toggleNotificationDropdown.bind(EventHandlers);
|
window.toggleNotificationDropdown = EventHandlers.toggleNotificationDropdown.bind(EventHandlers);
|
||||||
|
|||||||
@@ -250,7 +250,6 @@ function ensureToastContainer() {
|
|||||||
'new_bid': 'bg-green-500',
|
'new_bid': 'bg-green-500',
|
||||||
'bid_accepted': 'bg-purple-500',
|
'bid_accepted': 'bg-purple-500',
|
||||||
'swap_completed': 'bg-green-600',
|
'swap_completed': 'bg-green-600',
|
||||||
'sweep_completed': 'bg-orange-500',
|
|
||||||
'balance_change': 'bg-yellow-500',
|
'balance_change': 'bg-yellow-500',
|
||||||
'update_available': 'bg-blue-600',
|
'update_available': 'bg-blue-600',
|
||||||
'success': 'bg-blue-500'
|
'success': 'bg-blue-500'
|
||||||
@@ -610,7 +609,7 @@ function ensureToastContainer() {
|
|||||||
clickAction = `onclick="window.location.href='/bid/${options.bidId}'"`;
|
clickAction = `onclick="window.location.href='/bid/${options.bidId}'"`;
|
||||||
cursorStyle = 'cursor-pointer';
|
cursorStyle = 'cursor-pointer';
|
||||||
} else if (options.coinSymbol) {
|
} else if (options.coinSymbol) {
|
||||||
clickAction = `onclick="window.location.href='/wallet/${options.coinSymbol.toLowerCase()}'"`;
|
clickAction = `onclick="window.location.href='/wallet/${options.coinSymbol}'"`;
|
||||||
cursorStyle = 'cursor-pointer';
|
cursorStyle = 'cursor-pointer';
|
||||||
} else if (options.releaseUrl) {
|
} else if (options.releaseUrl) {
|
||||||
clickAction = `onclick="window.open('${options.releaseUrl}', '_blank')"`;
|
clickAction = `onclick="window.open('${options.releaseUrl}', '_blank')"`;
|
||||||
@@ -736,18 +735,6 @@ function ensureToastContainer() {
|
|||||||
shouldShowToast = config.showUpdateNotifications;
|
shouldShowToast = config.showUpdateNotifications;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'sweep_completed':
|
|
||||||
const sweepAmount = parseFloat(data.amount || 0).toFixed(8).replace(/\.?0+$/, '');
|
|
||||||
const sweepFee = parseFloat(data.fee || 0).toFixed(8).replace(/\.?0+$/, '');
|
|
||||||
const sweepTicker = data.ticker || data.coin_name;
|
|
||||||
toastTitle = `Swept ${sweepAmount} ${sweepTicker} to RPC wallet`;
|
|
||||||
toastOptions.subtitle = `Fee: ${sweepFee} ${sweepTicker} • TXID: ${(data.txid || '').substring(0, 12)}...`;
|
|
||||||
toastOptions.coinSymbol = sweepTicker;
|
|
||||||
toastOptions.txid = data.txid;
|
|
||||||
toastType = 'sweep_completed';
|
|
||||||
shouldShowToast = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'coin_balance_updated':
|
case 'coin_balance_updated':
|
||||||
if (data.coin && config.showBalanceChanges) {
|
if (data.coin && config.showBalanceChanges) {
|
||||||
this.handleBalanceUpdate(data);
|
this.handleBalanceUpdate(data);
|
||||||
|
|||||||
@@ -23,11 +23,6 @@
|
|||||||
types: ['default'],
|
types: ['default'],
|
||||||
hasSubfee: false,
|
hasSubfee: false,
|
||||||
hasSweepAll: true
|
hasSweepAll: true
|
||||||
},
|
|
||||||
13: {
|
|
||||||
types: ['plain', 'spark'],
|
|
||||||
hasSubfee: true,
|
|
||||||
hasSweepAll: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -69,17 +64,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cid === 13) {
|
|
||||||
switch(selectedType) {
|
|
||||||
case 'plain':
|
|
||||||
return this.safeParseFloat(balances.main || balances.balance);
|
|
||||||
case 'spark':
|
|
||||||
return this.safeParseFloat(balances.spark);
|
|
||||||
default:
|
|
||||||
return this.safeParseFloat(balances.main || balances.balance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.safeParseFloat(balances.main || balances.balance);
|
return this.safeParseFloat(balances.main || balances.balance);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -204,8 +188,7 @@
|
|||||||
balance: balance,
|
balance: balance,
|
||||||
blind: balance2,
|
blind: balance2,
|
||||||
anon: balance3,
|
anon: balance3,
|
||||||
mweb: balance2,
|
mweb: balance2
|
||||||
spark: balance2
|
|
||||||
};
|
};
|
||||||
WalletAmountManager.setAmount(percent, balances, coinId);
|
WalletAmountManager.setAmount(percent, balances, coinId);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,614 +0,0 @@
|
|||||||
const BidPage = {
|
|
||||||
bidId: null,
|
|
||||||
bidStateInd: null,
|
|
||||||
createdAtTimestamp: null,
|
|
||||||
autoRefreshInterval: null,
|
|
||||||
elapsedTimeInterval: null,
|
|
||||||
AUTO_REFRESH_SECONDS: 60,
|
|
||||||
refreshPaused: false,
|
|
||||||
swapType: null,
|
|
||||||
coinFrom: null,
|
|
||||||
coinTo: null,
|
|
||||||
previousStateInd: null,
|
|
||||||
|
|
||||||
INACTIVE_STATES: [8, 17, 18, 19, 21, 22, 23, 25, 31], // Completed, Failed variants, Timed-out, Abandoned, Error, Rejected, Expired
|
|
||||||
|
|
||||||
DELAYING_STATE: 20,
|
|
||||||
|
|
||||||
STATE_TOOLTIPS: {
|
|
||||||
'Bid Sent': 'Your bid has been broadcast to the network',
|
|
||||||
'Bid Receiving': 'Receiving partial bid message from the network',
|
|
||||||
'Bid Received': 'Bid received and waiting for decision to accept or reject',
|
|
||||||
'Bid Receiving accept': 'Receiving acceptance message from the other party',
|
|
||||||
'Bid Accepted': 'Bid accepted. The atomic swap process is starting',
|
|
||||||
'Bid Initiated': 'Swap initiated. First lock transaction is being created',
|
|
||||||
'Bid Participating': 'Participating in the swap. Second lock transaction is being created',
|
|
||||||
'Bid Completed': 'Swap completed successfully! Both parties received their coins',
|
|
||||||
'Bid Script coin locked': null,
|
|
||||||
'Bid Script coin spend tx valid': null,
|
|
||||||
'Bid Scriptless coin locked': null,
|
|
||||||
'Bid Script coin lock released': 'Adaptor signature revealed. The script coin can now be claimed',
|
|
||||||
'Bid Script tx redeemed': null,
|
|
||||||
'Bid Script pre-refund tx in chain': 'Pre-refund transaction detected. Swap may be failing',
|
|
||||||
'Bid Scriptless tx redeemed': null,
|
|
||||||
'Bid Scriptless tx recovered': null,
|
|
||||||
'Bid Failed, refunded': 'Swap failed but your coins have been refunded',
|
|
||||||
'Bid Failed, swiped': 'Swap failed due to an unexpected issue. Please check the event log for details',
|
|
||||||
'Bid Failed': 'Swap failed. Check events for details',
|
|
||||||
'Bid Delaying': 'Brief delay between swap steps to ensure network propagation',
|
|
||||||
'Bid Timed-out': 'Swap timed out waiting for the other party',
|
|
||||||
'Bid Abandoned': 'Swap was manually abandoned. Locked coins will be refunded after timelock',
|
|
||||||
'Bid Error': 'An error occurred. Check events for details',
|
|
||||||
'Bid Rejected': 'Bid was rejected by the offer owner',
|
|
||||||
'Bid Stalled (debug)': 'Debug mode: swap intentionally stalled for testing',
|
|
||||||
'Bid Exchanged script lock tx sigs msg': 'Exchanging adaptor signatures needed for lock transactions',
|
|
||||||
'Bid Exchanged script lock spend tx msg': 'Exchanging signed spend transaction for locked coins',
|
|
||||||
'Bid Request sent': 'Connection request sent to the other party',
|
|
||||||
'Bid Request accepted': 'Connection request accepted',
|
|
||||||
'Bid Expired': 'Bid expired before being accepted',
|
|
||||||
'Bid Auto accept delay': 'Waiting for automation delay before auto-accepting',
|
|
||||||
'Bid Auto accept failed': 'Automation failed to accept this bid',
|
|
||||||
'Bid Connect request sent': 'Sent connection request to peer',
|
|
||||||
'Bid Unknown bid state': 'Unknown state - please check the swap details',
|
|
||||||
|
|
||||||
'ITX Sent': 'Initiate transaction has been broadcast to the network',
|
|
||||||
'ITX Confirmed': 'Initiate transaction has been confirmed by miners',
|
|
||||||
'ITX Redeemed': 'Initiate transaction has been successfully claimed',
|
|
||||||
'ITX Refunded': 'Initiate transaction has been refunded',
|
|
||||||
'ITX In Mempool': 'Initiate transaction is in the mempool (unconfirmed)',
|
|
||||||
'ITX In Chain': 'Initiate transaction is included in a block',
|
|
||||||
'PTX Sent': 'Participate transaction has been broadcast to the network',
|
|
||||||
'PTX Confirmed': 'Participate transaction has been confirmed by miners',
|
|
||||||
'PTX Redeemed': 'Participate transaction has been successfully claimed',
|
|
||||||
'PTX Refunded': 'Participate transaction has been refunded',
|
|
||||||
'PTX In Mempool': 'Participate transaction is in the mempool (unconfirmed)',
|
|
||||||
'PTX In Chain': 'Participate transaction is included in a block'
|
|
||||||
},
|
|
||||||
|
|
||||||
getStateTooltip: function(stateText) {
|
|
||||||
const staticTooltip = this.STATE_TOOLTIPS[stateText];
|
|
||||||
if (staticTooltip !== null && staticTooltip !== undefined) {
|
|
||||||
return staticTooltip;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scriptlessCoins = ['XMR', 'WOW'];
|
|
||||||
let scriptCoin, scriptlessCoin;
|
|
||||||
|
|
||||||
if (scriptlessCoins.includes(this.coinFrom)) {
|
|
||||||
scriptlessCoin = this.coinFrom;
|
|
||||||
scriptCoin = this.coinTo;
|
|
||||||
} else if (scriptlessCoins.includes(this.coinTo)) {
|
|
||||||
scriptlessCoin = this.coinTo;
|
|
||||||
scriptCoin = this.coinFrom;
|
|
||||||
} else {
|
|
||||||
scriptCoin = this.coinFrom;
|
|
||||||
scriptlessCoin = this.coinTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dynamicTooltips = {
|
|
||||||
'Bid Script coin locked': `${scriptCoin} is locked in the atomic swap contract`,
|
|
||||||
'Bid Script coin spend tx valid': `The ${scriptCoin} spend transaction has been validated and is ready`,
|
|
||||||
'Bid Scriptless coin locked': `${scriptlessCoin} is locked using adaptor signatures`,
|
|
||||||
'Bid Script tx redeemed': `${scriptCoin} has been successfully claimed`,
|
|
||||||
'Bid Scriptless tx redeemed': `${scriptlessCoin} has been successfully claimed`,
|
|
||||||
'Bid Scriptless tx recovered': `${scriptlessCoin} recovered after swap failure`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return dynamicTooltips[stateText] || null;
|
|
||||||
},
|
|
||||||
|
|
||||||
EVENT_TOOLTIPS: {
|
|
||||||
'Lock tx A published': 'First lock transaction broadcast to the blockchain network',
|
|
||||||
'Lock tx A seen in mempool': 'First lock transaction detected in mempool (unconfirmed)',
|
|
||||||
'Lock tx A seen in chain': 'First lock transaction included in a block',
|
|
||||||
'Lock tx A confirmed in chain': 'First lock transaction has enough confirmations',
|
|
||||||
'Lock tx B published': 'Second lock transaction broadcast to the blockchain network',
|
|
||||||
'Lock tx B seen in mempool': 'Second lock transaction detected in mempool (unconfirmed)',
|
|
||||||
'Lock tx B seen in chain': 'Second lock transaction included in a block',
|
|
||||||
'Lock tx B confirmed in chain': 'Second lock transaction has enough confirmations',
|
|
||||||
'Lock tx A spend tx published': 'Transaction to claim coins from first lock has been broadcast',
|
|
||||||
'Lock tx A spend tx seen in chain': 'First lock spend transaction included in a block',
|
|
||||||
'Lock tx B spend tx published': 'Transaction to claim coins from second lock has been broadcast',
|
|
||||||
'Lock tx B spend tx seen in chain': 'Second lock spend transaction included in a block',
|
|
||||||
'Failed to publish lock tx B': 'ERROR: Could not broadcast second lock transaction',
|
|
||||||
'Failed to publish lock tx B spend': 'ERROR: Could not broadcast spend transaction for second lock',
|
|
||||||
'Failed to publish lock tx B refund': 'ERROR: Could not broadcast refund transaction',
|
|
||||||
'Detected invalid lock Tx B': 'ERROR: Second lock transaction is invalid or malformed',
|
|
||||||
'Lock tx A pre-refund tx published': 'Pre-refund transaction broadcast. Swap is being cancelled',
|
|
||||||
'Lock tx A refund spend tx published': 'Refund transaction for first lock has been broadcast',
|
|
||||||
'Lock tx A refund swipe tx published': 'Other party claimed your refund (swiped)',
|
|
||||||
'Lock tx B refund tx published': 'Refund transaction for second lock has been broadcast',
|
|
||||||
'Lock tx A conflicting txn/s': 'WARNING: Conflicting transaction detected for first lock',
|
|
||||||
'Lock tx A pre-refund tx seen in chain': 'Pre-refund transaction detected in blockchain',
|
|
||||||
'Lock tx A refund spend tx seen in chain': 'Refund spend transaction detected in blockchain',
|
|
||||||
'Initiate tx published': 'Secret-hash swap: Initiate transaction broadcast',
|
|
||||||
'Initiate tx redeem tx published': 'Secret-hash swap: Initiate transaction claimed',
|
|
||||||
'Initiate tx refund tx published': 'Secret-hash swap: Initiate transaction refunded',
|
|
||||||
'Participate tx published': 'Secret-hash swap: Participate transaction broadcast',
|
|
||||||
'Participate tx redeem tx published': 'Secret-hash swap: Participate transaction claimed',
|
|
||||||
'Participate tx refund tx published': 'Secret-hash swap: Participate transaction refunded',
|
|
||||||
'BCH mercy tx found': 'BCH specific: Mercy transaction detected',
|
|
||||||
'Lock tx B mercy tx published': 'BCH specific: Mercy transaction broadcast',
|
|
||||||
'Auto accepting': 'Automation is accepting this bid',
|
|
||||||
'Failed auto accepting': 'Automation constraints prevented accepting this bid',
|
|
||||||
'Debug tweak applied': 'Debug mode: A test tweak was applied'
|
|
||||||
},
|
|
||||||
|
|
||||||
STATE_PHASES: {
|
|
||||||
1: { phase: 'negotiation', order: 1, label: 'Negotiation' }, // BID_SENT
|
|
||||||
2: { phase: 'negotiation', order: 2, label: 'Negotiation' }, // BID_RECEIVING
|
|
||||||
3: { phase: 'negotiation', order: 3, label: 'Negotiation' }, // BID_RECEIVED
|
|
||||||
4: { phase: 'negotiation', order: 4, label: 'Negotiation' }, // BID_RECEIVING_ACC
|
|
||||||
5: { phase: 'accepted', order: 5, label: 'Accepted' }, // BID_ACCEPTED
|
|
||||||
6: { phase: 'locking', order: 6, label: 'Locking' }, // SWAP_INITIATED
|
|
||||||
7: { phase: 'locking', order: 7, label: 'Locking' }, // SWAP_PARTICIPATING
|
|
||||||
8: { phase: 'complete', order: 100, label: 'Complete' }, // SWAP_COMPLETED
|
|
||||||
9: { phase: 'locking', order: 8, label: 'Locking' }, // XMR_SWAP_SCRIPT_COIN_LOCKED
|
|
||||||
10: { phase: 'locking', order: 9, label: 'Locking' }, // XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX
|
|
||||||
11: { phase: 'locking', order: 10, label: 'Locking' }, // XMR_SWAP_NOSCRIPT_COIN_LOCKED
|
|
||||||
12: { phase: 'redemption', order: 11, label: 'Redemption' }, // XMR_SWAP_LOCK_RELEASED
|
|
||||||
13: { phase: 'redemption', order: 12, label: 'Redemption' }, // XMR_SWAP_SCRIPT_TX_REDEEMED
|
|
||||||
14: { phase: 'redemption', order: 11.5, label: 'Refunding' }, // XMR_SWAP_SCRIPT_TX_PREREFUND
|
|
||||||
15: { phase: 'redemption', order: 13, label: 'Redemption' }, // XMR_SWAP_NOSCRIPT_TX_REDEEMED
|
|
||||||
16: { phase: 'failed', order: 91, label: 'Recovered' }, // XMR_SWAP_NOSCRIPT_TX_RECOVERED
|
|
||||||
17: { phase: 'failed', order: 92, label: 'Failed' }, // XMR_SWAP_FAILED_REFUNDED
|
|
||||||
18: { phase: 'failed', order: 93, label: 'Failed' }, // XMR_SWAP_FAILED_SWIPED
|
|
||||||
19: { phase: 'failed', order: 94, label: 'Failed' }, // XMR_SWAP_FAILED
|
|
||||||
20: { phase: 'locking', order: 7.5, label: 'Locking' }, // SWAP_DELAYING
|
|
||||||
21: { phase: 'failed', order: 95, label: 'Failed' }, // SWAP_TIMEDOUT
|
|
||||||
22: { phase: 'failed', order: 96, label: 'Abandoned' }, // BID_ABANDONED
|
|
||||||
23: { phase: 'failed', order: 97, label: 'Error' }, // BID_ERROR
|
|
||||||
25: { phase: 'failed', order: 98, label: 'Rejected' }, // BID_REJECTED
|
|
||||||
27: { phase: 'accepted', order: 5.5, label: 'Accepted' }, // XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS
|
|
||||||
28: { phase: 'accepted', order: 5.6, label: 'Accepted' }, // XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX
|
|
||||||
29: { phase: 'negotiation', order: 0.5, label: 'Negotiation' }, // BID_REQUEST_SENT
|
|
||||||
30: { phase: 'negotiation', order: 0.6, label: 'Negotiation' }, // BID_REQUEST_ACCEPTED
|
|
||||||
31: { phase: 'failed', order: 99, label: 'Expired' }, // BID_EXPIRED
|
|
||||||
32: { phase: 'negotiation', order: 3.5, label: 'Negotiation' }, // BID_AACCEPT_DELAY
|
|
||||||
33: { phase: 'failed', order: 89, label: 'Failed' }, // BID_AACCEPT_FAIL
|
|
||||||
34: { phase: 'negotiation', order: 0.4, label: 'Negotiation' } // CONNECT_REQ_SENT
|
|
||||||
},
|
|
||||||
|
|
||||||
init: function(bidId, bidStateInd, createdAtTimestamp, stateTimeTimestamp, options) {
|
|
||||||
this.bidId = bidId;
|
|
||||||
this.bidStateInd = bidStateInd;
|
|
||||||
this.createdAtTimestamp = createdAtTimestamp;
|
|
||||||
this.stateTimeTimestamp = stateTimeTimestamp;
|
|
||||||
this.tooltipCounter = 0;
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
this.swapType = options.swapType || 'secret-hash';
|
|
||||||
this.coinFrom = options.coinFrom || '';
|
|
||||||
this.coinTo = options.coinTo || '';
|
|
||||||
|
|
||||||
if (this.bidStateInd === this.DELAYING_STATE) {
|
|
||||||
this.previousStateInd = this.findPreviousState();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.applyStateTooltips();
|
|
||||||
this.applyEventTooltips();
|
|
||||||
this.createProgressBar();
|
|
||||||
this.startElapsedTimeUpdater();
|
|
||||||
this.setupAutoRefresh();
|
|
||||||
},
|
|
||||||
|
|
||||||
findPreviousState: function() {
|
|
||||||
const sections = document.querySelectorAll('section');
|
|
||||||
let oldStatesSection = null;
|
|
||||||
|
|
||||||
sections.forEach(section => {
|
|
||||||
const h4 = section.querySelector('h4');
|
|
||||||
if (h4 && h4.textContent.includes('Old states')) {
|
|
||||||
oldStatesSection = section.nextElementSibling;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (oldStatesSection) {
|
|
||||||
const table = oldStatesSection.querySelector('table');
|
|
||||||
if (table) {
|
|
||||||
const rows = table.querySelectorAll('tr');
|
|
||||||
|
|
||||||
for (let i = rows.length - 1; i >= 0; i--) {
|
|
||||||
const cells = rows[i].querySelectorAll('td');
|
|
||||||
if (cells.length >= 2) {
|
|
||||||
const stateText = cells[cells.length - 1].textContent.trim();
|
|
||||||
if (!stateText.includes('Delaying')) {
|
|
||||||
return this.stateTextToIndex(stateText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
stateTextToIndex: function(stateText) {
|
|
||||||
const stateMap = {
|
|
||||||
'Sent': 1, 'Receiving': 2, 'Received': 3, 'Receiving accept': 4,
|
|
||||||
'Accepted': 5, 'Initiated': 6, 'Participating': 7, 'Completed': 8,
|
|
||||||
'Script coin locked': 9, 'Script coin spend tx valid': 10,
|
|
||||||
'Scriptless coin locked': 11, 'Script coin lock released': 12,
|
|
||||||
'Script tx redeemed': 13, 'Script pre-refund tx in chain': 14,
|
|
||||||
'Scriptless tx redeemed': 15, 'Scriptless tx recovered': 16,
|
|
||||||
'Failed, refunded': 17, 'Failed, swiped': 18, 'Failed': 19,
|
|
||||||
'Delaying': 20, 'Timed-out': 21, 'Abandoned': 22, 'Error': 23,
|
|
||||||
'Rejected': 25, 'Exchanged script lock tx sigs msg': 27,
|
|
||||||
'Exchanged script lock spend tx msg': 28, 'Request sent': 29,
|
|
||||||
'Request accepted': 30, 'Expired': 31
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(stateMap)) {
|
|
||||||
if (stateText.includes(key)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
isActiveState: function() {
|
|
||||||
return !this.INACTIVE_STATES.includes(this.bidStateInd);
|
|
||||||
},
|
|
||||||
|
|
||||||
setupAutoRefresh: function() {
|
|
||||||
const refreshBtn = document.getElementById('refresh');
|
|
||||||
if (!refreshBtn) return;
|
|
||||||
|
|
||||||
if (!this.isActiveState()) {
|
|
||||||
refreshBtn.style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const originalSpan = refreshBtn.querySelector('span');
|
|
||||||
if (!originalSpan) return;
|
|
||||||
|
|
||||||
let countdown = this.AUTO_REFRESH_SECONDS;
|
|
||||||
let isRefreshing = false;
|
|
||||||
let isPersistentlyPaused = false;
|
|
||||||
|
|
||||||
const updateCountdown = () => {
|
|
||||||
if (this.refreshPaused || isPersistentlyPaused || isRefreshing) return;
|
|
||||||
|
|
||||||
originalSpan.textContent = `Auto-refresh in ${countdown}s`;
|
|
||||||
countdown--;
|
|
||||||
|
|
||||||
if (countdown < 0 && !isRefreshing) {
|
|
||||||
isRefreshing = true;
|
|
||||||
if (this.autoRefreshInterval) {
|
|
||||||
clearInterval(this.autoRefreshInterval);
|
|
||||||
this.autoRefreshInterval = null;
|
|
||||||
}
|
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
updateCountdown();
|
|
||||||
this.autoRefreshInterval = setInterval(updateCountdown, 1000);
|
|
||||||
|
|
||||||
refreshBtn.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (isPersistentlyPaused) {
|
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
|
||||||
} else {
|
|
||||||
isPersistentlyPaused = true;
|
|
||||||
if (this.autoRefreshInterval) {
|
|
||||||
clearInterval(this.autoRefreshInterval);
|
|
||||||
this.autoRefreshInterval = null;
|
|
||||||
}
|
|
||||||
originalSpan.textContent = 'Paused (click to refresh)';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
refreshBtn.addEventListener('mouseenter', () => {
|
|
||||||
if (!isPersistentlyPaused) {
|
|
||||||
this.refreshPaused = true;
|
|
||||||
if (this.autoRefreshInterval) {
|
|
||||||
clearInterval(this.autoRefreshInterval);
|
|
||||||
this.autoRefreshInterval = null;
|
|
||||||
}
|
|
||||||
originalSpan.textContent = 'Click to pause';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
refreshBtn.addEventListener('mouseleave', () => {
|
|
||||||
if (!isPersistentlyPaused) {
|
|
||||||
this.refreshPaused = false;
|
|
||||||
countdown = this.AUTO_REFRESH_SECONDS;
|
|
||||||
if (!this.autoRefreshInterval) {
|
|
||||||
updateCountdown();
|
|
||||||
this.autoRefreshInterval = setInterval(updateCountdown, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
createTooltip: function(element, tooltipText) {
|
|
||||||
if (window.TooltipManager && typeof window.TooltipManager.create === 'function') {
|
|
||||||
try {
|
|
||||||
const tooltipContent = `
|
|
||||||
<div class="py-1 px-2 text-sm text-white">
|
|
||||||
${tooltipText}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
window.TooltipManager.create(element, tooltipContent, {
|
|
||||||
placement: 'top'
|
|
||||||
});
|
|
||||||
element.classList.add('cursor-help');
|
|
||||||
} catch (e) {
|
|
||||||
element.setAttribute('title', tooltipText);
|
|
||||||
element.classList.add('cursor-help');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
element.setAttribute('title', tooltipText);
|
|
||||||
element.classList.add('cursor-help');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
applyStateTooltips: function() {
|
|
||||||
const sections = document.querySelectorAll('section');
|
|
||||||
let oldStatesSection = null;
|
|
||||||
|
|
||||||
sections.forEach(section => {
|
|
||||||
const h4 = section.querySelector('h4');
|
|
||||||
if (h4 && h4.textContent.includes('Old states')) {
|
|
||||||
oldStatesSection = section.nextElementSibling;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (oldStatesSection) {
|
|
||||||
const table = oldStatesSection.querySelector('table');
|
|
||||||
if (table) {
|
|
||||||
const rows = table.querySelectorAll('tr');
|
|
||||||
rows.forEach(row => {
|
|
||||||
const cells = row.querySelectorAll('td');
|
|
||||||
if (cells.length >= 2) {
|
|
||||||
const stateCell = cells[cells.length - 1];
|
|
||||||
const stateText = stateCell.textContent.trim();
|
|
||||||
const tooltip = this.getStateTooltip(stateText) || this.getStateTooltip('Bid ' + stateText);
|
|
||||||
if (tooltip) {
|
|
||||||
this.addHelpIcon(stateCell, tooltip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const allRows = document.querySelectorAll('table tr');
|
|
||||||
allRows.forEach(row => {
|
|
||||||
const firstCell = row.querySelector('td');
|
|
||||||
if (firstCell) {
|
|
||||||
const labelText = firstCell.textContent.trim();
|
|
||||||
if (labelText === 'Bid State') {
|
|
||||||
const valueCell = row.querySelectorAll('td')[1];
|
|
||||||
if (valueCell) {
|
|
||||||
const stateText = valueCell.textContent.trim();
|
|
||||||
const tooltip = this.getStateTooltip(stateText) || this.getStateTooltip('Bid ' + stateText);
|
|
||||||
if (tooltip) {
|
|
||||||
this.addHelpIcon(valueCell, tooltip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
addHelpIcon: function(cell, tooltipText) {
|
|
||||||
if (cell.querySelector('.help-icon')) return;
|
|
||||||
|
|
||||||
const helpIcon = document.createElement('span');
|
|
||||||
helpIcon.className = 'help-icon cursor-help inline-flex items-center justify-center w-4 h-4 ml-2 text-xs font-medium text-white bg-blue-500 dark:bg-blue-600 rounded-full hover:bg-blue-600 dark:hover:bg-blue-500';
|
|
||||||
helpIcon.textContent = '?';
|
|
||||||
helpIcon.style.fontSize = '10px';
|
|
||||||
helpIcon.style.verticalAlign = 'middle';
|
|
||||||
helpIcon.style.flexShrink = '0';
|
|
||||||
|
|
||||||
cell.appendChild(helpIcon);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.createTooltip(helpIcon, tooltipText);
|
|
||||||
}, 50);
|
|
||||||
},
|
|
||||||
|
|
||||||
applyEventTooltips: function() {
|
|
||||||
const sections = document.querySelectorAll('section');
|
|
||||||
let eventsSection = null;
|
|
||||||
|
|
||||||
sections.forEach(section => {
|
|
||||||
const h4 = section.querySelector('h4');
|
|
||||||
if (h4 && h4.textContent.includes('Events')) {
|
|
||||||
eventsSection = section.nextElementSibling;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (eventsSection) {
|
|
||||||
const table = eventsSection.querySelector('table');
|
|
||||||
if (table) {
|
|
||||||
const rows = table.querySelectorAll('tr');
|
|
||||||
rows.forEach(row => {
|
|
||||||
const cells = row.querySelectorAll('td');
|
|
||||||
if (cells.length >= 2) {
|
|
||||||
const eventCell = cells[cells.length - 1];
|
|
||||||
const eventText = eventCell.textContent.trim();
|
|
||||||
|
|
||||||
let tooltip = this.EVENT_TOOLTIPS[eventText];
|
|
||||||
|
|
||||||
if (!tooltip) {
|
|
||||||
for (const [key, value] of Object.entries(this.EVENT_TOOLTIPS)) {
|
|
||||||
if (eventText.startsWith(key.replace(':', ''))) {
|
|
||||||
tooltip = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tooltip && eventText.startsWith('Warning:')) {
|
|
||||||
tooltip = 'System warning - check message for details';
|
|
||||||
}
|
|
||||||
if (!tooltip && eventText.startsWith('Error:')) {
|
|
||||||
tooltip = 'Error occurred - check message for details';
|
|
||||||
}
|
|
||||||
if (!tooltip && eventText.startsWith('Temporary RPC error')) {
|
|
||||||
tooltip = 'Temporary error checking transaction. Will retry automatically';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tooltip) {
|
|
||||||
this.addHelpIcon(eventCell, tooltip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createProgressBar: function() {
|
|
||||||
let stateForProgress = this.bidStateInd;
|
|
||||||
let isDelaying = false;
|
|
||||||
|
|
||||||
if (this.bidStateInd === this.DELAYING_STATE && this.previousStateInd) {
|
|
||||||
stateForProgress = this.previousStateInd;
|
|
||||||
isDelaying = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const phaseInfo = this.STATE_PHASES[stateForProgress];
|
|
||||||
if (!phaseInfo) return;
|
|
||||||
|
|
||||||
let progressPercent = 0;
|
|
||||||
const phase = phaseInfo.phase;
|
|
||||||
|
|
||||||
if (phase === 'negotiation') progressPercent = 15;
|
|
||||||
else if (phase === 'accepted') progressPercent = 30;
|
|
||||||
else if (phase === 'locking') progressPercent = 55;
|
|
||||||
else if (phase === 'redemption') progressPercent = 80;
|
|
||||||
else if (phase === 'complete') progressPercent = 100;
|
|
||||||
else if (phase === 'failed' || phase === 'error') progressPercent = 100;
|
|
||||||
|
|
||||||
const bidStateRow = document.querySelector('td.bold');
|
|
||||||
if (!bidStateRow) return;
|
|
||||||
|
|
||||||
let targetRow = null;
|
|
||||||
const rows = document.querySelectorAll('table tr');
|
|
||||||
rows.forEach(row => {
|
|
||||||
const firstTd = row.querySelector('td.bold');
|
|
||||||
if (firstTd && firstTd.textContent.trim() === 'Bid State') {
|
|
||||||
targetRow = row;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!targetRow) return;
|
|
||||||
|
|
||||||
const progressRow = document.createElement('tr');
|
|
||||||
progressRow.className = 'opacity-100 text-gray-500 dark:text-gray-100';
|
|
||||||
|
|
||||||
const isError = ['failed', 'error'].includes(phase);
|
|
||||||
const isComplete = phase === 'complete';
|
|
||||||
const barColor = isError ? 'bg-red-500' : (isComplete ? 'bg-green-500' : 'bg-blue-500');
|
|
||||||
|
|
||||||
let phaseLabel;
|
|
||||||
if (isError) {
|
|
||||||
phaseLabel = phaseInfo.label;
|
|
||||||
} else if (isComplete) {
|
|
||||||
phaseLabel = 'Complete';
|
|
||||||
} else if (isDelaying) {
|
|
||||||
phaseLabel = `${phaseInfo.label} (${progressPercent}%) - Delaying`;
|
|
||||||
} else {
|
|
||||||
phaseLabel = `${phaseInfo.label} (${progressPercent}%)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
progressRow.innerHTML = `
|
|
||||||
<td class="py-3 px-6 bold">Swap Progress</td>
|
|
||||||
<td class="py-3 px-6">
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<div class="flex-1 bg-gray-200 dark:bg-gray-600 rounded-full h-2.5 max-w-xs">
|
|
||||||
<div class="${barColor} h-2.5 rounded-full transition-all duration-500" style="width: ${progressPercent}%"></div>
|
|
||||||
</div>
|
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">${phaseLabel}</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
`;
|
|
||||||
|
|
||||||
targetRow.parentNode.insertBefore(progressRow, targetRow.nextSibling);
|
|
||||||
},
|
|
||||||
|
|
||||||
startElapsedTimeUpdater: function() {
|
|
||||||
if (!this.createdAtTimestamp) return;
|
|
||||||
|
|
||||||
let createdAtRow = null;
|
|
||||||
const rows = document.querySelectorAll('table tr');
|
|
||||||
rows.forEach(row => {
|
|
||||||
const firstTd = row.querySelector('td');
|
|
||||||
if (firstTd && firstTd.textContent.includes('Created At')) {
|
|
||||||
createdAtRow = row;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!createdAtRow) return;
|
|
||||||
|
|
||||||
const isCompleted = !this.isActiveState() && this.stateTimeTimestamp;
|
|
||||||
|
|
||||||
const elapsedRow = document.createElement('tr');
|
|
||||||
elapsedRow.className = 'opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600';
|
|
||||||
|
|
||||||
const labelText = isCompleted ? 'Swap Duration' : 'Time Elapsed';
|
|
||||||
const iconColor = isCompleted ? '#10B981' : '#3B82F6';
|
|
||||||
|
|
||||||
elapsedRow.innerHTML = `
|
|
||||||
<td class="flex items-center px-46 whitespace-nowrap">
|
|
||||||
<svg alt="" class="w-5 h-5 rounded-full ml-5" xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
|
||||||
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="${iconColor}" stroke-linejoin="round">
|
|
||||||
<circle cx="12" cy="12" r="11"></circle>
|
|
||||||
<polyline points="12,6 12,12 18,12" stroke="${iconColor}"></polyline>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<div class="py-3 pl-2 bold">
|
|
||||||
<div>${labelText}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="py-3 px-6" id="elapsed-time-display">Calculating...</td>
|
|
||||||
`;
|
|
||||||
createdAtRow.parentNode.insertBefore(elapsedRow, createdAtRow.nextSibling);
|
|
||||||
|
|
||||||
const elapsedDisplay = document.getElementById('elapsed-time-display');
|
|
||||||
|
|
||||||
if (isCompleted) {
|
|
||||||
const duration = this.stateTimeTimestamp - this.createdAtTimestamp;
|
|
||||||
elapsedDisplay.textContent = this.formatDuration(duration);
|
|
||||||
} else {
|
|
||||||
const updateElapsed = () => {
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
const elapsed = now - this.createdAtTimestamp;
|
|
||||||
elapsedDisplay.textContent = this.formatDuration(elapsed);
|
|
||||||
};
|
|
||||||
|
|
||||||
updateElapsed();
|
|
||||||
this.elapsedTimeInterval = setInterval(updateElapsed, 1000);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
formatDuration: function(seconds) {
|
|
||||||
if (seconds < 60) {
|
|
||||||
return `${seconds} second${seconds !== 1 ? 's' : ''}`;
|
|
||||||
}
|
|
||||||
const minutes = Math.floor(seconds / 60);
|
|
||||||
if (minutes < 60) {
|
|
||||||
const remainingSeconds = seconds % 60;
|
|
||||||
if (remainingSeconds > 0) {
|
|
||||||
return `${minutes} min ${remainingSeconds} sec`;
|
|
||||||
}
|
|
||||||
return `${minutes} minute${minutes !== 1 ? 's' : ''}`;
|
|
||||||
}
|
|
||||||
const hours = Math.floor(minutes / 60);
|
|
||||||
const remainingMinutes = minutes % 60;
|
|
||||||
if (hours < 24) {
|
|
||||||
if (remainingMinutes > 0) {
|
|
||||||
return `${hours} hr ${remainingMinutes} min`;
|
|
||||||
}
|
|
||||||
return `${hours} hour${hours !== 1 ? 's' : ''}`;
|
|
||||||
}
|
|
||||||
const days = Math.floor(hours / 24);
|
|
||||||
const remainingHours = hours % 24;
|
|
||||||
if (remainingHours > 0) {
|
|
||||||
return `${days} day${days !== 1 ? 's' : ''} ${remainingHours} hr`;
|
|
||||||
}
|
|
||||||
return `${days} day${days !== 1 ? 's' : ''}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
const PAGE_SIZE = 50;
|
const PAGE_SIZE = 50;
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
identities: new Map(),
|
dentities: new Map(),
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
wsConnected: false,
|
wsConnected: false,
|
||||||
jsonData: [],
|
jsonData: [],
|
||||||
|
|||||||
@@ -4,12 +4,10 @@
|
|||||||
const OfferPage = {
|
const OfferPage = {
|
||||||
xhr_rates: null,
|
xhr_rates: null,
|
||||||
xhr_bid_params: null,
|
xhr_bid_params: null,
|
||||||
xhr_bid_prefund: null,
|
|
||||||
|
|
||||||
init: function() {
|
init: function() {
|
||||||
this.xhr_rates = new XMLHttpRequest();
|
this.xhr_rates = new XMLHttpRequest();
|
||||||
this.xhr_bid_params = new XMLHttpRequest();
|
this.xhr_bid_params = new XMLHttpRequest();
|
||||||
this.xhr_bid_prefund = new XMLHttpRequest();
|
|
||||||
|
|
||||||
this.setupXHRHandlers();
|
this.setupXHRHandlers();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
@@ -35,20 +33,7 @@
|
|||||||
if (bidAmountSendInput) {
|
if (bidAmountSendInput) {
|
||||||
bidAmountSendInput.value = obj['amount_to'];
|
bidAmountSendInput.value = obj['amount_to'];
|
||||||
}
|
}
|
||||||
}
|
this.updateModalValues();
|
||||||
};
|
|
||||||
|
|
||||||
this.xhr_bid_prefund.onload = () => {
|
|
||||||
if (this.xhr_bid_prefund.status == 200) {
|
|
||||||
const obj = JSON.parse(this.xhr_bid_prefund.response);
|
|
||||||
const bidAmountInput = document.getElementById('bid_amount');
|
|
||||||
if (bidAmountInput) {
|
|
||||||
bidAmountInput.value = obj['amount_from'];
|
|
||||||
}
|
|
||||||
const prefundedBidInput = document.getElementById('prefunded_bid_tx');
|
|
||||||
if (prefundedBidInput) {
|
|
||||||
prefundedBidInput.value = obj['bid_tx'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -56,29 +41,12 @@
|
|||||||
setupEventListeners: function() {
|
setupEventListeners: function() {
|
||||||
const sendBidBtn = document.querySelector('button[name="sendbid"][value="Send Bid"]');
|
const sendBidBtn = document.querySelector('button[name="sendbid"][value="Send Bid"]');
|
||||||
if (sendBidBtn) {
|
if (sendBidBtn) {
|
||||||
sendBidBtn.addEventListener('click', (e) => {
|
sendBidBtn.onclick = this.showConfirmModal.bind(this);
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.showConfirmModal();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const modalCancelBtn = document.querySelector('#confirmModal [data-hide-modal]');
|
const modalCancelBtn = document.querySelector('#confirmModal .flex button:last-child');
|
||||||
if (modalCancelBtn) {
|
if (modalCancelBtn) {
|
||||||
modalCancelBtn.addEventListener('click', (e) => {
|
modalCancelBtn.onclick = this.hideConfirmModal.bind(this);
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
this.hideConfirmModal();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmModal = document.getElementById('confirmModal');
|
|
||||||
if (confirmModal) {
|
|
||||||
confirmModal.addEventListener('click', (e) => {
|
|
||||||
if (e.target === confirmModal || e.target.classList.contains('bg-opacity-50')) {
|
|
||||||
this.hideConfirmModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainCancelBtn = document.querySelector('button[name="cancel"]');
|
const mainCancelBtn = document.querySelector('button[name="cancel"]');
|
||||||
@@ -86,6 +54,16 @@
|
|||||||
mainCancelBtn.onclick = this.handleCancelClick.bind(this);
|
mainCancelBtn.onclick = this.handleCancelClick.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validMinsInput = document.querySelector('input[name="validmins"]');
|
||||||
|
if (validMinsInput) {
|
||||||
|
validMinsInput.addEventListener('input', this.updateModalValues.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
const addrFromSelect = document.querySelector('select[name="addr_from"]');
|
||||||
|
if (addrFromSelect) {
|
||||||
|
addrFromSelect.addEventListener('change', this.updateModalValues.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
const errorOkBtn = document.getElementById('errorOk');
|
const errorOkBtn = document.getElementById('errorOk');
|
||||||
if (errorOkBtn) {
|
if (errorOkBtn) {
|
||||||
errorOkBtn.addEventListener('click', this.hideErrorModal.bind(this));
|
errorOkBtn.addEventListener('click', this.hideErrorModal.bind(this));
|
||||||
@@ -134,6 +112,7 @@
|
|||||||
if (!amtVar) {
|
if (!amtVar) {
|
||||||
this.updateBidParams('rate');
|
this.updateBidParams('rate');
|
||||||
}
|
}
|
||||||
|
this.updateModalValues();
|
||||||
|
|
||||||
const errorMessages = document.querySelectorAll('.error-message');
|
const errorMessages = document.querySelectorAll('.error-message');
|
||||||
errorMessages.forEach(msg => msg.remove());
|
errorMessages.forEach(msg => msg.remove());
|
||||||
@@ -160,7 +139,6 @@
|
|||||||
const bidAmountSendInput = document.getElementById('bid_amount_send');
|
const bidAmountSendInput = document.getElementById('bid_amount_send');
|
||||||
const bidRateInput = document.getElementById('bid_rate');
|
const bidRateInput = document.getElementById('bid_rate');
|
||||||
const offerRateInput = document.getElementById('offer_rate');
|
const offerRateInput = document.getElementById('offer_rate');
|
||||||
const bidSubfee = document.getElementById('subfee_bid');
|
|
||||||
|
|
||||||
if (!coin_from || !coin_to || !amt_var || !rate_var) return;
|
if (!coin_from || !coin_to || !amt_var || !rate_var) return;
|
||||||
|
|
||||||
@@ -176,7 +154,7 @@
|
|||||||
const receiveAmount = (sendAmount / rate).toFixed(coin_from_exp);
|
const receiveAmount = (sendAmount / rate).toFixed(coin_from_exp);
|
||||||
bidAmountInput.value = receiveAmount;
|
bidAmountInput.value = receiveAmount;
|
||||||
}
|
}
|
||||||
} else if (value_changed === 'sending' || value_changed === 'subfee') {
|
} else if (value_changed === 'sending') {
|
||||||
if (bidAmountSendInput && bidAmountInput) {
|
if (bidAmountSendInput && bidAmountInput) {
|
||||||
const sendAmount = parseFloat(bidAmountSendInput.value) || 0;
|
const sendAmount = parseFloat(bidAmountSendInput.value) || 0;
|
||||||
const receiveAmount = (sendAmount / rate).toFixed(coin_from_exp);
|
const receiveAmount = (sendAmount / rate).toFixed(coin_from_exp);
|
||||||
@@ -192,31 +170,11 @@
|
|||||||
|
|
||||||
this.validateAmountsAfterChange();
|
this.validateAmountsAfterChange();
|
||||||
|
|
||||||
if (bidSubfee && bidSubfee.checked) {
|
|
||||||
bidAmountInput.readOnly = true;
|
|
||||||
|
|
||||||
const offer_id = document.getElementById('offer_id')?.value || '';
|
|
||||||
if (!offer_id) {
|
|
||||||
console.log("offer_id not found!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.xhr_bid_prefund.open('POST', '/json/getsubfeebidtx');
|
|
||||||
this.xhr_bid_prefund.setRequestHeader('Content-type', 'application/json;charset=UTF-8');
|
|
||||||
const data = { offer_id: offer_id, amount_to: bidAmountSendInput.value , bid_rate: rate};
|
|
||||||
this.xhr_bid_prefund.overrideMimeType("application/json");
|
|
||||||
this.xhr_bid_prefund.send(JSON.stringify(data));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bidAmountInput.readOnly = false;
|
|
||||||
const prefundedBidInput = document.getElementById('prefunded_bid_tx');
|
|
||||||
if (prefundedBidInput) {
|
|
||||||
prefundedBidInput.value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xhr_bid_params.open('POST', '/json/rate');
|
this.xhr_bid_params.open('POST', '/json/rate');
|
||||||
this.xhr_bid_params.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
this.xhr_bid_params.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||||
this.xhr_bid_params.overrideMimeType("application/json");
|
|
||||||
this.xhr_bid_params.send(`coin_from=${coin_from}&coin_to=${coin_to}&rate=${rate}&amt_from=${bidAmountInput?.value || '0'}`);
|
this.xhr_bid_params.send(`coin_from=${coin_from}&coin_to=${coin_to}&rate=${rate}&amt_from=${bidAmountInput?.value || '0'}`);
|
||||||
|
|
||||||
|
this.updateModalValues();
|
||||||
},
|
},
|
||||||
|
|
||||||
validateAmountsAfterChange: function() {
|
validateAmountsAfterChange: function() {
|
||||||
@@ -278,11 +236,6 @@
|
|||||||
this.showErrorModal('Validation Error', 'Please enter valid amounts for both sending and receiving.');
|
this.showErrorModal('Validation Error', 'Please enter valid amounts for both sending and receiving.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let subfee = false;
|
|
||||||
const checkbox = document.getElementById('subfee_bid');
|
|
||||||
if (checkbox) {
|
|
||||||
subfee = checkbox.checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
const coinFrom = document.getElementById('coin_from_name')?.value || '';
|
const coinFrom = document.getElementById('coin_from_name')?.value || '';
|
||||||
const coinTo = document.getElementById('coin_to_name')?.value || '';
|
const coinTo = document.getElementById('coin_to_name')?.value || '';
|
||||||
@@ -303,12 +256,7 @@
|
|||||||
if (modalAmtReceive) modalAmtReceive.textContent = receiveAmount.toFixed(8);
|
if (modalAmtReceive) modalAmtReceive.textContent = receiveAmount.toFixed(8);
|
||||||
if (modalReceiveCurrency) modalReceiveCurrency.textContent = ` ${tlaFrom}`;
|
if (modalReceiveCurrency) modalReceiveCurrency.textContent = ` ${tlaFrom}`;
|
||||||
if (modalAmtSend) modalAmtSend.textContent = sendAmount.toFixed(8);
|
if (modalAmtSend) modalAmtSend.textContent = sendAmount.toFixed(8);
|
||||||
if (modalSendCurrency) {
|
if (modalSendCurrency) modalSendCurrency.textContent = ` ${tlaTo}`;
|
||||||
modalSendCurrency.textContent = ` ${tlaTo}`;
|
|
||||||
if (subfee) {
|
|
||||||
modalSendCurrency.textContent += ` (incl fee)`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (modalAddrFrom) modalAddrFrom.textContent = addrFrom || 'Default';
|
if (modalAddrFrom) modalAddrFrom.textContent = addrFrom || 'Default';
|
||||||
if (modalValidMins) modalValidMins.textContent = validMins;
|
if (modalValidMins) modalValidMins.textContent = validMins;
|
||||||
|
|
||||||
@@ -327,6 +275,10 @@
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateModalValues: function() {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
handleBidsPageAddress: function() {
|
handleBidsPageAddress: function() {
|
||||||
const selectElement = document.querySelector('select[name="addr_from"]');
|
const selectElement = document.querySelector('select[name="addr_from"]');
|
||||||
const STORAGE_KEY = 'lastUsedAddressBids';
|
const STORAGE_KEY = 'lastUsedAddressBids';
|
||||||
|
|||||||
@@ -477,7 +477,6 @@ const ui = {
|
|||||||
const chartModule = {
|
const chartModule = {
|
||||||
chart: null,
|
chart: null,
|
||||||
currentCoin: 'BTC',
|
currentCoin: 'BTC',
|
||||||
hasChartData: false,
|
|
||||||
loadStartTime: 0,
|
loadStartTime: 0,
|
||||||
chartRefs: new WeakMap(),
|
chartRefs: new WeakMap(),
|
||||||
pendingAnimationFrame: null,
|
pendingAnimationFrame: null,
|
||||||
@@ -866,7 +865,6 @@ destroyChart: function() {
|
|||||||
console.warn(`No price data available for ${coinSymbol}`);
|
console.warn(`No price data available for ${coinSymbol}`);
|
||||||
chartModule.hideChartLoader();
|
chartModule.hideChartLoader();
|
||||||
chartModule.showNoDataMessage(coinSymbol);
|
chartModule.showNoDataMessage(coinSymbol);
|
||||||
chartModule.hasChartData = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -903,12 +901,16 @@ destroyChart: function() {
|
|||||||
if (chartData.length > 0 && chartModule.chart) {
|
if (chartData.length > 0 && chartModule.chart) {
|
||||||
chartModule.chart.data.datasets[0].data = chartData;
|
chartModule.chart.data.datasets[0].data = chartData;
|
||||||
chartModule.chart.data.datasets[0].label = `${coinSymbol} Price (USD)`;
|
chartModule.chart.data.datasets[0].label = `${coinSymbol} Price (USD)`;
|
||||||
|
if (coinSymbol === 'WOW') {
|
||||||
|
chartModule.chart.options.scales.x.time.unit = 'hour';
|
||||||
|
} else {
|
||||||
const resolution = window.config.chartConfig.resolutions[window.config.currentResolution];
|
const resolution = window.config.chartConfig.resolutions[window.config.currentResolution];
|
||||||
chartModule.chart.options.scales.x.time.unit =
|
chartModule.chart.options.scales.x.time.unit =
|
||||||
resolution && resolution.interval === 'hourly' ? 'hour' :
|
resolution && resolution.interval === 'hourly' ? 'hour' :
|
||||||
window.config.currentResolution === 'year' ? 'month' : 'day';
|
window.config.currentResolution === 'year' ? 'month' : 'day';
|
||||||
|
}
|
||||||
chartModule.chart.update('active');
|
chartModule.chart.update('active');
|
||||||
chartModule.hasChartData = true;
|
chartModule.currentCoin = coinSymbol;
|
||||||
const loadTime = Date.now() - chartModule.loadStartTime;
|
const loadTime = Date.now() - chartModule.loadStartTime;
|
||||||
ui.updateLoadTimeAndCache(loadTime, cachedData);
|
ui.updateLoadTimeAndCache(loadTime, cachedData);
|
||||||
}
|
}
|
||||||
@@ -923,12 +925,9 @@ destroyChart: function() {
|
|||||||
chartModule.chart.data.datasets[0].data = [];
|
chartModule.chart.data.datasets[0].data = [];
|
||||||
chartModule.chart.update('active');
|
chartModule.chart.update('active');
|
||||||
}
|
}
|
||||||
chartModule.hasChartData = false;
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
chartModule.currentCoin = coinSymbol;
|
|
||||||
chartModule.hideChartLoader();
|
chartModule.hideChartLoader();
|
||||||
app.updateResolutionButtons();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1126,6 +1125,7 @@ const app = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await chartModule.updateChart(defaultCoin);
|
await chartModule.updateChart(defaultCoin);
|
||||||
|
app.updateResolutionButtons(defaultCoin);
|
||||||
|
|
||||||
const chartTitle = document.getElementById('chart-title');
|
const chartTitle = document.getElementById('chart-title');
|
||||||
if (chartTitle) {
|
if (chartTitle) {
|
||||||
@@ -1233,8 +1233,11 @@ const app = {
|
|||||||
}
|
}
|
||||||
ui.setActiveContainer(`${coin.symbol.toLowerCase()}-container`);
|
ui.setActiveContainer(`${coin.symbol.toLowerCase()}-container`);
|
||||||
if (chartModule.chart) {
|
if (chartModule.chart) {
|
||||||
|
if (coin.symbol === 'WOW') {
|
||||||
window.config.currentResolution = 'day';
|
window.config.currentResolution = 'day';
|
||||||
|
}
|
||||||
chartModule.updateChart(coin.symbol);
|
chartModule.updateChart(coin.symbol);
|
||||||
|
app.updateResolutionButtons(coin.symbol);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1611,14 +1614,20 @@ refreshAllData: async function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateResolutionButtons: function() {
|
updateResolutionButtons: function(coinSymbol) {
|
||||||
const resolutionButtons = document.querySelectorAll('.resolution-button');
|
const resolutionButtons = document.querySelectorAll('.resolution-button');
|
||||||
|
|
||||||
resolutionButtons.forEach(button => {
|
resolutionButtons.forEach(button => {
|
||||||
const resolution = button.id.split('-')[1];
|
const resolution = button.id.split('-')[1];
|
||||||
if (!chartModule.hasChartData) {
|
if (coinSymbol === 'WOW') {
|
||||||
|
if (resolution === 'day') {
|
||||||
|
button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
|
||||||
|
button.classList.add('active');
|
||||||
|
button.disabled = false;
|
||||||
|
} else {
|
||||||
button.classList.add('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
|
button.classList.add('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
|
||||||
|
button.classList.remove('active');
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
|
button.classList.remove('text-gray-400', 'cursor-not-allowed', 'opacity-50', 'outline-none');
|
||||||
button.classList.toggle('active', resolution === window.config.currentResolution);
|
button.classList.toggle('active', resolution === window.config.currentResolution);
|
||||||
@@ -1650,9 +1659,12 @@ resolutionButtons.forEach(button => {
|
|||||||
button.addEventListener('click', () => {
|
button.addEventListener('click', () => {
|
||||||
const resolution = button.id.split('-')[1];
|
const resolution = button.id.split('-')[1];
|
||||||
const currentCoin = chartModule.currentCoin;
|
const currentCoin = chartModule.currentCoin;
|
||||||
|
|
||||||
|
if (currentCoin !== 'WOW' || resolution === 'day') {
|
||||||
window.config.currentResolution = resolution;
|
window.config.currentResolution = resolution;
|
||||||
chartModule.updateChart(currentCoin, true);
|
chartModule.updateChart(currentCoin, true);
|
||||||
app.updateResolutionButtons();
|
app.updateResolutionButtons(currentCoin);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,11 @@
|
|||||||
confirmCallback: null,
|
confirmCallback: null,
|
||||||
triggerElement: null,
|
triggerElement: null,
|
||||||
|
|
||||||
originalConnectionTypes: {},
|
|
||||||
|
|
||||||
init: function() {
|
init: function() {
|
||||||
this.setupTabs();
|
this.setupTabs();
|
||||||
this.setupCoinHeaders();
|
this.setupCoinHeaders();
|
||||||
this.setupConfirmModal();
|
this.setupConfirmModal();
|
||||||
this.setupNotificationSettings();
|
this.setupNotificationSettings();
|
||||||
this.setupMigrationIndicator();
|
|
||||||
this.setupServerDiscovery();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setupTabs: function() {
|
setupTabs: function() {
|
||||||
@@ -65,410 +61,6 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
pendingModeSwitch: null,
|
|
||||||
|
|
||||||
setupMigrationIndicator: function() {
|
|
||||||
const connectionTypeSelects = document.querySelectorAll('select[name^="connection_type_"]');
|
|
||||||
connectionTypeSelects.forEach(select => {
|
|
||||||
const originalValue = select.dataset.originalValue || select.value;
|
|
||||||
this.originalConnectionTypes[select.name] = originalValue;
|
|
||||||
|
|
||||||
select.addEventListener('change', (e) => {
|
|
||||||
const coinName = select.name.replace('connection_type_', '');
|
|
||||||
const electrumSection = document.getElementById(`electrum-section-${coinName}`);
|
|
||||||
const fundTransferSection = document.getElementById(`fund-transfer-section-${coinName}`);
|
|
||||||
const originalValue = this.originalConnectionTypes[select.name];
|
|
||||||
|
|
||||||
if (e.target.value === 'electrum') {
|
|
||||||
if (electrumSection) {
|
|
||||||
electrumSection.classList.remove('hidden');
|
|
||||||
|
|
||||||
const clearnetTextarea = document.getElementById(`electrum_clearnet_${coinName}`);
|
|
||||||
const onionTextarea = document.getElementById(`electrum_onion_${coinName}`);
|
|
||||||
|
|
||||||
if (clearnetTextarea && !clearnetTextarea.value.trim()) {
|
|
||||||
clearnetTextarea.value = electrumSection.dataset.defaultClearnet || '';
|
|
||||||
}
|
|
||||||
if (onionTextarea && !onionTextarea.value.trim()) {
|
|
||||||
onionTextarea.value = electrumSection.dataset.defaultOnion || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fundTransferSection) {
|
|
||||||
fundTransferSection.classList.add('hidden');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (electrumSection) {
|
|
||||||
electrumSection.classList.add('hidden');
|
|
||||||
}
|
|
||||||
if (fundTransferSection && originalValue === 'electrum') {
|
|
||||||
fundTransferSection.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setupWalletModeModal();
|
|
||||||
|
|
||||||
const coinsForm = document.getElementById('coins-form');
|
|
||||||
if (coinsForm) {
|
|
||||||
coinsForm.addEventListener('submit', (e) => {
|
|
||||||
const submitter = e.submitter;
|
|
||||||
if (!submitter || !submitter.name.startsWith('apply_')) return;
|
|
||||||
|
|
||||||
const coinName = submitter.name.replace('apply_', '');
|
|
||||||
const select = document.querySelector(`select[name="connection_type_${coinName}"]`);
|
|
||||||
if (!select) return;
|
|
||||||
|
|
||||||
const original = this.originalConnectionTypes[select.name];
|
|
||||||
const current = select.value;
|
|
||||||
|
|
||||||
if (original && current && original !== current) {
|
|
||||||
e.preventDefault();
|
|
||||||
const direction = (original === 'rpc' && current === 'electrum') ? 'lite' : 'rpc';
|
|
||||||
this.showWalletModeConfirmation(coinName, direction, submitter);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setupWalletModeModal: function() {
|
|
||||||
const confirmBtn = document.getElementById('walletModeConfirm');
|
|
||||||
const cancelBtn = document.getElementById('walletModeCancel');
|
|
||||||
|
|
||||||
if (confirmBtn) {
|
|
||||||
confirmBtn.addEventListener('click', () => {
|
|
||||||
this.hideWalletModeModal();
|
|
||||||
if (this.pendingModeSwitch) {
|
|
||||||
const { coinName, direction, submitter } = this.pendingModeSwitch;
|
|
||||||
this.showMigrationModal(coinName.toUpperCase(), direction);
|
|
||||||
const form = submitter.form;
|
|
||||||
const hiddenInput = document.createElement('input');
|
|
||||||
hiddenInput.type = 'hidden';
|
|
||||||
hiddenInput.name = submitter.name;
|
|
||||||
hiddenInput.value = submitter.value;
|
|
||||||
form.appendChild(hiddenInput);
|
|
||||||
|
|
||||||
let transferValue = null;
|
|
||||||
const transferRadio = document.querySelector('input[name="transfer_choice"]:checked');
|
|
||||||
const transferHidden = document.querySelector('input[name="transfer_choice"][type="hidden"]');
|
|
||||||
if (transferRadio) {
|
|
||||||
transferValue = transferRadio.value;
|
|
||||||
} else if (transferHidden) {
|
|
||||||
transferValue = transferHidden.value;
|
|
||||||
}
|
|
||||||
if (transferValue) {
|
|
||||||
const transferInput = document.createElement('input');
|
|
||||||
transferInput.type = 'hidden';
|
|
||||||
transferInput.name = `auto_transfer_now_${coinName}`;
|
|
||||||
transferInput.value = transferValue === 'auto' ? 'true' : 'false';
|
|
||||||
form.appendChild(transferInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
form.submit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancelBtn) {
|
|
||||||
cancelBtn.addEventListener('click', () => {
|
|
||||||
this.hideWalletModeModal();
|
|
||||||
if (this.pendingModeSwitch) {
|
|
||||||
const { coinName } = this.pendingModeSwitch;
|
|
||||||
const select = document.querySelector(`select[name="connection_type_${coinName}"]`);
|
|
||||||
if (select) {
|
|
||||||
select.value = this.originalConnectionTypes[select.name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.pendingModeSwitch = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateConfirmButtonState: function() {
|
|
||||||
const confirmBtn = document.getElementById('walletModeConfirm');
|
|
||||||
const checkbox = document.getElementById('walletModeKeyConfirmCheckbox');
|
|
||||||
if (confirmBtn && checkbox) {
|
|
||||||
if (checkbox.checked) {
|
|
||||||
confirmBtn.disabled = false;
|
|
||||||
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
||||||
} else {
|
|
||||||
confirmBtn.disabled = true;
|
|
||||||
confirmBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showWalletModeConfirmation: async function(coinName, direction, submitter) {
|
|
||||||
const modal = document.getElementById('walletModeModal');
|
|
||||||
const title = document.getElementById('walletModeTitle');
|
|
||||||
const message = document.getElementById('walletModeMessage');
|
|
||||||
const details = document.getElementById('walletModeDetails');
|
|
||||||
const confirmBtn = document.getElementById('walletModeConfirm');
|
|
||||||
|
|
||||||
if (!modal || !title || !message || !details) return;
|
|
||||||
|
|
||||||
this.pendingModeSwitch = { coinName, direction, submitter };
|
|
||||||
|
|
||||||
const displayName = coinName.charAt(0).toUpperCase() + coinName.slice(1).toLowerCase();
|
|
||||||
|
|
||||||
details.innerHTML = `
|
|
||||||
<div class="flex items-center justify-center py-4">
|
|
||||||
<svg class="animate-spin h-5 w-5 text-blue-500 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
<span>Loading...</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (confirmBtn) {
|
|
||||||
confirmBtn.disabled = true;
|
|
||||||
confirmBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
||||||
}
|
|
||||||
|
|
||||||
modal.classList.remove('hidden');
|
|
||||||
|
|
||||||
if (direction === 'lite') {
|
|
||||||
title.textContent = `Switch ${displayName} to Lite Wallet Mode`;
|
|
||||||
message.textContent = 'Write down this key before switching. It will only be shown ONCE.';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [infoResponse, seedResponse] = await Promise.all([
|
|
||||||
fetch('/json/modeswitchinfo', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ coin: coinName, direction: 'lite' })
|
|
||||||
}),
|
|
||||||
fetch('/json/getcoinseed', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ coin: coinName })
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
const info = await infoResponse.json();
|
|
||||||
const data = await seedResponse.json();
|
|
||||||
|
|
||||||
let transferSection = '';
|
|
||||||
if (info.require_transfer && info.legacy_balance_sats > 0) {
|
|
||||||
transferSection = `
|
|
||||||
<div class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded-lg p-3 mb-3">
|
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white mb-2">Funds Transfer Required</p>
|
|
||||||
<p class="text-xs text-gray-700 dark:text-gray-200 mb-2">
|
|
||||||
<strong>${info.legacy_balance} ${info.coin}</strong> on non-derivable addresses will be automatically transferred to a BIP84 address.
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-300 mb-2">
|
|
||||||
Est. fee: ${info.estimated_fee} ${info.coin}
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-gray-700 dark:text-gray-200">
|
|
||||||
This ensures your funds are recoverable using the extended key backup in external Electrum wallets.
|
|
||||||
</p>
|
|
||||||
<input type="hidden" name="transfer_choice" value="auto">
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else if (info.legacy_balance_sats > 0 && !info.show_transfer_option) {
|
|
||||||
transferSection = `
|
|
||||||
<p class="text-gray-700 dark:text-gray-300 text-xs mb-3">
|
|
||||||
Some funds on non-derivable addresses (${info.legacy_balance} ${info.coin}) - too low to transfer.
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.account_key) {
|
|
||||||
details.innerHTML = `
|
|
||||||
<p class="mb-2 text-red-600 dark:text-red-300 font-semibold">
|
|
||||||
IMPORTANT: Write down this key NOW. It will not be shown again.
|
|
||||||
</p>
|
|
||||||
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Extended Private Key (for external wallet import):</strong></p>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded p-2 mb-3">
|
|
||||||
<code id="extendedKeyDisplay" class="text-xs break-all font-mono text-gray-900 dark:text-gray-100">${'*'.repeat(Math.min(data.account_key.length, 80))}</code>
|
|
||||||
<code id="extendedKeyActual" class="text-xs break-all select-all font-mono text-gray-900 dark:text-gray-100 hidden">${data.account_key}</code>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<button type="button" id="toggleKeyVisibility" class="px-3 py-1 text-xs bg-blue-500 hover:bg-blue-600 text-white rounded">
|
|
||||||
Show Key
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="text-xs text-gray-600 dark:text-gray-300 mb-3 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded p-2">
|
|
||||||
<p class="font-medium mb-1 text-gray-800 dark:text-gray-100">To import in Electrum wallet:</p>
|
|
||||||
<ol class="list-decimal list-inside space-y-0.5">
|
|
||||||
<li>Open Electrum → File → New/Restore</li>
|
|
||||||
<li>Choose "Standard wallet" → "Use a master key"</li>
|
|
||||||
<li>Paste this key (starts with zprv... or yprv...)</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
${transferSection}
|
|
||||||
<div class="border-t border-gray-300 dark:border-gray-500 pt-3">
|
|
||||||
<label class="flex items-center cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-500 rounded p-1 -m-1">
|
|
||||||
<input type="checkbox" id="walletModeKeyConfirmCheckbox" class="mr-2 h-4 w-4 text-blue-600 rounded border-gray-300 dark:border-gray-500 focus:ring-blue-500 dark:bg-gray-700">
|
|
||||||
<span class="text-sm font-medium text-gray-800 dark:text-gray-100">I have written down this key</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const toggleBtn = document.getElementById('toggleKeyVisibility');
|
|
||||||
const keyDisplay = document.getElementById('extendedKeyDisplay');
|
|
||||||
const keyActual = document.getElementById('extendedKeyActual');
|
|
||||||
if (toggleBtn && keyDisplay && keyActual) {
|
|
||||||
toggleBtn.addEventListener('click', () => {
|
|
||||||
if (keyDisplay.classList.contains('hidden')) {
|
|
||||||
keyDisplay.classList.remove('hidden');
|
|
||||||
keyActual.classList.add('hidden');
|
|
||||||
toggleBtn.textContent = 'Show Key';
|
|
||||||
} else {
|
|
||||||
keyDisplay.classList.add('hidden');
|
|
||||||
keyActual.classList.remove('hidden');
|
|
||||||
toggleBtn.textContent = 'Hide Key';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkbox = document.getElementById('walletModeKeyConfirmCheckbox');
|
|
||||||
if (checkbox) {
|
|
||||||
checkbox.addEventListener('change', () => this.updateConfirmButtonState());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
details.innerHTML = `
|
|
||||||
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Before switching:</strong></p>
|
|
||||||
<ul class="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-200">
|
|
||||||
<li>Active swaps must be completed first</li>
|
|
||||||
<li>Wait for any pending transactions to confirm</li>
|
|
||||||
</ul>
|
|
||||||
${transferSection}
|
|
||||||
<p class="mt-3 text-green-700 dark:text-green-300">
|
|
||||||
<strong>Note:</strong> Your balance will remain accessible - same seed means same funds in both modes.
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
if (confirmBtn) {
|
|
||||||
confirmBtn.disabled = false;
|
|
||||||
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch coin seed:', error);
|
|
||||||
details.innerHTML = `
|
|
||||||
<p class="text-red-600 dark:text-red-300 mb-2">Failed to retrieve extended key. Please try again.</p>
|
|
||||||
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Before switching:</strong></p>
|
|
||||||
<ul class="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-200">
|
|
||||||
<li>Active swaps must be completed first</li>
|
|
||||||
<li>Wait for any pending transactions to confirm</li>
|
|
||||||
</ul>
|
|
||||||
`;
|
|
||||||
if (confirmBtn) {
|
|
||||||
confirmBtn.disabled = false;
|
|
||||||
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
title.textContent = `Switch ${displayName} to Full Node Mode`;
|
|
||||||
message.textContent = 'Please confirm you want to switch to full node mode.';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/json/modeswitchinfo', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ coin: coinName, direction: 'rpc' })
|
|
||||||
});
|
|
||||||
const info = await response.json();
|
|
||||||
|
|
||||||
let transferSection = '';
|
|
||||||
if (info.error) {
|
|
||||||
transferSection = `<p class="text-yellow-700 dark:text-yellow-300 text-sm">${info.error}</p>`;
|
|
||||||
} else if (info.balance_sats === 0) {
|
|
||||||
transferSection = `<p class="text-gray-600 dark:text-gray-300 text-sm">No funds to transfer.</p>`;
|
|
||||||
} else if (!info.can_transfer) {
|
|
||||||
transferSection = `
|
|
||||||
<p class="text-yellow-700 dark:text-yellow-300 text-sm">
|
|
||||||
Balance (${info.balance} ${info.coin}) is too low to transfer - fee would exceed funds.
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
transferSection = `
|
|
||||||
<div class="bg-gray-200 dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded-lg p-3 mb-3">
|
|
||||||
<p class="text-sm font-medium text-gray-900 dark:text-white mb-2">Fund Transfer Options</p>
|
|
||||||
<p class="text-xs text-gray-700 dark:text-gray-300 mb-3">
|
|
||||||
Balance: ${info.balance} ${info.coin} | Est. fee: ${info.estimated_fee} ${info.coin}
|
|
||||||
</p>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<label class="flex items-start cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 rounded p-1.5 -m-1">
|
|
||||||
<input type="radio" name="transfer_choice" value="auto" checked class="mt-0.5 mr-2 h-4 w-4 text-blue-600 border-gray-400 dark:border-gray-400 focus:ring-blue-500 bg-white dark:bg-gray-500">
|
|
||||||
<div>
|
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">Auto-transfer funds to RPC wallet</span>
|
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-300">Recommended. Ensures all funds visible in full node wallet.</p>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<label class="flex items-start cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 rounded p-1.5 -m-1">
|
|
||||||
<input type="radio" name="transfer_choice" value="manual" class="mt-0.5 mr-2 h-4 w-4 text-blue-600 border-gray-400 dark:border-gray-400 focus:ring-blue-500 bg-white dark:bg-gray-500">
|
|
||||||
<div>
|
|
||||||
<span class="text-sm font-medium text-gray-900 dark:text-white">Keep funds on current addresses</span>
|
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-300">Transfer manually later if needed.</p>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-gray-600 dark:text-gray-400 mt-3">
|
|
||||||
If you skip transfer, you will need to manually send funds from lite wallet addresses to your RPC wallet.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
details.innerHTML = `
|
|
||||||
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Switching to full node mode:</strong></p>
|
|
||||||
<ul class="list-disc list-inside space-y-1 mb-3 text-gray-700 dark:text-gray-200">
|
|
||||||
<li>Requires synced ${displayName} blockchain</li>
|
|
||||||
<li>Your wallet addresses will be synced</li>
|
|
||||||
<li>Active swaps must be completed first</li>
|
|
||||||
<li>Restart required after switch</li>
|
|
||||||
</ul>
|
|
||||||
${transferSection}
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (confirmBtn) {
|
|
||||||
confirmBtn.disabled = false;
|
|
||||||
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch mode switch info:', error);
|
|
||||||
details.innerHTML = `
|
|
||||||
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Switching to full node mode:</strong></p>
|
|
||||||
<ul class="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-200">
|
|
||||||
<li>Requires synced ${displayName} blockchain</li>
|
|
||||||
<li>Your wallet addresses will be synced</li>
|
|
||||||
<li>Active swaps must be completed first</li>
|
|
||||||
<li>Restart required after switch</li>
|
|
||||||
</ul>
|
|
||||||
`;
|
|
||||||
if (confirmBtn) {
|
|
||||||
confirmBtn.disabled = false;
|
|
||||||
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hideWalletModeModal: function() {
|
|
||||||
const modal = document.getElementById('walletModeModal');
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.add('hidden');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showMigrationModal: function(coinName, direction) {
|
|
||||||
const modal = document.getElementById('migrationModal');
|
|
||||||
const title = document.getElementById('migrationTitle');
|
|
||||||
const message = document.getElementById('migrationMessage');
|
|
||||||
|
|
||||||
if (modal && title && message) {
|
|
||||||
if (direction === 'lite') {
|
|
||||||
title.textContent = `Migrating ${coinName} to Lite Wallet`;
|
|
||||||
message.textContent = 'Checking wallet balance and migrating addresses. Please wait...';
|
|
||||||
} else {
|
|
||||||
title.textContent = `Switching ${coinName} to Full Node`;
|
|
||||||
message.textContent = 'Syncing wallet indices. Please wait...';
|
|
||||||
}
|
|
||||||
modal.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setupConfirmModal: function() {
|
setupConfirmModal: function() {
|
||||||
const confirmYesBtn = document.getElementById('confirmYes');
|
const confirmYesBtn = document.getElementById('confirmYes');
|
||||||
if (confirmYesBtn) {
|
if (confirmYesBtn) {
|
||||||
@@ -715,167 +307,6 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
SettingsPage.setupServerDiscovery = function() {
|
|
||||||
const discoverBtns = document.querySelectorAll('.discover-servers-btn');
|
|
||||||
discoverBtns.forEach(btn => {
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
const coin = btn.dataset.coin;
|
|
||||||
this.discoverServers(coin, btn);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const closeBtns = document.querySelectorAll('.close-discovered-btn');
|
|
||||||
closeBtns.forEach(btn => {
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
const coin = btn.dataset.coin;
|
|
||||||
const panel = document.getElementById(`discovered-servers-${coin}`);
|
|
||||||
if (panel) panel.classList.add('hidden');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
SettingsPage.discoverServers = function(coin, button) {
|
|
||||||
const originalHtml = button.innerHTML;
|
|
||||||
button.innerHTML = `<svg class="w-3.5 h-3.5 mr-1 animate-spin inline-block" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Discovering...`;
|
|
||||||
button.disabled = true;
|
|
||||||
|
|
||||||
const panel = document.getElementById(`discovered-servers-${coin}`);
|
|
||||||
const listContainer = document.getElementById(`discovered-list-${coin}`);
|
|
||||||
|
|
||||||
fetch('/json/electrumdiscover', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ coin: coin, ping: true })
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.error) {
|
|
||||||
listContainer.innerHTML = `<div class="text-sm text-red-500">${data.error}</div>`;
|
|
||||||
} else {
|
|
||||||
let html = '';
|
|
||||||
|
|
||||||
if (data.current_server) {
|
|
||||||
html += `
|
|
||||||
<div class="flex items-center mb-4 p-3 bg-gray-100 dark:bg-gray-600 border border-gray-200 dark:border-gray-500 rounded-lg">
|
|
||||||
<span class="w-2 h-2 bg-green-500 rounded-full mr-3 animate-pulse"></span>
|
|
||||||
<span class="text-sm text-gray-900 dark:text-white">
|
|
||||||
Connected to: <span class="font-mono font-medium">${data.current_server.host}:${data.current_server.port}</span>
|
|
||||||
</span>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.clearnet_servers && data.clearnet_servers.length > 0) {
|
|
||||||
html += `
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="text-sm font-semibold text-gray-900 dark:text-white mb-2 pb-2 border-b border-gray-200 dark:border-gray-600">
|
|
||||||
Clearnet
|
|
||||||
</div>
|
|
||||||
<div class="space-y-1">`;
|
|
||||||
data.clearnet_servers.forEach(srv => {
|
|
||||||
const statusClass = srv.online ? 'text-green-600 dark:text-green-400' : 'text-gray-400 dark:text-gray-500';
|
|
||||||
const statusText = srv.online ? (srv.latency_ms ? srv.latency_ms.toFixed(0) + 'ms' : 'online') : 'offline';
|
|
||||||
const statusDot = srv.online ? 'bg-green-500' : 'bg-gray-400';
|
|
||||||
html += `
|
|
||||||
<div class="flex items-center justify-between py-2 px-3 hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg cursor-pointer add-server-btn transition-colors border border-transparent hover:border-blue-500"
|
|
||||||
data-coin="${coin}" data-host="${srv.host}" data-port="${srv.port}" data-type="clearnet">
|
|
||||||
<div class="flex items-center flex-1 min-w-0">
|
|
||||||
<svg class="w-4 h-4 mr-2 text-blue-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
||||||
</svg>
|
|
||||||
<span class="font-mono text-sm text-gray-900 dark:text-white truncate">${srv.host}:${srv.port}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center ml-3">
|
|
||||||
<span class="w-2 h-2 ${statusDot} rounded-full mr-2"></span>
|
|
||||||
<span class="text-xs ${statusClass}">${statusText}</span>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
});
|
|
||||||
html += `
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.onion_servers && data.onion_servers.length > 0) {
|
|
||||||
html += `
|
|
||||||
<div class="mb-4">
|
|
||||||
<div class="text-sm font-semibold text-gray-900 dark:text-white mb-2 pb-2 border-b border-gray-200 dark:border-gray-600">
|
|
||||||
TOR (.onion)
|
|
||||||
</div>
|
|
||||||
<div class="space-y-1">`;
|
|
||||||
data.onion_servers.forEach(srv => {
|
|
||||||
const statusClass = srv.online ? 'text-green-600 dark:text-green-400' : 'text-gray-400 dark:text-gray-500';
|
|
||||||
const statusText = srv.online ? (srv.latency_ms ? srv.latency_ms.toFixed(0) + 'ms' : 'online') : 'offline';
|
|
||||||
const statusDot = srv.online ? 'bg-green-500' : 'bg-gray-400';
|
|
||||||
html += `
|
|
||||||
<div class="flex items-center justify-between py-2 px-3 hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg cursor-pointer add-server-btn transition-colors border border-transparent hover:border-blue-500"
|
|
||||||
data-coin="${coin}" data-host="${srv.host}" data-port="${srv.port}" data-type="onion">
|
|
||||||
<div class="flex items-center flex-1 min-w-0">
|
|
||||||
<svg class="w-4 h-4 mr-2 text-blue-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
||||||
</svg>
|
|
||||||
<span class="font-mono text-sm text-gray-900 dark:text-white truncate" title="${srv.host}">${srv.host.substring(0, 24)}...:${srv.port}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center ml-3">
|
|
||||||
<span class="w-2 h-2 ${statusDot} rounded-full mr-2"></span>
|
|
||||||
<span class="text-xs ${statusClass}">${statusText}</span>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
});
|
|
||||||
html += `
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.clearnet_servers?.length && !data.onion_servers?.length) {
|
|
||||||
const serverName = data.current_server ? `${data.current_server.host}:${data.current_server.port}` : 'The connected server';
|
|
||||||
html = `<div class="text-sm text-gray-500 dark:text-gray-400 py-4 text-center">No servers discovered. <span class="font-mono">${serverName}</span> does not return peer lists.</div>`;
|
|
||||||
} else {
|
|
||||||
html += `<div class="text-xs text-gray-500 dark:text-gray-400 pt-3 border-t border-gray-200 dark:border-gray-600">Click a server to add it to your list</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
listContainer.innerHTML = html;
|
|
||||||
|
|
||||||
listContainer.querySelectorAll('.add-server-btn').forEach(item => {
|
|
||||||
item.addEventListener('click', () => {
|
|
||||||
const host = item.dataset.host;
|
|
||||||
const port = item.dataset.port;
|
|
||||||
const type = item.dataset.type;
|
|
||||||
const coinName = item.dataset.coin;
|
|
||||||
|
|
||||||
const textareaId = type === 'onion' ?
|
|
||||||
`electrum_onion_${coinName}` : `electrum_clearnet_${coinName}`;
|
|
||||||
const textarea = document.getElementById(textareaId);
|
|
||||||
|
|
||||||
if (textarea) {
|
|
||||||
const serverLine = `${host}:${port}`;
|
|
||||||
const currentValue = textarea.value.trim();
|
|
||||||
|
|
||||||
if (currentValue.split('\n').some(line => line.trim() === serverLine)) {
|
|
||||||
item.classList.add('bg-yellow-100', 'dark:bg-yellow-800/30');
|
|
||||||
setTimeout(() => item.classList.remove('bg-yellow-100', 'dark:bg-yellow-800/30'), 500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.value = currentValue ? currentValue + '\n' + serverLine : serverLine;
|
|
||||||
item.classList.add('bg-green-100', 'dark:bg-green-800/30');
|
|
||||||
setTimeout(() => item.classList.remove('bg-green-100', 'dark:bg-green-800/30'), 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
panel.classList.remove('hidden');
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
listContainer.innerHTML = `<div class="text-xs text-red-500">Failed to discover servers: ${err.message}</div>`;
|
|
||||||
panel.classList.remove('hidden');
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
button.innerHTML = originalHtml;
|
|
||||||
button.disabled = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
SettingsPage.cleanup = function() {
|
SettingsPage.cleanup = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
this.setupWithdrawalConfirmation();
|
this.setupWithdrawalConfirmation();
|
||||||
this.setupTransactionDisplay();
|
this.setupTransactionDisplay();
|
||||||
this.setupWebSocketUpdates();
|
this.setupWebSocketUpdates();
|
||||||
this.setupTransactionPagination();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setupAddressCopy: function() {
|
setupAddressCopy: function() {
|
||||||
@@ -341,292 +340,13 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleBalanceUpdate: function(balanceData) {
|
handleBalanceUpdate: function(balanceData) {
|
||||||
if (!balanceData || !Array.isArray(balanceData)) return;
|
|
||||||
|
|
||||||
const coinId = this.currentCoinId;
|
console.log('Balance updated:', balanceData);
|
||||||
if (!coinId) return;
|
|
||||||
|
|
||||||
const matchingCoins = balanceData.filter(coin =>
|
|
||||||
coin.ticker && coin.ticker.toLowerCase() === coinId.toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
matchingCoins.forEach(coinData => {
|
|
||||||
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname][data-balance-type]');
|
|
||||||
balanceElements.forEach(element => {
|
|
||||||
const elementCoinName = element.getAttribute('data-coinname');
|
|
||||||
if (elementCoinName === coinData.name) {
|
|
||||||
const balanceType = element.getAttribute('data-balance-type');
|
|
||||||
const value = coinData[balanceType];
|
|
||||||
if (value !== undefined) {
|
|
||||||
const ticker = coinData.ticker || coinId.toUpperCase();
|
|
||||||
const newBalance = balanceType === 'est_fee' ? value : `${value} ${ticker}`;
|
|
||||||
if (element.textContent !== newBalance) {
|
|
||||||
element.textContent = newBalance;
|
|
||||||
console.log(`Updated ${balanceType}: ${coinData.name} -> ${newBalance}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updatePendingForCoin(coinData);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.refreshTransactions();
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePendingForCoin: function(coinData) {
|
|
||||||
const pendingAmount = parseFloat(coinData.pending || '0');
|
|
||||||
|
|
||||||
|
|
||||||
const pendingElements = document.querySelectorAll('.inline-block.py-1.px-2.rounded-full.bg-green-100');
|
|
||||||
|
|
||||||
pendingElements.forEach(el => {
|
|
||||||
const text = el.textContent || '';
|
|
||||||
|
|
||||||
if (text.includes('Pending:') && text.includes(coinData.ticker)) {
|
|
||||||
if (pendingAmount > 0) {
|
|
||||||
el.textContent = `Pending: +${coinData.pending} ${coinData.ticker}`;
|
|
||||||
el.style.display = '';
|
|
||||||
} else {
|
|
||||||
el.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
refreshTransactions: function() {
|
|
||||||
const txTable = document.querySelector('#transaction-history-section tbody');
|
|
||||||
if (txTable) {
|
|
||||||
const pathParts = window.location.pathname.split('/');
|
|
||||||
const ticker = pathParts[pathParts.length - 1];
|
|
||||||
|
|
||||||
fetch(`/json/wallettransactions/${ticker}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ page_no: 1 })
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.transactions && data.transactions.length > 0) {
|
|
||||||
const currentPageSpan = document.getElementById('currentPageTx');
|
|
||||||
const totalPagesSpan = document.getElementById('totalPagesTx');
|
|
||||||
if (currentPageSpan) currentPageSpan.textContent = data.page_no;
|
|
||||||
if (totalPagesSpan) totalPagesSpan.textContent = data.total_pages;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error refreshing transactions:', error));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSwapEvent: function(eventData) {
|
handleSwapEvent: function(eventData) {
|
||||||
if (window.BalanceUpdatesManager) {
|
|
||||||
window.BalanceUpdatesManager.fetchBalanceData()
|
|
||||||
.then(data => this.handleBalanceUpdate(data))
|
|
||||||
.catch(error => console.error('Error updating balance after swap:', error));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setupTransactionPagination: function() {
|
console.log('Swap event:', eventData);
|
||||||
const txContainer = document.getElementById('tx-container');
|
|
||||||
if (!txContainer) return;
|
|
||||||
|
|
||||||
const pathParts = window.location.pathname.split('/');
|
|
||||||
const ticker = pathParts[pathParts.length - 1];
|
|
||||||
|
|
||||||
let currentPage = 1;
|
|
||||||
let totalPages = 1;
|
|
||||||
let isLoading = false;
|
|
||||||
|
|
||||||
const prevBtn = document.getElementById('prevPageTx');
|
|
||||||
const nextBtn = document.getElementById('nextPageTx');
|
|
||||||
const currentPageSpan = document.getElementById('currentPageTx');
|
|
||||||
const totalPagesSpan = document.getElementById('totalPagesTx');
|
|
||||||
const paginationControls = document.getElementById('tx-pagination-section');
|
|
||||||
|
|
||||||
const copyToClipboard = (text, button) => {
|
|
||||||
const showSuccess = () => {
|
|
||||||
const originalHTML = button.innerHTML;
|
|
||||||
button.innerHTML = `<svg class="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
||||||
</svg>`;
|
|
||||||
setTimeout(() => {
|
|
||||||
button.innerHTML = originalHTML;
|
|
||||||
}, 1500);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
||||||
navigator.clipboard.writeText(text).then(showSuccess).catch(err => {
|
|
||||||
console.error('Clipboard API failed:', err);
|
|
||||||
fallbackCopy(text, showSuccess);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
fallbackCopy(text, showSuccess);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fallbackCopy = (text, onSuccess) => {
|
|
||||||
const textArea = document.createElement('textarea');
|
|
||||||
textArea.value = text;
|
|
||||||
textArea.style.position = 'fixed';
|
|
||||||
textArea.style.left = '-999999px';
|
|
||||||
textArea.style.top = '-999999px';
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.focus();
|
|
||||||
textArea.select();
|
|
||||||
try {
|
|
||||||
document.execCommand('copy');
|
|
||||||
onSuccess();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Fallback copy failed:', err);
|
|
||||||
}
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadTransactions = async (page) => {
|
|
||||||
if (isLoading) return;
|
|
||||||
isLoading = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/json/wallettransactions/${ticker}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ page_no: page })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (data.error) {
|
|
||||||
console.error('Error loading transactions:', data.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPage = data.page_no;
|
|
||||||
totalPages = data.total_pages;
|
|
||||||
|
|
||||||
currentPageSpan.textContent = currentPage;
|
|
||||||
totalPagesSpan.textContent = totalPages;
|
|
||||||
|
|
||||||
txContainer.innerHTML = '';
|
|
||||||
|
|
||||||
if (data.transactions && data.transactions.length > 0) {
|
|
||||||
data.transactions.forEach(tx => {
|
|
||||||
const card = document.createElement('div');
|
|
||||||
card.className = 'bg-white dark:bg-gray-600 rounded-lg border border-gray-200 dark:border-gray-500 p-4 hover:shadow-md transition-shadow';
|
|
||||||
|
|
||||||
let typeClass = 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300';
|
|
||||||
let amountClass = 'text-gray-700 dark:text-gray-200';
|
|
||||||
let typeIcon = '';
|
|
||||||
let amountPrefix = '';
|
|
||||||
if (tx.type === 'Incoming') {
|
|
||||||
typeClass = 'bg-green-100 text-green-600 dark:bg-green-900 dark:text-green-300';
|
|
||||||
amountClass = 'text-green-600 dark:text-green-400';
|
|
||||||
typeIcon = '↓';
|
|
||||||
amountPrefix = '+';
|
|
||||||
} else if (tx.type === 'Outgoing') {
|
|
||||||
typeClass = 'bg-red-100 text-red-600 dark:bg-red-900 dark:text-red-300';
|
|
||||||
amountClass = 'text-red-600 dark:text-red-400';
|
|
||||||
typeIcon = '↑';
|
|
||||||
amountPrefix = '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
let confirmClass = 'text-gray-600 dark:text-gray-300';
|
|
||||||
if (tx.confirmations === 0) {
|
|
||||||
confirmClass = 'text-yellow-600 dark:text-yellow-400 font-medium';
|
|
||||||
} else if (tx.confirmations >= 1 && tx.confirmations <= 5) {
|
|
||||||
confirmClass = 'text-blue-600 dark:text-blue-400';
|
|
||||||
} else if (tx.confirmations >= 6) {
|
|
||||||
confirmClass = 'text-green-600 dark:text-green-400';
|
|
||||||
}
|
|
||||||
|
|
||||||
card.innerHTML = `
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2 mb-3">
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<span class="inline-flex items-center gap-1 py-1 px-2 rounded-full text-xs font-semibold ${typeClass}">
|
|
||||||
${typeIcon} ${tx.type}
|
|
||||||
</span>
|
|
||||||
<span class="font-semibold ${amountClass}">
|
|
||||||
${amountPrefix}${tx.amount} ${ticker.toUpperCase()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-4 text-sm">
|
|
||||||
<span class="${confirmClass}">${tx.confirmations} Confirmations</span>
|
|
||||||
<span class="text-gray-500 dark:text-gray-400">${tx.timestamp}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${tx.address ? `
|
|
||||||
<div class="flex items-center gap-2 mb-2">
|
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400 w-16 flex-shrink-0">Address:</span>
|
|
||||||
<span class="font-mono text-xs text-gray-700 dark:text-gray-200 break-all flex-1">${tx.address}</span>
|
|
||||||
<button class="copy-address-btn p-1.5 hover:bg-gray-100 dark:hover:bg-gray-500 rounded flex-shrink-0 focus:outline-none focus:ring-0" title="Copy Address">
|
|
||||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400 w-16 flex-shrink-0">Txid:</span>
|
|
||||||
<span class="font-mono text-xs text-gray-700 dark:text-gray-200 break-all flex-1">${tx.txid}</span>
|
|
||||||
<button class="copy-txid-btn p-1.5 hover:bg-gray-100 dark:hover:bg-gray-500 rounded flex-shrink-0 focus:outline-none focus:ring-0" title="Copy Transaction ID">
|
|
||||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const copyAddressBtn = card.querySelector('.copy-address-btn');
|
|
||||||
if (copyAddressBtn) {
|
|
||||||
copyAddressBtn.addEventListener('click', () => copyToClipboard(tx.address, copyAddressBtn));
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyTxidBtn = card.querySelector('.copy-txid-btn');
|
|
||||||
if (copyTxidBtn) {
|
|
||||||
copyTxidBtn.addEventListener('click', () => copyToClipboard(tx.txid, copyTxidBtn));
|
|
||||||
}
|
|
||||||
|
|
||||||
txContainer.appendChild(card);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (totalPages > 1 && paginationControls) {
|
|
||||||
paginationControls.style.display = 'block';
|
|
||||||
} else if (paginationControls) {
|
|
||||||
paginationControls.style.display = 'none';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
txContainer.innerHTML = '<div class="text-center py-8 text-gray-500 dark:text-gray-400">No transactions found</div>';
|
|
||||||
if (paginationControls) paginationControls.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
prevBtn.style.display = currentPage > 1 ? 'inline-flex' : 'none';
|
|
||||||
nextBtn.style.display = currentPage < totalPages ? 'inline-flex' : 'none';
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching transactions:', error);
|
|
||||||
} finally {
|
|
||||||
isLoading = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (prevBtn) {
|
|
||||||
prevBtn.addEventListener('click', () => {
|
|
||||||
if (currentPage > 1) {
|
|
||||||
loadTransactions(currentPage - 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextBtn) {
|
|
||||||
nextBtn.addEventListener('click', () => {
|
|
||||||
if (currentPage < totalPages) {
|
|
||||||
loadTransactions(currentPage + 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadTransactions(1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -75,28 +75,9 @@
|
|||||||
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);
|
||||||
@@ -105,72 +86,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coinData.scan_status || coinData.electrum_synced !== undefined) {
|
|
||||||
this.updateScanStatus(coinData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coinData.version) {
|
|
||||||
const versionEl = document.querySelector(`.electrum-version[data-coin="${coinData.name}"]`);
|
|
||||||
if (versionEl && versionEl.textContent !== coinData.version) {
|
|
||||||
versionEl.textContent = coinData.version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (coinData.electrum_server) {
|
|
||||||
const serverEl = document.querySelector(`.electrum-server[data-coin="${coinData.name}"]`);
|
|
||||||
if (serverEl && serverEl.textContent !== coinData.electrum_server) {
|
|
||||||
serverEl.textContent = coinData.electrum_server;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateScanStatus: function(coinData) {
|
|
||||||
const scanStatusEl = document.querySelector(`.scan-status[data-coin="${coinData.name}"]`);
|
|
||||||
if (!scanStatusEl) return;
|
|
||||||
|
|
||||||
const status = coinData.scan_status;
|
|
||||||
if (status && status.in_progress) {
|
|
||||||
scanStatusEl.innerHTML = `
|
|
||||||
<div class="flex items-center justify-between text-xs">
|
|
||||||
<span class="text-blue-600 dark:text-blue-300">
|
|
||||||
<svg class="inline-block w-3 h-3 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
Scanning ${status.status}
|
|
||||||
</span>
|
|
||||||
<span class="text-blue-500 dark:text-blue-200 font-medium">${status.progress}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full bg-blue-200 dark:bg-gray-700 rounded-full h-1 mt-1">
|
|
||||||
<div class="bg-blue-600 dark:bg-blue-400 h-1 rounded-full transition-all" style="width: ${status.progress}%"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else if (coinData.electrum_synced) {
|
|
||||||
const height = coinData.electrum_height || '';
|
|
||||||
scanStatusEl.innerHTML = `
|
|
||||||
<div class="bg-green-50 dark:bg-gray-500 p-2 rounded">
|
|
||||||
<div class="flex items-center text-xs text-green-600 dark:text-green-400">
|
|
||||||
Electrum Wallet Synced (${height})
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else if (coinData.electrum_synced === false) {
|
|
||||||
scanStatusEl.innerHTML = `
|
|
||||||
<div class="bg-yellow-50 dark:bg-gray-500 p-2 rounded">
|
|
||||||
<div class="flex items-center text-xs text-yellow-600 dark:text-yellow-400">
|
|
||||||
Waiting for Electrum Server...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else {
|
|
||||||
scanStatusEl.innerHTML = `
|
|
||||||
<div class="bg-green-50 dark:bg-gray-500 p-2 rounded">
|
|
||||||
<div class="flex items-center text-xs text-green-600 dark:text-green-400">
|
|
||||||
Electrum Wallet Synced
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSpecificBalance: function(coinName, labelText, balance, ticker, isPending = false) {
|
updateSpecificBalance: function(coinName, labelText, balance, ticker, isPending = false) {
|
||||||
@@ -187,13 +102,12 @@
|
|||||||
const currentLabel = labelElement.textContent.trim();
|
const currentLabel = labelElement.textContent.trim();
|
||||||
|
|
||||||
if (currentLabel === labelText) {
|
if (currentLabel === labelText) {
|
||||||
const cleanBalance = balance.toString().replace(/^\+/, '');
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
|
const cleanBalance = balance.toString().replace(/^\+/, '');
|
||||||
element.textContent = `+${cleanBalance} ${ticker}`;
|
element.textContent = `+${cleanBalance} ${ticker}`;
|
||||||
} else {
|
} else {
|
||||||
element.textContent = `${balance} ${ticker}`;
|
element.textContent = `${balance} ${ticker}`;
|
||||||
}
|
}
|
||||||
element.setAttribute('data-original-value', `${cleanBalance} ${ticker}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +139,6 @@
|
|||||||
if (pendingSpan) {
|
if (pendingSpan) {
|
||||||
const cleanPending = coinData.pending.toString().replace(/^\+/, '');
|
const cleanPending = coinData.pending.toString().replace(/^\+/, '');
|
||||||
pendingSpan.textContent = `+${cleanPending} ${coinData.ticker || coinData.name}`;
|
pendingSpan.textContent = `+${cleanPending} ${coinData.ticker || coinData.name}`;
|
||||||
pendingSpan.setAttribute('data-original-value', `+${cleanPending} ${coinData.ticker || coinData.name}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let initialUSD = '$0.00';
|
let initialUSD = '$0.00';
|
||||||
@@ -305,7 +218,7 @@
|
|||||||
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
||||||
for (const element of balanceElements) {
|
for (const element of balanceElements) {
|
||||||
if (element.getAttribute('data-coinname') === coinName) {
|
if (element.getAttribute('data-coinname') === coinName) {
|
||||||
return element.closest('.bg-gray-50, .dark\\:bg-gray-500');
|
return element.closest('.bg-white, .dark\\:bg-gray-500');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -417,7 +330,6 @@
|
|||||||
if (pendingSpan) {
|
if (pendingSpan) {
|
||||||
const cleanPending = pendingAmount.toString().replace(/^\+/, '');
|
const cleanPending = pendingAmount.toString().replace(/^\+/, '');
|
||||||
pendingSpan.textContent = `+${cleanPending} ${ticker}`;
|
pendingSpan.textContent = `+${cleanPending} ${ticker}`;
|
||||||
pendingSpan.setAttribute('data-original-value', `+${cleanPending} ${ticker}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -525,14 +525,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if data.can_abandon == true and not edit_bid %}
|
{% if data.can_abandon == true and not edit_bid %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="abandon_bid" type="submit" value="Abandon Bid" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Abandon Bid</button>
|
<button name="abandon_bid" type="submit" value="Abandon Bid" data-confirm class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Abandon Bid</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if data.was_received and not edit_bid and data.can_accept_bid %}
|
{% if data.was_received and not edit_bid and data.can_accept_bid %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="accept_bid" value="Accept Bid" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Accept Bid</button>
|
<button name="accept_bid" value="Accept Bid" type="submit" data-confirm data-confirm-action="Accept" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Accept Bid</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -689,16 +689,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
overrideButtonConfirm(acceptBidBtn, 'Accept');
|
overrideButtonConfirm(acceptBidBtn, 'Accept');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/js/pages/bid-page.js"></script>
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
BidPage.init('{{ bid_id }}', {{ data.bid_state_ind }}, {{ data.created_at_timestamp }}, {{ data.state_time_timestamp or 'null' }}, {
|
|
||||||
swapType: 'secret-hash',
|
|
||||||
coinFrom: '{{ data.ticker_from }}',
|
|
||||||
coinTo: '{{ data.ticker_to }}'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -801,14 +801,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if data.can_abandon == true %}
|
{% if data.can_abandon == true %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="abandon_bid" type="submit" value="Abandon Bid" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md focus:ring-0 focus:outline-none">Abandon Bid</button>
|
<button name="abandon_bid" type="submit" value="Abandon Bid" data-confirm class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md focus:ring-0 focus:outline-none">Abandon Bid</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if data.was_received and not edit_bid and data.can_accept_bid %}
|
{% if data.was_received and not edit_bid and data.can_accept_bid %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="accept_bid" value="Accept Bid" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md focus:ring-0 focus:outline-none">Accept Bid</button>
|
<button name="accept_bid" value="Accept Bid" type="submit" data-confirm data-confirm-action="Accept" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md focus:ring-0 focus:outline-none">Accept Bid</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -965,16 +965,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
overrideButtonConfirm(acceptBidBtn, 'Accept');
|
overrideButtonConfirm(acceptBidBtn, 'Accept');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/js/pages/bid-page.js"></script>
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
BidPage.init('{{ bid_id }}', {{ data.bid_state_ind }}, {{ data.created_at_timestamp }}, {{ data.state_time_timestamp or 'null' }}, {
|
|
||||||
swapType: 'adaptor-sig',
|
|
||||||
coinFrom: '{{ data.ticker_from }}',
|
|
||||||
coinTo: '{{ data.ticker_to }}'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
<div class="w-full md:w-1/2 mb-6 md:mb-0">
|
<div class="w-full md:w-1/2 mb-6 md:mb-0">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="text-sm text-gray-90 dark:text-white font-medium">© 2026~ (BSX) BasicSwap</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
<p class="text-sm text-gray-90 dark:text-white font-medium">© 2025~ (BSX) BasicSwap</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||||
<p class="text-sm text-coolGray-400 font-medium">BSX: v{{ version }}</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
<p class="text-sm text-coolGray-400 font-medium">BSX: v{{ version }}</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||||
<p class="text-sm text-coolGray-400 font-medium">GUI: v3.5.0</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
<p class="text-sm text-coolGray-400 font-medium">GUI: v3.3.1</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||||
<p class="mr-2 text-sm font-bold dark:text-white text-gray-90 ">Made with </p>
|
<p class="mr-2 text-sm font-bold dark:text-white text-gray-90 ">Made with </p>
|
||||||
{{ love_svg | safe }}
|
{{ love_svg | safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -224,7 +224,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<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">Chain A local fee rate</td>
|
<td class="py-3 px-6 bold">Chain A local fee rate</td>
|
||||||
<td class="py-3 px-6">{{ data.a_fee_rate_verify }} (Fee source: {{ data.a_fee_rate_verify_src }}{% if data.a_fee_warn == "high" %} WARNING - HIGH {% elif data.a_fee_warn == "low" %} WARNING - LOW {% elif data.a_fee_warn is defined %} WARNING {% endif %})</td>
|
<td class="py-3 px-6">{{ data.a_fee_rate_verify }} (Fee source: {{ data.a_fee_rate_verify_src }}{% if data.a_fee_warn == true %} WARNING {% endif %})</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
@@ -419,22 +419,11 @@
|
|||||||
name="bid_amount_send"
|
name="bid_amount_send"
|
||||||
value=""
|
value=""
|
||||||
max="{{ data.amt_to }}"
|
max="{{ data.amt_to }}"
|
||||||
haveamount="{{ data.coin_to_balance }}"
|
|
||||||
exp="{{ data.coin_to_exp }}"
|
|
||||||
onchange="validateMaxAmount(this, parseFloat('{{ data.amt_to }}')); updateBidParams('sending');">
|
onchange="validateMaxAmount(this, parseFloat('{{ data.amt_to }}')); updateBidParams('sending');">
|
||||||
<div class="absolute inset-y-0 right-3 flex items-center pointer-events-none text-gray-400 dark:text-gray-300 text-sm">
|
<div class="absolute inset-y-0 right-3 flex items-center pointer-events-none text-gray-400 dark:text-gray-300 text-sm">
|
||||||
max {{ data.amt_to }} ({{ data.tla_to }})
|
max {{ data.amt_to }} ({{ data.tla_to }})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex space-x-2">
|
|
||||||
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" data-set-bid-amount="1" data-input-id="bid_amount_send">max</button>
|
|
||||||
{% if data.bid_can_subfee == true %}
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" name="subfee_bid" id="subfee_bid" value="sfb" onchange="updateBidParams('subfee');"/>
|
|
||||||
<span for="subfee_bid">Subfee</span>
|
|
||||||
</label>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<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">
|
||||||
@@ -732,8 +721,6 @@
|
|||||||
<input type="hidden" id="coin_to_name" value="{{ data.coin_to }}">
|
<input type="hidden" id="coin_to_name" value="{{ data.coin_to }}">
|
||||||
<input type="hidden" id="tla_from" value="{{ data.tla_from }}">
|
<input type="hidden" id="tla_from" value="{{ data.tla_from }}">
|
||||||
<input type="hidden" id="tla_to" value="{{ data.tla_to }}">
|
<input type="hidden" id="tla_to" value="{{ data.tla_to }}">
|
||||||
<input type="hidden" id="offer_id" value="{{ offer_id }}">
|
|
||||||
<input type="hidden" name="prefunded_bid_tx" id="prefunded_bid_tx" value="{{ data.prefunded_bid_tx }}">
|
|
||||||
<input type="hidden" name="formid" value="{{ form_id }}">
|
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||||
</form>
|
</form>
|
||||||
<p id="rates_display"></p>
|
<p id="rates_display"></p>
|
||||||
|
|||||||
@@ -111,23 +111,9 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Connection Type</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Connection Type</label>
|
||||||
{% if c.supports_electrum %}
|
|
||||||
<div class="relative">
|
|
||||||
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-1 focus:outline-none" name="connection_type_{{ c.name }}" data-original-value="{{ c.connection_type }}">
|
|
||||||
<option value="rpc" {% if c.connection_type == 'rpc' %} selected{% endif %}>Full Node (RPC)</option>
|
|
||||||
<option value="electrum" {% if c.connection_type == 'electrum' %} selected{% endif %}>Light Wallet (Electrum)</option>
|
|
||||||
</select>
|
|
||||||
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
|
||||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="px-3 py-2 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded-lg text-sm text-gray-900 dark:text-gray-100">
|
<div class="px-3 py-2 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded-lg text-sm text-gray-900 dark:text-gray-100">
|
||||||
{{ c.connection_type }}
|
{{ c.connection_type }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if c.manage_daemon is defined %}
|
{% if c.manage_daemon is defined %}
|
||||||
@@ -158,163 +144,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if c.supports_electrum %}
|
|
||||||
<div class="mb-6">
|
|
||||||
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
|
|
||||||
Mode Information
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4 space-y-4">
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p class="text-xs font-medium text-gray-900 dark:text-white mb-1">Light Wallet Mode (Electrum):</p>
|
|
||||||
<ul class="text-xs text-gray-700 dark:text-gray-200 space-y-0.5 ml-3">
|
|
||||||
<li>• No blockchain download needed - connect via external Electrum servers</li>
|
|
||||||
<li>• Uses BIP84 derivation (native SegWit) - lower fees, modern addresses (bc1q.../ltc1q...)</li>
|
|
||||||
<li>• You receive an extended private key (zprv/...) that can be imported into external wallets</li>
|
|
||||||
<li>• Best for: fresh installs, low storage, quick setup, mobile-friendly</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p class="text-xs font-medium text-gray-900 dark:text-white mb-1">Full Node Mode (RPC):</p>
|
|
||||||
<ul class="text-xs text-gray-700 dark:text-gray-200 space-y-0.5 ml-3">
|
|
||||||
<li>• Maximum privacy - no external servers, your node validates everything</li>
|
|
||||||
<li>• More wallet features: coin control, RBF, CPFP, raw transactions</li>
|
|
||||||
<li>• Supports legacy address types and coin-specific features (e.g. MWEB for LTC)</li>
|
|
||||||
<li>• Best for: existing node users, power users, maximum control</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p class="text-xs font-medium text-gray-900 dark:text-white mb-1">When switching modes:</p>
|
|
||||||
<ul class="text-xs text-gray-700 dark:text-gray-200 space-y-0.5 ml-3">
|
|
||||||
<li>• To Light: Save your BIP84 key shown during switch (for external wallet import)</li>
|
|
||||||
<li>• To Full Node: Funds on light wallet addresses must be transferred (network fee applies)</li>
|
|
||||||
<li>• Both modes share the same seed - switching is safe, just save keys when shown</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pt-2 border-t border-gray-200 dark:border-gray-600">
|
|
||||||
<p class="text-xs text-red-600 dark:text-red-400"><strong>Active Swaps:</strong> Complete all swaps before switching modes.</p>
|
|
||||||
{% if c.name == 'litecoin' %}
|
|
||||||
<p class="text-xs text-gray-700 dark:text-gray-200 mt-1"><strong>MWEB:</strong> Not supported in light wallet mode.</p>
|
|
||||||
{% endif %}
|
|
||||||
<p class="text-xs text-purple-600 dark:text-purple-400 mt-1"><strong>If TOR enabled:</strong> Electrum connections routed through TOR.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if c.supports_electrum %}
|
|
||||||
<div id="electrum-section-{{ c.name }}" class="mb-6 {% if c.connection_type != 'electrum' %}hidden{% endif %}"
|
|
||||||
data-default-clearnet="{{ c.clearnet_servers_text }}"
|
|
||||||
data-default-onion="{{ c.onion_servers_text }}">
|
|
||||||
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
|
|
||||||
Electrum Servers
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div class="mb-6">
|
|
||||||
<h5 class="text-xs font-medium text-gray-600 dark:text-gray-400 mb-3">Clearnet</h5>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
|
||||||
<textarea class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-1 focus:outline-none font-mono" name="electrum_clearnet_{{ c.name }}" id="electrum_clearnet_{{ c.name }}" rows="3" placeholder="electrum.blockstream.info:50002 electrum.emzy.de:50002">{% if c.connection_type == 'electrum' %}{{ c.clearnet_servers_text }}{% endif %}</textarea>
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">One per line. Format: host:port (50002=SSL, 50001=non-SSL)</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4 pt-2">
|
|
||||||
<h5 class="text-xs font-medium text-gray-600 dark:text-gray-400 mb-3">TOR (.onion)</h5>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
|
||||||
<textarea class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-1 focus:outline-none font-mono text-xs" name="electrum_onion_{{ c.name }}" id="electrum_onion_{{ c.name }}" rows="3" placeholder="explorerzyd...onion:110 lksvbmwwi2b...onion:50001">{% if c.connection_type == 'electrum' %}{{ c.onion_servers_text }}{% endif %}</textarea>
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">One per line. Used when TOR is enabled.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Discover Servers Button -->
|
|
||||||
<div class="mb-4 flex justify-end">
|
|
||||||
<button type="button" class="discover-servers-btn inline-flex items-center px-3 py-1.5 text-xs font-medium text-white bg-blue-500 hover:bg-blue-600 rounded-md transition-colors focus:outline-none focus:ring-0" data-coin="{{ c.name }}">
|
|
||||||
Discover {{ c.name }} electrum servers
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Discovered Servers Panel -->
|
|
||||||
<div id="discovered-servers-{{ c.name }}" class="hidden mb-4">
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-xl overflow-hidden">
|
|
||||||
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-600">
|
|
||||||
<h5 class="text-sm font-semibold text-gray-900 dark:text-white">
|
|
||||||
Discovered Servers
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="close-discovered-btn text-gray-400 hover:text-gray-600 dark:text-gray-300 dark:hover:text-white transition-colors" data-coin="{{ c.name }}">
|
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="px-6 py-4">
|
|
||||||
<div id="discovered-list-{{ c.name }}" class="space-y-1 max-h-64 overflow-y-auto">
|
|
||||||
<div class="text-sm text-gray-500 dark:text-gray-400">Click "Discover Servers" to find available servers...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="fund-transfer-section-{{ c.name }}" class="mb-6 hidden">
|
|
||||||
{% if c.lite_wallet_balance %}
|
|
||||||
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
|
|
||||||
Pending Balance
|
|
||||||
</h4>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
|
||||||
<div class="p-3 bg-orange-50 dark:bg-orange-900/30 border border-orange-200 dark:border-orange-700 rounded-lg">
|
|
||||||
<p class="text-sm font-medium text-orange-800 dark:text-orange-200 mb-2">
|
|
||||||
<svg class="inline w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
|
|
||||||
Light Wallet Balance Detected
|
|
||||||
</p>
|
|
||||||
<div class="text-xs text-orange-700 dark:text-orange-300 space-y-1">
|
|
||||||
<p><strong>Confirmed:</strong> {{ "%.8f"|format(c.lite_wallet_balance.confirmed) }} {{ c.display_name }}</p>
|
|
||||||
{% if c.lite_wallet_balance.unconfirmed > 0 %}
|
|
||||||
<p><strong>Unconfirmed:</strong> {{ "%.8f"|format(c.lite_wallet_balance.unconfirmed) }} {{ c.display_name }}</p>
|
|
||||||
{% endif %}
|
|
||||||
<p class="text-xs text-orange-600 dark:text-orange-400 mt-2">
|
|
||||||
{% if c.lite_wallet_balance.is_pending_sweep %}
|
|
||||||
<span class="inline-flex items-center"><svg class="animate-spin h-3 w-3 mr-1" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Sweep pending - waiting for confirmations...</span>
|
|
||||||
{% else %}
|
|
||||||
These funds will be swept to your RPC wallet automatically.
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% if c.lite_wallet_balance.confirmed > 0 %}
|
|
||||||
<div class="mt-3">
|
|
||||||
<button type="submit" name="force_sweep_{{ c.name }}" value="1" class="inline-flex items-center px-3 py-1.5 text-xs font-medium text-white bg-orange-600 hover:bg-orange-700 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 dark:focus:ring-offset-gray-800" onclick="return confirm('Sweep {{ '%.8f'|format(c.lite_wallet_balance.confirmed) }} {{ c.display_name }} to your RPC wallet now? Network fee will apply.');">
|
|
||||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
|
||||||
Force Sweep Now
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if general_settings.debug %}
|
|
||||||
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4 {% if c.lite_wallet_balance %}mt-6{% endif %}">
|
|
||||||
Advanced
|
|
||||||
</h4>
|
|
||||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Address Gap Limit</label>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<input type="number" name="gap_limit_{{ c.name }}" value="{{ c.address_gap_limit }}" min="5" max="100" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-24 p-2.5 focus:ring-1 focus:outline-none" placeholder="50">
|
|
||||||
<span class="ml-2 text-xs text-gray-500 dark:text-gray-400">(5-100)</span>
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Number of consecutive unfunded addresses to scan. Increase if you generated many unused addresses.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if c.name in ('wownero', 'monero') %}
|
{% if c.name in ('wownero', 'monero') %}
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
|
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
|
||||||
@@ -827,53 +656,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="migrationModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
|
|
||||||
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity duration-300 ease-out"></div>
|
|
||||||
<div class="relative z-50 min-h-screen px-4 flex items-center justify-center">
|
|
||||||
<div class="bg-white dark:bg-gray-500 rounded-lg max-w-md w-full p-6 shadow-lg">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="flex justify-center mb-4">
|
|
||||||
<svg class="animate-spin h-12 w-12 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-2" id="migrationTitle">Migrating Wallet</h2>
|
|
||||||
<p class="text-gray-600 dark:text-gray-200" id="migrationMessage">Extracting addresses from wallet. Please wait...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="walletModeModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
|
|
||||||
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity duration-300 ease-out"></div>
|
|
||||||
<div class="relative z-50 min-h-screen px-4 flex items-center justify-center">
|
|
||||||
<div class="bg-white dark:bg-gray-500 rounded-lg max-w-lg w-full p-6 shadow-lg">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="flex justify-center mb-4">
|
|
||||||
<svg class="h-12 w-12 text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4" id="walletModeTitle">Switch Wallet Mode</h2>
|
|
||||||
<p class="text-gray-600 dark:text-gray-200 mb-4" id="walletModeMessage">Are you sure you want to switch wallet modes?</p>
|
|
||||||
<div id="walletModeDetails" class="text-left bg-gray-100 dark:bg-gray-600 rounded-lg p-4 mb-4 text-sm text-gray-700 dark:text-gray-200">
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center gap-4">
|
|
||||||
<button type="button" id="walletModeConfirm"
|
|
||||||
class="px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
|
|
||||||
Switch Mode
|
|
||||||
</button>
|
|
||||||
<button type="button" id="walletModeCancel"
|
|
||||||
class="px-4 py-2.5 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 font-medium text-sm text-gray-800 dark:text-white rounded-md border border-gray-300 dark:border-gray-500 focus:ring-0 focus:outline-none">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/static/js/pages/settings-page.js"></script>
|
<script src="/static/js/pages/settings-page.js"></script>
|
||||||
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="caps-warning" class="hidden mt-2 text-sm text-red-600 dark:text-white flex items-center">
|
<div id="caps-warning" class="hidden mt-2 text-sm text-amber-700 dark:text-amber-300 flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
+18
-316
@@ -35,8 +35,6 @@
|
|||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% if w.havedata %}
|
{% if w.havedata %}
|
||||||
{% if w.error %}
|
{% if w.error %}
|
||||||
<section class="py-4 px-6" id="messages_error" role="alert">
|
<section class="py-4 px-6" id="messages_error" role="alert">
|
||||||
@@ -84,36 +82,6 @@
|
|||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if legacy_funds_info and legacy_funds_info.has_legacy_funds %}
|
|
||||||
<section class="py-4 px-6" id="legacy_funds_warning">
|
|
||||||
<div class="lg:container mx-auto">
|
|
||||||
<div class="p-6 rounded-lg bg-yellow-50 border border-yellow-400 dark:bg-yellow-900/30 dark:border-yellow-700">
|
|
||||||
<div class="flex flex-wrap justify-between items-center -m-2">
|
|
||||||
<div class="flex-1 p-2">
|
|
||||||
<div class="flex flex-wrap -m-1">
|
|
||||||
<div class="w-auto p-1">
|
|
||||||
<svg class="w-6 h-6 text-yellow-600 dark:text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="ml-3 flex-1">
|
|
||||||
<p class="font-semibold text-lg lg:text-sm text-yellow-700 dark:text-yellow-300">Legacy Address Funds</p>
|
|
||||||
<p class="mt-1 text-sm text-yellow-600 dark:text-yellow-400">
|
|
||||||
{{ legacy_funds_info.legacy_balance }} {{ legacy_funds_info.coin }} on legacy addresses won't be visible in external Electrum wallet.
|
|
||||||
To use funds with external wallets, transfer to a new address.
|
|
||||||
</p>
|
|
||||||
<p class="mt-2 text-xs text-yellow-500 dark:text-yellow-500">
|
|
||||||
Use the withdraw function below to send funds to a new <code class="bg-yellow-100 dark:bg-yellow-800/50 px-1 rounded">{{ w.ticker | lower }}1...</code> address.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<form method="post" autocomplete="off">
|
<form method="post" autocomplete="off">
|
||||||
<div class="px-6 py-0 h-full overflow-hidden">
|
<div class="px-6 py-0 h-full overflow-hidden">
|
||||||
@@ -138,21 +106,18 @@
|
|||||||
<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 }}" data-balance-type="balance">{{ w.balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}">{{ 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>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if w.pending_out %}
|
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-yellow-100 text-yellow-600 dark:bg-gray-500 dark:text-yellow-400">Unconfirmed: -{{ w.pending_out }} {{ w.ticker }} </span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if w.cid == '1' %} {# PART #}
|
{% if w.cid == '1' %} {# PART #}
|
||||||
<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 }}" data-balance-type="blind_balance">{{ w.blind_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}">{{ 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 +127,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 }}" data-balance-type="anon_balance">{{ w.anon_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}">{{ 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>
|
||||||
@@ -174,53 +139,15 @@
|
|||||||
<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 }} MWEB"> </span>MWEB 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 }} MWEB"> </span>MWEB Balance: </td>
|
||||||
<td class="py-3 px-6 bold">
|
<td class="py-3 px-6 bold">
|
||||||
{% if is_electrum_mode %}
|
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
||||||
<span class="text-gray-400 dark:text-gray-300">Not available in light mode</span>
|
|
||||||
{% else %}
|
|
||||||
<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>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</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 #}
|
|
||||||
<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="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
|
|
||||||
(<span class="usd-value"></span>)
|
|
||||||
{% 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>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% 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">
|
|
||||||
<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="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
|
|
||||||
(<span class="usd-value"></span>)
|
|
||||||
{% 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>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# / LTC #}
|
{# / LTC #}
|
||||||
{# / FIRO #}
|
|
||||||
{% if w.locked_utxos %}
|
{% if w.locked_utxos %}
|
||||||
<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">Locked Outputs:</td>
|
<td class="py-3 px-6 bold">Locked Outputs:</td>
|
||||||
@@ -236,19 +163,6 @@
|
|||||||
<td class="py-3 px-6 bold">{{ w.name }} Version:</td>
|
<td class="py-3 px-6 bold">{{ w.name }} Version:</td>
|
||||||
<td class="py-3 px-6">{{ w.version }}</td>
|
<td class="py-3 px-6">{{ w.version }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<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">Wallet Mode:</td>
|
|
||||||
<td class="py-3 px-6">
|
|
||||||
{% if w.connection_type == 'electrum' %}
|
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300">Light Wallet (Electrum)</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300">Full Node</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if use_tor %}
|
|
||||||
<span class="inline-block py-1 px-2 ml-2 rounded-full bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300" title="Electrum connections routed through TOR">TOR</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<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">Blockheight:</td>
|
<td class="py-3 px-6 bold">Blockheight:</td>
|
||||||
<td class="py-3 px-6">{{ w.blocks }}
|
<td class="py-3 px-6">{{ w.blocks }}
|
||||||
@@ -264,70 +178,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<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">Synced:</td>
|
<td class="py-3 px-6 bold">Synced:</td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">{{ w.synced }}</td>
|
||||||
{% if is_electrum_mode %}
|
|
||||||
{% if w.electrum_synced %}
|
|
||||||
<span class="text-green-600 dark:text-green-400">Electrum Wallet Synced ({{ w.electrum_height }})</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="text-yellow-600 dark:text-yellow-400">Waiting for Electrum Server...</span>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{{ w.synced }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% if is_electrum_mode and w.electrum_server %}
|
|
||||||
<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">Server:</td>
|
|
||||||
<td class="py-3 px-6 font-mono text-sm">
|
|
||||||
{{ w.electrum_server }}
|
|
||||||
{% if w.electrum_status == 'connected' %}
|
|
||||||
<span class="ml-2 inline-flex items-center">
|
|
||||||
<span class="text-xs text-green-600 dark:text-green-400">(Connected)</span>
|
|
||||||
</span>
|
|
||||||
{% elif w.electrum_status == 'all_failed' %}
|
|
||||||
<span class="ml-2 inline-flex items-center">
|
|
||||||
<span class="text-xs text-red-600 dark:text-red-400">(All Servers Failed)</span>
|
|
||||||
</span>
|
|
||||||
{% elif w.electrum_status == 'disconnected' %}
|
|
||||||
<span class="ml-2 inline-flex items-center">
|
|
||||||
<span class="text-xs text-red-600 dark:text-red-400">(Disconnected - Reconnecting...)</span>
|
|
||||||
</span>
|
|
||||||
{% elif w.electrum_status == 'error' %}
|
|
||||||
<span class="ml-2 inline-flex items-center">
|
|
||||||
<span class="w-2 h-2 rounded-full bg-yellow-500 mr-1"></span>
|
|
||||||
<span class="text-xs text-yellow-600 dark:text-yellow-400">(Connection Error)</span>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_electrum_mode and w.electrum_all_failed and w.electrum_using_defaults %}
|
|
||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
|
||||||
<td colspan="2" class="py-3 px-6">
|
|
||||||
<div class="p-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-700 rounded-lg">
|
|
||||||
<p class="text-sm font-medium text-red-800 dark:text-red-200 mb-1">
|
|
||||||
<svg class="inline w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>
|
|
||||||
Default Electrum Servers Unavailable
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-red-700 dark:text-red-300">
|
|
||||||
All default servers failed to connect. Please configure custom Electrum servers in
|
|
||||||
<a href="/settings" class="underline font-medium hover:text-red-900 dark:hover:text-red-100">Settings</a>
|
|
||||||
under the {{ w.name }} section.
|
|
||||||
</p>
|
|
||||||
{% if w.electrum_last_error %}
|
|
||||||
<p class="text-xs text-red-600 dark:text-red-400 mt-1 font-mono">Last error: {{ w.electrum_last_error }}</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if is_electrum_mode and w.electrum_version %}
|
|
||||||
<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">Server Version:</td>
|
|
||||||
<td class="py-3 px-6">{{ w.electrum_version }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if w.bootstrapping %}
|
{% if w.bootstrapping %}
|
||||||
<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">Bootstrapping:</td>
|
<td class="py-3 px-6 bold">Bootstrapping:</td>
|
||||||
@@ -348,7 +200,6 @@
|
|||||||
<td class="py-3 px-6">{{ w.expected_seed }}</td>
|
<td class="py-3 px-6">{{ w.expected_seed }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -435,8 +286,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if w.cid in '1, 3, 6, 9, 13' %}
|
{% if w.cid in '1, 3, 6, 9' %}
|
||||||
{# PART | LTC | XMR | WOW | FIRO #}
|
{# PART | LTC | XMR | WOW | #}
|
||||||
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
|
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
|
||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<div class="flex flex-wrap -m-3">
|
<div class="flex flex-wrap -m-3">
|
||||||
@@ -468,8 +319,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{# / PART #}
|
{# / PART #}
|
||||||
{% elif w.cid == '3' %}
|
{% elif w.cid == '3' %}
|
||||||
{# LTC - MWEB not available in light mode #}
|
{# LTC #}
|
||||||
{% if not is_electrum_mode %}
|
|
||||||
<div id="qrcode-mweb" class="qrcode" data-qrcode data-address="{{ w.mweb_address }}"> </div>
|
<div id="qrcode-mweb" class="qrcode" data-qrcode data-address="{{ w.mweb_address }}"> </div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -483,24 +333,7 @@
|
|||||||
<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="newmwebaddr_{{ w.cid }}" value="New MWEB Address"> {{ circular_arrows_svg }} New MWEB Address </button>
|
<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="newmwebaddr_{{ w.cid }}" value="New MWEB Address"> {{ circular_arrows_svg }} New MWEB Address </button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
{# / LTC #}
|
{# / LTC #}
|
||||||
{% elif w.cid == '13' %}
|
|
||||||
{# FIRO #}
|
|
||||||
<div id="qrcode-spark" class="qrcode" data-qrcode data-address="{{ w.spark_address }}"> </div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Spark Address: </div>
|
|
||||||
<div class="text-center relative">
|
|
||||||
<div class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="stealth_address">{{ w.spark_address }}</div>
|
|
||||||
<span class="absolute inset-y-0 right-0 flex items-center pr-3 cursor-pointer" id="copyIcon"></span>
|
|
||||||
</div>
|
|
||||||
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
|
|
||||||
<div class="py-3 px-6 bold mt-5">
|
|
||||||
<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="newsparkaddr_{{ w.cid }}" value="New Spark Address"> {{ circular_arrows_svg }} New Spark Address </button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{# / FIRO #}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -516,6 +349,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Address copy functionality handled by external JS -->
|
||||||
|
|
||||||
<section class="p-6">
|
<section class="p-6">
|
||||||
<div class="lg:container mx-auto">
|
<div class="lg:container mx-auto">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -547,7 +384,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 }}" data-balance-type="balance">{{ w.balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -556,29 +393,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>MWEB 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>MWEB Balance: </td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
{% if is_electrum_mode %}
|
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
||||||
<span class="text-gray-400 dark:text-gray-300">Not available in light mode</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="mweb_balance">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
|
||||||
(<span class="usd-value"></span>)
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% elif w.cid == '13' %}
|
|
||||||
{# FIRO #}
|
|
||||||
<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-3 px-6">
|
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
|
|
||||||
(<span class="usd-value"></span>)
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% elif w.cid == '13' %}
|
|
||||||
{# FIRO #}
|
|
||||||
<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-3 px-6">
|
|
||||||
<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>
|
||||||
@@ -587,14 +402,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 }}" data-balance-type="blind_balance">{{ w.blind_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}">{{ 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 }}" data-balance-type="anon_balance">{{ w.anon_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -672,14 +487,6 @@
|
|||||||
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">100%</button>
|
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">100%</button>
|
||||||
|
|
||||||
{# / LTC #}
|
{# / LTC #}
|
||||||
|
|
||||||
{% elif w.cid == '13' %}
|
|
||||||
{# FIRO #}
|
|
||||||
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.spark_balance }}')">25%</button>
|
|
||||||
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.spark_balance }}')">50%</button>
|
|
||||||
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.spark_balance }}')">100%</button>
|
|
||||||
|
|
||||||
{# / FIRO #}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }})">25%</button>
|
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }})">25%</button>
|
||||||
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }})">50%</button>
|
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }})">50%</button>
|
||||||
@@ -734,7 +541,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{# / PART #}
|
{# / PART #}
|
||||||
{% elif w.cid == '3' and not is_electrum_mode %} {# LTC - only show in full node mode #}
|
{% elif w.cid == '3' %} {# LTC #}
|
||||||
<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">Type From:</td>
|
<td class="py-3 px-6 bold">Type From:</td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
@@ -746,21 +553,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% elif w.cid == '13' %} {# FIRO #}
|
|
||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
|
||||||
<td class="py-3 px-6 bold">Type From:</td>
|
|
||||||
<td class="py-3 px-6">
|
|
||||||
<div class="w-full md:flex-1">
|
|
||||||
<div class="relative"> {{ select_box_arrow_svg }} <select id="withdraw_type" class="{{ select_box_class }}" name="withdraw_type_from_{{ w.cid }}">
|
|
||||||
<option value="spark" {% if w.wd_type_from=='spark' %} selected{% endif %}>Spark</option>
|
|
||||||
<option value="plain" {% if w.wd_type_from=='plain' %} selected{% endif %}>Plain</option>
|
|
||||||
</select> </div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# / LTC #}
|
{# / LTC #}
|
||||||
{# / FIRO #}
|
|
||||||
{% if w.cid not in '6,9' %} {# Not XMR WOW #}
|
{% if w.cid not in '6,9' %} {# Not XMR WOW #}
|
||||||
<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 Rate:</td>
|
<td class="py-3 px-6 bold">Fee Rate:</td>
|
||||||
@@ -769,7 +563,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 }}" data-balance-type="est_fee">{{ w.est_fee }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}">{{ 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>
|
||||||
@@ -799,8 +593,6 @@
|
|||||||
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="estfee_{{ w.cid }}" value="Estimate Fee">Estimate {{ w.ticker }} Fee </button> </div>
|
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="estfee_{{ w.cid }}" value="Estimate Fee">Estimate {{ w.ticker }} Fee </button> </div>
|
||||||
{# / XMR | WOW #}
|
{# / XMR | WOW #}
|
||||||
{% elif w.show_utxo_groups %}
|
{% elif w.show_utxo_groups %}
|
||||||
{% elif is_electrum_mode %}
|
|
||||||
{# Hide UTXO Groups button in electrum/lite wallet mode #}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="showutxogroups" name="showutxogroups" value="Show UTXO Groups"> {{ utxo_groups_svg | safe }} Show UTXO Groups </button> </div>
|
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="showutxogroups" name="showutxogroups" value="Show UTXO Groups"> {{ utxo_groups_svg | safe }} Show UTXO Groups </button> </div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -888,96 +680,6 @@
|
|||||||
<input type="hidden" name="formid" value="{{ form_id }}">
|
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if w.havedata and not w.error %}
|
|
||||||
<section class="p-6">
|
|
||||||
<div class="lg:container mx-auto">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<h4 class="font-semibold text-2xl text-black dark:text-white">Transaction History</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<div class="px-6 py-0 h-full overflow-hidden">
|
|
||||||
<div class="border-coolGray-100">
|
|
||||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
|
||||||
<div class="w-full pt-2">
|
|
||||||
<div class="lg:container mt-5 mx-auto">
|
|
||||||
<div id="transaction-history-section" class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
|
||||||
<div class="px-6">
|
|
||||||
{% if is_electrum_mode %}
|
|
||||||
<div class="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
||||||
Transaction history is not available in Light Wallet mode.
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div id="tx-container" class="space-y-3 pb-6">
|
|
||||||
<div class="text-center py-8 text-gray-500 dark:text-gray-400">Loading transactions...</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{% if not is_electrum_mode %}
|
|
||||||
<section id="tx-pagination-section" style="display: none;">
|
|
||||||
<div class="px-6 py-0 h-full overflow-hidden">
|
|
||||||
<div class="pb-6">
|
|
||||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
|
||||||
<div class="w-full pt-2">
|
|
||||||
<div class="lg:container mx-auto">
|
|
||||||
<div class="py-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
|
|
||||||
<div class="px-6">
|
|
||||||
<div class="flex flex-wrap justify-end items-center space-x-4">
|
|
||||||
<button id="prevPageTx" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-md transition duration-200 focus:ring-0 focus:outline-none">
|
|
||||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
||||||
</svg>
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
<p class="text-sm font-heading dark:text-white">
|
|
||||||
Page <span id="currentPageTx">1</span> of <span id="totalPagesTx">1</span>
|
|
||||||
</p>
|
|
||||||
<button id="nextPageTx" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-md transition duration-200 focus:ring-0 focus:outline-none">
|
|
||||||
Next
|
|
||||||
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if is_electrum_mode %}
|
|
||||||
<section id="tx-pagination-section">
|
|
||||||
<div class="px-6 py-0 h-full overflow-hidden">
|
|
||||||
<div class="pb-6">
|
|
||||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
|
||||||
<div class="w-full pt-2">
|
|
||||||
<div class="lg:container mx-auto">
|
|
||||||
<div class="py-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div id="confirmModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
|
<div id="confirmModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
|
||||||
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity duration-300 ease-out"></div>
|
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity duration-300 ease-out"></div>
|
||||||
<div class="relative z-50 min-h-screen px-4 flex items-center justify-center">
|
<div class="relative z-50 min-h-screen px-4 flex items-center justify-center">
|
||||||
|
|||||||
@@ -48,24 +48,14 @@
|
|||||||
<div class="px-6 mb-6">
|
<div class="px-6 mb-6">
|
||||||
<h4 class="text-xl font-bold dark:text-white">{{ w.name }}
|
<h4 class="text-xl font-bold dark:text-white">{{ w.name }}
|
||||||
<span class="inline-block font-medium text-xs text-gray-500 dark:text-white">({{ w.ticker }})</span>
|
<span class="inline-block font-medium text-xs text-gray-500 dark:text-white">({{ w.ticker }})</span>
|
||||||
{% if w.connection_type == 'electrum' %}
|
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-yellow-100 text-xs text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300">Light Wallet (Electrum)</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-700 dark:bg-green-900 dark:text-green-300">Full Node</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if use_tor %}
|
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-purple-100 text-xs text-purple-700 dark:bg-purple-900 dark:text-purple-300">TOR</span>
|
|
||||||
{% endif %}
|
|
||||||
</h4>
|
</h4>
|
||||||
<p class="pt-2 text-xs text-gray-500 dark:text-gray-200">Version: <span class="electrum-version" data-coin="{{ w.name }}">{{ w.version }}</span> {% if w.updating %} <span class="hidden inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-700 dark:hover:bg-gray-700">Updating..</span>{% endif %}</p>
|
<p class="text-xs text-gray-500 dark:text-gray-200">Version: {{ w.version }} {% if w.updating %} <span class="hidden inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-700 dark:hover:bg-gray-700">Updating..</span></p>
|
||||||
{% if w.electrum_server %}
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-200">Server: <span class="electrum-server" data-coin="{{ w.name }}">{{ w.electrum_server }}</span></p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<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 }}" data-balance-type="balance">{{ 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 }}">{{ 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>
|
||||||
@@ -81,16 +71,10 @@
|
|||||||
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
|
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if w.pending_out %}
|
|
||||||
<div class="flex mb-2 justify-between items-center">
|
|
||||||
<h4 class="text-xs font-bold text-yellow-600 dark:text-yellow-400">Unconfirmed:</h4>
|
|
||||||
<span class="bold inline-block py-1 px-2 rounded-full bg-yellow-100 text-xs text-yellow-600 dark:bg-gray-500 dark:text-yellow-400 coinname-value" data-coinname="{{ w.name }}">-{{ w.pending_out }} {{ w.ticker }}</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% 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 }}" data-balance-type="blind_balance">{{ 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 }}">{{ 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 +92,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 }}" data-balance-type="anon_balance">{{ 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 }}">{{ 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>
|
||||||
@@ -126,10 +110,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %} {# / PART #}
|
{% endif %} {# / PART #}
|
||||||
{% if w.cid == '3' and w.connection_type != 'electrum' %} {# LTC - MWEB not available in electrum mode #}
|
{% if w.cid == '3' %} {# LTC #}
|
||||||
<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 }}" data-balance-type="mweb_balance">{{ 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 }}">{{ 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>
|
||||||
@@ -148,28 +132,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# / LTC #}
|
{# / LTC #}
|
||||||
{% if w.cid == '13' %} {# FIRO #}
|
|
||||||
<div class="flex mb-2 justify-between items-center">
|
|
||||||
<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 }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex mb-2 justify-between items-center">
|
|
||||||
<h4 class="text-xs font-medium dark:text-white">Spark USD value:</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 usd-value"></div>
|
|
||||||
</div>
|
|
||||||
{% if w.spark_pending %}
|
|
||||||
<div class="flex mb-2 justify-between items-center">
|
|
||||||
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Spark Pending:</h4>
|
|
||||||
<span class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 coinname-value" data-coinname="{{ w.name }}">
|
|
||||||
+{{ w.spark_pending }} {{ w.ticker }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex mb-2 justify-between items-center">
|
|
||||||
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Spark Pending USD value:</h4>
|
|
||||||
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{# / FIRO #}
|
|
||||||
<hr class="border-t border-gray-100 dark:border-gray-500 my-5">
|
<hr class="border-t border-gray-100 dark:border-gray-500 my-5">
|
||||||
<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">Blocks:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Blocks:</h4>
|
||||||
@@ -197,39 +159,6 @@
|
|||||||
<h4 class="text-xs font-medium dark:text-white">Expected Seed:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Expected Seed:</h4>
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.expected_seed }}</span>
|
<span class="inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200">{{ w.expected_seed }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% if w.connection_type == 'electrum' %}
|
|
||||||
<div class="scan-status mt-10 p-2 rounded" data-coin="{{ w.name }}">
|
|
||||||
{% if w.scan_status and w.scan_status.in_progress %}
|
|
||||||
<div class="bg-blue-50 dark:bg-gray-500 p-2 rounded">
|
|
||||||
<div class="flex items-center justify-between text-xs">
|
|
||||||
<span class="text-blue-600 dark:text-blue-300">
|
|
||||||
<svg class="inline-block w-3 h-3 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
Scanning {{ w.scan_status.status }}
|
|
||||||
</span>
|
|
||||||
<span class="text-blue-500 dark:text-blue-200 font-medium">{{ w.scan_status.progress }}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full bg-blue-200 dark:bg-gray-700 rounded-full h-1 mt-1">
|
|
||||||
<div class="bg-blue-600 dark:bg-blue-400 h-1 rounded-full" style="width: {{ w.scan_status.progress }}%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% elif w.electrum_synced %}
|
|
||||||
<div class="bg-green-50 dark:bg-gray-500 p-2 rounded">
|
|
||||||
<div class="flex items-center text-xs text-green-600 dark:text-green-400">
|
|
||||||
Electrum Wallet Synced ({{ w.electrum_height }})
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="bg-yellow-50 dark:bg-gray-500 p-2 rounded">
|
|
||||||
<div class="flex items-center text-xs text-yellow-600 dark:text-yellow-400">
|
|
||||||
Waiting for Electrum Server...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="flex justify-between mb-1 mt-10">
|
<div class="flex justify-between mb-1 mt-10">
|
||||||
<span class="text-xs font-medium dark:text-gray-200">Blockchain</span>
|
<span class="text-xs font-medium dark:text-gray-200">Blockchain</span>
|
||||||
<span class="text-xs font-medium dark:text-gray-200">{{ w.synced }}%</span>
|
<span class="text-xs font-medium dark:text-gray-200">{{ w.synced }}%</span>
|
||||||
@@ -250,7 +179,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1253,7 +1253,6 @@ def amm_autostart_api(swap_client, post_string, params=None):
|
|||||||
|
|
||||||
settings_path = os.path.join(swap_client.data_dir, cfg.CONFIG_FILENAME)
|
settings_path = os.path.join(swap_client.data_dir, cfg.CONFIG_FILENAME)
|
||||||
settings_path_new = settings_path + ".new"
|
settings_path_new = settings_path + ".new"
|
||||||
if os.path.exists(settings_path):
|
|
||||||
shutil.copyfile(settings_path, settings_path + ".last")
|
shutil.copyfile(settings_path, settings_path + ".last")
|
||||||
with open(settings_path_new, "w") as fp:
|
with open(settings_path_new, "w") as fp:
|
||||||
json.dump(swap_client.settings, fp, indent=4)
|
json.dump(swap_client.settings, fp, indent=4)
|
||||||
|
|||||||
+18
-79
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2026 tecnovert
|
# Copyright (c) 2022-2024 tecnovert
|
||||||
# Copyright (c) 2024 The Basicswap developers
|
# Copyright (c) 2024 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.
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
import traceback
|
import traceback
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import List
|
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
from .util import (
|
from .util import (
|
||||||
getCoinType,
|
getCoinType,
|
||||||
@@ -185,14 +184,14 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
|||||||
parsed_data["swap_type"] = page_data["swap_type"]
|
parsed_data["swap_type"] = page_data["swap_type"]
|
||||||
swap_type = swap_type_from_string(parsed_data["swap_type"])
|
swap_type = swap_type_from_string(parsed_data["swap_type"])
|
||||||
elif (
|
elif (
|
||||||
parsed_data["coin_from"] in swap_client.coins_without_segwit
|
parsed_data["coin_from"] in swap_client.adaptor_swap_only_coins
|
||||||
and parsed_data["coin_to"] in swap_client.coins_without_segwit
|
or parsed_data["coin_to"] in swap_client.adaptor_swap_only_coins
|
||||||
):
|
):
|
||||||
parsed_data["swap_type"] = strSwapType(SwapTypes.SELLER_FIRST)
|
|
||||||
swap_type = SwapTypes.SELLER_FIRST
|
|
||||||
else:
|
|
||||||
parsed_data["swap_type"] = strSwapType(SwapTypes.XMR_SWAP)
|
parsed_data["swap_type"] = strSwapType(SwapTypes.XMR_SWAP)
|
||||||
swap_type = SwapTypes.XMR_SWAP
|
swap_type = SwapTypes.XMR_SWAP
|
||||||
|
else:
|
||||||
|
parsed_data["swap_type"] = strSwapType(SwapTypes.SELLER_FIRST)
|
||||||
|
swap_type = SwapTypes.SELLER_FIRST
|
||||||
|
|
||||||
if swap_type == SwapTypes.XMR_SWAP:
|
if swap_type == SwapTypes.XMR_SWAP:
|
||||||
page_data["swap_style"] = "xmr"
|
page_data["swap_style"] = "xmr"
|
||||||
@@ -219,8 +218,6 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
|||||||
if have_data_entry(form_data, "fee_from_extra"):
|
if have_data_entry(form_data, "fee_from_extra"):
|
||||||
page_data["fee_from_extra"] = int(get_data_entry(form_data, "fee_from_extra"))
|
page_data["fee_from_extra"] = int(get_data_entry(form_data, "fee_from_extra"))
|
||||||
parsed_data["fee_from_extra"] = page_data["fee_from_extra"]
|
parsed_data["fee_from_extra"] = page_data["fee_from_extra"]
|
||||||
else:
|
|
||||||
page_data["fee_from_extra"] = 0
|
|
||||||
|
|
||||||
if have_data_entry(form_data, "fee_to_conf"):
|
if have_data_entry(form_data, "fee_to_conf"):
|
||||||
page_data["fee_to_conf"] = int(get_data_entry(form_data, "fee_to_conf"))
|
page_data["fee_to_conf"] = int(get_data_entry(form_data, "fee_to_conf"))
|
||||||
@@ -229,8 +226,6 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
|||||||
if have_data_entry(form_data, "fee_to_extra"):
|
if have_data_entry(form_data, "fee_to_extra"):
|
||||||
page_data["fee_to_extra"] = int(get_data_entry(form_data, "fee_to_extra"))
|
page_data["fee_to_extra"] = int(get_data_entry(form_data, "fee_to_extra"))
|
||||||
parsed_data["fee_to_extra"] = page_data["fee_to_extra"]
|
parsed_data["fee_to_extra"] = page_data["fee_to_extra"]
|
||||||
else:
|
|
||||||
page_data["fee_to_extra"] = 0
|
|
||||||
|
|
||||||
if have_data_entry(form_data, "check_offer"):
|
if have_data_entry(form_data, "check_offer"):
|
||||||
page_data["check_offer"] = True
|
page_data["check_offer"] = True
|
||||||
@@ -254,14 +249,6 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
|||||||
get_data_entry(form_data, "valid_for_seconds")
|
get_data_entry(form_data, "valid_for_seconds")
|
||||||
)
|
)
|
||||||
|
|
||||||
if swap_client.debug:
|
|
||||||
if have_data_entry(form_data, "lock_type"):
|
|
||||||
parsed_data["lock_type"] = TxLockTypes(
|
|
||||||
int(get_data_entry(form_data, "lock_type"))
|
|
||||||
)
|
|
||||||
if have_data_entry(form_data, "lock_blocks"):
|
|
||||||
parsed_data["lock_blocks"] = int(get_data_entry(form_data, "lock_blocks"))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(errors) == 0 and page_data["swap_style"] == "xmr":
|
if len(errors) == 0 and page_data["swap_style"] == "xmr":
|
||||||
reverse_bid: bool = swap_client.is_reverse_ads_bid(coin_from, coin_to)
|
reverse_bid: bool = swap_client.is_reverse_ads_bid(coin_from, coin_to)
|
||||||
@@ -355,15 +342,7 @@ def postNewOfferFromParsed(swap_client, parsed_data):
|
|||||||
lock_type = TxLockTypes.ABS_LOCK_TIME
|
lock_type = TxLockTypes.ABS_LOCK_TIME
|
||||||
|
|
||||||
extra_options = {}
|
extra_options = {}
|
||||||
lock_value: int = parsed_data.get("lock_seconds", -1)
|
|
||||||
if swap_client.debug:
|
|
||||||
if "lock_type" in parsed_data:
|
|
||||||
lock_type = parsed_data["lock_type"]
|
|
||||||
if "lock_blocks" in parsed_data:
|
|
||||||
lock_value = parsed_data["lock_blocks"]
|
|
||||||
|
|
||||||
if "fee_from_conf" in parsed_data:
|
|
||||||
extra_options["from_fee_conf_target"] = parsed_data["fee_from_conf"]
|
|
||||||
if "fee_from_conf" in parsed_data:
|
if "fee_from_conf" in parsed_data:
|
||||||
extra_options["from_fee_conf_target"] = parsed_data["fee_from_conf"]
|
extra_options["from_fee_conf_target"] = parsed_data["fee_from_conf"]
|
||||||
if "from_fee_multiplier_percent" in parsed_data:
|
if "from_fee_multiplier_percent" in parsed_data:
|
||||||
@@ -414,7 +393,7 @@ def postNewOfferFromParsed(swap_client, parsed_data):
|
|||||||
parsed_data["amt_bid_min"],
|
parsed_data["amt_bid_min"],
|
||||||
swap_type,
|
swap_type,
|
||||||
lock_type=lock_type,
|
lock_type=lock_type,
|
||||||
lock_value=lock_value,
|
lock_value=parsed_data["lock_seconds"],
|
||||||
addr_send_from=parsed_data["addr_from"],
|
addr_send_from=parsed_data["addr_from"],
|
||||||
extra_options=extra_options,
|
extra_options=extra_options,
|
||||||
)
|
)
|
||||||
@@ -500,7 +479,7 @@ def page_newoffer(self, url_split, post_string):
|
|||||||
"debug_ui": swap_client.debug_ui,
|
"debug_ui": swap_client.debug_ui,
|
||||||
"automation_strat_id": -1,
|
"automation_strat_id": -1,
|
||||||
"amt_bid_min": format_amount(1, 3),
|
"amt_bid_min": format_amount(1, 3),
|
||||||
"swap_type": strSwapType(SwapTypes.XMR_SWAP),
|
"swap_type": strSwapType(SwapTypes.SELLER_FIRST),
|
||||||
}
|
}
|
||||||
|
|
||||||
post_data = parse.parse_qs(post_string)
|
post_data = parse.parse_qs(post_string)
|
||||||
@@ -584,7 +563,7 @@ def page_newoffer(self, url_split, post_string):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def page_offer(self, url_split: List[str], post_string: str) -> bytes:
|
def page_offer(self, url_split, post_string):
|
||||||
ensure(len(url_split) > 2, "Offer ID not specified")
|
ensure(len(url_split) > 2, "Offer ID not specified")
|
||||||
offer_id = decode_offer_id(url_split[2])
|
offer_id = decode_offer_id(url_split[2])
|
||||||
server = self.server
|
server = self.server
|
||||||
@@ -675,11 +654,6 @@ def page_offer(self, url_split: List[str], post_string: str) -> bytes:
|
|||||||
amount_from = offer.amount_from
|
amount_from = offer.amount_from
|
||||||
debugind = int(get_data_entry_or(form_data, "debugind", -1))
|
debugind = int(get_data_entry_or(form_data, "debugind", -1))
|
||||||
|
|
||||||
if have_data_entry(form_data, "subfee_bid"):
|
|
||||||
extra_options["prefunded_tx"] = bytes.fromhex(
|
|
||||||
get_data_entry(form_data, "prefunded_bid_tx")
|
|
||||||
)
|
|
||||||
|
|
||||||
sent_bid_id = swap_client.postBid(
|
sent_bid_id = swap_client.postBid(
|
||||||
offer_id,
|
offer_id,
|
||||||
amount_from,
|
amount_from,
|
||||||
@@ -774,30 +748,24 @@ def page_offer(self, url_split: List[str], post_string: str) -> bytes:
|
|||||||
ci_leader = ci_to if reverse_bid else ci_from
|
ci_leader = ci_to if reverse_bid else ci_from
|
||||||
|
|
||||||
if xmr_offer:
|
if xmr_offer:
|
||||||
a_fee_rate_now, fee_source = ci_leader.get_fee_rate()
|
int_fee_rate_now, fee_source = ci_leader.get_fee_rate()
|
||||||
a_fee_rate_now = ci_leader.make_int(a_fee_rate_now)
|
|
||||||
|
|
||||||
chain_a_fee_rate: int = (
|
|
||||||
xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate
|
|
||||||
)
|
|
||||||
data["xmr_type"] = True
|
data["xmr_type"] = True
|
||||||
data["a_fee_rate"] = ci_leader.format_amount(chain_a_fee_rate)
|
data["a_fee_rate"] = ci_leader.format_amount(xmr_offer.a_fee_rate)
|
||||||
data["a_fee_rate_verify"] = ci_leader.format_amount(a_fee_rate_now)
|
data["a_fee_rate_verify"] = ci_leader.format_amount(
|
||||||
|
int_fee_rate_now, conv_int=True
|
||||||
|
)
|
||||||
data["a_fee_rate_verify_src"] = fee_source
|
data["a_fee_rate_verify_src"] = fee_source
|
||||||
|
data["a_fee_warn"] = xmr_offer.a_fee_rate < int_fee_rate_now
|
||||||
|
|
||||||
warning_threshold: float = 1.2
|
from_fee_rate = xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate
|
||||||
if chain_a_fee_rate * warning_threshold < a_fee_rate_now:
|
|
||||||
data["a_fee_warn"] = "low"
|
|
||||||
elif chain_a_fee_rate > a_fee_rate_now * warning_threshold:
|
|
||||||
data["a_fee_warn"] = "high"
|
|
||||||
|
|
||||||
lock_spend_tx_vsize = (
|
lock_spend_tx_vsize = (
|
||||||
ci_from.xmr_swap_b_lock_spend_tx_vsize()
|
ci_from.xmr_swap_b_lock_spend_tx_vsize()
|
||||||
if reverse_bid
|
if reverse_bid
|
||||||
else ci_from.xmr_swap_a_lock_spend_tx_vsize()
|
else ci_from.xmr_swap_a_lock_spend_tx_vsize()
|
||||||
)
|
)
|
||||||
lock_spend_tx_fee = ci_from.make_int(
|
lock_spend_tx_fee = ci_from.make_int(
|
||||||
chain_a_fee_rate * lock_spend_tx_vsize / 1000, r=1
|
from_fee_rate * lock_spend_tx_vsize / 1000, r=1
|
||||||
)
|
)
|
||||||
data["amt_from_lock_spend_tx_fee"] = ci_from.format_amount(
|
data["amt_from_lock_spend_tx_fee"] = ci_from.format_amount(
|
||||||
lock_spend_tx_fee // ci_from.COIN()
|
lock_spend_tx_fee // ci_from.COIN()
|
||||||
@@ -829,33 +797,6 @@ def page_offer(self, url_split: List[str], post_string: str) -> bytes:
|
|||||||
)
|
)
|
||||||
data["amt_swapped"] = ci_from.format_amount(amt_swapped)
|
data["amt_swapped"] = ci_from.format_amount(amt_swapped)
|
||||||
|
|
||||||
if show_bid_form:
|
|
||||||
coin_to_id = int(ci_to.coin_type())
|
|
||||||
wallet_coin_to_id = coin_to_id
|
|
||||||
if coin_to_id in (Coins.PART_ANON, Coins.PART_BLIND):
|
|
||||||
wallet_coin_to_id = Coins.PART
|
|
||||||
|
|
||||||
swap_client.updateWalletsInfo(only_coin=wallet_coin_to_id)
|
|
||||||
coin_to_wallet = swap_client.getCachedWalletsInfo(
|
|
||||||
{"coin_id": wallet_coin_to_id}
|
|
||||||
)[wallet_coin_to_id]
|
|
||||||
if coin_to_id == Coins.PART_ANON:
|
|
||||||
balance_key = "anon_balance"
|
|
||||||
elif coin_to_id == Coins.PART_BLIND:
|
|
||||||
balance_key = "blind_balance"
|
|
||||||
else:
|
|
||||||
balance_key = "balance"
|
|
||||||
data["coin_to_balance"] = coin_to_wallet[balance_key]
|
|
||||||
|
|
||||||
bid_can_subfee: bool = True
|
|
||||||
if offer.swap_type != SwapTypes.XMR_SWAP:
|
|
||||||
bid_can_subfee = False
|
|
||||||
if coin_to_id in (Coins.XMR, Coins.WOW):
|
|
||||||
bid_can_subfee = False
|
|
||||||
if offer.amount_negotiable is False:
|
|
||||||
bid_can_subfee = False
|
|
||||||
data["bid_can_subfee"] = bid_can_subfee
|
|
||||||
|
|
||||||
template = server.env.get_template("offer.html")
|
template = server.env.get_template("offer.html")
|
||||||
return self.render_template(
|
return self.render_template(
|
||||||
template,
|
template,
|
||||||
@@ -874,9 +815,7 @@ def page_offer(self, url_split: List[str], post_string: str) -> bytes:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def format_timestamp(
|
def format_timestamp(timestamp, with_ago=True, is_expired=False):
|
||||||
timestamp: int, with_ago: bool = True, is_expired: bool = False
|
|
||||||
) -> str:
|
|
||||||
current_time = int(time.time())
|
current_time = int(time.time())
|
||||||
|
|
||||||
if is_expired:
|
if is_expired:
|
||||||
|
|||||||
@@ -104,10 +104,6 @@ def page_settings(self, url_split, post_string):
|
|||||||
"TODO: If running in docker see doc/tor.md to enable/disable tor."
|
"TODO: If running in docker see doc/tor.md to enable/disable tor."
|
||||||
)
|
)
|
||||||
|
|
||||||
electrum_supported_coins = (
|
|
||||||
"bitcoin",
|
|
||||||
"litecoin",
|
|
||||||
)
|
|
||||||
for name, c in swap_client.settings["chainclients"].items():
|
for name, c in swap_client.settings["chainclients"].items():
|
||||||
if have_data_entry(form_data, "apply_" + name):
|
if have_data_entry(form_data, "apply_" + name):
|
||||||
data = {"lookups": get_data_entry(form_data, "lookups_" + name)}
|
data = {"lookups": get_data_entry(form_data, "lookups_" + name)}
|
||||||
@@ -142,70 +138,10 @@ def page_settings(self, url_split, post_string):
|
|||||||
data["anon_tx_ring_size"] = int(
|
data["anon_tx_ring_size"] = int(
|
||||||
get_data_entry(form_data, "rct_ring_size_" + name)
|
get_data_entry(form_data, "rct_ring_size_" + name)
|
||||||
)
|
)
|
||||||
if name in electrum_supported_coins:
|
|
||||||
new_connection_type = get_data_entry_or(
|
|
||||||
form_data, "connection_type_" + name, None
|
|
||||||
)
|
|
||||||
if new_connection_type and new_connection_type != c.get(
|
|
||||||
"connection_type"
|
|
||||||
):
|
|
||||||
coin_id = swap_client.getCoinIdFromName(name)
|
|
||||||
has_active_swaps = False
|
|
||||||
for bid_id, (bid, offer) in list(
|
|
||||||
swap_client.swaps_in_progress.items()
|
|
||||||
):
|
|
||||||
if (
|
|
||||||
offer.coin_from == coin_id
|
|
||||||
or offer.coin_to == coin_id
|
|
||||||
):
|
|
||||||
has_active_swaps = True
|
|
||||||
break
|
|
||||||
if has_active_swaps:
|
|
||||||
display_name = getCoinName(coin_id)
|
|
||||||
err_messages.append(
|
|
||||||
f"Cannot change {display_name} connection mode while swaps are in progress. "
|
|
||||||
f"Please wait for all {display_name} swaps to complete."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
data["connection_type"] = new_connection_type
|
|
||||||
if new_connection_type == "electrum":
|
|
||||||
data["manage_daemon"] = False
|
|
||||||
elif new_connection_type == "rpc":
|
|
||||||
data["manage_daemon"] = True
|
|
||||||
clearnet_servers = get_data_entry_or(
|
|
||||||
form_data, "electrum_clearnet_" + name, ""
|
|
||||||
).strip()
|
|
||||||
data["electrum_clearnet_servers"] = clearnet_servers
|
|
||||||
onion_servers = get_data_entry_or(
|
|
||||||
form_data, "electrum_onion_" + name, ""
|
|
||||||
).strip()
|
|
||||||
data["electrum_onion_servers"] = onion_servers
|
|
||||||
auto_transfer_now = have_data_entry(
|
|
||||||
form_data, "auto_transfer_now_" + name
|
|
||||||
)
|
|
||||||
if auto_transfer_now:
|
|
||||||
transfer_value = get_data_entry_or(
|
|
||||||
form_data, "auto_transfer_now_" + name, "false"
|
|
||||||
)
|
|
||||||
data["auto_transfer_now"] = transfer_value == "true"
|
|
||||||
gap_limit_str = get_data_entry_or(
|
|
||||||
form_data, "gap_limit_" + name, "50"
|
|
||||||
).strip()
|
|
||||||
try:
|
|
||||||
gap_limit = int(gap_limit_str)
|
|
||||||
if gap_limit < 5:
|
|
||||||
gap_limit = 5
|
|
||||||
elif gap_limit > 100:
|
|
||||||
gap_limit = 100
|
|
||||||
data["address_gap_limit"] = gap_limit
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
settings_changed, suggest_reboot, migration_message = (
|
settings_changed, suggest_reboot = swap_client.editSettings(
|
||||||
swap_client.editSettings(name, data)
|
name, data
|
||||||
)
|
)
|
||||||
if migration_message:
|
|
||||||
messages.append(migration_message)
|
|
||||||
if settings_changed is True:
|
if settings_changed is True:
|
||||||
messages.append("Settings applied.")
|
messages.append("Settings applied.")
|
||||||
if suggest_reboot is True:
|
if suggest_reboot is True:
|
||||||
@@ -220,71 +156,19 @@ def page_settings(self, url_split, post_string):
|
|||||||
display_name = getCoinName(swap_client.getCoinIdFromName(name))
|
display_name = getCoinName(swap_client.getCoinIdFromName(name))
|
||||||
messages.append(display_name + " disabled, shutting down.")
|
messages.append(display_name + " disabled, shutting down.")
|
||||||
swap_client.stopRunning()
|
swap_client.stopRunning()
|
||||||
elif have_data_entry(form_data, "force_sweep_" + name):
|
|
||||||
coin_id = swap_client.getCoinIdFromName(name)
|
|
||||||
display_name = getCoinName(coin_id)
|
|
||||||
try:
|
|
||||||
result = swap_client.sweepLiteWalletFunds(coin_id)
|
|
||||||
if result.get("success"):
|
|
||||||
amount = result.get("amount", 0)
|
|
||||||
fee = result.get("fee", 0)
|
|
||||||
txid = result.get("txid", "")
|
|
||||||
messages.append(
|
|
||||||
f"Successfully swept {amount:.8f} {display_name} to RPC wallet. "
|
|
||||||
f"Fee: {fee:.8f}. TXID: {txid} (1 confirmation required)"
|
|
||||||
)
|
|
||||||
elif result.get("skipped"):
|
|
||||||
messages.append(
|
|
||||||
f"{display_name}: {result.get('reason', 'Sweep skipped')}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
err_messages.append(
|
|
||||||
f"{display_name}: Sweep failed - {result.get('error', 'Unknown error')}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
err_messages.append(f"{display_name}: Sweep failed - {str(e)}")
|
|
||||||
except InactiveCoin as ex:
|
except InactiveCoin as ex:
|
||||||
err_messages.append("InactiveCoin {}".format(Coins(ex.coinid).name))
|
err_messages.append("InactiveCoin {}".format(Coins(ex.coinid).name))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_messages.append(str(e))
|
err_messages.append(str(e))
|
||||||
chains_formatted = []
|
chains_formatted = []
|
||||||
electrum_supported_coins = (
|
|
||||||
"bitcoin",
|
|
||||||
"litecoin",
|
|
||||||
)
|
|
||||||
|
|
||||||
sorted_names = sorted(swap_client.settings["chainclients"].keys())
|
sorted_names = sorted(swap_client.settings["chainclients"].keys())
|
||||||
from basicswap.interface.electrumx import (
|
|
||||||
DEFAULT_ELECTRUM_SERVERS,
|
|
||||||
DEFAULT_ONION_SERVERS,
|
|
||||||
)
|
|
||||||
|
|
||||||
for name in sorted_names:
|
for name in sorted_names:
|
||||||
c = swap_client.settings["chainclients"][name]
|
c = swap_client.settings["chainclients"][name]
|
||||||
try:
|
try:
|
||||||
display_name = getCoinName(swap_client.getCoinIdFromName(name))
|
display_name = getCoinName(swap_client.getCoinIdFromName(name))
|
||||||
except Exception:
|
except Exception:
|
||||||
display_name = name
|
display_name = name
|
||||||
|
|
||||||
clearnet_servers = c.get("electrum_clearnet_servers", None)
|
|
||||||
onion_servers = c.get("electrum_onion_servers", None)
|
|
||||||
|
|
||||||
if not clearnet_servers:
|
|
||||||
default_clearnet = DEFAULT_ELECTRUM_SERVERS.get(name, [])
|
|
||||||
clearnet_servers = [
|
|
||||||
f"{s['host']}:{s['port']}:{str(s.get('ssl', True)).lower()}"
|
|
||||||
for s in default_clearnet
|
|
||||||
]
|
|
||||||
if not onion_servers:
|
|
||||||
default_onion = DEFAULT_ONION_SERVERS.get(name, [])
|
|
||||||
onion_servers = [
|
|
||||||
f"{s['host']}:{s['port']}:{str(s.get('ssl', False)).lower()}"
|
|
||||||
for s in default_onion
|
|
||||||
]
|
|
||||||
|
|
||||||
clearnet_text = "\n".join(clearnet_servers) if clearnet_servers else ""
|
|
||||||
onion_text = "\n".join(onion_servers) if onion_servers else ""
|
|
||||||
|
|
||||||
chains_formatted.append(
|
chains_formatted.append(
|
||||||
{
|
{
|
||||||
"name": name,
|
"name": name,
|
||||||
@@ -292,10 +176,6 @@ def page_settings(self, url_split, post_string):
|
|||||||
"lookups": c.get("chain_lookups", "local"),
|
"lookups": c.get("chain_lookups", "local"),
|
||||||
"manage_daemon": c.get("manage_daemon", "Unknown"),
|
"manage_daemon": c.get("manage_daemon", "Unknown"),
|
||||||
"connection_type": c.get("connection_type", "Unknown"),
|
"connection_type": c.get("connection_type", "Unknown"),
|
||||||
"supports_electrum": name in electrum_supported_coins,
|
|
||||||
"clearnet_servers_text": clearnet_text,
|
|
||||||
"onion_servers_text": onion_text,
|
|
||||||
"address_gap_limit": c.get("address_gap_limit", 50),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if name in ("monero", "wownero"):
|
if name in ("monero", "wownero"):
|
||||||
@@ -323,14 +203,6 @@ def page_settings(self, url_split, post_string):
|
|||||||
else:
|
else:
|
||||||
chains_formatted[-1]["can_disable"] = True
|
chains_formatted[-1]["can_disable"] = True
|
||||||
|
|
||||||
try:
|
|
||||||
coin_id = swap_client.getCoinIdFromName(name)
|
|
||||||
lite_balance_info = swap_client.getLiteWalletBalanceInfo(coin_id)
|
|
||||||
if lite_balance_info:
|
|
||||||
chains_formatted[-1]["lite_wallet_balance"] = lite_balance_info
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
general_settings = {
|
general_settings = {
|
||||||
"debug": swap_client.debug,
|
"debug": swap_client.debug,
|
||||||
"debug_ui": swap_client.debug_ui,
|
"debug_ui": swap_client.debug_ui,
|
||||||
|
|||||||
+6
-202
@@ -32,15 +32,11 @@ DONATION_ADDRESSES = {
|
|||||||
|
|
||||||
|
|
||||||
def format_wallet_data(swap_client, ci, w):
|
def format_wallet_data(swap_client, ci, w):
|
||||||
coin_id = ci.coin_type()
|
|
||||||
connection_type = swap_client.coin_clients.get(coin_id, {}).get(
|
|
||||||
"connection_type", w.get("connection_type", "rpc")
|
|
||||||
)
|
|
||||||
wf = {
|
wf = {
|
||||||
"name": ci.coin_name(),
|
"name": ci.coin_name(),
|
||||||
"version": w.get("version", "?"),
|
"version": w.get("version", "?"),
|
||||||
"ticker": ci.ticker_mainnet(),
|
"ticker": ci.ticker_mainnet(),
|
||||||
"cid": str(int(coin_id)),
|
"cid": str(int(ci.coin_type())),
|
||||||
"balance": w.get("balance", "?"),
|
"balance": w.get("balance", "?"),
|
||||||
"blocks": w.get("blocks", "?"),
|
"blocks": w.get("blocks", "?"),
|
||||||
"synced": w.get("synced", "?"),
|
"synced": w.get("synced", "?"),
|
||||||
@@ -49,7 +45,6 @@ def format_wallet_data(swap_client, ci, w):
|
|||||||
"locked": w.get("locked", "?"),
|
"locked": w.get("locked", "?"),
|
||||||
"updating": w.get("updating", "?"),
|
"updating": w.get("updating", "?"),
|
||||||
"havedata": True,
|
"havedata": True,
|
||||||
"connection_type": connection_type,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if "wallet_blocks" in w:
|
if "wallet_blocks" in w:
|
||||||
@@ -75,9 +70,6 @@ def format_wallet_data(swap_client, ci, w):
|
|||||||
if pending > 0.0:
|
if pending > 0.0:
|
||||||
wf["pending"] = ci.format_amount(pending)
|
wf["pending"] = ci.format_amount(pending)
|
||||||
|
|
||||||
if "unconfirmed" in w and float(w["unconfirmed"]) < 0.0:
|
|
||||||
wf["pending_out"] = ci.format_amount(abs(ci.make_int(w["unconfirmed"])))
|
|
||||||
|
|
||||||
if ci.coin_type() == Coins.PART:
|
if ci.coin_type() == Coins.PART:
|
||||||
wf["stealth_address"] = w.get("stealth_address", "?")
|
wf["stealth_address"] = w.get("stealth_address", "?")
|
||||||
wf["blind_balance"] = w.get("blind_balance", "?")
|
wf["blind_balance"] = w.get("blind_balance", "?")
|
||||||
@@ -90,97 +82,11 @@ def format_wallet_data(swap_client, ci, w):
|
|||||||
wf["mweb_address"] = w.get("mweb_address", "?")
|
wf["mweb_address"] = w.get("mweb_address", "?")
|
||||||
wf["mweb_balance"] = w.get("mweb_balance", "?")
|
wf["mweb_balance"] = w.get("mweb_balance", "?")
|
||||||
wf["mweb_pending"] = w.get("mweb_pending", "?")
|
wf["mweb_pending"] = w.get("mweb_pending", "?")
|
||||||
elif ci.coin_type() == Coins.FIRO:
|
|
||||||
wf["spark_address"] = w.get("spark_address", "?")
|
|
||||||
wf["spark_balance"] = w.get("spark_balance", "?")
|
|
||||||
wf["spark_pending"] = w.get("spark_pending", "?")
|
|
||||||
|
|
||||||
if hasattr(ci, "getScanStatus"):
|
|
||||||
wf["scan_status"] = ci.getScanStatus()
|
|
||||||
|
|
||||||
if connection_type == "electrum" and hasattr(ci, "_backend") and ci._backend:
|
|
||||||
backend = ci._backend
|
|
||||||
wf["electrum_server"] = backend.getServerHost()
|
|
||||||
wf["electrum_version"] = backend.getServerVersion()
|
|
||||||
try:
|
|
||||||
conn_status = backend.getConnectionStatus()
|
|
||||||
wf["electrum_connected"] = conn_status.get("connected", False)
|
|
||||||
wf["electrum_failures"] = conn_status.get("failures", 0)
|
|
||||||
wf["electrum_using_defaults"] = conn_status.get("using_defaults", True)
|
|
||||||
wf["electrum_all_failed"] = conn_status.get("all_failed", False)
|
|
||||||
wf["electrum_last_error"] = conn_status.get("last_error")
|
|
||||||
if conn_status.get("connected"):
|
|
||||||
wf["electrum_status"] = "connected"
|
|
||||||
elif conn_status.get("all_failed"):
|
|
||||||
wf["electrum_status"] = "all_failed"
|
|
||||||
else:
|
|
||||||
wf["electrum_status"] = "disconnected"
|
|
||||||
except Exception:
|
|
||||||
wf["electrum_connected"] = False
|
|
||||||
wf["electrum_status"] = "error"
|
|
||||||
try:
|
|
||||||
sync_status = backend.getSyncStatus()
|
|
||||||
wf["electrum_synced"] = sync_status.get("synced", False)
|
|
||||||
wf["electrum_height"] = sync_status.get("height", 0)
|
|
||||||
except Exception:
|
|
||||||
wf["electrum_synced"] = False
|
|
||||||
wf["electrum_height"] = 0
|
|
||||||
|
|
||||||
checkAddressesOwned(swap_client, ci, wf)
|
checkAddressesOwned(swap_client, ci, wf)
|
||||||
return wf
|
return wf
|
||||||
|
|
||||||
|
|
||||||
def format_transactions(ci, transactions, coin_id):
|
|
||||||
formatted_txs = []
|
|
||||||
|
|
||||||
if coin_id in (Coins.XMR, Coins.WOW):
|
|
||||||
for tx in transactions:
|
|
||||||
tx_type = tx.get("type", "")
|
|
||||||
direction = (
|
|
||||||
"Incoming"
|
|
||||||
if tx_type == "in"
|
|
||||||
else "Outgoing" if tx_type == "out" else tx_type.capitalize()
|
|
||||||
)
|
|
||||||
|
|
||||||
formatted_txs.append(
|
|
||||||
{
|
|
||||||
"txid": tx.get("txid", ""),
|
|
||||||
"type": direction,
|
|
||||||
"amount": ci.format_amount(tx.get("amount", 0)),
|
|
||||||
"confirmations": tx.get("confirmations", 0),
|
|
||||||
"timestamp": format_timestamp(tx.get("timestamp", 0)),
|
|
||||||
"height": tx.get("height", 0),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
for tx in transactions:
|
|
||||||
category = tx.get("category", "")
|
|
||||||
if category == "send":
|
|
||||||
direction = "Outgoing"
|
|
||||||
amount = abs(tx.get("amount", 0))
|
|
||||||
elif category == "receive":
|
|
||||||
direction = "Incoming"
|
|
||||||
amount = tx.get("amount", 0)
|
|
||||||
else:
|
|
||||||
direction = category.capitalize()
|
|
||||||
amount = abs(tx.get("amount", 0))
|
|
||||||
|
|
||||||
formatted_txs.append(
|
|
||||||
{
|
|
||||||
"txid": tx.get("txid", ""),
|
|
||||||
"type": direction,
|
|
||||||
"amount": ci.format_amount(ci.make_int(amount)),
|
|
||||||
"confirmations": tx.get("confirmations", 0),
|
|
||||||
"timestamp": format_timestamp(
|
|
||||||
tx.get("time", tx.get("timereceived", 0))
|
|
||||||
),
|
|
||||||
"address": tx.get("address", ""),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return formatted_txs
|
|
||||||
|
|
||||||
|
|
||||||
def page_wallets(self, url_split, post_string):
|
def page_wallets(self, url_split, post_string):
|
||||||
server = self.server
|
server = self.server
|
||||||
swap_client = server.swap_client
|
swap_client = server.swap_client
|
||||||
@@ -225,7 +131,6 @@ def page_wallets(self, url_split, post_string):
|
|||||||
"err_messages": err_messages,
|
"err_messages": err_messages,
|
||||||
"wallets": wallets_formatted,
|
"wallets": wallets_formatted,
|
||||||
"summary": summary,
|
"summary": summary,
|
||||||
"use_tor": getattr(swap_client, "use_tor_proxy", False),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -246,25 +151,8 @@ def page_wallet(self, url_split, post_string):
|
|||||||
show_utxo_groups: bool = False
|
show_utxo_groups: bool = False
|
||||||
withdrawal_successful: bool = False
|
withdrawal_successful: bool = False
|
||||||
force_refresh: bool = False
|
force_refresh: bool = False
|
||||||
|
|
||||||
tx_filters = {
|
|
||||||
"page_no": 1,
|
|
||||||
"limit": 30,
|
|
||||||
"offset": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
form_data = self.checkForm(post_string, "wallet", err_messages)
|
form_data = self.checkForm(post_string, "wallet", err_messages)
|
||||||
if form_data:
|
if form_data:
|
||||||
if have_data_entry(form_data, "pageback"):
|
|
||||||
tx_filters["page_no"] = int(form_data[b"pageno"][0]) - 1
|
|
||||||
if tx_filters["page_no"] < 1:
|
|
||||||
tx_filters["page_no"] = 1
|
|
||||||
elif have_data_entry(form_data, "pageforwards"):
|
|
||||||
tx_filters["page_no"] = int(form_data[b"pageno"][0]) + 1
|
|
||||||
|
|
||||||
if tx_filters["page_no"] > 1:
|
|
||||||
tx_filters["offset"] = (tx_filters["page_no"] - 1) * 30
|
|
||||||
|
|
||||||
cid = str(int(coin_id))
|
cid = str(int(coin_id))
|
||||||
|
|
||||||
estimate_fee: bool = have_data_entry(form_data, "estfee_" + cid)
|
estimate_fee: bool = have_data_entry(form_data, "estfee_" + cid)
|
||||||
@@ -273,13 +161,8 @@ 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):
|
|
||||||
swap_client.cacheNewStealthAddressForCoin(coin_id)
|
|
||||||
elif have_data_entry(form_data, "reseed_" + cid):
|
elif have_data_entry(form_data, "reseed_" + cid):
|
||||||
try:
|
try:
|
||||||
swap_client.reseedWallet(coin_id)
|
swap_client.reseedWallet(coin_id)
|
||||||
@@ -287,22 +170,6 @@ def page_wallet(self, url_split, post_string):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
err_messages.append("Reseed failed " + str(ex))
|
err_messages.append("Reseed failed " + str(ex))
|
||||||
swap_client.updateWalletsInfo(True, coin_id)
|
swap_client.updateWalletsInfo(True, coin_id)
|
||||||
elif have_data_entry(form_data, "importkey_" + cid):
|
|
||||||
try:
|
|
||||||
wif_key = form_data[bytes("wifkey_" + cid, "utf-8")][0].decode("utf-8")
|
|
||||||
if wif_key:
|
|
||||||
result = swap_client.importWIFKey(coin_id, wif_key)
|
|
||||||
if result.get("success"):
|
|
||||||
messages.append(
|
|
||||||
f"Imported key for address: {result['address']}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
err_messages.append(f"Import failed: {result.get('error')}")
|
|
||||||
else:
|
|
||||||
err_messages.append("Missing WIF key")
|
|
||||||
except Exception as ex:
|
|
||||||
err_messages.append(f"Import failed: {ex}")
|
|
||||||
swap_client.updateWalletsInfo(True, coin_id)
|
|
||||||
elif withdraw or estimate_fee:
|
elif withdraw or estimate_fee:
|
||||||
subfee = True if have_data_entry(form_data, "subfee_" + cid) else False
|
subfee = True if have_data_entry(form_data, "subfee_" + cid) else False
|
||||||
page_data["wd_subfee_" + cid] = subfee
|
page_data["wd_subfee_" + cid] = subfee
|
||||||
@@ -341,20 +208,13 @@ def page_wallet(self, url_split, post_string):
|
|||||||
page_data["wd_type_to_" + cid] = type_to
|
page_data["wd_type_to_" + cid] = type_to
|
||||||
except Exception as e: # noqa: F841
|
except Exception as e: # noqa: F841
|
||||||
err_messages.append("Missing type")
|
err_messages.append("Missing type")
|
||||||
elif coin_id in (Coins.LTC, Coins.FIRO):
|
elif coin_id == Coins.LTC:
|
||||||
try:
|
try:
|
||||||
type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][
|
type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][
|
||||||
0
|
0
|
||||||
].decode("utf-8")
|
].decode("utf-8")
|
||||||
page_data["wd_type_from_" + cid] = type_from
|
page_data["wd_type_from_" + cid] = type_from
|
||||||
except Exception as e: # noqa: F841
|
except Exception as e: # noqa: F841
|
||||||
if (
|
|
||||||
swap_client.coin_clients[coin_id].get("connection_type")
|
|
||||||
== "electrum"
|
|
||||||
):
|
|
||||||
type_from = "plain"
|
|
||||||
page_data["wd_type_from_" + cid] = type_from
|
|
||||||
else:
|
|
||||||
err_messages.append("Missing type")
|
err_messages.append("Missing type")
|
||||||
|
|
||||||
if len(err_messages) == 0:
|
if len(err_messages) == 0:
|
||||||
@@ -370,9 +230,9 @@ def page_wallet(self, url_split, post_string):
|
|||||||
value, ticker, type_from, type_to, address, txid
|
value, ticker, type_from, type_to, address, txid
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif coin_id in (Coins.LTC, Coins.FIRO):
|
elif coin_id == Coins.LTC:
|
||||||
txid = swap_client.withdrawCoinExtended(
|
txid = swap_client.withdrawLTC(
|
||||||
coin_id, type_from, value, address, subfee
|
type_from, value, address, subfee
|
||||||
)
|
)
|
||||||
messages.append(
|
messages.append(
|
||||||
"Withdrew {} {} (from {}) to address {}<br/>In txid: {}".format(
|
"Withdrew {} {} (from {}) to address {}<br/>In txid: {}".format(
|
||||||
@@ -445,12 +305,8 @@ def page_wallet(self, url_split, post_string):
|
|||||||
if swap_client.debug is True:
|
if swap_client.debug is True:
|
||||||
swap_client.log.error(traceback.format_exc())
|
swap_client.log.error(traceback.format_exc())
|
||||||
|
|
||||||
is_electrum_mode = (
|
|
||||||
swap_client.coin_clients.get(coin_id, {}).get("connection_type") == "electrum"
|
|
||||||
)
|
|
||||||
|
|
||||||
swap_client.updateWalletsInfo(
|
swap_client.updateWalletsInfo(
|
||||||
force_refresh, only_coin=coin_id, wait_for_complete=not is_electrum_mode
|
force_refresh, only_coin=coin_id, wait_for_complete=True
|
||||||
)
|
)
|
||||||
wallets = swap_client.getCachedWalletsInfo({"coin_id": coin_id})
|
wallets = swap_client.getCachedWalletsInfo({"coin_id": coin_id})
|
||||||
wallet_data = {}
|
wallet_data = {}
|
||||||
@@ -472,18 +328,6 @@ def page_wallet(self, url_split, post_string):
|
|||||||
cid = str(int(coin_id))
|
cid = str(int(coin_id))
|
||||||
|
|
||||||
wallet_data = format_wallet_data(swap_client, ci, w)
|
wallet_data = format_wallet_data(swap_client, ci, w)
|
||||||
wallet_data["is_electrum_mode"] = (
|
|
||||||
getattr(ci, "_connection_type", "rpc") == "electrum"
|
|
||||||
)
|
|
||||||
|
|
||||||
if hasattr(ci, "getAccountKey") and k not in (Coins.XMR, Coins.WOW):
|
|
||||||
try:
|
|
||||||
chain = swap_client.chain
|
|
||||||
zprv_prefix = 0x04B2430C if chain == "mainnet" else 0x045F18BC
|
|
||||||
seed_key = swap_client.getWalletKey(k, 1)
|
|
||||||
wallet_data["account_key"] = ci.getAccountKey(seed_key, zprv_prefix)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
fee_rate, fee_src = swap_client.getFeeRateForCoin(k)
|
fee_rate, fee_src = swap_client.getFeeRateForCoin(k)
|
||||||
est_fee = swap_client.estimateWithdrawFee(k, fee_rate)
|
est_fee = swap_client.estimateWithdrawFee(k, fee_rate)
|
||||||
@@ -498,8 +342,6 @@ def page_wallet(self, url_split, post_string):
|
|||||||
wallet_data["main_address"] = w.get("main_address", "Refresh necessary")
|
wallet_data["main_address"] = w.get("main_address", "Refresh necessary")
|
||||||
elif k == Coins.LTC:
|
elif k == Coins.LTC:
|
||||||
wallet_data["mweb_address"] = w.get("mweb_address", "Refresh necessary")
|
wallet_data["mweb_address"] = w.get("mweb_address", "Refresh necessary")
|
||||||
elif k == Coins.FIRO:
|
|
||||||
wallet_data["spark_address"] = w.get("spark_address", "Refresh necessary")
|
|
||||||
|
|
||||||
if "wd_type_from_" + cid in page_data:
|
if "wd_type_from_" + cid in page_data:
|
||||||
wallet_data["wd_type_from"] = page_data["wd_type_from_" + cid]
|
wallet_data["wd_type_from"] = page_data["wd_type_from_" + cid]
|
||||||
@@ -528,10 +370,6 @@ 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()
|
||||||
@@ -562,33 +400,6 @@ def page_wallet(self, url_split, post_string):
|
|||||||
"coin_name": wallet_data.get("name", ticker),
|
"coin_name": wallet_data.get("name", ticker),
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions = []
|
|
||||||
total_transactions = 0
|
|
||||||
is_electrum_mode = False
|
|
||||||
legacy_funds_info = None
|
|
||||||
if wallet_data.get("havedata", False) and not wallet_data.get("error"):
|
|
||||||
try:
|
|
||||||
ci = swap_client.ci(coin_id)
|
|
||||||
is_electrum_mode = getattr(ci, "_connection_type", "rpc") == "electrum"
|
|
||||||
if not is_electrum_mode:
|
|
||||||
count = tx_filters.get("limit", 30)
|
|
||||||
skip = tx_filters.get("offset", 0)
|
|
||||||
|
|
||||||
all_txs = ci.listWalletTransactions(count=10000, skip=0)
|
|
||||||
if all_txs and coin_id not in (Coins.XMR, Coins.WOW):
|
|
||||||
all_txs = list(reversed(all_txs))
|
|
||||||
elif not all_txs:
|
|
||||||
all_txs = []
|
|
||||||
total_transactions = len(all_txs)
|
|
||||||
|
|
||||||
raw_txs = all_txs[skip : skip + count] if all_txs else []
|
|
||||||
transactions = format_transactions(ci, raw_txs, coin_id)
|
|
||||||
else:
|
|
||||||
if coin_id in (Coins.BTC, Coins.LTC):
|
|
||||||
legacy_funds_info = swap_client.getElectrumLegacyFundsInfo(coin_id)
|
|
||||||
except Exception as e:
|
|
||||||
swap_client.log.warning(f"Failed to fetch transactions for {ticker}: {e}")
|
|
||||||
|
|
||||||
template = server.env.get_template("wallet.html")
|
template = server.env.get_template("wallet.html")
|
||||||
return self.render_template(
|
return self.render_template(
|
||||||
template,
|
template,
|
||||||
@@ -600,12 +411,5 @@ def page_wallet(self, url_split, post_string):
|
|||||||
"block_unknown_seeds": swap_client._restrict_unknown_seed_wallets,
|
"block_unknown_seeds": swap_client._restrict_unknown_seed_wallets,
|
||||||
"donation_info": donation_info,
|
"donation_info": donation_info,
|
||||||
"debug_ui": swap_client.debug_ui,
|
"debug_ui": swap_client.debug_ui,
|
||||||
"transactions": transactions,
|
|
||||||
"tx_page_no": tx_filters.get("page_no", 1),
|
|
||||||
"tx_total": total_transactions,
|
|
||||||
"tx_limit": tx_filters.get("limit", 30),
|
|
||||||
"is_electrum_mode": is_electrum_mode,
|
|
||||||
"legacy_funds_info": legacy_funds_info,
|
|
||||||
"use_tor": getattr(swap_client, "use_tor_proxy", False),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+8
-46
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ def describeBid(
|
|||||||
elif bid.state == BidStates.BID_ABANDONED:
|
elif bid.state == BidStates.BID_ABANDONED:
|
||||||
state_description = "Bid abandoned"
|
state_description = "Bid abandoned"
|
||||||
elif bid.state == BidStates.BID_ERROR:
|
elif bid.state == BidStates.BID_ERROR:
|
||||||
state_description = "Bid error"
|
state_description = bid.state_note
|
||||||
elif offer.swap_type == SwapTypes.XMR_SWAP:
|
elif offer.swap_type == SwapTypes.XMR_SWAP:
|
||||||
if bid.state == BidStates.BID_SENT:
|
if bid.state == BidStates.BID_SENT:
|
||||||
state_description = "Waiting for offerer to accept"
|
state_description = "Waiting for offerer to accept"
|
||||||
@@ -331,7 +331,6 @@ def describeBid(
|
|||||||
"ticker_from": ci_from.ticker(),
|
"ticker_from": ci_from.ticker(),
|
||||||
"ticker_to": ci_to.ticker(),
|
"ticker_to": ci_to.ticker(),
|
||||||
"bid_state": strBidState(bid.state),
|
"bid_state": strBidState(bid.state),
|
||||||
"bid_state_ind": int(bid.state),
|
|
||||||
"state_description": state_description,
|
"state_description": state_description,
|
||||||
"itx_state": strTxState(bid.getITxState()),
|
"itx_state": strTxState(bid.getITxState()),
|
||||||
"ptx_state": strTxState(bid.getPTxState()),
|
"ptx_state": strTxState(bid.getPTxState()),
|
||||||
@@ -344,8 +343,6 @@ def describeBid(
|
|||||||
if for_api
|
if for_api
|
||||||
else format_timestamp(bid.created_at, with_seconds=True)
|
else format_timestamp(bid.created_at, with_seconds=True)
|
||||||
),
|
),
|
||||||
"created_at_timestamp": bid.created_at,
|
|
||||||
"state_time_timestamp": getLastStateTimestamp(bid),
|
|
||||||
"expired_at": (
|
"expired_at": (
|
||||||
bid.expire_at
|
bid.expire_at
|
||||||
if for_api
|
if for_api
|
||||||
@@ -626,14 +623,6 @@ def listOldBidStates(bid):
|
|||||||
return old_states
|
return old_states
|
||||||
|
|
||||||
|
|
||||||
def getLastStateTimestamp(bid):
|
|
||||||
if not bid.states or len(bid.states) < 12:
|
|
||||||
return None
|
|
||||||
num_states = len(bid.states) // 12
|
|
||||||
last_entry = struct.unpack_from("<iq", bid.states[(num_states - 1) * 12 :])
|
|
||||||
return last_entry[1]
|
|
||||||
|
|
||||||
|
|
||||||
def getCoinName(c):
|
def getCoinName(c):
|
||||||
if c == Coins.PART_ANON:
|
if c == Coins.PART_ANON:
|
||||||
return chainparams[Coins.PART]["name"].capitalize() + " Anon"
|
return chainparams[Coins.PART]["name"].capitalize() + " Anon"
|
||||||
@@ -654,7 +643,7 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False):
|
|||||||
for k, v in swap_client.coin_clients.items():
|
for k, v in swap_client.coin_clients.items():
|
||||||
if k not in chainparams:
|
if k not in chainparams:
|
||||||
continue
|
continue
|
||||||
if v["connection_type"] in ("rpc", "electrum"):
|
if v["connection_type"] == "rpc":
|
||||||
coins.append((int(k), getCoinName(k)))
|
coins.append((int(k), getCoinName(k)))
|
||||||
if split_from:
|
if split_from:
|
||||||
coins_from.append(coins[-1])
|
coins_from.append(coins[-1])
|
||||||
@@ -681,7 +670,7 @@ def listAvailableCoinsWithBalances(swap_client, with_variants=True, split_from=F
|
|||||||
for k, v in swap_client.coin_clients.items():
|
for k, v in swap_client.coin_clients.items():
|
||||||
if k not in chainparams:
|
if k not in chainparams:
|
||||||
continue
|
continue
|
||||||
if v["connection_type"] in ("rpc", "electrum"):
|
if v["connection_type"] == "rpc":
|
||||||
|
|
||||||
balance = "0.0"
|
balance = "0.0"
|
||||||
if k in wallets:
|
if k in wallets:
|
||||||
@@ -746,22 +735,9 @@ def checkAddressesOwned(swap_client, ci, wallet_info):
|
|||||||
|
|
||||||
if wallet_info["stealth_address"] != "?":
|
if wallet_info["stealth_address"] != "?":
|
||||||
if not ci.isAddressMine(wallet_info["stealth_address"]):
|
if not ci.isAddressMine(wallet_info["stealth_address"]):
|
||||||
ci._log.warning(
|
ci._log.error(
|
||||||
"Unowned stealth address: {} - clearing cache and regenerating".format(
|
"Unowned stealth address: {}".format(wallet_info["stealth_address"])
|
||||||
wallet_info["stealth_address"]
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
key_str = "stealth_addr_" + ci.coin_name().lower()
|
|
||||||
swap_client.clearStringKV(key_str)
|
|
||||||
try:
|
|
||||||
new_addr = ci.getNewStealthAddress()
|
|
||||||
swap_client.setStringKV(key_str, new_addr)
|
|
||||||
wallet_info["stealth_address"] = new_addr
|
|
||||||
ci._log.info(
|
|
||||||
"Regenerated stealth address for {}".format(ci.coin_name())
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
ci._log.error("Failed to regenerate stealth address: {}".format(e))
|
|
||||||
wallet_info["stealth_address"] = "Error: unowned address"
|
wallet_info["stealth_address"] = "Error: unowned address"
|
||||||
elif (
|
elif (
|
||||||
swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed()
|
swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed()
|
||||||
@@ -771,23 +747,9 @@ def checkAddressesOwned(swap_client, ci, wallet_info):
|
|||||||
if "deposit_address" in wallet_info:
|
if "deposit_address" in wallet_info:
|
||||||
if wallet_info["deposit_address"] != "Refresh necessary":
|
if wallet_info["deposit_address"] != "Refresh necessary":
|
||||||
if not ci.isAddressMine(wallet_info["deposit_address"]):
|
if not ci.isAddressMine(wallet_info["deposit_address"]):
|
||||||
ci._log.warning(
|
ci._log.error(
|
||||||
"Unowned deposit address: {} - clearing cache and regenerating".format(
|
"Unowned deposit address: {}".format(wallet_info["deposit_address"])
|
||||||
wallet_info["deposit_address"]
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
key_str = "receive_addr_" + ci.coin_name().lower()
|
|
||||||
swap_client.clearStringKV(key_str)
|
|
||||||
try:
|
|
||||||
coin_type = ci.coin_type()
|
|
||||||
new_addr = swap_client.getReceiveAddressForCoin(coin_type)
|
|
||||||
swap_client.setStringKV(key_str, new_addr)
|
|
||||||
wallet_info["deposit_address"] = new_addr
|
|
||||||
ci._log.info(
|
|
||||||
"Regenerated deposit address for {}".format(ci.coin_name())
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
ci._log.error("Failed to regenerate deposit address: {}".format(e))
|
|
||||||
wallet_info["deposit_address"] = "Error: unowned address"
|
wallet_info["deposit_address"] = "Error: unowned address"
|
||||||
elif (
|
elif (
|
||||||
swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed()
|
swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import json
|
|||||||
import time
|
import time
|
||||||
import decimal
|
import decimal
|
||||||
|
|
||||||
|
|
||||||
COIN = 100000000
|
COIN = 100000000
|
||||||
|
|
||||||
|
|
||||||
@@ -189,13 +190,10 @@ def format_amount(i: int, display_scale: int, scale: int = None) -> str:
|
|||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
def format_timestamp(
|
def format_timestamp(value: int, with_seconds: bool = False) -> str:
|
||||||
value: int, with_seconds: bool = False, with_timezone: bool = False
|
|
||||||
) -> str:
|
|
||||||
str_format = "%Y-%m-%d %H:%M"
|
str_format = "%Y-%m-%d %H:%M"
|
||||||
if with_seconds:
|
if with_seconds:
|
||||||
str_format += ":%S"
|
str_format += ":%S"
|
||||||
if with_timezone:
|
|
||||||
str_format += " %z"
|
str_format += " %z"
|
||||||
return time.strftime(str_format, time.localtime(value))
|
return time.strftime(str_format, time.localtime(value))
|
||||||
|
|
||||||
|
|||||||
@@ -45,21 +45,3 @@ class BSXLogger(logging.Logger):
|
|||||||
def info_s(self, msg, *args, **kwargs):
|
def info_s(self, msg, *args, **kwargs):
|
||||||
if self.safe_logs is False:
|
if self.safe_logs is False:
|
||||||
self.info(msg, *args, **kwargs)
|
self.info(msg, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class BSXLogAdapter(logging.LoggerAdapter):
|
|
||||||
def __init__(self, logger, prefix):
|
|
||||||
super().__init__(logger, {})
|
|
||||||
self.prefix = prefix
|
|
||||||
|
|
||||||
def process(self, msg, kwargs):
|
|
||||||
return f"{self.prefix} {msg}", kwargs
|
|
||||||
|
|
||||||
def addr(self, addr: str) -> str:
|
|
||||||
return self.logger.addr(addr)
|
|
||||||
|
|
||||||
def id(self, concept_id: bytes, prefix: str = "") -> str:
|
|
||||||
return self.logger.id(concept_id, prefix)
|
|
||||||
|
|
||||||
def info_s(self, msg, *args, **kwargs):
|
|
||||||
return self.logger.info_s(msg, *args, **kwargs)
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from basicswap.contrib.test_framework.messages import (
|
|||||||
uint256_from_str,
|
uint256_from_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
AES_BLOCK_SIZE = 16
|
AES_BLOCK_SIZE = 16
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,823 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
|
||||||
# Distributed under the MIT software license, see the accompanying
|
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
|
||||||
|
|
||||||
import time
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from typing import Dict, List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class WalletBackend(ABC):
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def getBalance(self, addresses: List[str]) -> Dict[str, int]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def findAddressWithBalance(
|
|
||||||
self, addresses: List[str], min_balance: int
|
|
||||||
) -> Optional[tuple]:
|
|
||||||
balances = self.getBalance(addresses)
|
|
||||||
for addr, balance in balances.items():
|
|
||||||
if balance >= min_balance:
|
|
||||||
return (addr, balance)
|
|
||||||
return None
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def getUnspentOutputs(
|
|
||||||
self, addresses: List[str], min_confirmations: int = 0
|
|
||||||
) -> List[dict]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def broadcastTransaction(self, tx_hex: str) -> str:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def getTransaction(self, txid: str) -> Optional[dict]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def getTransactionRaw(self, txid: str) -> Optional[str]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def getBlockHeight(self) -> int:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def estimateFee(self, blocks: int = 6) -> int:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def isConnected(self) -> bool:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def getAddressHistory(self, address: str) -> List[dict]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FullNodeBackend(WalletBackend):
|
|
||||||
|
|
||||||
def __init__(self, rpc_client, coin_type, log):
|
|
||||||
self._rpc = rpc_client
|
|
||||||
self._coin_type = coin_type
|
|
||||||
self._log = log
|
|
||||||
|
|
||||||
def getBalance(self, addresses: List[str]) -> Dict[str, int]:
|
|
||||||
result = {}
|
|
||||||
for addr in addresses:
|
|
||||||
result[addr] = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
utxos = self._rpc("listunspent", [0, 9999999, addresses])
|
|
||||||
for utxo in utxos:
|
|
||||||
addr = utxo.get("address")
|
|
||||||
if addr in result:
|
|
||||||
result[addr] += int(utxo.get("amount", 0) * 1e8)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.warning(f"FullNodeBackend.getBalance error: {e}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getUnspentOutputs(
|
|
||||||
self, addresses: List[str], min_confirmations: int = 0
|
|
||||||
) -> List[dict]:
|
|
||||||
try:
|
|
||||||
utxos = self._rpc("listunspent", [min_confirmations, 9999999, addresses])
|
|
||||||
result = []
|
|
||||||
for utxo in utxos:
|
|
||||||
result.append(
|
|
||||||
{
|
|
||||||
"txid": utxo.get("txid"),
|
|
||||||
"vout": utxo.get("vout"),
|
|
||||||
"value": int(utxo.get("amount", 0) * 1e8),
|
|
||||||
"address": utxo.get("address"),
|
|
||||||
"confirmations": utxo.get("confirmations", 0),
|
|
||||||
"scriptPubKey": utxo.get("scriptPubKey"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
|
||||||
self._log.warning(f"FullNodeBackend.getUnspentOutputs error: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def broadcastTransaction(self, tx_hex: str) -> str:
|
|
||||||
return self._rpc("sendrawtransaction", [tx_hex])
|
|
||||||
|
|
||||||
def getTransaction(self, txid: str) -> Optional[dict]:
|
|
||||||
try:
|
|
||||||
return self._rpc("getrawtransaction", [txid, True])
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getTransactionRaw(self, txid: str) -> Optional[str]:
|
|
||||||
try:
|
|
||||||
return self._rpc("getrawtransaction", [txid, False])
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getBlockHeight(self) -> int:
|
|
||||||
return self._rpc("getblockcount")
|
|
||||||
|
|
||||||
def estimateFee(self, blocks: int = 6) -> int:
|
|
||||||
try:
|
|
||||||
result = self._rpc("estimatesmartfee", [blocks])
|
|
||||||
if "feerate" in result:
|
|
||||||
return int(result["feerate"] * 1e8 / 1000)
|
|
||||||
return 1
|
|
||||||
except Exception:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def isConnected(self) -> bool:
|
|
||||||
try:
|
|
||||||
self._rpc("getblockchaininfo")
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def getAddressHistory(self, address: str) -> List[dict]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def importAddress(self, address: str, label: str = "", rescan: bool = False):
|
|
||||||
try:
|
|
||||||
self._rpc("importaddress", [address, label, rescan])
|
|
||||||
except Exception as e:
|
|
||||||
if "already in wallet" not in str(e).lower():
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
class ElectrumBackend(WalletBackend):
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
coin_type,
|
|
||||||
log,
|
|
||||||
clearnet_servers=None,
|
|
||||||
onion_servers=None,
|
|
||||||
chain="mainnet",
|
|
||||||
proxy_host=None,
|
|
||||||
proxy_port=None,
|
|
||||||
):
|
|
||||||
from basicswap.interface.electrumx import ElectrumServer
|
|
||||||
from basicswap.chainparams import Coins, chainparams
|
|
||||||
|
|
||||||
self._coin_type = coin_type
|
|
||||||
self._log = log
|
|
||||||
self._subscribed_scripthashes = set()
|
|
||||||
|
|
||||||
coin_params = chainparams.get(coin_type, chainparams.get(Coins.BTC))
|
|
||||||
self._network_params = coin_params.get(chain, coin_params.get("mainnet", {}))
|
|
||||||
|
|
||||||
coin_name_map = {
|
|
||||||
Coins.BTC: "bitcoin",
|
|
||||||
Coins.LTC: "litecoin",
|
|
||||||
}
|
|
||||||
coin_name = coin_name_map.get(coin_type, "bitcoin")
|
|
||||||
|
|
||||||
self._host = "localhost"
|
|
||||||
self._port = 50002
|
|
||||||
self._use_ssl = True
|
|
||||||
|
|
||||||
self._server = ElectrumServer(
|
|
||||||
coin_name,
|
|
||||||
clearnet_servers=clearnet_servers,
|
|
||||||
onion_servers=onion_servers,
|
|
||||||
log=log,
|
|
||||||
proxy_host=proxy_host,
|
|
||||||
proxy_port=proxy_port,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._realtime_callback = None
|
|
||||||
self._address_to_scripthash = {}
|
|
||||||
|
|
||||||
self._cached_height = 0
|
|
||||||
self._cached_height_time = 0
|
|
||||||
self._height_cache_ttl = 5
|
|
||||||
|
|
||||||
self._cached_fee = {}
|
|
||||||
self._cached_fee_time = {}
|
|
||||||
self._fee_cache_ttl = 300
|
|
||||||
|
|
||||||
self._max_batch_size = 5
|
|
||||||
self._background_mode = False
|
|
||||||
|
|
||||||
def setBackgroundMode(self, enabled: bool):
|
|
||||||
self._background_mode = enabled
|
|
||||||
|
|
||||||
def _call(self, method: str, params: list = None, timeout: int = 10):
|
|
||||||
if self._background_mode and hasattr(self._server, "call_background"):
|
|
||||||
return self._server.call_background(method, params, timeout)
|
|
||||||
if hasattr(self._server, "call_user"):
|
|
||||||
return self._server.call_user(method, params, timeout)
|
|
||||||
return self._server.call(method, params, timeout)
|
|
||||||
|
|
||||||
def _call_batch(self, calls: list, timeout: int = 15):
|
|
||||||
if self._background_mode and hasattr(self._server, "call_batch_background"):
|
|
||||||
return self._server.call_batch_background(calls, timeout)
|
|
||||||
if hasattr(self._server, "call_batch_user"):
|
|
||||||
return self._server.call_batch_user(calls, timeout)
|
|
||||||
return self._server.call_batch(calls, timeout)
|
|
||||||
|
|
||||||
def _is_server_stopping(self) -> bool:
|
|
||||||
return getattr(self._server, "_stopping", False)
|
|
||||||
|
|
||||||
def _split_batch_call(
|
|
||||||
self, scripthashes: list, method: str, batch_size: int = None
|
|
||||||
) -> list:
|
|
||||||
if batch_size is None:
|
|
||||||
batch_size = self._max_batch_size
|
|
||||||
|
|
||||||
all_results = []
|
|
||||||
for i in range(0, len(scripthashes), batch_size):
|
|
||||||
if self._is_server_stopping():
|
|
||||||
self._log.debug("_split_batch_call: server stopping, aborting")
|
|
||||||
break
|
|
||||||
chunk = scripthashes[i : i + batch_size]
|
|
||||||
try:
|
|
||||||
calls = [(method, [sh]) for sh in chunk]
|
|
||||||
results = self._call_batch(calls)
|
|
||||||
all_results.extend(results)
|
|
||||||
except Exception:
|
|
||||||
if self._is_server_stopping():
|
|
||||||
self._log.debug(
|
|
||||||
"_split_batch_call: server stopping after batch failure, aborting"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
for sh in chunk:
|
|
||||||
if self._is_server_stopping():
|
|
||||||
self._log.debug(
|
|
||||||
"_split_batch_call: server stopping during fallback, aborting"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
result = self._call(method, [sh])
|
|
||||||
all_results.append(result)
|
|
||||||
except Exception:
|
|
||||||
all_results.append(None)
|
|
||||||
return all_results
|
|
||||||
|
|
||||||
def _isUnsupportedAddress(self, address: str) -> bool:
|
|
||||||
if address.startswith("ltcmweb1"):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _addressToScripthash(self, address: str) -> str:
|
|
||||||
from basicswap.interface.electrumx import scripthash_from_address
|
|
||||||
|
|
||||||
return scripthash_from_address(address, self._network_params)
|
|
||||||
|
|
||||||
def getBalance(self, addresses: List[str]) -> Dict[str, int]:
|
|
||||||
result = {}
|
|
||||||
for addr in addresses:
|
|
||||||
result[addr] = 0
|
|
||||||
|
|
||||||
if not addresses:
|
|
||||||
return result
|
|
||||||
|
|
||||||
addr_list = [addr for addr in addresses if not self._isUnsupportedAddress(addr)]
|
|
||||||
if not addr_list:
|
|
||||||
return result
|
|
||||||
|
|
||||||
addr_to_scripthash = {}
|
|
||||||
for addr in addr_list:
|
|
||||||
try:
|
|
||||||
addr_to_scripthash[addr] = self._addressToScripthash(addr)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f"getBalance: scripthash error for {addr[:10]}...: {e}")
|
|
||||||
|
|
||||||
if not addr_to_scripthash:
|
|
||||||
return result
|
|
||||||
|
|
||||||
scripthashes = list(addr_to_scripthash.values())
|
|
||||||
scripthash_to_addr = {v: k for k, v in addr_to_scripthash.items()}
|
|
||||||
|
|
||||||
batch_results = self._split_batch_call(
|
|
||||||
scripthashes, "blockchain.scripthash.get_balance"
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, balance in enumerate(batch_results):
|
|
||||||
if balance and isinstance(balance, dict):
|
|
||||||
addr = scripthash_to_addr.get(scripthashes[i])
|
|
||||||
if addr:
|
|
||||||
confirmed = balance.get("confirmed", 0)
|
|
||||||
unconfirmed = balance.get("unconfirmed", 0)
|
|
||||||
result[addr] = confirmed + unconfirmed
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getDetailedBalance(self, addresses: List[str]) -> Dict[str, dict]:
|
|
||||||
result = {}
|
|
||||||
for addr in addresses:
|
|
||||||
result[addr] = {"confirmed": 0, "unconfirmed": 0}
|
|
||||||
|
|
||||||
if not addresses:
|
|
||||||
return result
|
|
||||||
|
|
||||||
addr_list = [addr for addr in addresses if not self._isUnsupportedAddress(addr)]
|
|
||||||
if not addr_list:
|
|
||||||
return result
|
|
||||||
|
|
||||||
batch_size = self._max_batch_size
|
|
||||||
for batch_start in range(0, len(addr_list), batch_size):
|
|
||||||
if self._is_server_stopping():
|
|
||||||
break
|
|
||||||
batch = addr_list[batch_start : batch_start + batch_size]
|
|
||||||
|
|
||||||
addr_to_scripthash = {}
|
|
||||||
for addr in batch:
|
|
||||||
try:
|
|
||||||
addr_to_scripthash[addr] = self._addressToScripthash(addr)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(
|
|
||||||
f"getDetailedBalance: scripthash error for {addr[:10]}...: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not addr_to_scripthash:
|
|
||||||
continue
|
|
||||||
|
|
||||||
scripthashes = list(addr_to_scripthash.values())
|
|
||||||
scripthash_to_addr = {v: k for k, v in addr_to_scripthash.items()}
|
|
||||||
batch_success = False
|
|
||||||
|
|
||||||
for attempt in range(2):
|
|
||||||
try:
|
|
||||||
batch_results = self._server.get_balance_batch(scripthashes)
|
|
||||||
for i, balance in enumerate(batch_results):
|
|
||||||
if balance and isinstance(balance, dict):
|
|
||||||
addr = scripthash_to_addr.get(scripthashes[i])
|
|
||||||
if addr:
|
|
||||||
result[addr] = {
|
|
||||||
"confirmed": balance.get("confirmed", 0),
|
|
||||||
"unconfirmed": balance.get("unconfirmed", 0),
|
|
||||||
}
|
|
||||||
batch_success = True
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
if self._is_server_stopping():
|
|
||||||
break
|
|
||||||
if attempt == 0:
|
|
||||||
self._log.debug(
|
|
||||||
f"Batch detailed balance query failed, reconnecting: {e}"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
self._server.disconnect()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
time.sleep(0.5)
|
|
||||||
else:
|
|
||||||
self._log.debug(
|
|
||||||
f"Batch detailed balance query failed after retry, falling back: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not batch_success:
|
|
||||||
for addr, scripthash in addr_to_scripthash.items():
|
|
||||||
if self._is_server_stopping():
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
balance = self._call(
|
|
||||||
"blockchain.scripthash.get_balance", [scripthash]
|
|
||||||
)
|
|
||||||
if balance and isinstance(balance, dict):
|
|
||||||
result[addr] = {
|
|
||||||
"confirmed": balance.get("confirmed", 0),
|
|
||||||
"unconfirmed": balance.get("unconfirmed", 0),
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(
|
|
||||||
f"ElectrumBackend.getDetailedBalance error for {addr[:10]}...: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def findAddressWithBalance(
|
|
||||||
self, addresses: List[str], min_balance: int
|
|
||||||
) -> Optional[tuple]:
|
|
||||||
if not addresses:
|
|
||||||
return None
|
|
||||||
|
|
||||||
addr_list = [addr for addr in addresses if not self._isUnsupportedAddress(addr)]
|
|
||||||
if not addr_list:
|
|
||||||
return None
|
|
||||||
|
|
||||||
batch_size = 50
|
|
||||||
for batch_start in range(0, len(addr_list), batch_size):
|
|
||||||
batch = addr_list[batch_start : batch_start + batch_size]
|
|
||||||
|
|
||||||
addr_to_scripthash = {}
|
|
||||||
for addr in batch:
|
|
||||||
try:
|
|
||||||
addr_to_scripthash[addr] = self._addressToScripthash(addr)
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not addr_to_scripthash:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
scripthashes = list(addr_to_scripthash.values())
|
|
||||||
batch_results = self._server.get_balance_batch(scripthashes)
|
|
||||||
scripthash_to_addr = {v: k for k, v in addr_to_scripthash.items()}
|
|
||||||
|
|
||||||
for i, balance in enumerate(batch_results):
|
|
||||||
if balance and isinstance(balance, dict):
|
|
||||||
confirmed = balance.get("confirmed", 0)
|
|
||||||
unconfirmed = balance.get("unconfirmed", 0)
|
|
||||||
total = confirmed + unconfirmed
|
|
||||||
if total >= min_balance:
|
|
||||||
addr = scripthash_to_addr.get(scripthashes[i])
|
|
||||||
if addr:
|
|
||||||
return (addr, total)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f"findAddressWithBalance batch error: {e}")
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getUnspentOutputs(
|
|
||||||
self, addresses: List[str], min_confirmations: int = 0
|
|
||||||
) -> List[dict]:
|
|
||||||
result = []
|
|
||||||
if not addresses:
|
|
||||||
return result
|
|
||||||
|
|
||||||
try:
|
|
||||||
current_height = self.getBlockHeight()
|
|
||||||
|
|
||||||
for addr in addresses:
|
|
||||||
if self._isUnsupportedAddress(addr):
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
scripthash = self._addressToScripthash(addr)
|
|
||||||
utxos = self._call(
|
|
||||||
"blockchain.scripthash.listunspent", [scripthash]
|
|
||||||
)
|
|
||||||
if utxos:
|
|
||||||
for utxo in utxos:
|
|
||||||
height = utxo.get("height", 0)
|
|
||||||
if height <= 0:
|
|
||||||
confirmations = 0
|
|
||||||
else:
|
|
||||||
confirmations = current_height - height + 1
|
|
||||||
|
|
||||||
if confirmations >= min_confirmations:
|
|
||||||
result.append(
|
|
||||||
{
|
|
||||||
"txid": utxo.get("tx_hash"),
|
|
||||||
"vout": utxo.get("tx_pos"),
|
|
||||||
"value": utxo.get("value", 0),
|
|
||||||
"address": addr,
|
|
||||||
"confirmations": confirmations,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(
|
|
||||||
f"ElectrumBackend.getUnspentOutputs error for {addr[:10]}...: {e}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.warning(f"ElectrumBackend.getUnspentOutputs error: {e}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def broadcastTransaction(self, tx_hex: str) -> str:
|
|
||||||
import time
|
|
||||||
|
|
||||||
max_retries = 3
|
|
||||||
retry_delay = 0.5
|
|
||||||
|
|
||||||
for attempt in range(max_retries):
|
|
||||||
try:
|
|
||||||
result = self._server.call("blockchain.transaction.broadcast", [tx_hex])
|
|
||||||
if result:
|
|
||||||
return result
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = str(e).lower()
|
|
||||||
if any(
|
|
||||||
pattern in error_msg
|
|
||||||
for pattern in [
|
|
||||||
"missing inputs",
|
|
||||||
"bad-txns",
|
|
||||||
"txn-mempool-conflict",
|
|
||||||
"already in block chain",
|
|
||||||
"transaction already exists",
|
|
||||||
"insufficient fee",
|
|
||||||
"dust",
|
|
||||||
"non-bip68-final",
|
|
||||||
"non-final",
|
|
||||||
"locktime",
|
|
||||||
]
|
|
||||||
):
|
|
||||||
raise
|
|
||||||
if attempt < max_retries - 1:
|
|
||||||
self._log.debug(
|
|
||||||
f"broadcastTransaction retry {attempt + 1}/{max_retries}: {e}"
|
|
||||||
)
|
|
||||||
time.sleep(retry_delay * (2**attempt)) # Exponential backoff
|
|
||||||
continue
|
|
||||||
raise
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getTransaction(self, txid: str) -> Optional[dict]:
|
|
||||||
try:
|
|
||||||
return self._call("blockchain.transaction.get", [txid, True])
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getTransactionRaw(self, txid: str) -> Optional[str]:
|
|
||||||
try:
|
|
||||||
tx_hex = self._call("blockchain.transaction.get", [txid, False])
|
|
||||||
return tx_hex
|
|
||||||
except Exception as e:
|
|
||||||
self._log.warning(f"getTransactionRaw failed for {txid[:16]}...: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getTransactionBatch(self, txids: List[str]) -> Dict[str, Optional[dict]]:
|
|
||||||
result = {}
|
|
||||||
if not txids:
|
|
||||||
return result
|
|
||||||
|
|
||||||
try:
|
|
||||||
calls = [("blockchain.transaction.get", [txid, True]) for txid in txids]
|
|
||||||
responses = self._call_batch(calls)
|
|
||||||
for txid, tx_info in zip(txids, responses):
|
|
||||||
result[txid] = tx_info if tx_info else None
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f"getTransactionBatch error: {e}")
|
|
||||||
for txid in txids:
|
|
||||||
result[txid] = self.getTransaction(txid)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getTransactionBatchRaw(self, txids: List[str]) -> Dict[str, Optional[str]]:
|
|
||||||
result = {}
|
|
||||||
if not txids:
|
|
||||||
return result
|
|
||||||
|
|
||||||
try:
|
|
||||||
calls = [("blockchain.transaction.get", [txid, False]) for txid in txids]
|
|
||||||
responses = self._call_batch(calls)
|
|
||||||
for txid, tx_hex in zip(txids, responses):
|
|
||||||
result[txid] = tx_hex if tx_hex else None
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f"getTransactionBatchRaw error: {e}")
|
|
||||||
for txid in txids:
|
|
||||||
result[txid] = self.getTransactionRaw(txid)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getBlockHeight(self) -> int:
|
|
||||||
import time
|
|
||||||
|
|
||||||
if hasattr(self._server, "get_subscribed_height"):
|
|
||||||
subscribed_height = self._server.get_subscribed_height()
|
|
||||||
if subscribed_height > 0:
|
|
||||||
if subscribed_height > self._cached_height:
|
|
||||||
self._cached_height = subscribed_height
|
|
||||||
self._cached_height_time = time.time()
|
|
||||||
return subscribed_height
|
|
||||||
|
|
||||||
now = time.time()
|
|
||||||
if (
|
|
||||||
self._cached_height > 0
|
|
||||||
and (now - self._cached_height_time) < self._height_cache_ttl
|
|
||||||
):
|
|
||||||
return self._cached_height
|
|
||||||
|
|
||||||
try:
|
|
||||||
header = self._call("blockchain.headers.subscribe", [])
|
|
||||||
if header:
|
|
||||||
height = header.get("height", 0)
|
|
||||||
if height > 0:
|
|
||||||
self._cached_height = height
|
|
||||||
self._cached_height_time = now
|
|
||||||
return height
|
|
||||||
return self._cached_height if self._cached_height > 0 else 0
|
|
||||||
except Exception:
|
|
||||||
return self._cached_height if self._cached_height > 0 else 0
|
|
||||||
|
|
||||||
def estimateFee(self, blocks: int = 6) -> int:
|
|
||||||
now = time.time()
|
|
||||||
cache_key = blocks
|
|
||||||
if cache_key in self._cached_fee:
|
|
||||||
if (now - self._cached_fee_time.get(cache_key, 0)) < self._fee_cache_ttl:
|
|
||||||
return self._cached_fee[cache_key]
|
|
||||||
|
|
||||||
try:
|
|
||||||
fee = self._call("blockchain.estimatefee", [blocks])
|
|
||||||
if fee and fee > 0:
|
|
||||||
result = int(fee * 1e8 / 1000)
|
|
||||||
self._cached_fee[cache_key] = result
|
|
||||||
self._cached_fee_time[cache_key] = now
|
|
||||||
return result
|
|
||||||
return self._cached_fee.get(cache_key, 1)
|
|
||||||
except Exception:
|
|
||||||
return self._cached_fee.get(cache_key, 1)
|
|
||||||
|
|
||||||
def isConnected(self) -> bool:
|
|
||||||
try:
|
|
||||||
self._call("server.ping", [])
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def getServerVersion(self) -> str:
|
|
||||||
version = self._server.get_server_version()
|
|
||||||
if not version:
|
|
||||||
try:
|
|
||||||
self._call("server.ping", [])
|
|
||||||
version = self._server.get_server_version()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return version or "electrum"
|
|
||||||
|
|
||||||
def getServerHost(self) -> str:
|
|
||||||
host, port = self._server.get_current_server()
|
|
||||||
if host and port:
|
|
||||||
return f"{host}:{port}"
|
|
||||||
return f"{self._host}:{self._port}"
|
|
||||||
|
|
||||||
def getConnectionStatus(self) -> dict:
|
|
||||||
if hasattr(self._server, "getConnectionStatus"):
|
|
||||||
status = self._server.getConnectionStatus()
|
|
||||||
else:
|
|
||||||
status = {
|
|
||||||
"connected": self.isConnected(),
|
|
||||||
"failures": 0,
|
|
||||||
"last_error": None,
|
|
||||||
"all_failed": False,
|
|
||||||
"using_defaults": True,
|
|
||||||
"server_count": 1,
|
|
||||||
}
|
|
||||||
status["server"] = self.getServerHost()
|
|
||||||
status["version"] = self.getServerVersion()
|
|
||||||
return status
|
|
||||||
|
|
||||||
def recentlyReconnected(self, grace_seconds: int = 30) -> bool:
|
|
||||||
if hasattr(self._server, "recently_reconnected"):
|
|
||||||
return self._server.recently_reconnected(grace_seconds)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def getAddressHistory(self, address: str) -> List[dict]:
|
|
||||||
if self._isUnsupportedAddress(address):
|
|
||||||
return []
|
|
||||||
try:
|
|
||||||
scripthash = self._addressToScripthash(address)
|
|
||||||
history = self._call("blockchain.scripthash.get_history", [scripthash])
|
|
||||||
if history:
|
|
||||||
return [
|
|
||||||
{"txid": h.get("tx_hash"), "height": h.get("height", 0)}
|
|
||||||
for h in history
|
|
||||||
]
|
|
||||||
return []
|
|
||||||
except Exception:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def getAddressHistoryBackground(self, address: str) -> List[dict]:
|
|
||||||
if self._isUnsupportedAddress(address):
|
|
||||||
return []
|
|
||||||
try:
|
|
||||||
scripthash = self._addressToScripthash(address)
|
|
||||||
history = self._server.call_background(
|
|
||||||
"blockchain.scripthash.get_history", [scripthash]
|
|
||||||
)
|
|
||||||
if history:
|
|
||||||
return [
|
|
||||||
{"txid": h.get("tx_hash"), "height": h.get("height", 0)}
|
|
||||||
for h in history
|
|
||||||
]
|
|
||||||
return []
|
|
||||||
except Exception:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def getBatchBalance(self, scripthashes: List[str]) -> Dict[str, int]:
|
|
||||||
result = {}
|
|
||||||
for sh in scripthashes:
|
|
||||||
result[sh] = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
calls = [("blockchain.scripthash.get_balance", [sh]) for sh in scripthashes]
|
|
||||||
responses = self._call_batch(calls)
|
|
||||||
for sh, balance in zip(scripthashes, responses):
|
|
||||||
if balance:
|
|
||||||
confirmed = balance.get("confirmed", 0)
|
|
||||||
unconfirmed = balance.get("unconfirmed", 0)
|
|
||||||
result[sh] = confirmed + unconfirmed
|
|
||||||
except Exception as e:
|
|
||||||
self._log.warning(f"ElectrumBackend.getBatchBalance error: {e}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def getBatchUnspent(
|
|
||||||
self, scripthashes: List[str], min_confirmations: int = 0
|
|
||||||
) -> Dict[str, List[dict]]:
|
|
||||||
result = {}
|
|
||||||
for sh in scripthashes:
|
|
||||||
result[sh] = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
current_height = self.getBlockHeight()
|
|
||||||
|
|
||||||
calls = [("blockchain.scripthash.listunspent", [sh]) for sh in scripthashes]
|
|
||||||
responses = self._call_batch(calls)
|
|
||||||
for sh, utxos in zip(scripthashes, responses):
|
|
||||||
if utxos:
|
|
||||||
for utxo in utxos:
|
|
||||||
height = utxo.get("height", 0)
|
|
||||||
if height <= 0:
|
|
||||||
confirmations = 0
|
|
||||||
else:
|
|
||||||
confirmations = current_height - height + 1
|
|
||||||
|
|
||||||
if confirmations >= min_confirmations:
|
|
||||||
result[sh].append(
|
|
||||||
{
|
|
||||||
"txid": utxo.get("tx_hash"),
|
|
||||||
"vout": utxo.get("tx_pos"),
|
|
||||||
"value": utxo.get("value", 0),
|
|
||||||
"confirmations": confirmations,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.warning(f"ElectrumBackend.getBatchUnspent error: {e}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def enableRealtimeNotifications(self, callback) -> None:
|
|
||||||
self._realtime_callback = callback
|
|
||||||
self._server.enable_realtime_notifications()
|
|
||||||
self._log.info(f"Real-time notifications enabled for {self._coin_type}")
|
|
||||||
|
|
||||||
def _create_scripthash_callback(self, scripthash):
|
|
||||||
|
|
||||||
def callback(sh, new_status):
|
|
||||||
self._handle_scripthash_notification(sh, new_status)
|
|
||||||
|
|
||||||
return callback
|
|
||||||
|
|
||||||
def _handle_scripthash_notification(self, scripthash, new_status):
|
|
||||||
if not self._realtime_callback:
|
|
||||||
return
|
|
||||||
|
|
||||||
address = None
|
|
||||||
for addr, sh in self._address_to_scripthash.items():
|
|
||||||
if sh == scripthash:
|
|
||||||
address = addr
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._realtime_callback(
|
|
||||||
self._coin_type, address, scripthash, "balance_change"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f"Error in realtime callback: {e}")
|
|
||||||
|
|
||||||
def subscribeAddressWithCallback(self, address: str) -> str:
|
|
||||||
if self._isUnsupportedAddress(address):
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
scripthash = self._addressToScripthash(address)
|
|
||||||
self._address_to_scripthash[address] = scripthash
|
|
||||||
|
|
||||||
if self._realtime_callback:
|
|
||||||
status = self._server.subscribe_with_callback(
|
|
||||||
scripthash, self._create_scripthash_callback(scripthash)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
status = self._call("blockchain.scripthash.subscribe", [scripthash])
|
|
||||||
|
|
||||||
self._subscribed_scripthashes.add(scripthash)
|
|
||||||
return status
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f"Failed to subscribe to {address}: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getSyncStatus(self) -> dict:
|
|
||||||
import time
|
|
||||||
|
|
||||||
height = 0
|
|
||||||
height_time = 0
|
|
||||||
if hasattr(self._server, "get_subscribed_height"):
|
|
||||||
height = self._server.get_subscribed_height()
|
|
||||||
height_time = getattr(self._server, "_subscribed_height_time", 0)
|
|
||||||
|
|
||||||
if self._cached_height > 0:
|
|
||||||
if self._cached_height > height:
|
|
||||||
height = self._cached_height
|
|
||||||
if self._cached_height_time > height_time:
|
|
||||||
height_time = self._cached_height_time
|
|
||||||
|
|
||||||
now = time.time()
|
|
||||||
stale_threshold = 300
|
|
||||||
last_activity = getattr(self._server, "_last_activity", 0)
|
|
||||||
most_recent = max(height_time, last_activity)
|
|
||||||
is_synced = height > 0 and (now - most_recent) < stale_threshold
|
|
||||||
return {
|
|
||||||
"height": height,
|
|
||||||
"synced": is_synced,
|
|
||||||
"last_update": height_time,
|
|
||||||
}
|
|
||||||
|
|
||||||
def getServer(self):
|
|
||||||
return self._server
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
|||||||
# 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,12 +140,6 @@ 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
|
||||||
|
|||||||
@@ -1,32 +1,3 @@
|
|||||||
0.16.3
|
|
||||||
==============
|
|
||||||
|
|
||||||
- Automatic fee validation.
|
|
||||||
- Prevent sending bids to offers
|
|
||||||
- Reject received offers, and
|
|
||||||
- Prevent sending offers where the chain feerates are out of range.
|
|
||||||
- Valid feerate range is the node's estimated feerate for confirmation in 24 blocks to 4x the estimated feerate.
|
|
||||||
- The minimum feerate confirmation can be adjusted with the "low_fee_conf_target" setting.
|
|
||||||
- If "low_feerate" is set above 0 it is used instead of the dynamic feerate with "low_fee_conf_target".
|
|
||||||
- The maximum feerate multiplier can be adjusted with the "high_estimated_feerate_multiplier" setting.
|
|
||||||
- If "high_estimated_feerate_multiplier" is set below 1.0 the max feerate can be set with the "high_feerate" setting.
|
|
||||||
- New setting "startup_delay":
|
|
||||||
- Adjusts the time waited for coin daemons to start between "startup_tries".
|
|
||||||
- Valid as a base setting and can be overridden per coin with chainclients settings.
|
|
||||||
- Add subfee bids.
|
|
||||||
- Enables a user to create a bid specifying the amount before the lock tx fee.
|
|
||||||
- Currently only works when the coin to is not XMR like.
|
|
||||||
- Set Adaptor sig bid type as default where possible.
|
|
||||||
- UI:
|
|
||||||
- offer page:
|
|
||||||
- Fixed feerate from other chain displayed for reversed swaps.
|
|
||||||
- Added warning text for fee above 1.2 x local estimate.
|
|
||||||
- Added subfee bid option.
|
|
||||||
- Increase DCR fee estimate by 1 byte.
|
|
||||||
- Waits for the refund and refund spend txn locks to expire before trying to submit them.
|
|
||||||
- Fixed bug where initiate tx amount was not checked for secret hash swaps.
|
|
||||||
|
|
||||||
|
|
||||||
0.14.5
|
0.14.5
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|||||||
@@ -79,13 +79,13 @@ def main():
|
|||||||
continue
|
continue
|
||||||
if coin_name in ("monero", "wownero"):
|
if coin_name in ("monero", "wownero"):
|
||||||
with open(
|
with open(
|
||||||
os.path.join(fragments_dir, f"1_{coin_name}-wallet.yml"), "rb"
|
os.path.join(fragments_dir, "1_{coin_name}-wallet.yml"), "rb"
|
||||||
) as fp_in:
|
) as fp_in:
|
||||||
for line in fp_in:
|
for line in fp_in:
|
||||||
fp.write(line)
|
fp.write(line)
|
||||||
fpp.write(line)
|
fpp.write(line)
|
||||||
with open(
|
with open(
|
||||||
os.path.join(fragments_dir, f"8_{coin_name}-daemon.yml"), "rb"
|
os.path.join(fragments_dir, "8_{coin_name}-daemon.yml"), "rb"
|
||||||
) as fp_in:
|
) as fp_in:
|
||||||
for line in fp_in:
|
for line in fp_in:
|
||||||
fp.write(line)
|
fp.write(line)
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
(define python-coincurve-basicswap
|
(define python-coincurve-basicswap
|
||||||
(package
|
(package
|
||||||
(name "python-coincurve-basicswap")
|
(name "python-coincurve-basicswap")
|
||||||
(version "basicswap_v0.3")
|
(version "basicswap_v0.2")
|
||||||
(source
|
(source
|
||||||
(origin
|
(origin
|
||||||
(method git-fetch)
|
(method git-fetch)
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
(file-name
|
(file-name
|
||||||
(git-file-name name version))
|
(git-file-name name version))
|
||||||
(sha256
|
(sha256
|
||||||
(base32 "08bc8175v4d479lgavkcclc0kkh3icxm9i0i26wqd1g3bv0is8cm"))))
|
(base32 "1vm9cvwr0z02zc0mp7l8qj9vhg8kmfrzysiwzg91zkgmccza9ryc"))))
|
||||||
(build-system pyproject-build-system)
|
(build-system pyproject-build-system)
|
||||||
(arguments
|
(arguments
|
||||||
`(#:phases
|
`(#:phases
|
||||||
@@ -135,15 +135,15 @@
|
|||||||
(define-public basicswap
|
(define-public basicswap
|
||||||
(package
|
(package
|
||||||
(name "basicswap")
|
(name "basicswap")
|
||||||
(version "0.16.4")
|
(version "0.15.1")
|
||||||
(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 "136b311dc68f11b9c12ebd6877c5f718d705603a")))
|
(commit "0bc9d3a5db40f54d79e2ab18be58b6bbc20740d1")))
|
||||||
(sha256
|
(sha256
|
||||||
(base32
|
(base32
|
||||||
"0ikr8ik9rklvafd1j8zj0y38vric02qhmj7pvp3kvzbmd2fxx95p"))
|
"1x6c6hynvbayq4cyv9s6vwgsgdmhm7r1av6iy7pax103lj20habf"))
|
||||||
(file-name (git-file-name name version))))
|
(file-name (git-file-name name version))))
|
||||||
(build-system pyproject-build-system)
|
(build-system pyproject-build-system)
|
||||||
|
|
||||||
|
|||||||
+2
-10
@@ -8,7 +8,7 @@ description = "Simple atomic swap system"
|
|||||||
keywords = ["crypto", "cryptocurrency", "particl", "bitcoin", "monero", "wownero"]
|
keywords = ["crypto", "cryptocurrency", "particl", "bitcoin", "monero", "wownero"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.9"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
@@ -36,7 +36,7 @@ dev = [
|
|||||||
"pre-commit",
|
"pre-commit",
|
||||||
"pytest",
|
"pytest",
|
||||||
"ruff",
|
"ruff",
|
||||||
"black==26.3.1",
|
"black==25.11.0",
|
||||||
"selenium",
|
"selenium",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -48,11 +48,3 @@ allow-direct-references = true
|
|||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
exclude = ["basicswap/contrib","basicswap/interface/contrib"]
|
exclude = ["basicswap/contrib","basicswap/interface/contrib"]
|
||||||
|
|
||||||
[tool.codespell]
|
|
||||||
check-filenames = true
|
|
||||||
disable-colors = true
|
|
||||||
quiet-level = 7
|
|
||||||
dictionary = "tests/lint/spelling.extra_dictionary.txt,-"
|
|
||||||
ignore-words = "tests/lint/spelling.ignore-words.txt"
|
|
||||||
skip = ".git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static"
|
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
pyzmq==27.1.0
|
pyzmq==27.1.0
|
||||||
python-gnupg==0.5.6
|
python-gnupg==0.5.5
|
||||||
Jinja2==3.1.6
|
Jinja2==3.1.6
|
||||||
pycryptodome==3.23.0
|
pycryptodome==3.23.0
|
||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
|
|||||||
+3
-3
@@ -122,9 +122,9 @@ pysocks==1.7.1 \
|
|||||||
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
|
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
|
||||||
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
|
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
python-gnupg==0.5.6 \
|
python-gnupg==0.5.5 \
|
||||||
--hash=sha256:5743e96212d38923fc19083812dc127907e44dbd3bcf0db4d657e291d3c21eac \
|
--hash=sha256:3fdcaf76f60a1b948ff8e37dc398d03cf9ce7427065d583082b92da7a4ff5a63 \
|
||||||
--hash=sha256:b5050a55663d8ab9fcc8d97556d229af337a87a3ebebd7054cbd8b7e2043394a
|
--hash=sha256:51fa7b8831ff0914bc73d74c59b99c613de7247b91294323c39733bb85ac3fc1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
pyzmq==27.1.0 \
|
pyzmq==27.1.0 \
|
||||||
--hash=sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d \
|
--hash=sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d \
|
||||||
|
|||||||
+6
-19
@@ -729,25 +729,7 @@ def process_offers(args, config, script_state) -> None:
|
|||||||
matching_sent_offers.append(offer)
|
matching_sent_offers.append(offer)
|
||||||
offers_found += 1
|
offers_found += 1
|
||||||
|
|
||||||
offer_amount_from = float(offer.get("amount_from", 0))
|
if wallet_balance <= float(offer_template["min_coin_from_amt"]):
|
||||||
min_coin_from_amt = float(offer_template.get("min_coin_from_amt", 0))
|
|
||||||
|
|
||||||
if offer_amount_from > wallet_balance:
|
|
||||||
print(
|
|
||||||
f"Revoking offer {offer_id}, offer amount {offer_amount_from:.8f} > wallet balance {wallet_balance:.8f}"
|
|
||||||
)
|
|
||||||
result = read_json_api(f"revokeoffer/{offer_id}")
|
|
||||||
if args.debug:
|
|
||||||
print("revokeoffer", result)
|
|
||||||
else:
|
|
||||||
print("Offer revoked, will repost with accurate amount")
|
|
||||||
for i, prev_offer in enumerate(prev_template_offers):
|
|
||||||
if prev_offer.get("offer_id") == offer_id:
|
|
||||||
del prev_template_offers[i]
|
|
||||||
break
|
|
||||||
write_state(args.statefile, script_state)
|
|
||||||
offers_found -= 1
|
|
||||||
elif wallet_balance <= min_coin_from_amt:
|
|
||||||
print(
|
print(
|
||||||
"Revoking offer {}, wallet from balance below minimum".format(
|
"Revoking offer {}, wallet from balance below minimum".format(
|
||||||
offer_id
|
offer_id
|
||||||
@@ -1187,6 +1169,11 @@ def process_offers(args, config, script_state) -> None:
|
|||||||
)
|
)
|
||||||
use_rate = offer_template["minrate"]
|
use_rate = offer_template["minrate"]
|
||||||
|
|
||||||
|
# Final minimum rate check after all adjustments
|
||||||
|
if use_rate < offer_template["minrate"]:
|
||||||
|
print("Warning: Final rate clamping to minimum after all adjustments.")
|
||||||
|
use_rate = offer_template["minrate"]
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print(
|
print(
|
||||||
"Creating offer for: {} at rate: {}".format(
|
"Creating offer for: {} at rate: {}".format(
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -14,12 +15,12 @@ import subprocess
|
|||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
from .util import read_json_api
|
from .util import read_json_api
|
||||||
from basicswap.basicswap import Coins
|
|
||||||
from basicswap.rpc import callrpc
|
from basicswap.rpc import callrpc
|
||||||
from basicswap.util import toBool
|
from basicswap.util import toBool
|
||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||||
from basicswap.bin.prepare import downloadPIVXParams
|
from basicswap.bin.prepare import downloadPIVXParams
|
||||||
|
|
||||||
|
|
||||||
TEST_HTTP_HOST = os.getenv(
|
TEST_HTTP_HOST = os.getenv(
|
||||||
"TEST_HTTP_HOST", "127.0.0.1"
|
"TEST_HTTP_HOST", "127.0.0.1"
|
||||||
) # Set to 0.0.0.0 when used in docker
|
) # Set to 0.0.0.0 when used in docker
|
||||||
@@ -93,7 +94,7 @@ def prepareDataDir(
|
|||||||
fp.write("fallbackfee=0.01\n")
|
fp.write("fallbackfee=0.01\n")
|
||||||
fp.write("acceptnonstdtxn=0\n")
|
fp.write("acceptnonstdtxn=0\n")
|
||||||
fp.write("txindex=1\n")
|
fp.write("txindex=1\n")
|
||||||
fp.write("wallet=bsx_wallet\n")
|
fp.write("wallet=wallet.dat\n")
|
||||||
|
|
||||||
fp.write("findpeers=0\n")
|
fp.write("findpeers=0\n")
|
||||||
|
|
||||||
@@ -124,65 +125,6 @@ def prepareDataDir(
|
|||||||
return node_dir
|
return node_dir
|
||||||
|
|
||||||
|
|
||||||
def prepare_balance(
|
|
||||||
use_delay_event,
|
|
||||||
coin,
|
|
||||||
amount: float,
|
|
||||||
port_target_node: int,
|
|
||||||
port_take_from_node: int,
|
|
||||||
test_balance: bool = True,
|
|
||||||
) -> None:
|
|
||||||
if coin == Coins.PART_BLIND:
|
|
||||||
coin_ticker: str = "PART"
|
|
||||||
balance_type: str = "blind_balance"
|
|
||||||
address_type: str = "stealth_address"
|
|
||||||
type_to: str = "blind"
|
|
||||||
elif coin == Coins.PART_ANON:
|
|
||||||
coin_ticker: str = "PART"
|
|
||||||
balance_type: str = "anon_balance"
|
|
||||||
address_type: str = "stealth_address"
|
|
||||||
type_to: str = "anon"
|
|
||||||
else:
|
|
||||||
coin_ticker: str = coin.name
|
|
||||||
balance_type: str = "balance"
|
|
||||||
address_type: str = "deposit_address"
|
|
||||||
js_w = read_json_api(port_target_node, "wallets")
|
|
||||||
current_balance: float = float(js_w[coin_ticker][balance_type])
|
|
||||||
|
|
||||||
if test_balance and current_balance >= amount:
|
|
||||||
return
|
|
||||||
post_json = {
|
|
||||||
"value": amount,
|
|
||||||
"address": js_w[coin_ticker][address_type],
|
|
||||||
"subfee": False,
|
|
||||||
}
|
|
||||||
if coin in (Coins.XMR, Coins.WOW):
|
|
||||||
post_json["sweepall"] = False
|
|
||||||
if coin in (Coins.PART_BLIND, Coins.PART_ANON):
|
|
||||||
post_json["type_to"] = type_to
|
|
||||||
json_rv = read_json_api(
|
|
||||||
port_take_from_node,
|
|
||||||
f"wallets/{coin_ticker.lower()}/withdraw",
|
|
||||||
post_json,
|
|
||||||
)
|
|
||||||
assert len(json_rv["txid"]) == 64
|
|
||||||
wait_for_amount: float = amount
|
|
||||||
if not test_balance:
|
|
||||||
wait_for_amount += current_balance
|
|
||||||
delay_iterations = 100 if coin == Coins.NAV else 30
|
|
||||||
delay_time = 5 if coin == Coins.NAV else 3
|
|
||||||
wait_for_balance(
|
|
||||||
use_delay_event,
|
|
||||||
"http://127.0.0.1:{}/json/wallets/{}".format(
|
|
||||||
port_target_node, coin_ticker.lower()
|
|
||||||
),
|
|
||||||
balance_type,
|
|
||||||
wait_for_amount,
|
|
||||||
iterations=delay_iterations,
|
|
||||||
delay_time=delay_time,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def checkForks(ro):
|
def checkForks(ro):
|
||||||
try:
|
try:
|
||||||
if "bip9_softforks" in ro:
|
if "bip9_softforks" in ro:
|
||||||
@@ -235,17 +177,11 @@ def wait_for_bid(
|
|||||||
)
|
)
|
||||||
if isinstance(state, (list, tuple)):
|
if isinstance(state, (list, tuple)):
|
||||||
if bid[5] in state:
|
if bid[5] in state:
|
||||||
swap_client.log.debug(
|
|
||||||
f"TEST: wait_for_bid found {bid_id.hex()}: Bid state {bid[5]}, target {state}."
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
elif state is not None and state != bid[5]:
|
elif state is not None and state != bid[5]:
|
||||||
continue
|
continue
|
||||||
swap_client.log.debug(
|
|
||||||
f"TEST: wait_for_bid found {bid_id.hex()}: Bid state {bid[5]}, target {state}."
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if i > 0 and i % 10 == 0:
|
if i > 0 and i % 10 == 0:
|
||||||
@@ -294,7 +230,7 @@ def wait_for_event(
|
|||||||
|
|
||||||
|
|
||||||
def wait_for_offer(delay_event, swap_client, offer_id, wait_for=20):
|
def wait_for_offer(delay_event, swap_client, offer_id, wait_for=20):
|
||||||
logging.info(f"wait_for_offer {offer_id.hex()}")
|
logging.info("wait_for_offer %s", offer_id.hex())
|
||||||
for i in range(wait_for):
|
for i in range(wait_for):
|
||||||
if delay_event.is_set():
|
if delay_event.is_set():
|
||||||
raise ValueError("Test stopped.")
|
raise ValueError("Test stopped.")
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ from tests.basicswap.extended.test_doge import (
|
|||||||
import basicswap.config as cfg
|
import basicswap.config as cfg
|
||||||
import basicswap.bin.run as runSystem
|
import basicswap.bin.run as runSystem
|
||||||
|
|
||||||
|
|
||||||
TEST_PATH = os.path.expanduser(os.getenv("TEST_PATH", "~/test_basicswap1"))
|
TEST_PATH = os.path.expanduser(os.getenv("TEST_PATH", "~/test_basicswap1"))
|
||||||
|
|
||||||
PARTICL_PORT_BASE = int(os.getenv("PARTICL_PORT_BASE", BASE_PORT))
|
PARTICL_PORT_BASE = int(os.getenv("PARTICL_PORT_BASE", BASE_PORT))
|
||||||
@@ -137,7 +139,6 @@ def run_prepare(
|
|||||||
use_rpcauth=False,
|
use_rpcauth=False,
|
||||||
extra_settings={},
|
extra_settings={},
|
||||||
port_ofs=0,
|
port_ofs=0,
|
||||||
extra_args=[],
|
|
||||||
):
|
):
|
||||||
config_path = os.path.join(datadir_path, cfg.CONFIG_FILENAME)
|
config_path = os.path.join(datadir_path, cfg.CONFIG_FILENAME)
|
||||||
|
|
||||||
@@ -179,7 +180,7 @@ def run_prepare(
|
|||||||
"-noextractover",
|
"-noextractover",
|
||||||
"-noreleasesizecheck",
|
"-noreleasesizecheck",
|
||||||
"-xmrrestoreheight=0",
|
"-xmrrestoreheight=0",
|
||||||
] + extra_args
|
]
|
||||||
if mnemonic_in:
|
if mnemonic_in:
|
||||||
testargs.append(f'-particl_mnemonic="{mnemonic_in}"')
|
testargs.append(f'-particl_mnemonic="{mnemonic_in}"')
|
||||||
|
|
||||||
@@ -530,7 +531,9 @@ def run_prepare(
|
|||||||
for opt in EXTRA_CONFIG_JSON.get("doge{}".format(node_id), []):
|
for opt in EXTRA_CONFIG_JSON.get("doge{}".format(node_id), []):
|
||||||
fp.write(opt + "\n")
|
fp.write(opt + "\n")
|
||||||
|
|
||||||
settings["startup_delay"] = 1
|
with open(config_path) as fs:
|
||||||
|
settings = json.load(fs)
|
||||||
|
|
||||||
settings["min_delay_event"] = 1
|
settings["min_delay_event"] = 1
|
||||||
settings["max_delay_event"] = 4
|
settings["max_delay_event"] = 4
|
||||||
settings["min_delay_event_short"] = 1
|
settings["min_delay_event_short"] = 1
|
||||||
@@ -582,7 +585,7 @@ def prepare_nodes(
|
|||||||
|
|
||||||
class TestBase(unittest.TestCase):
|
class TestBase(unittest.TestCase):
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super(TestBase, cls).setUpClass()
|
||||||
|
|
||||||
cls.delay_event = threading.Event()
|
cls.delay_event = threading.Event()
|
||||||
signal.signal(
|
signal.signal(
|
||||||
@@ -619,18 +622,6 @@ class TestBase(unittest.TestCase):
|
|||||||
raise ValueError(f"wait_for_particl_height failed http_port: {http_port}")
|
raise ValueError(f"wait_for_particl_height failed http_port: {http_port}")
|
||||||
|
|
||||||
|
|
||||||
def run_process(client_id):
|
|
||||||
client_path = os.path.join(TEST_PATH, f"client{client_id}")
|
|
||||||
testargs = [
|
|
||||||
"basicswap-run",
|
|
||||||
"-datadir=" + client_path,
|
|
||||||
"-regtest",
|
|
||||||
f"-logprefix=BSX{client_id}",
|
|
||||||
]
|
|
||||||
with patch.object(sys, "argv", testargs):
|
|
||||||
runSystem.main()
|
|
||||||
|
|
||||||
|
|
||||||
class XmrTestBase(TestBase):
|
class XmrTestBase(TestBase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
@@ -641,17 +632,27 @@ class XmrTestBase(TestBase):
|
|||||||
|
|
||||||
prepare_nodes(3, "monero")
|
prepare_nodes(3, "monero")
|
||||||
|
|
||||||
|
def run_thread(self, client_id):
|
||||||
|
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
|
||||||
|
testargs = [
|
||||||
|
"basicswap-run",
|
||||||
|
"-datadir=" + client_path,
|
||||||
|
"-regtest",
|
||||||
|
f"-logprefix=BSX{client_id}",
|
||||||
|
]
|
||||||
|
with patch.object(sys, "argv", testargs):
|
||||||
|
runSystem.main()
|
||||||
|
|
||||||
def start_processes(self):
|
def start_processes(self):
|
||||||
multiprocessing.set_start_method("spawn")
|
|
||||||
self.delay_event.clear()
|
self.delay_event.clear()
|
||||||
|
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
self.processes.append(
|
self.processes.append(
|
||||||
multiprocessing.Process(target=run_process, args=(i,))
|
multiprocessing.Process(target=self.run_thread, args=(i,))
|
||||||
)
|
)
|
||||||
self.processes[-1].start()
|
self.processes[-1].start()
|
||||||
|
|
||||||
waitForServer(self.delay_event, 12701, 60)
|
waitForServer(self.delay_event, 12701)
|
||||||
|
|
||||||
def waitForMainAddress():
|
def waitForMainAddress():
|
||||||
for i in range(20):
|
for i in range(20):
|
||||||
@@ -663,12 +664,13 @@ class XmrTestBase(TestBase):
|
|||||||
)
|
)
|
||||||
return wallets["XMR"]["main_address"]
|
return wallets["XMR"]["main_address"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Waiting for main address {e}")
|
print("Waiting for main address {}".format(str(e)))
|
||||||
self.delay_event.wait(1)
|
self.delay_event.wait(1)
|
||||||
raise ValueError("waitForMainAddress timedout")
|
raise ValueError("waitForMainAddress timedout")
|
||||||
|
|
||||||
xmr_addr1 = waitForMainAddress()
|
xmr_addr1 = waitForMainAddress()
|
||||||
num_blocks: int = 100
|
|
||||||
|
num_blocks = 100
|
||||||
|
|
||||||
xmr_auth = None
|
xmr_auth = None
|
||||||
if os.getenv("XMR_RPC_USER", "") != "":
|
if os.getenv("XMR_RPC_USER", "") != "":
|
||||||
@@ -680,7 +682,7 @@ class XmrTestBase(TestBase):
|
|||||||
]
|
]
|
||||||
< num_blocks
|
< num_blocks
|
||||||
):
|
):
|
||||||
logging.info(f"Mining {num_blocks} Monero blocks to {xmr_addr1}.")
|
logging.info("Mining {} Monero blocks to {}.".format(num_blocks, xmr_addr1))
|
||||||
callrpc_xmr(
|
callrpc_xmr(
|
||||||
XMR_BASE_RPC_PORT + 1,
|
XMR_BASE_RPC_PORT + 1,
|
||||||
"generateblocks",
|
"generateblocks",
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ from tests.basicswap.common import (
|
|||||||
)
|
)
|
||||||
from basicswap.bin.run import startDaemon
|
from basicswap.bin.run import startDaemon
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.DEBUG
|
logger.level = logging.DEBUG
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
@@ -111,7 +112,7 @@ def prepareOtherDir(datadir, nodeId, conf_file="dash.conf"):
|
|||||||
fp.write("acceptnonstdtxn=0\n")
|
fp.write("acceptnonstdtxn=0\n")
|
||||||
|
|
||||||
if conf_file == "bitcoin.conf":
|
if conf_file == "bitcoin.conf":
|
||||||
fp.write("wallet=bsx_wallet\n")
|
fp.write("wallet=wallet.dat\n")
|
||||||
|
|
||||||
|
|
||||||
def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
||||||
@@ -136,7 +137,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
|||||||
fp.write("debug=1\n")
|
fp.write("debug=1\n")
|
||||||
fp.write("debugexclude=libevent\n")
|
fp.write("debugexclude=libevent\n")
|
||||||
fp.write("zmqpubsmsg=tcp://127.0.0.1:" + str(BASE_ZMQ_PORT + nodeId) + "\n")
|
fp.write("zmqpubsmsg=tcp://127.0.0.1:" + str(BASE_ZMQ_PORT + nodeId) + "\n")
|
||||||
fp.write("wallet=bsx_wallet\n")
|
fp.write("wallet=wallet.dat\n")
|
||||||
fp.write("fallbackfee=0.01\n")
|
fp.write("fallbackfee=0.01\n")
|
||||||
|
|
||||||
fp.write("acceptnonstdtxn=0\n")
|
fp.write("acceptnonstdtxn=0\n")
|
||||||
@@ -175,7 +176,6 @@ 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",
|
|
||||||
},
|
},
|
||||||
"dash": {
|
"dash": {
|
||||||
"connection_type": "rpc",
|
"connection_type": "rpc",
|
||||||
@@ -185,7 +185,6 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
|||||||
"bindir": DASH_BINDIR,
|
"bindir": DASH_BINDIR,
|
||||||
"use_csv": True,
|
"use_csv": True,
|
||||||
"use_segwit": False,
|
"use_segwit": False,
|
||||||
"wallet_name": "bsx_wallet",
|
|
||||||
},
|
},
|
||||||
"bitcoin": {
|
"bitcoin": {
|
||||||
"connection_type": "rpc",
|
"connection_type": "rpc",
|
||||||
@@ -194,7 +193,6 @@ 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,
|
||||||
@@ -288,7 +286,7 @@ class Test(unittest.TestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super(Test, cls).setUpClass()
|
||||||
|
|
||||||
k = PrivateKey()
|
k = PrivateKey()
|
||||||
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret)
|
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret)
|
||||||
@@ -315,7 +313,7 @@ class Test(unittest.TestCase):
|
|||||||
cfg.BITCOIN_BINDIR,
|
cfg.BITCOIN_BINDIR,
|
||||||
btc_data_dir,
|
btc_data_dir,
|
||||||
"regtest",
|
"regtest",
|
||||||
"-wallet=bsx_wallet -legacy create",
|
"-wallet=wallet.dat -legacy create",
|
||||||
"bitcoin-wallet",
|
"bitcoin-wallet",
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -323,7 +321,7 @@ class Test(unittest.TestCase):
|
|||||||
cfg.BITCOIN_BINDIR,
|
cfg.BITCOIN_BINDIR,
|
||||||
btc_data_dir,
|
btc_data_dir,
|
||||||
"regtest",
|
"regtest",
|
||||||
"-wallet=bsx_wallet create",
|
"-wallet=wallet.dat create",
|
||||||
"bitcoin-wallet",
|
"bitcoin-wallet",
|
||||||
)
|
)
|
||||||
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
|
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
|
||||||
@@ -335,7 +333,7 @@ class Test(unittest.TestCase):
|
|||||||
|
|
||||||
if os.path.exists(os.path.join(DASH_BINDIR, 'dash-wallet')):
|
if os.path.exists(os.path.join(DASH_BINDIR, 'dash-wallet')):
|
||||||
logging.info('Creating DASH wallet.')
|
logging.info('Creating DASH wallet.')
|
||||||
callrpc_cli(DASH_BINDIR, dash_data_dir, 'regtest', '-wallet=bsx_wallet create', 'dash-wallet')
|
callrpc_cli(DASH_BINDIR, dash_data_dir, 'regtest', '-wallet=wallet.dat create', 'dash-wallet')
|
||||||
"""
|
"""
|
||||||
cls.daemons.append(startDaemon(dash_data_dir, DASH_BINDIR, DASHD))
|
cls.daemons.append(startDaemon(dash_data_dir, DASH_BINDIR, DASHD))
|
||||||
logging.info("Started %s %d", DASHD, cls.daemons[-1].handle.pid)
|
logging.info("Started %s %d", DASHD, cls.daemons[-1].handle.pid)
|
||||||
@@ -348,7 +346,7 @@ class Test(unittest.TestCase):
|
|||||||
cfg.PARTICL_BINDIR,
|
cfg.PARTICL_BINDIR,
|
||||||
data_dir,
|
data_dir,
|
||||||
"regtest",
|
"regtest",
|
||||||
"-wallet=bsx_wallet -legacy create",
|
"-wallet=wallet.dat -legacy create",
|
||||||
"particl-wallet",
|
"particl-wallet",
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -356,7 +354,7 @@ class Test(unittest.TestCase):
|
|||||||
cfg.PARTICL_BINDIR,
|
cfg.PARTICL_BINDIR,
|
||||||
data_dir,
|
data_dir,
|
||||||
"regtest",
|
"regtest",
|
||||||
"-wallet=bsx_wallet create",
|
"-wallet=wallet.dat create",
|
||||||
"particl-wallet",
|
"particl-wallet",
|
||||||
)
|
)
|
||||||
cls.daemons.append(startDaemon(data_dir, cfg.PARTICL_BINDIR, cfg.PARTICLD))
|
cls.daemons.append(startDaemon(data_dir, cfg.PARTICL_BINDIR, cfg.PARTICLD))
|
||||||
@@ -412,15 +410,15 @@ class Test(unittest.TestCase):
|
|||||||
|
|
||||||
waitForRPC(dashRpc, delay_event, rpc_command="getblockchaininfo")
|
waitForRPC(dashRpc, delay_event, rpc_command="getblockchaininfo")
|
||||||
if len(dashRpc("listwallets")) < 1:
|
if len(dashRpc("listwallets")) < 1:
|
||||||
dashRpc("createwallet bsx_wallet")
|
dashRpc("createwallet wallet.dat")
|
||||||
|
|
||||||
sc.start()
|
sc.start()
|
||||||
|
|
||||||
waitForRPC(dashRpc, delay_event)
|
waitForRPC(dashRpc, delay_event)
|
||||||
num_blocks = 500
|
num_blocks = 500
|
||||||
logging.info(f"Mining {num_blocks} dash blocks")
|
logging.info("Mining %d dash blocks", num_blocks)
|
||||||
cls.dash_addr = dashRpc("getnewaddress mining_addr")
|
cls.dash_addr = dashRpc("getnewaddress mining_addr")
|
||||||
dashRpc(f"generatetoaddress {num_blocks} {cls.dash_addr}")
|
dashRpc("generatetoaddress {} {}".format(num_blocks, cls.dash_addr))
|
||||||
|
|
||||||
ro = dashRpc("getblockchaininfo")
|
ro = dashRpc("getblockchaininfo")
|
||||||
try:
|
try:
|
||||||
@@ -434,8 +432,8 @@ class Test(unittest.TestCase):
|
|||||||
|
|
||||||
waitForRPC(btcRpc, delay_event)
|
waitForRPC(btcRpc, delay_event)
|
||||||
cls.btc_addr = btcRpc("getnewaddress mining_addr bech32")
|
cls.btc_addr = btcRpc("getnewaddress mining_addr bech32")
|
||||||
logging.info(f"Mining {num_blocks} Bitcoin blocks to {cls.btc_addr}")
|
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr)
|
||||||
btcRpc(f"generatetoaddress {num_blocks} {cls.btc_addr}")
|
btcRpc("generatetoaddress {} {}".format(num_blocks, cls.btc_addr))
|
||||||
|
|
||||||
ro = btcRpc("getblockchaininfo")
|
ro = btcRpc("getblockchaininfo")
|
||||||
checkForks(ro)
|
checkForks(ro)
|
||||||
@@ -452,7 +450,7 @@ class Test(unittest.TestCase):
|
|||||||
|
|
||||||
# Wait for height, or sequencelock is thrown off by genesis blocktime
|
# Wait for height, or sequencelock is thrown off by genesis blocktime
|
||||||
num_blocks = 3
|
num_blocks = 3
|
||||||
logging.info(f"Waiting for Particl chain height {num_blocks}")
|
logging.info("Waiting for Particl chain height %d", num_blocks)
|
||||||
for i in range(60):
|
for i in range(60):
|
||||||
particl_blocks = cls.swap_clients[0].callrpc("getblockcount")
|
particl_blocks = cls.swap_clients[0].callrpc("getblockcount")
|
||||||
print("particl_blocks", particl_blocks)
|
print("particl_blocks", particl_blocks)
|
||||||
@@ -476,7 +474,7 @@ class Test(unittest.TestCase):
|
|||||||
cls.swap_clients.clear()
|
cls.swap_clients.clear()
|
||||||
cls.daemons.clear()
|
cls.daemons.clear()
|
||||||
|
|
||||||
super().tearDownClass()
|
super(Test, cls).tearDownClass()
|
||||||
|
|
||||||
def test_02_part_dash(self):
|
def test_02_part_dash(self):
|
||||||
logging.info("---------- Test PART to DASH")
|
logging.info("---------- Test PART to DASH")
|
||||||
@@ -686,9 +684,9 @@ class Test(unittest.TestCase):
|
|||||||
offer_id = swap_clients[0].postOffer(
|
offer_id = swap_clients[0].postOffer(
|
||||||
Coins.DASH,
|
Coins.DASH,
|
||||||
Coins.BTC,
|
Coins.BTC,
|
||||||
0.01 * COIN,
|
0.001 * COIN,
|
||||||
1.0 * COIN,
|
1.0 * COIN,
|
||||||
0.01 * COIN,
|
0.001 * COIN,
|
||||||
SwapTypes.SELLER_FIRST,
|
SwapTypes.SELLER_FIRST,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -712,7 +710,7 @@ class Test(unittest.TestCase):
|
|||||||
del swap_clients[0].getChainClientSettings(Coins.DASH)["override_feerate"]
|
del swap_clients[0].getChainClientSettings(Coins.DASH)["override_feerate"]
|
||||||
|
|
||||||
def test_08_wallet(self):
|
def test_08_wallet(self):
|
||||||
logging.info(f"---------- Test {self.test_coin_from.name} wallet")
|
logging.info("---------- Test {} wallet".format(self.test_coin_from.name))
|
||||||
|
|
||||||
logging.info("Test withdrawal")
|
logging.info("Test withdrawal")
|
||||||
addr = dashRpc('getnewaddress "Withdrawal test"')
|
addr = dashRpc('getnewaddress "Withdrawal test"')
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024 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.
|
||||||
|
|
||||||
@@ -75,6 +75,24 @@ def make_rpc_func(node_id, base_rpc_port):
|
|||||||
return rpc_func
|
return rpc_func
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_dcr_height(http_port, num_blocks=3):
|
||||||
|
logging.info("Waiting for DCR chain height %d", num_blocks)
|
||||||
|
for i in range(60):
|
||||||
|
if test_delay_event.is_set():
|
||||||
|
raise ValueError("Test stopped.")
|
||||||
|
try:
|
||||||
|
wallet = read_json_api(http_port, "wallets/dcr")
|
||||||
|
decred_blocks = wallet["blocks"]
|
||||||
|
print("decred_blocks", decred_blocks)
|
||||||
|
if decred_blocks >= num_blocks:
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print("Error reading wallets", str(e))
|
||||||
|
|
||||||
|
test_delay_event.wait(1)
|
||||||
|
raise ValueError(f"wait_for_decred_blocks failed http_port: {http_port}")
|
||||||
|
|
||||||
|
|
||||||
def run_test_success_path(self, coin_from: Coins, coin_to: Coins):
|
def run_test_success_path(self, coin_from: Coins, coin_to: Coins):
|
||||||
logging.info(f"---------- Test {coin_from.name} to {coin_to.name}")
|
logging.info(f"---------- Test {coin_from.name} to {coin_to.name}")
|
||||||
|
|
||||||
@@ -671,7 +689,7 @@ def prepareDCDDataDir(datadir, node_id, conf_file, dir_prefix, num_nodes=3):
|
|||||||
"noseeders=1\n",
|
"noseeders=1\n",
|
||||||
"nodnsseed=1\n",
|
"nodnsseed=1\n",
|
||||||
"nodiscoverip=1\n",
|
"nodiscoverip=1\n",
|
||||||
"miningaddr=SsppG7KLiH52NC7iJmUVGVq89FLS83E5vho\n",
|
"miningaddr=SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH\n",
|
||||||
]
|
]
|
||||||
|
|
||||||
for i in range(0, num_nodes):
|
for i in range(0, num_nodes):
|
||||||
@@ -707,8 +725,7 @@ class Test(BaseTest):
|
|||||||
dcr_daemons = []
|
dcr_daemons = []
|
||||||
start_ltc_nodes = False
|
start_ltc_nodes = False
|
||||||
start_xmr_nodes = True
|
start_xmr_nodes = True
|
||||||
# Addresses differ after 2.1.2, simnet bip44id changed from 1 to 115
|
dcr_mining_addr = "SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH"
|
||||||
dcr_mining_addr = "SsppG7KLiH52NC7iJmUVGVq89FLS83E5vho"
|
|
||||||
extra_wait_time = 0
|
extra_wait_time = 0
|
||||||
max_fee: int = 10000
|
max_fee: int = 10000
|
||||||
|
|
||||||
@@ -722,8 +739,7 @@ class Test(BaseTest):
|
|||||||
def prepareExtraCoins(cls):
|
def prepareExtraCoins(cls):
|
||||||
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
||||||
if not cls.restore_instance:
|
if not cls.restore_instance:
|
||||||
dcr_mining_addr = ci0.rpc_wallet("getnewaddress")
|
assert ci0.rpc_wallet("getnewaddress") == cls.dcr_mining_addr
|
||||||
assert dcr_mining_addr == cls.dcr_mining_addr
|
|
||||||
cls.dcr_ticket_account = ci0.rpc_wallet(
|
cls.dcr_ticket_account = ci0.rpc_wallet(
|
||||||
"getaccount",
|
"getaccount",
|
||||||
[
|
[
|
||||||
@@ -747,20 +763,20 @@ class Test(BaseTest):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
logging.info("Finalising Decred Test")
|
logging.info("Finalising Decred Test")
|
||||||
super().tearDownClass()
|
super(Test, cls).tearDownClass()
|
||||||
|
|
||||||
stopDaemons(cls.dcr_daemons)
|
stopDaemons(cls.dcr_daemons)
|
||||||
cls.dcr_daemons.clear()
|
cls.dcr_daemons.clear()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def coins_loop(cls):
|
def coins_loop(cls):
|
||||||
super().coins_loop()
|
super(Test, cls).coins_loop()
|
||||||
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
||||||
|
|
||||||
num_passed: int = 0
|
num_passed: int = 0
|
||||||
for i in range(30):
|
for i in range(30):
|
||||||
try:
|
try:
|
||||||
ci0.rpc_wallet("purchaseticket", [cls.dcr_ticket_account, 0, 1])
|
ci0.rpc_wallet("purchaseticket", [cls.dcr_ticket_account, 0.1, 0])
|
||||||
num_passed += 1
|
num_passed += 1
|
||||||
if num_passed >= 5:
|
if num_passed >= 5:
|
||||||
break
|
break
|
||||||
@@ -860,16 +876,15 @@ class Test(BaseTest):
|
|||||||
"use_csv": True,
|
"use_csv": True,
|
||||||
"use_segwit": True,
|
"use_segwit": True,
|
||||||
"blocks_confirmed": 1,
|
"blocks_confirmed": 1,
|
||||||
"min_relay_fee": 0.00001,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_0001_decred_address(self):
|
def test_0001_decred_address(self):
|
||||||
logging.info(f"---------- Test {self.test_coin.name}")
|
logging.info("---------- Test {}".format(self.test_coin.name))
|
||||||
|
|
||||||
coin_settings = {"rpcport": 0, "rpcauth": "none"}
|
coin_settings = {"rpcport": 0, "rpcauth": "none"}
|
||||||
coin_settings.update(REQUIRED_SETTINGS)
|
coin_settings.update(REQUIRED_SETTINGS)
|
||||||
|
|
||||||
ci = DCRInterface(coin_settings, "mainnet", self.swap_clients[0])
|
ci = DCRInterface(coin_settings, "mainnet")
|
||||||
|
|
||||||
k = ci.getNewRandomKey()
|
k = ci.getNewRandomKey()
|
||||||
K = ci.getPubkey(k)
|
K = ci.getPubkey(k)
|
||||||
@@ -887,17 +902,17 @@ class Test(BaseTest):
|
|||||||
masterpubkey = loop_ci.rpc_wallet("getmasterpubkey")
|
masterpubkey = loop_ci.rpc_wallet("getmasterpubkey")
|
||||||
masterpubkey_data = loop_ci.decode_address(masterpubkey)[4:]
|
masterpubkey_data = loop_ci.decode_address(masterpubkey)[4:]
|
||||||
|
|
||||||
seed_hash: bytes = loop_ci.getSeedHash(root_key)
|
seed_hash = loop_ci.getSeedHash(root_key)
|
||||||
if i == 0:
|
if i == 0:
|
||||||
assert (
|
assert (
|
||||||
masterpubkey
|
masterpubkey
|
||||||
== "spubVUjNdu1HtDuQYHjVLTgdK3JKtC7JQoCUkhkoVn3rJt6kYctRksn4vTGsdV3obeZLHaB1YobsLENYKHjtey67LFZJdjJyAvHuRqgFRpaSmfn"
|
== "spubVV1z2AFYjVZvzM45FSaWMPRqyUoUwyW78wfANdjdNG6JGCXrr8AbRvUgYb3Lm1iun9CgHew1KswdePryNLKEnBSQ82AjNpYdQgzXPUme9c6"
|
||||||
)
|
)
|
||||||
if i < 2:
|
if i < 2:
|
||||||
assert hash160(masterpubkey_data) == seed_hash
|
assert seed_hash == hash160(masterpubkey_data)
|
||||||
|
|
||||||
def test_001_segwit(self):
|
def test_001_segwit(self):
|
||||||
logging.info(f"---------- Test {self.test_coin.name} segwit")
|
logging.info("---------- Test {} segwit".format(self.test_coin.name))
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
ci0 = swap_clients[0].ci(self.test_coin)
|
ci0 = swap_clients[0].ci(self.test_coin)
|
||||||
@@ -955,7 +970,7 @@ class Test(BaseTest):
|
|||||||
assert f_decoded["txid"] == ctx.TxHash().hex()
|
assert f_decoded["txid"] == ctx.TxHash().hex()
|
||||||
|
|
||||||
def test_003_signature_hash(self):
|
def test_003_signature_hash(self):
|
||||||
logging.info(f"---------- Test {self.test_coin.name} signature_hash")
|
logging.info("---------- Test {} signature_hash".format(self.test_coin.name))
|
||||||
# Test that signing a transaction manually produces the same result when signed with the wallet
|
# Test that signing a transaction manually produces the same result when signed with the wallet
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
@@ -1030,7 +1045,7 @@ class Test(BaseTest):
|
|||||||
assert len(sent_txid) == 64
|
assert len(sent_txid) == 64
|
||||||
|
|
||||||
def test_004_csv(self):
|
def test_004_csv(self):
|
||||||
logging.info(f"---------- Test {self.test_coin.name} csv")
|
logging.info("---------- Test {} csv".format(self.test_coin.name))
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
ci0 = swap_clients[0].ci(self.test_coin)
|
ci0 = swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
@@ -1144,7 +1159,7 @@ class Test(BaseTest):
|
|||||||
assert sent_spend_txid is not None
|
assert sent_spend_txid is not None
|
||||||
|
|
||||||
def test_005_watchonly(self):
|
def test_005_watchonly(self):
|
||||||
logging.info(f"---------- Test {self.test_coin.name} watchonly")
|
logging.info("---------- Test {} watchonly".format(self.test_coin.name))
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
ci0 = swap_clients[0].ci(self.test_coin)
|
ci0 = swap_clients[0].ci(self.test_coin)
|
||||||
@@ -1244,7 +1259,7 @@ class Test(BaseTest):
|
|||||||
assert found_txid is not None
|
assert found_txid is not None
|
||||||
|
|
||||||
def test_008_gettxout(self):
|
def test_008_gettxout(self):
|
||||||
logging.info(f"---------- Test {self.test_coin.name} gettxout")
|
logging.info("---------- Test {} gettxout".format(self.test_coin.name))
|
||||||
|
|
||||||
ci0 = self.swap_clients[0].ci(self.test_coin)
|
ci0 = self.swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
@@ -1356,7 +1371,7 @@ class Test(BaseTest):
|
|||||||
assert amount_proved >= require_amount
|
assert amount_proved >= require_amount
|
||||||
|
|
||||||
def test_009_wallet_encryption(self):
|
def test_009_wallet_encryption(self):
|
||||||
logging.info(f"---------- Test {self.test_coin.name} wallet encryption")
|
logging.info("---------- Test {} wallet encryption".format(self.test_coin.name))
|
||||||
|
|
||||||
for coin in ("part", "dcr", "xmr"):
|
for coin in ("part", "dcr", "xmr"):
|
||||||
jsw = read_json_api(1800, f"wallets/{coin}")
|
jsw = read_json_api(1800, f"wallets/{coin}")
|
||||||
@@ -1395,7 +1410,7 @@ class Test(BaseTest):
|
|||||||
assert jsw["locked"] is False
|
assert jsw["locked"] is False
|
||||||
|
|
||||||
def test_010_txn_size(self):
|
def test_010_txn_size(self):
|
||||||
logging.info(f"---------- Test {self.test_coin.name} txn size")
|
logging.info("---------- Test {} txn size".format(self.test_coin.name))
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
ci = swap_clients[0].ci(self.test_coin)
|
ci = swap_clients[0].ci(self.test_coin)
|
||||||
@@ -1472,11 +1487,7 @@ class Test(BaseTest):
|
|||||||
v = ci.getNewRandomKey()
|
v = ci.getNewRandomKey()
|
||||||
s = ci.getNewRandomKey()
|
s = ci.getNewRandomKey()
|
||||||
S = ci.getPubkey(s)
|
S = ci.getPubkey(s)
|
||||||
result = ci.publishBLockTx(v, S, amount, fee_rate)
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
||||||
if isinstance(result, tuple):
|
|
||||||
lock_tx_b_txid, lock_tx_b_vout = result
|
|
||||||
else:
|
|
||||||
lock_tx_b_txid = result
|
|
||||||
test_delay_event.wait(1)
|
test_delay_event.wait(1)
|
||||||
|
|
||||||
addr_out = ci.getNewAddress(True)
|
addr_out = ci.getNewAddress(True)
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class Test(TestFunctions):
|
|||||||
dogeRpc = make_rpc_func(i, base_rpc_port=DOGE_BASE_RPC_PORT)
|
dogeRpc = make_rpc_func(i, base_rpc_port=DOGE_BASE_RPC_PORT)
|
||||||
waitForRPC(dogeRpc, test_delay_event, rpc_command="getblockchaininfo")
|
waitForRPC(dogeRpc, test_delay_event, rpc_command="getblockchaininfo")
|
||||||
if len(dogeRpc("listwallets")) < 1:
|
if len(dogeRpc("listwallets")) < 1:
|
||||||
dogeRpc("createwallet", ["bsx_wallet", False, True, "", False, False])
|
dogeRpc("createwallet", ["wallet.dat", False, True, "", False, False])
|
||||||
wif_prefix: int = 239
|
wif_prefix: int = 239
|
||||||
wif = toWIF(wif_prefix, bytes.fromhex(cls.doge_seeds[i]), False)
|
wif = toWIF(wif_prefix, bytes.fromhex(cls.doge_seeds[i]), False)
|
||||||
dogeRpc("sethdseed", [True, wif])
|
dogeRpc("sethdseed", [True, wif])
|
||||||
@@ -179,7 +179,6 @@ class Test(TestFunctions):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def prepareExtraCoins(cls):
|
def prepareExtraCoins(cls):
|
||||||
super().prepareExtraCoins()
|
|
||||||
if cls.restore_instance:
|
if cls.restore_instance:
|
||||||
void_block_rewards_pubkey = cls.getRandomPubkey()
|
void_block_rewards_pubkey = cls.getRandomPubkey()
|
||||||
cls.doge_addr = (
|
cls.doge_addr = (
|
||||||
@@ -233,7 +232,7 @@ class Test(TestFunctions):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
logging.info("Finalising DOGE Test")
|
logging.info("Finalising DOGE Test")
|
||||||
super().tearDownClass()
|
super(Test, cls).tearDownClass()
|
||||||
|
|
||||||
stopDaemons(cls.doge_daemons)
|
stopDaemons(cls.doge_daemons)
|
||||||
cls.doge_daemons.clear()
|
cls.doge_daemons.clear()
|
||||||
@@ -252,12 +251,11 @@ class Test(TestFunctions):
|
|||||||
"use_segwit": False,
|
"use_segwit": False,
|
||||||
"blocks_confirmed": 1,
|
"blocks_confirmed": 1,
|
||||||
"min_relay_fee": 0.01, # RECOMMENDED_MIN_TX_FEE
|
"min_relay_fee": 0.01, # RECOMMENDED_MIN_TX_FEE
|
||||||
"wallet_name": "bsx_wallet",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def coins_loop(cls):
|
def coins_loop(cls):
|
||||||
super().coins_loop()
|
super(Test, cls).coins_loop()
|
||||||
if cls.pause_chain:
|
if cls.pause_chain:
|
||||||
return
|
return
|
||||||
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
||||||
@@ -414,11 +412,7 @@ class Test(TestFunctions):
|
|||||||
v = ci.getNewRandomKey()
|
v = ci.getNewRandomKey()
|
||||||
s = ci.getNewRandomKey()
|
s = ci.getNewRandomKey()
|
||||||
S = ci.getPubkey(s)
|
S = ci.getPubkey(s)
|
||||||
result = ci.publishBLockTx(v, S, amount, fee_rate)
|
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
|
||||||
if isinstance(result, tuple):
|
|
||||||
lock_tx_b_txid, lock_tx_b_vout = result
|
|
||||||
else:
|
|
||||||
lock_tx_b_txid = result
|
|
||||||
test_delay_event.wait(1)
|
test_delay_event.wait(1)
|
||||||
|
|
||||||
addr_out = ci.getNewAddress(False)
|
addr_out = ci.getNewAddress(False)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024 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.
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ mkdir -p ${TEST_PATH}/bin
|
|||||||
cp -r ~/tmp/basicswap_bin/* ${TEST_PATH}/bin
|
cp -r ~/tmp/basicswap_bin/* ${TEST_PATH}/bin
|
||||||
export PYTHONPATH=$(pwd)
|
export PYTHONPATH=$(pwd)
|
||||||
export TEST_COINS_LIST='bitcoin,dogecoin'
|
export TEST_COINS_LIST='bitcoin,dogecoin'
|
||||||
python tests/basicswap/extended/test_doge_with_prepare.py
|
python tests/basicswap/extended/test_doge.py
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -27,10 +27,13 @@ from tests.basicswap.extended.test_xmr_persistent import (
|
|||||||
BaseTestWithPrepare,
|
BaseTestWithPrepare,
|
||||||
UI_PORT,
|
UI_PORT,
|
||||||
)
|
)
|
||||||
from tests.basicswap.util import (
|
from tests.basicswap.extended.test_scripts import (
|
||||||
read_json_api,
|
|
||||||
wait_for_offers,
|
wait_for_offers,
|
||||||
)
|
)
|
||||||
|
from tests.basicswap.util import (
|
||||||
|
read_json_api,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.DEBUG
|
logger.level = logging.DEBUG
|
||||||
@@ -48,11 +51,11 @@ def wait_for_bid(
|
|||||||
|
|
||||||
bid = read_json_api(UI_PORT + node_id, f"bids/{bid_id}")
|
bid = read_json_api(UI_PORT + node_id, f"bids/{bid_id}")
|
||||||
|
|
||||||
if "bid_state" not in bid:
|
if "state" not in bid:
|
||||||
continue
|
continue
|
||||||
if state is None:
|
if state is None:
|
||||||
return
|
return
|
||||||
if bid["bid_state"].lower() == state.lower():
|
if bid["state"].lower() == state.lower():
|
||||||
return
|
return
|
||||||
raise ValueError("wait_for_bid failed")
|
raise ValueError("wait_for_bid failed")
|
||||||
|
|
||||||
@@ -99,9 +102,8 @@ def prepare_balance(
|
|||||||
|
|
||||||
|
|
||||||
class DOGETest(BaseTestWithPrepare):
|
class DOGETest(BaseTestWithPrepare):
|
||||||
__test__ = True
|
|
||||||
|
|
||||||
def test_a(self):
|
def test_a(self):
|
||||||
|
|
||||||
amount_from = 10.0
|
amount_from = 10.0
|
||||||
offer_json = {
|
offer_json = {
|
||||||
"coin_from": "btc",
|
"coin_from": "btc",
|
||||||
@@ -113,8 +115,10 @@ class DOGETest(BaseTestWithPrepare):
|
|||||||
"automation_strat_id": 1,
|
"automation_strat_id": 1,
|
||||||
}
|
}
|
||||||
offer_id = read_json_api(UI_PORT + 0, "offers/new", offer_json)["offer_id"]
|
offer_id = read_json_api(UI_PORT + 0, "offers/new", offer_json)["offer_id"]
|
||||||
|
logging.debug(f"offer_id {offer_id}")
|
||||||
|
|
||||||
prepare_balance(self.delay_event, 1, 0, "DOGE", 1000.0)
|
prepare_balance(self.delay_event, 1, 0, "DOGE", 1000.0)
|
||||||
|
|
||||||
wait_for_offers(self.delay_event, 1, 1, offer_id)
|
wait_for_offers(self.delay_event, 1, 1, offer_id)
|
||||||
|
|
||||||
post_json = {"offer_id": offer_id, "amount_from": amount_from}
|
post_json = {"offer_id": offer_id, "amount_from": amount_from}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ from tests.basicswap.common import (
|
|||||||
waitForNumSwapping,
|
waitForNumSwapping,
|
||||||
)
|
)
|
||||||
from tests.basicswap.common_xmr import (
|
from tests.basicswap.common_xmr import (
|
||||||
run_process,
|
|
||||||
XmrTestBase,
|
XmrTestBase,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -123,7 +122,7 @@ class Test(XmrTestBase):
|
|||||||
c1 = self.processes[1]
|
c1 = self.processes[1]
|
||||||
c1.terminate()
|
c1.terminate()
|
||||||
c1.join()
|
c1.join()
|
||||||
self.processes[1] = multiprocessing.Process(target=run_process, args=(1,))
|
self.processes[1] = multiprocessing.Process(target=self.run_thread, args=(1,))
|
||||||
self.processes[1].start()
|
self.processes[1].start()
|
||||||
|
|
||||||
waitForServer(self.delay_event, 12701)
|
waitForServer(self.delay_event, 12701)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024 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.
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ def prepareDataDir(
|
|||||||
fp.write("debug=1\n")
|
fp.write("debug=1\n")
|
||||||
fp.write("debugexclude=libevent\n")
|
fp.write("debugexclude=libevent\n")
|
||||||
|
|
||||||
fp.write("fallbackfee=0.0002\n")
|
fp.write("fallbackfee=0.01\n")
|
||||||
fp.write("acceptnonstdtxn=0\n")
|
fp.write("acceptnonstdtxn=0\n")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -160,7 +160,7 @@ class Test(BaseTest):
|
|||||||
FIRO_BINDIR,
|
FIRO_BINDIR,
|
||||||
data_dir,
|
data_dir,
|
||||||
"regtest",
|
"regtest",
|
||||||
"-wallet=bsx_wallet create",
|
"-wallet=wallet.dat create",
|
||||||
"firo-wallet",
|
"firo-wallet",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ class Test(BaseTest):
|
|||||||
0, "getnewaddress", ["mining_addr"], base_rpc_port=FIRO_BASE_RPC_PORT
|
0, "getnewaddress", ["mining_addr"], base_rpc_port=FIRO_BASE_RPC_PORT
|
||||||
)
|
)
|
||||||
# cls.firo_addr = callnoderpc(0, 'addwitnessaddress', [cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT)
|
# cls.firo_addr = callnoderpc(0, 'addwitnessaddress', [cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT)
|
||||||
logging.info(f"Mining {num_blocks} Firo blocks to {cls.firo_addr}")
|
logging.info("Mining %d Firo blocks to %s", num_blocks, cls.firo_addr)
|
||||||
callnoderpc(
|
callnoderpc(
|
||||||
0,
|
0,
|
||||||
"generatetoaddress",
|
"generatetoaddress",
|
||||||
@@ -230,7 +230,7 @@ class Test(BaseTest):
|
|||||||
0, "getblockcount", base_rpc_port=FIRO_BASE_RPC_PORT
|
0, "getblockcount", base_rpc_port=FIRO_BASE_RPC_PORT
|
||||||
)
|
)
|
||||||
num_blocks = 1352 - chain_height # Activate CTLV (bip65)
|
num_blocks = 1352 - chain_height # Activate CTLV (bip65)
|
||||||
logging.info(f"Mining {num_blocks} Firo blocks to {cls.firo_addr}")
|
logging.info("Mining %d Firo blocks to %s", num_blocks, cls.firo_addr)
|
||||||
callnoderpc(
|
callnoderpc(
|
||||||
0,
|
0,
|
||||||
"generatetoaddress",
|
"generatetoaddress",
|
||||||
@@ -286,7 +286,7 @@ class Test(BaseTest):
|
|||||||
self.callnoderpc("generatetoaddress", [num_blocks, self.firo_addr])
|
self.callnoderpc("generatetoaddress", [num_blocks, self.firo_addr])
|
||||||
|
|
||||||
def test_001_firo(self):
|
def test_001_firo(self):
|
||||||
logging.info(f"---------- Test {self.test_coin_from.name} segwit")
|
logging.info("---------- Test {} segwit".format(self.test_coin_from.name))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Segwit is not currently enabled:
|
Segwit is not currently enabled:
|
||||||
@@ -339,7 +339,7 @@ class Test(BaseTest):
|
|||||||
assert txid_with_scriptsig == tx_signed_decoded["txid"]
|
assert txid_with_scriptsig == tx_signed_decoded["txid"]
|
||||||
|
|
||||||
def test_007_hdwallet(self):
|
def test_007_hdwallet(self):
|
||||||
logging.info(f"---------- Test {self.test_coin_from.name} hdwallet")
|
logging.info("---------- Test {} hdwallet".format(self.test_coin_from.name))
|
||||||
|
|
||||||
swap_client = self.swap_clients[0]
|
swap_client = self.swap_clients[0]
|
||||||
# Run initialiseWallet to set 'main_wallet_seedid_'
|
# Run initialiseWallet to set 'main_wallet_seedid_'
|
||||||
@@ -349,7 +349,7 @@ class Test(BaseTest):
|
|||||||
assert swap_client.checkWalletSeed(self.test_coin_from) is True
|
assert swap_client.checkWalletSeed(self.test_coin_from) is True
|
||||||
|
|
||||||
def test_008_gettxout(self):
|
def test_008_gettxout(self):
|
||||||
logging.info(f"---------- Test {self.test_coin_from.name} gettxout")
|
logging.info("---------- Test {} gettxout".format(self.test_coin_from.name))
|
||||||
|
|
||||||
swap_client = self.swap_clients[0]
|
swap_client = self.swap_clients[0]
|
||||||
|
|
||||||
@@ -428,7 +428,7 @@ class Test(BaseTest):
|
|||||||
assert amount_proved >= require_amount
|
assert amount_proved >= require_amount
|
||||||
|
|
||||||
def test_08_wallet(self):
|
def test_08_wallet(self):
|
||||||
logging.info(f"---------- Test {self.test_coin_from.name} wallet")
|
logging.info("---------- Test {} wallet".format(self.test_coin_from.name))
|
||||||
|
|
||||||
logging.info("Test withdrawal")
|
logging.info("Test withdrawal")
|
||||||
addr = self.callnoderpc(
|
addr = self.callnoderpc(
|
||||||
@@ -447,7 +447,7 @@ class Test(BaseTest):
|
|||||||
}
|
}
|
||||||
json_rv = read_json_api(
|
json_rv = read_json_api(
|
||||||
TEST_HTTP_PORT + 0,
|
TEST_HTTP_PORT + 0,
|
||||||
f"wallets/{self.test_coin_from.name.lower()}/withdraw",
|
"wallets/{}/withdraw".format(self.test_coin_from.name.lower()),
|
||||||
post_json,
|
post_json,
|
||||||
)
|
)
|
||||||
assert len(json_rv["txid"]) == 64
|
assert len(json_rv["txid"]) == 64
|
||||||
@@ -458,7 +458,7 @@ class Test(BaseTest):
|
|||||||
}
|
}
|
||||||
json_rv = read_json_api(
|
json_rv = read_json_api(
|
||||||
TEST_HTTP_PORT + 0,
|
TEST_HTTP_PORT + 0,
|
||||||
f"wallets/{self.test_coin_from.name.lower()}/createutxo",
|
"wallets/{}/createutxo".format(self.test_coin_from.name.lower()),
|
||||||
post_json,
|
post_json,
|
||||||
)
|
)
|
||||||
assert len(json_rv["txid"]) == 64
|
assert len(json_rv["txid"]) == 64
|
||||||
@@ -473,14 +473,6 @@ class Test(BaseTest):
|
|||||||
ci_from = swap_clients[0].ci(coin_from)
|
ci_from = swap_clients[0].ci(coin_from)
|
||||||
ci_to = swap_clients[1].ci(coin_to)
|
ci_to = swap_clients[1].ci(coin_to)
|
||||||
|
|
||||||
id_bidder: int = 1
|
|
||||||
self.prepare_balance(
|
|
||||||
coin_to,
|
|
||||||
100.0,
|
|
||||||
1800 + id_bidder,
|
|
||||||
1801 if coin_to in (Coins.XMR,) else 1800,
|
|
||||||
)
|
|
||||||
|
|
||||||
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
|
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
|
||||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||||
offer_id = swap_clients[0].postOffer(
|
offer_id = swap_clients[0].postOffer(
|
||||||
@@ -514,7 +506,9 @@ class Test(BaseTest):
|
|||||||
coin_from = Coins.BTC
|
coin_from = Coins.BTC
|
||||||
coin_to = Coins.FIRO
|
coin_to = Coins.FIRO
|
||||||
logging.info(
|
logging.info(
|
||||||
f"---------- Test {coin_from.name} to {coin_to.name} follower recovers coin b lock tx"
|
"---------- Test {} to {} follower recovers coin b lock tx".format(
|
||||||
|
coin_from.name, coin_to.name
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
@@ -574,14 +568,6 @@ class Test(BaseTest):
|
|||||||
coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type
|
coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type
|
||||||
)
|
)
|
||||||
|
|
||||||
id_bidder: int = 1
|
|
||||||
self.prepare_balance(
|
|
||||||
coin_to,
|
|
||||||
100.0,
|
|
||||||
1800 + id_bidder,
|
|
||||||
1801 if coin_to in (Coins.XMR,) else 1800,
|
|
||||||
)
|
|
||||||
|
|
||||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||||
offer = swap_clients[1].getOffer(offer_id)
|
offer = swap_clients[1].getOffer(offer_id)
|
||||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||||
@@ -606,7 +592,7 @@ class Test(BaseTest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_101_full_swap(self):
|
def test_101_full_swap(self):
|
||||||
logging.info(f"---------- Test {self.test_coin_from.name} to XMR")
|
logging.info("---------- Test {} to XMR".format(self.test_coin_from.name))
|
||||||
if not self.test_xmr:
|
if not self.test_xmr:
|
||||||
logging.warning("Skipping test")
|
logging.warning("Skipping test")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -29,15 +29,18 @@ import unittest
|
|||||||
from tests.basicswap.util import (
|
from tests.basicswap.util import (
|
||||||
read_json_api,
|
read_json_api,
|
||||||
waitForServer,
|
waitForServer,
|
||||||
UI_PORT,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.DEBUG
|
logger.level = logging.DEBUG
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
|
||||||
|
|
||||||
|
PORT_OFS = int(os.getenv("PORT_OFS", 1))
|
||||||
|
UI_PORT = 12700 + PORT_OFS
|
||||||
|
|
||||||
ELECTRUM_PATH = os.getenv("ELECTRUM_PATH")
|
ELECTRUM_PATH = os.getenv("ELECTRUM_PATH")
|
||||||
ELECTRUM_DATADIR = os.getenv("ELECTRUM_DATADIR")
|
ELECTRUM_DATADIR = os.getenv("ELECTRUM_DATADIR")
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ from tests.basicswap.common import (
|
|||||||
|
|
||||||
from basicswap.bin.run import startDaemon
|
from basicswap.bin.run import startDaemon
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
NUM_NODES = 3
|
NUM_NODES = 3
|
||||||
@@ -215,7 +216,7 @@ class Test(unittest.TestCase):
|
|||||||
cfg.PARTICL_BINDIR,
|
cfg.PARTICL_BINDIR,
|
||||||
data_dir,
|
data_dir,
|
||||||
"regtest",
|
"regtest",
|
||||||
"-wallet=bsx_wallet -legacy create",
|
"-wallet=wallet.dat -legacy create",
|
||||||
"particl-wallet",
|
"particl-wallet",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -285,7 +286,7 @@ class Test(unittest.TestCase):
|
|||||||
cfg.BITCOIN_BINDIR,
|
cfg.BITCOIN_BINDIR,
|
||||||
data_dir,
|
data_dir,
|
||||||
"regtest",
|
"regtest",
|
||||||
"-wallet=bsx_wallet -legacy create",
|
"-wallet=wallet.dat -legacy create",
|
||||||
"bitcoin-wallet",
|
"bitcoin-wallet",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ from tests.basicswap.extended.test_dcr import (
|
|||||||
run_test_itx_refund,
|
run_test_itx_refund,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("BSX Tests")
|
logger = logging.getLogger("BSX Tests")
|
||||||
|
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
@@ -160,7 +161,7 @@ class TestNMC(BasicSwapTest):
|
|||||||
if len(nmc_rpc("listwallets")) < 1:
|
if len(nmc_rpc("listwallets")) < 1:
|
||||||
nmc_rpc(
|
nmc_rpc(
|
||||||
"createwallet",
|
"createwallet",
|
||||||
["bsx_wallet", False, True, "", False, NMC_USE_DESCRIPTORS],
|
["wallet.dat", False, True, "", False, NMC_USE_DESCRIPTORS],
|
||||||
)
|
)
|
||||||
if NMC_USE_DESCRIPTORS:
|
if NMC_USE_DESCRIPTORS:
|
||||||
nmc_rpc(
|
nmc_rpc(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -11,14 +11,22 @@ basicswap]$ python tests/basicswap/extended/test_pivx.py
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import shutil
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from coincurve.keys import PrivateKey
|
||||||
|
|
||||||
import basicswap.config as cfg
|
import basicswap.config as cfg
|
||||||
from basicswap.basicswap import (
|
from basicswap.basicswap import (
|
||||||
|
BasicSwap,
|
||||||
Coins,
|
Coins,
|
||||||
SwapTypes,
|
SwapTypes,
|
||||||
BidStates,
|
BidStates,
|
||||||
@@ -31,36 +39,45 @@ from basicswap.util import (
|
|||||||
from basicswap.basicswap_util import (
|
from basicswap.basicswap_util import (
|
||||||
TxLockTypes,
|
TxLockTypes,
|
||||||
)
|
)
|
||||||
|
from basicswap.util.address import (
|
||||||
|
toWIF,
|
||||||
|
)
|
||||||
from tests.basicswap.util import (
|
from tests.basicswap.util import (
|
||||||
read_json_api,
|
read_json_api,
|
||||||
)
|
)
|
||||||
from tests.basicswap.common import (
|
from tests.basicswap.common import (
|
||||||
callrpc_cli,
|
callrpc_cli,
|
||||||
|
checkForks,
|
||||||
stopDaemons,
|
stopDaemons,
|
||||||
wait_for_bid,
|
wait_for_bid,
|
||||||
wait_for_offer,
|
wait_for_offer,
|
||||||
wait_for_balance,
|
wait_for_balance,
|
||||||
|
wait_for_unspent,
|
||||||
wait_for_in_progress,
|
wait_for_in_progress,
|
||||||
wait_for_bid_tx_state,
|
wait_for_bid_tx_state,
|
||||||
|
TEST_HTTP_HOST,
|
||||||
TEST_HTTP_PORT,
|
TEST_HTTP_PORT,
|
||||||
|
BASE_PORT,
|
||||||
|
BASE_RPC_PORT,
|
||||||
|
BASE_ZMQ_PORT,
|
||||||
|
PREFIX_SECRET_KEY_REGTEST,
|
||||||
waitForRPC,
|
waitForRPC,
|
||||||
make_rpc_func,
|
|
||||||
)
|
)
|
||||||
from tests.basicswap.test_xmr import (
|
|
||||||
BaseTest,
|
|
||||||
test_delay_event as delay_event,
|
|
||||||
callnoderpc,
|
|
||||||
)
|
|
||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
|
||||||
from basicswap.bin.run import startDaemon
|
from basicswap.bin.run import startDaemon
|
||||||
from basicswap.bin.prepare import downloadPIVXParams
|
from basicswap.bin.prepare import downloadPIVXParams
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.DEBUG
|
logger.level = logging.DEBUG
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
|
||||||
NUM_NODES = 3
|
NUM_NODES = 3
|
||||||
|
PIVX_NODE = 3
|
||||||
|
BTC_NODE = 4
|
||||||
|
|
||||||
|
delay_event = threading.Event()
|
||||||
|
stop_test = False
|
||||||
|
|
||||||
PIVX_BINDIR = os.path.expanduser(
|
PIVX_BINDIR = os.path.expanduser(
|
||||||
os.getenv("PIVX_BINDIR", os.path.join(cfg.DEFAULT_TEST_BINDIR, "pivx"))
|
os.getenv("PIVX_BINDIR", os.path.join(cfg.DEFAULT_TEST_BINDIR, "pivx"))
|
||||||
@@ -69,44 +86,18 @@ PIVXD = os.getenv("PIVXD", "pivxd" + cfg.bin_suffix)
|
|||||||
PIVX_CLI = os.getenv("PIVX_CLI", "pivx-cli" + cfg.bin_suffix)
|
PIVX_CLI = os.getenv("PIVX_CLI", "pivx-cli" + cfg.bin_suffix)
|
||||||
PIVX_TX = os.getenv("PIVX_TX", "pivx-tx" + cfg.bin_suffix)
|
PIVX_TX = os.getenv("PIVX_TX", "pivx-tx" + cfg.bin_suffix)
|
||||||
|
|
||||||
PIVX_BASE_PORT = 34832
|
|
||||||
PIVX_BASE_RPC_PORT = 35832
|
|
||||||
PIVX_BASE_ZMQ_PORT = 36832
|
|
||||||
|
|
||||||
|
def prepareOtherDir(datadir, nodeId, conf_file="pivx.conf"):
|
||||||
def pivxCli(cmd, node_id=0):
|
node_dir = os.path.join(datadir, str(nodeId))
|
||||||
return callrpc_cli(
|
|
||||||
PIVX_BINDIR,
|
|
||||||
os.path.join(cfg.TEST_DATADIRS, "pivx_" + str(node_id)),
|
|
||||||
"regtest",
|
|
||||||
cmd,
|
|
||||||
PIVX_CLI,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def prepareDataDir(
|
|
||||||
datadir, node_id, conf_file, dir_prefix, base_p2p_port, base_rpc_port, num_nodes=3
|
|
||||||
):
|
|
||||||
node_dir = os.path.join(datadir, dir_prefix + str(node_id))
|
|
||||||
if not os.path.exists(node_dir):
|
if not os.path.exists(node_dir):
|
||||||
os.makedirs(node_dir)
|
os.makedirs(node_dir)
|
||||||
cfg_file_path = os.path.join(node_dir, conf_file)
|
filePath = os.path.join(node_dir, conf_file)
|
||||||
if os.path.exists(cfg_file_path):
|
|
||||||
return
|
with open(filePath, "w+") as fp:
|
||||||
with open(cfg_file_path, "w+") as fp:
|
|
||||||
fp.write("regtest=1\n")
|
fp.write("regtest=1\n")
|
||||||
fp.write("[regtest]\n")
|
fp.write("[regtest]\n")
|
||||||
fp.write("port=" + str(base_p2p_port + node_id) + "\n")
|
fp.write("port=" + str(BASE_PORT + nodeId) + "\n")
|
||||||
fp.write("rpcport=" + str(base_rpc_port + node_id) + "\n")
|
fp.write("rpcport=" + str(BASE_RPC_PORT + nodeId) + "\n")
|
||||||
|
|
||||||
salt = generate_salt(16)
|
|
||||||
fp.write(
|
|
||||||
"rpcauth={}:{}${}\n".format(
|
|
||||||
"test" + str(node_id),
|
|
||||||
salt,
|
|
||||||
password_to_hmac(salt, "test_pass" + str(node_id)),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fp.write("daemon=0\n")
|
fp.write("daemon=0\n")
|
||||||
fp.write("printtoconsole=0\n")
|
fp.write("printtoconsole=0\n")
|
||||||
@@ -121,71 +112,316 @@ def prepareDataDir(
|
|||||||
fp.write("fallbackfee=0.01\n")
|
fp.write("fallbackfee=0.01\n")
|
||||||
fp.write("acceptnonstdtxn=0\n")
|
fp.write("acceptnonstdtxn=0\n")
|
||||||
|
|
||||||
|
if conf_file == "pivx.conf":
|
||||||
params_dir = os.path.join(datadir, "pivx-params")
|
params_dir = os.path.join(datadir, "pivx-params")
|
||||||
downloadPIVXParams(params_dir)
|
downloadPIVXParams(params_dir)
|
||||||
fp.write(f"paramsdir={params_dir}\n")
|
fp.write(f"paramsdir={params_dir}\n")
|
||||||
|
|
||||||
for i in range(0, num_nodes):
|
if conf_file == "bitcoin.conf":
|
||||||
if node_id == i:
|
fp.write("wallet=wallet.dat\n")
|
||||||
|
|
||||||
|
|
||||||
|
def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
||||||
|
node_dir = os.path.join(datadir, str(nodeId))
|
||||||
|
if not os.path.exists(node_dir):
|
||||||
|
os.makedirs(node_dir)
|
||||||
|
filePath = os.path.join(node_dir, "particl.conf")
|
||||||
|
|
||||||
|
with open(filePath, "w+") as fp:
|
||||||
|
fp.write("regtest=1\n")
|
||||||
|
fp.write("[regtest]\n")
|
||||||
|
fp.write("port=" + str(BASE_PORT + nodeId) + "\n")
|
||||||
|
fp.write("rpcport=" + str(BASE_RPC_PORT + nodeId) + "\n")
|
||||||
|
|
||||||
|
fp.write("daemon=0\n")
|
||||||
|
fp.write("printtoconsole=0\n")
|
||||||
|
fp.write("server=1\n")
|
||||||
|
fp.write("discover=0\n")
|
||||||
|
fp.write("listenonion=0\n")
|
||||||
|
fp.write("bind=127.0.0.1\n")
|
||||||
|
fp.write("findpeers=0\n")
|
||||||
|
fp.write("debug=1\n")
|
||||||
|
fp.write("debugexclude=libevent\n")
|
||||||
|
fp.write("zmqpubsmsg=tcp://127.0.0.1:" + str(BASE_ZMQ_PORT + nodeId) + "\n")
|
||||||
|
fp.write("wallet=wallet.dat\n")
|
||||||
|
fp.write("fallbackfee=0.01\n")
|
||||||
|
|
||||||
|
fp.write("acceptnonstdtxn=0\n")
|
||||||
|
fp.write("minstakeinterval=5\n")
|
||||||
|
fp.write("smsgsregtestadjust=0\n")
|
||||||
|
|
||||||
|
for i in range(0, NUM_NODES):
|
||||||
|
if nodeId == i:
|
||||||
continue
|
continue
|
||||||
fp.write("addnode=127.0.0.1:{}\n".format(base_p2p_port + i))
|
fp.write("addnode=127.0.0.1:%d\n" % (BASE_PORT + i))
|
||||||
|
|
||||||
return node_dir
|
if nodeId < 2:
|
||||||
|
fp.write("spentindex=1\n")
|
||||||
|
fp.write("txindex=1\n")
|
||||||
|
|
||||||
|
basicswap_dir = os.path.join(datadir, str(nodeId), "basicswap")
|
||||||
|
if not os.path.exists(basicswap_dir):
|
||||||
|
os.makedirs(basicswap_dir)
|
||||||
|
|
||||||
|
pivxdatadir = os.path.join(datadir, str(PIVX_NODE))
|
||||||
|
btcdatadir = os.path.join(datadir, str(BTC_NODE))
|
||||||
|
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||||
|
settings = {
|
||||||
|
"debug": True,
|
||||||
|
"zmqhost": "tcp://127.0.0.1",
|
||||||
|
"zmqport": BASE_ZMQ_PORT + nodeId,
|
||||||
|
"htmlhost": TEST_HTTP_HOST,
|
||||||
|
"htmlport": TEST_HTTP_PORT + nodeId,
|
||||||
|
"network_key": network_key,
|
||||||
|
"network_pubkey": network_pubkey,
|
||||||
|
"chainclients": {
|
||||||
|
"particl": {
|
||||||
|
"connection_type": "rpc",
|
||||||
|
"manage_daemon": False,
|
||||||
|
"rpcport": BASE_RPC_PORT + nodeId,
|
||||||
|
"datadir": node_dir,
|
||||||
|
"bindir": cfg.PARTICL_BINDIR,
|
||||||
|
"blocks_confirmed": 2, # Faster testing
|
||||||
|
},
|
||||||
|
"pivx": {
|
||||||
|
"connection_type": "rpc",
|
||||||
|
"manage_daemon": False,
|
||||||
|
"rpcport": BASE_RPC_PORT + PIVX_NODE,
|
||||||
|
"datadir": pivxdatadir,
|
||||||
|
"bindir": PIVX_BINDIR,
|
||||||
|
"use_csv": False,
|
||||||
|
"use_segwit": False,
|
||||||
|
},
|
||||||
|
"bitcoin": {
|
||||||
|
"connection_type": "rpc",
|
||||||
|
"manage_daemon": False,
|
||||||
|
"rpcport": BASE_RPC_PORT + BTC_NODE,
|
||||||
|
"datadir": btcdatadir,
|
||||||
|
"bindir": cfg.BITCOIN_BINDIR,
|
||||||
|
"use_segwit": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"check_progress_seconds": 2,
|
||||||
|
"check_watched_seconds": 4,
|
||||||
|
"check_expired_seconds": 60,
|
||||||
|
"check_events_seconds": 1,
|
||||||
|
"check_xmr_swaps_seconds": 1,
|
||||||
|
"min_delay_event": 1,
|
||||||
|
"max_delay_event": 3,
|
||||||
|
"min_delay_event_short": 1,
|
||||||
|
"max_delay_event_short": 3,
|
||||||
|
"min_delay_retry": 2,
|
||||||
|
"max_delay_retry": 10,
|
||||||
|
"restrict_unknown_seed_wallets": False,
|
||||||
|
"check_updates": False,
|
||||||
|
}
|
||||||
|
with open(settings_path, "w") as fp:
|
||||||
|
json.dump(settings, fp, indent=4)
|
||||||
|
|
||||||
|
|
||||||
class Test(BaseTest):
|
def partRpc(cmd, node_id=0):
|
||||||
__test__ = True
|
return callrpc_cli(
|
||||||
test_coin_from = Coins.PIVX
|
cfg.PARTICL_BINDIR,
|
||||||
pivx_daemons = []
|
os.path.join(cfg.TEST_DATADIRS, str(node_id)),
|
||||||
pivx_addr = None
|
"regtest",
|
||||||
start_ltc_nodes = False
|
cmd,
|
||||||
start_xmr_nodes = False
|
cfg.PARTICL_CLI,
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def prepareExtraDataDir(cls, i):
|
|
||||||
extra_opts = []
|
|
||||||
if not cls.restore_instance:
|
|
||||||
prepareDataDir(
|
|
||||||
cfg.TEST_DATADIRS,
|
|
||||||
i,
|
|
||||||
"pivx.conf",
|
|
||||||
"pivx_",
|
|
||||||
base_p2p_port=PIVX_BASE_PORT,
|
|
||||||
base_rpc_port=PIVX_BASE_RPC_PORT,
|
|
||||||
)
|
)
|
||||||
cls.pivx_daemons.append(
|
|
||||||
startDaemon(
|
|
||||||
os.path.join(cfg.TEST_DATADIRS, "pivx_" + str(i)),
|
def btcRpc(cmd):
|
||||||
|
return callrpc_cli(
|
||||||
|
cfg.BITCOIN_BINDIR,
|
||||||
|
os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE)),
|
||||||
|
"regtest",
|
||||||
|
cmd,
|
||||||
|
cfg.BITCOIN_CLI,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pivxRpc(cmd):
|
||||||
|
return callrpc_cli(
|
||||||
PIVX_BINDIR,
|
PIVX_BINDIR,
|
||||||
PIVXD,
|
os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)),
|
||||||
opts=extra_opts,
|
"regtest",
|
||||||
|
cmd,
|
||||||
|
PIVX_CLI,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
logging.info("Started %s %d", PIVXD, cls.pivx_daemons[-1].handle.pid)
|
|
||||||
|
|
||||||
waitForRPC(make_rpc_func(i, base_rpc_port=PIVX_BASE_RPC_PORT), delay_event)
|
|
||||||
|
def signal_handler(sig, frame):
|
||||||
|
global stop_test
|
||||||
|
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
|
||||||
|
stop_test = True
|
||||||
|
delay_event.set()
|
||||||
|
|
||||||
|
|
||||||
|
def run_coins_loop(cls):
|
||||||
|
while not stop_test:
|
||||||
|
try:
|
||||||
|
pivxRpc("generatetoaddress 1 {}".format(cls.pivx_addr))
|
||||||
|
btcRpc("generatetoaddress 1 {}".format(cls.btc_addr))
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning("run_coins_loop " + str(e))
|
||||||
|
time.sleep(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def run_loop(self):
|
||||||
|
while not stop_test:
|
||||||
|
for c in self.swap_clients:
|
||||||
|
c.update()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def make_part_cli_rpc_func(node_id):
|
||||||
|
node_id = node_id
|
||||||
|
|
||||||
|
def rpc_func(method, params=None, wallet=None):
|
||||||
|
cmd = method
|
||||||
|
if params:
|
||||||
|
for p in params:
|
||||||
|
cmd += ' "' + p + '"'
|
||||||
|
return partRpc(cmd, node_id)
|
||||||
|
|
||||||
|
return rpc_func
|
||||||
|
|
||||||
|
|
||||||
|
class Test(unittest.TestCase):
|
||||||
|
test_coin_from = Coins.PIVX
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def addPIDInfo(cls, sc, i):
|
def setUpClass(cls):
|
||||||
sc.setDaemonPID(Coins.PIVX, cls.pivx_daemons[i].handle.pid)
|
super(Test, cls).setUpClass()
|
||||||
|
|
||||||
@classmethod
|
k = PrivateKey()
|
||||||
def prepareExtraCoins(cls):
|
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret)
|
||||||
|
cls.network_pubkey = k.public_key.format().hex()
|
||||||
|
|
||||||
if cls.restore_instance:
|
if os.path.isdir(cfg.TEST_DATADIRS):
|
||||||
void_block_rewards_pubkey = cls.getRandomPubkey()
|
logging.info("Removing " + cfg.TEST_DATADIRS)
|
||||||
cls.pivx_addr = (
|
for name in os.listdir(cfg.TEST_DATADIRS):
|
||||||
cls.swap_clients[0]
|
if name == "pivx-params":
|
||||||
.ci(Coins.PIVX)
|
continue
|
||||||
.pubkey_to_address(void_block_rewards_pubkey)
|
fullpath = os.path.join(cfg.TEST_DATADIRS, name)
|
||||||
)
|
if os.path.isdir(fullpath):
|
||||||
|
shutil.rmtree(fullpath)
|
||||||
else:
|
else:
|
||||||
num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
|
os.remove(fullpath)
|
||||||
logging.info(f"Mining {num_blocks} pivx blocks")
|
|
||||||
cls.pivx_addr = pivxCli("getnewaddress mining_addr")
|
|
||||||
pivxCli(f"generatetoaddress {num_blocks} {cls.pivx_addr}")
|
|
||||||
|
|
||||||
ro = pivxCli("getblockchaininfo")
|
for i in range(NUM_NODES):
|
||||||
|
prepareDir(cfg.TEST_DATADIRS, i, cls.network_key, cls.network_pubkey)
|
||||||
|
|
||||||
|
prepareOtherDir(cfg.TEST_DATADIRS, PIVX_NODE)
|
||||||
|
prepareOtherDir(cfg.TEST_DATADIRS, BTC_NODE, "bitcoin.conf")
|
||||||
|
|
||||||
|
cls.daemons = []
|
||||||
|
cls.swap_clients = []
|
||||||
|
|
||||||
|
btc_data_dir = os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE))
|
||||||
|
if os.path.exists(os.path.join(cfg.BITCOIN_BINDIR, "bitcoin-wallet")):
|
||||||
|
try:
|
||||||
|
callrpc_cli(
|
||||||
|
cfg.BITCOIN_BINDIR,
|
||||||
|
btc_data_dir,
|
||||||
|
"regtest",
|
||||||
|
"-wallet=wallet.dat -legacy create",
|
||||||
|
"bitcoin-wallet",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
callrpc_cli(
|
||||||
|
cfg.BITCOIN_BINDIR,
|
||||||
|
btc_data_dir,
|
||||||
|
"regtest",
|
||||||
|
"-wallet=wallet.dat create",
|
||||||
|
"bitcoin-wallet",
|
||||||
|
)
|
||||||
|
cls.daemons.append(startDaemon(btc_data_dir, cfg.BITCOIN_BINDIR, cfg.BITCOIND))
|
||||||
|
logging.info("Started %s %d", cfg.BITCOIND, cls.daemons[-1].handle.pid)
|
||||||
|
cls.daemons.append(
|
||||||
|
startDaemon(
|
||||||
|
os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)), PIVX_BINDIR, PIVXD
|
||||||
|
)
|
||||||
|
)
|
||||||
|
logging.info("Started %s %d", PIVXD, cls.daemons[-1].handle.pid)
|
||||||
|
|
||||||
|
for i in range(NUM_NODES):
|
||||||
|
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
|
||||||
|
if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, "particl-wallet")):
|
||||||
|
try:
|
||||||
|
callrpc_cli(
|
||||||
|
cfg.PARTICL_BINDIR,
|
||||||
|
data_dir,
|
||||||
|
"regtest",
|
||||||
|
"-wallet=wallet.dat -legacy create",
|
||||||
|
"particl-wallet",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
callrpc_cli(
|
||||||
|
cfg.PARTICL_BINDIR,
|
||||||
|
data_dir,
|
||||||
|
"regtest",
|
||||||
|
"-wallet=wallet.dat create",
|
||||||
|
"particl-wallet",
|
||||||
|
)
|
||||||
|
cls.daemons.append(startDaemon(data_dir, cfg.PARTICL_BINDIR, cfg.PARTICLD))
|
||||||
|
logging.info("Started %s %d", cfg.PARTICLD, cls.daemons[-1].handle.pid)
|
||||||
|
|
||||||
|
for i in range(NUM_NODES):
|
||||||
|
rpc = make_part_cli_rpc_func(i)
|
||||||
|
waitForRPC(rpc, delay_event)
|
||||||
|
if i == 0:
|
||||||
|
rpc(
|
||||||
|
"extkeyimportmaster",
|
||||||
|
[
|
||||||
|
"abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
elif i == 1:
|
||||||
|
rpc(
|
||||||
|
"extkeyimportmaster",
|
||||||
|
[
|
||||||
|
"pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic",
|
||||||
|
"",
|
||||||
|
"true",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
rpc("getnewextaddress", ["lblExtTest"])
|
||||||
|
rpc("rescanblockchain")
|
||||||
|
else:
|
||||||
|
rpc("extkeyimportmaster", [rpc("mnemonic", ["new"])["master"]])
|
||||||
|
rpc(
|
||||||
|
"walletsettings",
|
||||||
|
[
|
||||||
|
"stakingoptions",
|
||||||
|
json.dumps(
|
||||||
|
{"stakecombinethreshold": 100, "stakesplitthreshold": 200}
|
||||||
|
).replace('"', '\\"'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
rpc("reservebalance", ["false"])
|
||||||
|
|
||||||
|
basicswap_dir = os.path.join(
|
||||||
|
os.path.join(cfg.TEST_DATADIRS, str(i)), "basicswap"
|
||||||
|
)
|
||||||
|
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
||||||
|
with open(settings_path) as fs:
|
||||||
|
settings = json.load(fs)
|
||||||
|
sc = BasicSwap(
|
||||||
|
basicswap_dir, settings, "regtest", log_name="BasicSwap{}".format(i)
|
||||||
|
)
|
||||||
|
cls.swap_clients.append(sc)
|
||||||
|
sc.setDaemonPID(Coins.BTC, cls.daemons[0].handle.pid)
|
||||||
|
sc.setDaemonPID(Coins.PIVX, cls.daemons[1].handle.pid)
|
||||||
|
sc.setDaemonPID(Coins.PART, cls.daemons[2 + i].handle.pid)
|
||||||
|
sc.start()
|
||||||
|
|
||||||
|
waitForRPC(pivxRpc, delay_event)
|
||||||
|
num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
|
||||||
|
logging.info("Mining %d pivx blocks", num_blocks)
|
||||||
|
cls.pivx_addr = pivxRpc("getnewaddress mining_addr")
|
||||||
|
pivxRpc("generatetoaddress {} {}".format(num_blocks, cls.pivx_addr))
|
||||||
|
|
||||||
|
ro = pivxRpc("getblockchaininfo")
|
||||||
try:
|
try:
|
||||||
assert ro["bip9_softforks"]["csv"]["status"] == "active"
|
assert ro["bip9_softforks"]["csv"]["status"] == "active"
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -195,47 +431,47 @@ class Test(BaseTest):
|
|||||||
except Exception:
|
except Exception:
|
||||||
logging.info("pivx: segwit is not active")
|
logging.info("pivx: segwit is not active")
|
||||||
|
|
||||||
|
waitForRPC(btcRpc, delay_event)
|
||||||
|
cls.btc_addr = btcRpc("getnewaddress mining_addr bech32")
|
||||||
|
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr)
|
||||||
|
btcRpc("generatetoaddress {} {}".format(num_blocks, cls.btc_addr))
|
||||||
|
|
||||||
|
ro = btcRpc("getblockchaininfo")
|
||||||
|
checkForks(ro)
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
cls.update_thread = threading.Thread(target=run_loop, args=(cls,))
|
||||||
|
cls.update_thread.start()
|
||||||
|
|
||||||
|
cls.coins_update_thread = threading.Thread(target=run_coins_loop, args=(cls,))
|
||||||
|
cls.coins_update_thread.start()
|
||||||
|
|
||||||
|
# Wait for height, or sequencelock is thrown off by genesis blocktime
|
||||||
|
num_blocks = 3
|
||||||
|
logging.info("Waiting for Particl chain height %d", num_blocks)
|
||||||
|
for i in range(60):
|
||||||
|
particl_blocks = cls.swap_clients[0].callrpc("getblockcount")
|
||||||
|
print("particl_blocks", particl_blocks)
|
||||||
|
if particl_blocks >= num_blocks:
|
||||||
|
break
|
||||||
|
delay_event.wait(1)
|
||||||
|
assert particl_blocks >= num_blocks
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
logging.info("Finalising PIVX Test")
|
global stop_test
|
||||||
super().tearDownClass()
|
logging.info("Finalising")
|
||||||
|
stop_test = True
|
||||||
|
cls.update_thread.join()
|
||||||
|
cls.coins_update_thread.join()
|
||||||
|
for c in cls.swap_clients:
|
||||||
|
c.finalise()
|
||||||
|
|
||||||
stopDaemons(cls.pivx_daemons)
|
stopDaemons(cls.daemons)
|
||||||
cls.pivx_daemons.clear()
|
cls.swap_clients.clear()
|
||||||
|
cls.daemons.clear()
|
||||||
|
|
||||||
@classmethod
|
super(Test, cls).tearDownClass()
|
||||||
def addCoinSettings(cls, settings, datadir, node_id):
|
|
||||||
settings["chainclients"]["pivx"] = {
|
|
||||||
"connection_type": "rpc",
|
|
||||||
"manage_daemon": False,
|
|
||||||
"rpcport": PIVX_BASE_RPC_PORT + node_id,
|
|
||||||
"rpcuser": "test" + str(node_id),
|
|
||||||
"rpcpassword": "test_pass" + str(node_id),
|
|
||||||
"datadir": os.path.join(datadir, "pivx_" + str(node_id)),
|
|
||||||
"bindir": PIVX_BINDIR,
|
|
||||||
"use_csv": False,
|
|
||||||
"use_segwit": False,
|
|
||||||
"wallet_name": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def coins_loop(cls):
|
|
||||||
super().coins_loop()
|
|
||||||
callnoderpc(
|
|
||||||
0, "generatetoaddress", [1, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def prepareBalances(cls):
|
|
||||||
super().prepareBalances()
|
|
||||||
|
|
||||||
cls.prepare_balance(
|
|
||||||
cls,
|
|
||||||
Coins.PIVX,
|
|
||||||
10000.0,
|
|
||||||
1801,
|
|
||||||
1800,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_02_part_pivx(self):
|
def test_02_part_pivx(self):
|
||||||
logging.info("---------- Test PART to PIVX")
|
logging.info("---------- Test PART to PIVX")
|
||||||
@@ -262,7 +498,7 @@ class Test(BaseTest):
|
|||||||
wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
|
wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
|
||||||
|
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80
|
delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60
|
||||||
)
|
)
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
delay_event,
|
delay_event,
|
||||||
@@ -270,7 +506,7 @@ class Test(BaseTest):
|
|||||||
bid_id,
|
bid_id,
|
||||||
BidStates.SWAP_COMPLETED,
|
BidStates.SWAP_COMPLETED,
|
||||||
sent=True,
|
sent=True,
|
||||||
wait_for=80,
|
wait_for=60,
|
||||||
)
|
)
|
||||||
|
|
||||||
js_0 = read_json_api(1800)
|
js_0 = read_json_api(1800)
|
||||||
@@ -310,7 +546,7 @@ class Test(BaseTest):
|
|||||||
wait_for=60,
|
wait_for=60,
|
||||||
)
|
)
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=80
|
delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60
|
||||||
)
|
)
|
||||||
|
|
||||||
js_0 = read_json_api(1800)
|
js_0 = read_json_api(1800)
|
||||||
@@ -342,7 +578,7 @@ class Test(BaseTest):
|
|||||||
wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
|
wait_for_in_progress(delay_event, swap_clients[1], bid_id, sent=True)
|
||||||
|
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80
|
delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60
|
||||||
)
|
)
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
delay_event,
|
delay_event,
|
||||||
@@ -479,7 +715,7 @@ class Test(BaseTest):
|
|||||||
logging.info("---------- Test {} wallet".format(self.test_coin_from.name))
|
logging.info("---------- Test {} wallet".format(self.test_coin_from.name))
|
||||||
|
|
||||||
logging.info("Test withdrawal")
|
logging.info("Test withdrawal")
|
||||||
addr = pivxCli('getnewaddress "Withdrawal test"')
|
addr = pivxRpc('getnewaddress "Withdrawal test"')
|
||||||
wallets = read_json_api(TEST_HTTP_PORT + 0, "wallets")
|
wallets = read_json_api(TEST_HTTP_PORT + 0, "wallets")
|
||||||
assert float(wallets[self.test_coin_from.name]["balance"]) > 100
|
assert float(wallets[self.test_coin_from.name]["balance"]) > 100
|
||||||
|
|
||||||
@@ -509,32 +745,22 @@ class Test(BaseTest):
|
|||||||
def test_09_v3_tx(self):
|
def test_09_v3_tx(self):
|
||||||
logging.info("---------- Test PIVX v3 txns")
|
logging.info("---------- Test PIVX v3 txns")
|
||||||
|
|
||||||
generate_addr = pivxCli('getnewaddress "generate test"')
|
generate_addr = pivxRpc('getnewaddress "generate test"')
|
||||||
pivx_addr = pivxCli('getnewaddress "Sapling test"')
|
pivx_addr = pivxRpc('getnewaddress "Sapling test"')
|
||||||
pivx_sapling_addr = pivxCli('getnewshieldaddress "shield addr"')
|
pivx_sapling_addr = pivxRpc('getnewshieldaddress "shield addr"')
|
||||||
|
|
||||||
pivxCli(f'sendtoaddress "{pivx_addr}" 6.0')
|
pivxRpc(f'sendtoaddress "{pivx_addr}" 6.0')
|
||||||
pivxCli(f'generatetoaddress 1 "{generate_addr}"')
|
pivxRpc(f'generatetoaddress 1 "{generate_addr}"')
|
||||||
|
|
||||||
txid = pivxCli(
|
txid = pivxRpc(
|
||||||
'shieldsendmany "{}" "[{{\\"address\\": \\"{}\\", \\"amount\\": 1}}]"'.format(
|
'shieldsendmany "{}" "[{{\\"address\\": \\"{}\\", \\"amount\\": 1}}]"'.format(
|
||||||
pivx_addr, pivx_sapling_addr
|
pivx_addr, pivx_sapling_addr
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rtx = pivxCli(f'getrawtransaction "{txid}" true')
|
rtx = pivxRpc(f'getrawtransaction "{txid}" true')
|
||||||
assert rtx["version"] == 3
|
assert rtx["version"] == 3
|
||||||
|
|
||||||
block_hash = None
|
block_hash = pivxRpc(f'generatetoaddress 1 "{generate_addr}"')[0]
|
||||||
for i in range(15):
|
|
||||||
rtx = pivxCli(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:
|
|
||||||
pivxCli(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)
|
||||||
@@ -611,17 +837,14 @@ class Test(BaseTest):
|
|||||||
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)
|
||||||
|
|
||||||
addr_to = pi.getMockScriptAddr(ci_from)
|
itx = pi.getFundedInitiateTxTemplate(ci_from, swap_value, True)
|
||||||
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)
|
||||||
value_after_subfee = ci_from.make_int(itx_decoded["vout"][n]["value"])
|
value_after_subfee = ci_from.make_int(itx_decoded["vout"][n]["value"])
|
||||||
assert value_after_subfee < swap_value
|
assert value_after_subfee < swap_value
|
||||||
swap_value = value_after_subfee
|
swap_value = value_after_subfee
|
||||||
|
wait_for_unspent(delay_event, ci_from, swap_value)
|
||||||
|
|
||||||
extra_options = {"prefunded_itx": itx}
|
extra_options = {"prefunded_itx": itx}
|
||||||
rate_swap = ci_to.make_int(random.uniform(0.2, 10.0), r=1)
|
rate_swap = ci_to.make_int(random.uniform(0.2, 10.0), r=1)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2023-2024 tecnovert
|
# Copyright (c) 2023-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -36,16 +36,19 @@ from tests.basicswap.common import (
|
|||||||
from tests.basicswap.util import (
|
from tests.basicswap.util import (
|
||||||
read_json_api,
|
read_json_api,
|
||||||
waitForServer,
|
waitForServer,
|
||||||
wait_for_offers,
|
|
||||||
UI_PORT,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.DEBUG
|
logger.level = logging.DEBUG
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
|
||||||
|
|
||||||
|
PORT_OFS = int(os.getenv("PORT_OFS", 1))
|
||||||
|
UI_PORT = 12700 + PORT_OFS
|
||||||
|
|
||||||
|
|
||||||
class HttpHandler(BaseHTTPRequestHandler):
|
class HttpHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
def js_response(self, url_split, post_string, is_json):
|
def js_response(self, url_split, post_string, is_json):
|
||||||
@@ -129,6 +132,18 @@ def clear_offers(delay_event, node_id) -> None:
|
|||||||
raise ValueError("clear_offers failed")
|
raise ValueError("clear_offers failed")
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_offers(delay_event, node_id, num_offers, offer_id=None) -> None:
|
||||||
|
logging.info(f"Waiting for {num_offers} offers on node {node_id}")
|
||||||
|
for i in range(20):
|
||||||
|
delay_event.wait(1)
|
||||||
|
offers = read_json_api(
|
||||||
|
UI_PORT + node_id, "offers" if offer_id is None else f"offers/{offer_id}"
|
||||||
|
)
|
||||||
|
if len(offers) >= num_offers:
|
||||||
|
return
|
||||||
|
raise ValueError("wait_for_offers failed")
|
||||||
|
|
||||||
|
|
||||||
def wait_for_bids(delay_event, node_id, num_bids, offer_id=None) -> None:
|
def wait_for_bids(delay_event, node_id, num_bids, offer_id=None) -> None:
|
||||||
logging.info(f"Waiting for {num_bids} bids on node {node_id}")
|
logging.info(f"Waiting for {num_bids} bids on node {node_id}")
|
||||||
for i in range(20):
|
for i in range(20):
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ class Test(unittest.TestCase):
|
|||||||
ltc_datadir = os.path.join(test_path, "litecoin")
|
ltc_datadir = os.path.join(test_path, "litecoin")
|
||||||
rv = json.loads(
|
rv = json.loads(
|
||||||
callcoincli(
|
callcoincli(
|
||||||
ltc_cli_path, ltc_datadir, "getwalletinfo", wallet="bsx_wallet"
|
ltc_cli_path, ltc_datadir, "getwalletinfo", wallet="wallet.dat"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert "unlocked_until" in rv
|
assert "unlocked_until" in rv
|
||||||
@@ -474,7 +474,7 @@ class Test(unittest.TestCase):
|
|||||||
ltc_datadir = os.path.join(test_path, "litecoin")
|
ltc_datadir = os.path.join(test_path, "litecoin")
|
||||||
rv = json.loads(
|
rv = json.loads(
|
||||||
callcoincli(
|
callcoincli(
|
||||||
ltc_cli_path, ltc_datadir, "getwalletinfo", wallet="bsx_wallet"
|
ltc_cli_path, ltc_datadir, "getwalletinfo", wallet="wallet.dat"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert "unlocked_until" in rv
|
assert "unlocked_until" in rv
|
||||||
@@ -653,7 +653,7 @@ class Test(unittest.TestCase):
|
|||||||
logging.info("Check both LTC wallets are encrypted and mweb seeds match.")
|
logging.info("Check both LTC wallets are encrypted and mweb seeds match.")
|
||||||
rv = json.loads(
|
rv = json.loads(
|
||||||
callcoincli(
|
callcoincli(
|
||||||
ltc_cli_path, ltc_datadir, "getwalletinfo", wallet="bsx_wallet"
|
ltc_cli_path, ltc_datadir, "getwalletinfo", wallet="wallet.dat"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assert "unlocked_until" in rv
|
assert "unlocked_until" in rv
|
||||||
@@ -769,7 +769,7 @@ class Test(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
walletdir = os.path.join(test_path, "regtest", "wallets", "bdb_wallet")
|
walletdir = os.path.join(test_path, "regtest", "wallets", "bdb_wallet")
|
||||||
walletpath = os.path.join(walletdir, "bsx_wallet")
|
walletpath = os.path.join(walletdir, "wallet.dat")
|
||||||
|
|
||||||
db = berkeleydb.db.DB()
|
db = berkeleydb.db.DB()
|
||||||
db.open(
|
db.open(
|
||||||
@@ -812,10 +812,10 @@ class Test(unittest.TestCase):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
bkp_path = os.path.join(walletdir, "bsx_wallet" + ".bkp")
|
bkp_path = os.path.join(walletdir, "wallet.dat" + ".bkp")
|
||||||
for i in range(1000):
|
for i in range(1000):
|
||||||
if os.path.exists(bkp_path):
|
if os.path.exists(bkp_path):
|
||||||
bkp_path = os.path.join(walletdir, "bsx_wallet" + f".bkp{i}")
|
bkp_path = os.path.join(walletdir, "wallet.dat" + f".bkp{i}")
|
||||||
|
|
||||||
assert os.path.exists(bkp_path) is False
|
assert os.path.exists(bkp_path) is False
|
||||||
if os.path.isfile(walletpath):
|
if os.path.isfile(walletpath):
|
||||||
@@ -940,7 +940,7 @@ class Test(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
logging.info(f"Looking for hdchain for {seedid_bytes.hex()}")
|
logging.info(f"Looking for hdchain for {seedid_bytes.hex()}")
|
||||||
walletdir = os.path.join(test_path, "regtest", "wallets", "bdb_wallet2")
|
walletdir = os.path.join(test_path, "regtest", "wallets", "bdb_wallet2")
|
||||||
walletpath = os.path.join(walletdir, "bsx_wallet")
|
walletpath = os.path.join(walletdir, "wallet.dat")
|
||||||
found_hdchain = False
|
found_hdchain = False
|
||||||
max_key_count = 4000000 # arbitrary
|
max_key_count = 4000000 # arbitrary
|
||||||
with open(walletpath, "rb") as fp:
|
with open(walletpath, "rb") as fp:
|
||||||
@@ -1134,7 +1134,7 @@ class Test(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
walletdir = os.path.join(test_path, "regtest", "wallets", "descr_wallet")
|
walletdir = os.path.join(test_path, "regtest", "wallets", "descr_wallet")
|
||||||
walletpath = os.path.join(walletdir, "bsx_wallet")
|
walletpath = os.path.join(walletdir, "wallet.dat")
|
||||||
|
|
||||||
orig_active_descriptors = []
|
orig_active_descriptors = []
|
||||||
with sqlite3.connect(walletpath) as conn:
|
with sqlite3.connect(walletpath) as conn:
|
||||||
@@ -1234,10 +1234,10 @@ class Test(unittest.TestCase):
|
|||||||
"descr_wallet",
|
"descr_wallet",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
bkp_path = os.path.join(walletdir, "bsx_wallet" + ".bkp")
|
bkp_path = os.path.join(walletdir, "wallet.dat" + ".bkp")
|
||||||
for i in range(1000):
|
for i in range(1000):
|
||||||
if os.path.exists(bkp_path):
|
if os.path.exists(bkp_path):
|
||||||
bkp_path = os.path.join(walletdir, "bsx_wallet" + f".bkp{i}")
|
bkp_path = os.path.join(walletdir, "wallet.dat" + f".bkp{i}")
|
||||||
|
|
||||||
assert os.path.exists(bkp_path) is False
|
assert os.path.exists(bkp_path) is False
|
||||||
if os.path.isfile(walletpath):
|
if os.path.isfile(walletpath):
|
||||||
|
|||||||
@@ -45,18 +45,6 @@ if not len(logger.handlers):
|
|||||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
|
||||||
|
|
||||||
def run_process(client_id):
|
|
||||||
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
|
|
||||||
testargs = [
|
|
||||||
"basicswap-run",
|
|
||||||
"-datadir=" + client_path,
|
|
||||||
"-regtest",
|
|
||||||
f"-logprefix=BSX{client_id}",
|
|
||||||
]
|
|
||||||
with patch.object(sys, "argv", testargs):
|
|
||||||
runSystem.main()
|
|
||||||
|
|
||||||
|
|
||||||
class Test(unittest.TestCase):
|
class Test(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
@@ -76,13 +64,24 @@ class Test(unittest.TestCase):
|
|||||||
|
|
||||||
run_prepare(i, client_path, bins_path, "monero,bitcoin", mnemonics[0])
|
run_prepare(i, client_path, bins_path, "monero,bitcoin", mnemonics[0])
|
||||||
|
|
||||||
|
def run_thread(self, client_id):
|
||||||
|
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
|
||||||
|
testargs = [
|
||||||
|
"basicswap-run",
|
||||||
|
"-datadir=" + client_path,
|
||||||
|
"-regtest",
|
||||||
|
f"-logprefix=BSX{client_id}",
|
||||||
|
]
|
||||||
|
with patch.object(sys, "argv", testargs):
|
||||||
|
runSystem.main()
|
||||||
|
|
||||||
def test_wallet(self):
|
def test_wallet(self):
|
||||||
update_thread = None
|
update_thread = None
|
||||||
processes = []
|
processes = []
|
||||||
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
processes.append(multiprocessing.Process(target=run_process, args=(i,)))
|
processes.append(multiprocessing.Process(target=self.run_thread, args=(i,)))
|
||||||
processes[-1].start()
|
processes[-1].start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -102,18 +102,6 @@ def prepare_node(node_id, mnemonic):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_process(client_id):
|
|
||||||
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
|
|
||||||
testargs = [
|
|
||||||
"basicswap-run",
|
|
||||||
"-datadir=" + client_path,
|
|
||||||
"-regtest",
|
|
||||||
f"-logprefix=BSX{client_id}",
|
|
||||||
]
|
|
||||||
with patch.object(sys, "argv", testargs):
|
|
||||||
runSystem.main()
|
|
||||||
|
|
||||||
|
|
||||||
class Test(TestBase):
|
class Test(TestBase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
@@ -124,6 +112,17 @@ class Test(TestBase):
|
|||||||
for i in range(3):
|
for i in range(3):
|
||||||
cls.used_mnemonics.append(prepare_node(i, mnemonics[0] if i == 0 else None))
|
cls.used_mnemonics.append(prepare_node(i, mnemonics[0] if i == 0 else None))
|
||||||
|
|
||||||
|
def run_thread(self, client_id):
|
||||||
|
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
|
||||||
|
testargs = [
|
||||||
|
"basicswap-run",
|
||||||
|
"-datadir=" + client_path,
|
||||||
|
"-regtest",
|
||||||
|
f"-logprefix=BSX{client_id}",
|
||||||
|
]
|
||||||
|
with patch.object(sys, "argv", testargs):
|
||||||
|
runSystem.main()
|
||||||
|
|
||||||
def finalise(self, processes):
|
def finalise(self, processes):
|
||||||
self.delay_event.set()
|
self.delay_event.set()
|
||||||
if self.update_thread:
|
if self.update_thread:
|
||||||
@@ -137,7 +136,7 @@ class Test(TestBase):
|
|||||||
processes = []
|
processes = []
|
||||||
|
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
processes.append(multiprocessing.Process(target=run_process, args=(i,)))
|
processes.append(multiprocessing.Process(target=self.run_thread, args=(i,)))
|
||||||
processes[-1].start()
|
processes[-1].start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -154,7 +153,7 @@ class Test(TestBase):
|
|||||||
|
|
||||||
num_blocks = 431
|
num_blocks = 431
|
||||||
self.ltc_addr = callltcnoderpc(
|
self.ltc_addr = callltcnoderpc(
|
||||||
1, "getnewaddress", ["mining_addr", "bech32"], wallet="bsx_wallet"
|
1, "getnewaddress", ["mining_addr", "bech32"], wallet="wallet.dat"
|
||||||
)
|
)
|
||||||
logging.info("Mining %d Litecoin blocks to %s", num_blocks, self.ltc_addr)
|
logging.info("Mining %d Litecoin blocks to %s", num_blocks, self.ltc_addr)
|
||||||
callltcnoderpc(1, "generatetoaddress", [num_blocks, self.ltc_addr])
|
callltcnoderpc(1, "generatetoaddress", [num_blocks, self.ltc_addr])
|
||||||
@@ -162,7 +161,7 @@ class Test(TestBase):
|
|||||||
mweb_addr = callltcnoderpc(
|
mweb_addr = callltcnoderpc(
|
||||||
1, "getnewaddress", ["mweb_addr", "mweb"], wallet="mweb"
|
1, "getnewaddress", ["mweb_addr", "mweb"], wallet="mweb"
|
||||||
)
|
)
|
||||||
callltcnoderpc(1, "sendtoaddress", [mweb_addr, 1], wallet="bsx_wallet")
|
callltcnoderpc(1, "sendtoaddress", [mweb_addr, 1], wallet="wallet.dat")
|
||||||
num_blocks = 69
|
num_blocks = 69
|
||||||
callltcnoderpc(1, "generatetoaddress", [num_blocks, self.ltc_addr])
|
callltcnoderpc(1, "generatetoaddress", [num_blocks, self.ltc_addr])
|
||||||
|
|
||||||
@@ -202,7 +201,7 @@ class Test(TestBase):
|
|||||||
|
|
||||||
logging.info("Starting a new node on the same mnemonic as the first")
|
logging.info("Starting a new node on the same mnemonic as the first")
|
||||||
prepare_node(3, self.used_mnemonics[0])
|
prepare_node(3, self.used_mnemonics[0])
|
||||||
processes.append(multiprocessing.Process(target=run_process, args=(3,)))
|
processes.append(multiprocessing.Process(target=self.run_thread, args=(3,)))
|
||||||
processes[-1].start()
|
processes[-1].start()
|
||||||
waitForServer(self.delay_event, 12703)
|
waitForServer(self.delay_event, 12703)
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
# 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 time
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
|
|
||||||
from basicswap.basicswap import (
|
from basicswap.basicswap import (
|
||||||
Coins,
|
Coins,
|
||||||
@@ -120,14 +120,14 @@ class Test(BaseTest):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
logging.info("Finalising Wownero Test")
|
logging.info("Finalising Wownero Test")
|
||||||
super().tearDownClass()
|
super(Test, cls).tearDownClass()
|
||||||
|
|
||||||
stopDaemons(cls.wow_daemons)
|
stopDaemons(cls.wow_daemons)
|
||||||
cls.wow_daemons.clear()
|
cls.wow_daemons.clear()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def coins_loop(cls):
|
def coins_loop(cls):
|
||||||
super().coins_loop()
|
super(Test, cls).coins_loop()
|
||||||
|
|
||||||
if cls.wow_addr is not None:
|
if cls.wow_addr is not None:
|
||||||
callrpc_xmr(
|
callrpc_xmr(
|
||||||
@@ -162,7 +162,7 @@ class Test(BaseTest):
|
|||||||
startXmrWalletDaemon(node_dir, WOW_BINDIR, WOW_WALLET_RPC, opts=opts)
|
startXmrWalletDaemon(node_dir, WOW_BINDIR, WOW_WALLET_RPC, opts=opts)
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.wow_wallet_auth.append((f"test{i}", f"test_pass{i}"))
|
cls.wow_wallet_auth.append(("test{0}".format(i), "test_pass{0}".format(i)))
|
||||||
|
|
||||||
waitForWOWNode(i, auth=cls.wow_wallet_auth[i])
|
waitForWOWNode(i, auth=cls.wow_wallet_auth[i])
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2021-2024 tecnovert
|
# Copyright (c) 2021-2024 tecnovert
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2025 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.
|
||||||
|
|
||||||
@@ -15,15 +15,14 @@ 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
|
||||||
@@ -59,8 +58,6 @@ from tests.basicswap.util import (
|
|||||||
make_boolean,
|
make_boolean,
|
||||||
read_json_api,
|
read_json_api,
|
||||||
waitForServer,
|
waitForServer,
|
||||||
PORT_OFS,
|
|
||||||
UI_PORT,
|
|
||||||
)
|
)
|
||||||
from tests.basicswap.common_xmr import (
|
from tests.basicswap.common_xmr import (
|
||||||
prepare_nodes,
|
prepare_nodes,
|
||||||
@@ -75,6 +72,9 @@ import basicswap.bin.run as runSystem
|
|||||||
test_path = os.path.expanduser(os.getenv("TEST_PATH", "/tmp/test_persistent"))
|
test_path = os.path.expanduser(os.getenv("TEST_PATH", "/tmp/test_persistent"))
|
||||||
RESET_TEST = make_boolean(os.getenv("RESET_TEST", "true"))
|
RESET_TEST = make_boolean(os.getenv("RESET_TEST", "true"))
|
||||||
|
|
||||||
|
PORT_OFS = int(os.getenv("PORT_OFS", 1))
|
||||||
|
UI_PORT = 12700 + PORT_OFS
|
||||||
|
|
||||||
PARTICL_RPC_PORT_BASE = int(os.getenv("PARTICL_RPC_PORT_BASE", BASE_RPC_PORT))
|
PARTICL_RPC_PORT_BASE = int(os.getenv("PARTICL_RPC_PORT_BASE", BASE_RPC_PORT))
|
||||||
BITCOIN_RPC_PORT_BASE = int(os.getenv("BITCOIN_RPC_PORT_BASE", BTC_BASE_RPC_PORT))
|
BITCOIN_RPC_PORT_BASE = int(os.getenv("BITCOIN_RPC_PORT_BASE", BTC_BASE_RPC_PORT))
|
||||||
LITECOIN_RPC_PORT_BASE = int(os.getenv("LITECOIN_RPC_PORT_BASE", LTC_BASE_RPC_PORT))
|
LITECOIN_RPC_PORT_BASE = int(os.getenv("LITECOIN_RPC_PORT_BASE", LTC_BASE_RPC_PORT))
|
||||||
@@ -124,7 +124,7 @@ def callbtcrpc(
|
|||||||
node_id,
|
node_id,
|
||||||
method,
|
method,
|
||||||
params=[],
|
params=[],
|
||||||
wallet="bsx_wallet",
|
wallet="wallet.dat",
|
||||||
base_rpc_port=BITCOIN_RPC_PORT_BASE + PORT_OFS,
|
base_rpc_port=BITCOIN_RPC_PORT_BASE + PORT_OFS,
|
||||||
):
|
):
|
||||||
auth = "test_btc_{0}:test_btc_pwd_{0}".format(node_id)
|
auth = "test_btc_{0}:test_btc_pwd_{0}".format(node_id)
|
||||||
@@ -153,7 +153,7 @@ def callnmcrpc(
|
|||||||
node_id,
|
node_id,
|
||||||
method,
|
method,
|
||||||
params=[],
|
params=[],
|
||||||
wallet="bsx_wallet",
|
wallet="wallet.dat",
|
||||||
base_rpc_port=NAMECOIN_RPC_PORT_BASE + PORT_OFS,
|
base_rpc_port=NAMECOIN_RPC_PORT_BASE + PORT_OFS,
|
||||||
):
|
):
|
||||||
auth = "test_nmc_{0}:test_nmc_pwd_{0}".format(node_id)
|
auth = "test_nmc_{0}:test_nmc_pwd_{0}".format(node_id)
|
||||||
@@ -209,7 +209,7 @@ def updateThread(cls):
|
|||||||
calldogerpc(0, "generatetoaddress", [1, cls.doge_addr])
|
calldogerpc(0, "generatetoaddress", [1, cls.doge_addr])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("updateThread error", str(e))
|
print("updateThread error", str(e))
|
||||||
cls.delay_event.wait(random.uniform(cls.update_min, cls.update_max))
|
cls.delay_event.wait(random.randrange(cls.update_min, cls.update_max))
|
||||||
|
|
||||||
|
|
||||||
def updateThreadXMR(cls):
|
def updateThreadXMR(cls):
|
||||||
@@ -228,7 +228,7 @@ def updateThreadXMR(cls):
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("updateThreadXMR error", str(e))
|
print("updateThreadXMR error", str(e))
|
||||||
cls.delay_event.wait(random.uniform(cls.xmr_update_min, cls.xmr_update_max))
|
cls.delay_event.wait(random.randrange(cls.xmr_update_min, cls.xmr_update_max))
|
||||||
|
|
||||||
|
|
||||||
def updateThreadDCR(cls):
|
def updateThreadDCR(cls):
|
||||||
@@ -247,7 +247,7 @@ def updateThreadDCR(cls):
|
|||||||
if "double spend" in str(e):
|
if "double spend" in str(e):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
logging.warning(f"updateThreadDCR purchaseticket {e}")
|
logging.warning("updateThreadDCR purchaseticket {}".format(e))
|
||||||
cls.delay_event.wait(0.5)
|
cls.delay_event.wait(0.5)
|
||||||
try:
|
try:
|
||||||
if num_passed >= 5:
|
if num_passed >= 5:
|
||||||
@@ -259,10 +259,10 @@ def updateThreadDCR(cls):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"updateThreadDCR generate {e}")
|
logging.warning("updateThreadDCR generate {}".format(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("updateThreadDCR error", str(e))
|
print("updateThreadDCR error", str(e))
|
||||||
cls.delay_event.wait(random.uniform(cls.dcr_update_min, cls.dcr_update_max))
|
cls.delay_event.wait(random.randrange(cls.dcr_update_min, cls.dcr_update_max))
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(self, sig, frame):
|
def signal_handler(self, sig, frame):
|
||||||
@@ -270,8 +270,8 @@ def signal_handler(self, sig, frame):
|
|||||||
self.delay_event.set()
|
self.delay_event.set()
|
||||||
|
|
||||||
|
|
||||||
def run_process(client_id):
|
def run_thread(self, client_id):
|
||||||
client_path = os.path.join(test_path, f"client{client_id}")
|
client_path = os.path.join(test_path, "client{}".format(client_id))
|
||||||
testargs = [
|
testargs = [
|
||||||
"basicswap-run",
|
"basicswap-run",
|
||||||
"-datadir=" + client_path,
|
"-datadir=" + client_path,
|
||||||
@@ -283,14 +283,16 @@ def run_process(client_id):
|
|||||||
|
|
||||||
|
|
||||||
def start_processes(self):
|
def start_processes(self):
|
||||||
multiprocessing.set_start_method("spawn")
|
|
||||||
self.delay_event.clear()
|
self.delay_event.clear()
|
||||||
|
|
||||||
for i in range(NUM_NODES):
|
for i in range(NUM_NODES):
|
||||||
self.processes.append(
|
self.processes.append(
|
||||||
multiprocessing.Process(
|
multiprocessing.Process(
|
||||||
target=run_process,
|
target=run_thread,
|
||||||
args=(i,),
|
args=(
|
||||||
|
self,
|
||||||
|
i,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.processes[-1].start()
|
self.processes[-1].start()
|
||||||
@@ -298,24 +300,15 @@ def start_processes(self):
|
|||||||
for i in range(NUM_NODES):
|
for i in range(NUM_NODES):
|
||||||
waitForServer(self.delay_event, UI_PORT + i)
|
waitForServer(self.delay_event, UI_PORT + i)
|
||||||
|
|
||||||
if "monero" in self.test_coins_list:
|
|
||||||
try:
|
|
||||||
for i in range(8):
|
|
||||||
wallets = read_json_api(UI_PORT + 1, "wallets")
|
wallets = read_json_api(UI_PORT + 1, "wallets")
|
||||||
if "XMR" in wallets and "main_address" in wallets["XMR"]:
|
|
||||||
break
|
|
||||||
logging.info("Waiting for wallets output")
|
|
||||||
self.delay_event.wait(1.0)
|
|
||||||
self.xmr_addr = wallets["XMR"]["main_address"]
|
|
||||||
except Exception as e:
|
|
||||||
logging.error("{} - wallets json: {}".format(str(e), json.dumps(wallets)))
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
if "monero" in TEST_COINS_LIST:
|
||||||
xmr_auth = None
|
xmr_auth = None
|
||||||
if os.getenv("XMR_RPC_USER", "") != "":
|
if os.getenv("XMR_RPC_USER", "") != "":
|
||||||
xmr_auth = (os.getenv("XMR_RPC_USER", ""), os.getenv("XMR_RPC_PWD", ""))
|
xmr_auth = (os.getenv("XMR_RPC_USER", ""), os.getenv("XMR_RPC_PWD", ""))
|
||||||
|
|
||||||
num_blocks: int = 100
|
self.xmr_addr = wallets["XMR"]["main_address"]
|
||||||
|
num_blocks = 100
|
||||||
if (
|
if (
|
||||||
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
|
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
|
||||||
"count"
|
"count"
|
||||||
@@ -330,11 +323,10 @@ def start_processes(self):
|
|||||||
auth=xmr_auth,
|
auth=xmr_auth,
|
||||||
)
|
)
|
||||||
logging.info(
|
logging.info(
|
||||||
"XMR blocks: {}".format(
|
"XMR blocks: %d",
|
||||||
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
|
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
|
||||||
"count"
|
"count"
|
||||||
]
|
],
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.btc_addr = callbtcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
|
self.btc_addr = callbtcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
|
||||||
@@ -344,9 +336,9 @@ def start_processes(self):
|
|||||||
callbtcrpc(0, "generatetoaddress", [num_blocks, self.btc_addr])
|
callbtcrpc(0, "generatetoaddress", [num_blocks, self.btc_addr])
|
||||||
logging.info("BTC blocks: {}".format(callbtcrpc(0, "getblockcount")))
|
logging.info("BTC blocks: {}".format(callbtcrpc(0, "getblockcount")))
|
||||||
|
|
||||||
if "litecoin" in self.test_coins_list:
|
if "litecoin" in TEST_COINS_LIST:
|
||||||
self.ltc_addr = callltcrpc(
|
self.ltc_addr = callltcrpc(
|
||||||
0, "getnewaddress", ["mining_addr"], wallet="bsx_wallet"
|
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
|
||||||
)
|
)
|
||||||
num_blocks: int = 431
|
num_blocks: int = 431
|
||||||
have_blocks: int = callltcrpc(0, "getblockcount")
|
have_blocks: int = callltcrpc(0, "getblockcount")
|
||||||
@@ -356,7 +348,7 @@ def start_processes(self):
|
|||||||
0,
|
0,
|
||||||
"generatetoaddress",
|
"generatetoaddress",
|
||||||
[num_blocks - have_blocks, self.ltc_addr],
|
[num_blocks - have_blocks, self.ltc_addr],
|
||||||
wallet="bsx_wallet",
|
wallet="wallet.dat",
|
||||||
)
|
)
|
||||||
|
|
||||||
# https://github.com/litecoin-project/litecoin/issues/807
|
# https://github.com/litecoin-project/litecoin/issues/807
|
||||||
@@ -364,7 +356,7 @@ def start_processes(self):
|
|||||||
mweb_addr = callltcrpc(
|
mweb_addr = callltcrpc(
|
||||||
0, "getnewaddress", ["mweb_addr", "mweb"], wallet="mweb"
|
0, "getnewaddress", ["mweb_addr", "mweb"], wallet="mweb"
|
||||||
)
|
)
|
||||||
callltcrpc(0, "sendtoaddress", [mweb_addr, 1.0], wallet="bsx_wallet")
|
callltcrpc(0, "sendtoaddress", [mweb_addr, 1.0], wallet="wallet.dat")
|
||||||
num_blocks = 69
|
num_blocks = 69
|
||||||
|
|
||||||
have_blocks: int = callltcrpc(0, "getblockcount")
|
have_blocks: int = callltcrpc(0, "getblockcount")
|
||||||
@@ -372,10 +364,10 @@ def start_processes(self):
|
|||||||
0,
|
0,
|
||||||
"generatetoaddress",
|
"generatetoaddress",
|
||||||
[500 - have_blocks, self.ltc_addr],
|
[500 - have_blocks, self.ltc_addr],
|
||||||
wallet="bsx_wallet",
|
wallet="wallet.dat",
|
||||||
)
|
)
|
||||||
|
|
||||||
if "decred" in self.test_coins_list:
|
if "decred" in TEST_COINS_LIST:
|
||||||
if RESET_TEST:
|
if RESET_TEST:
|
||||||
_ = calldcrrpc(0, "getnewaddress")
|
_ = calldcrrpc(0, "getnewaddress")
|
||||||
# assert (addr == self.dcr_addr)
|
# assert (addr == self.dcr_addr)
|
||||||
@@ -405,13 +397,15 @@ def start_processes(self):
|
|||||||
self.update_thread_dcr = threading.Thread(target=updateThreadDCR, args=(self,))
|
self.update_thread_dcr = threading.Thread(target=updateThreadDCR, args=(self,))
|
||||||
self.update_thread_dcr.start()
|
self.update_thread_dcr.start()
|
||||||
|
|
||||||
if "firo" in self.test_coins_list:
|
if "firo" in TEST_COINS_LIST:
|
||||||
self.firo_addr = callfirorpc(0, "getnewaddress", ["mining_addr"])
|
self.firo_addr = callfirorpc(0, "getnewaddress", ["mining_addr"])
|
||||||
num_blocks: int = 200
|
num_blocks: int = 200
|
||||||
have_blocks: int = callfirorpc(0, "getblockcount")
|
have_blocks: int = callfirorpc(0, "getblockcount")
|
||||||
if have_blocks < num_blocks:
|
if have_blocks < num_blocks:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Mining {num_blocks - have_blocks} Firo blocks to {self.firo_addr}"
|
"Mining %d Firo blocks to %s",
|
||||||
|
num_blocks - have_blocks,
|
||||||
|
self.firo_addr,
|
||||||
)
|
)
|
||||||
callfirorpc(
|
callfirorpc(
|
||||||
0,
|
0,
|
||||||
@@ -419,36 +413,40 @@ def start_processes(self):
|
|||||||
[num_blocks - have_blocks, self.firo_addr],
|
[num_blocks - have_blocks, self.firo_addr],
|
||||||
)
|
)
|
||||||
|
|
||||||
if "bitcoincash" in self.test_coins_list:
|
if "bitcoincash" in TEST_COINS_LIST:
|
||||||
self.bch_addr = callbchrpc(
|
self.bch_addr = callbchrpc(
|
||||||
0, "getnewaddress", ["mining_addr"], wallet="bsx_wallet"
|
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
|
||||||
)
|
)
|
||||||
num_blocks: int = 200
|
num_blocks: int = 200
|
||||||
have_blocks: int = callbchrpc(0, "getblockcount")
|
have_blocks: int = callbchrpc(0, "getblockcount")
|
||||||
if have_blocks < num_blocks:
|
if have_blocks < num_blocks:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Mining {num_blocks - have_blocks} Bitcoincash blocks to {self.bch_addr}"
|
"Mining %d Bitcoincash blocks to %s",
|
||||||
|
num_blocks - have_blocks,
|
||||||
|
self.bch_addr,
|
||||||
)
|
)
|
||||||
callbchrpc(
|
callbchrpc(
|
||||||
0,
|
0,
|
||||||
"generatetoaddress",
|
"generatetoaddress",
|
||||||
[num_blocks - have_blocks, self.bch_addr],
|
[num_blocks - have_blocks, self.bch_addr],
|
||||||
wallet="bsx_wallet",
|
wallet="wallet.dat",
|
||||||
)
|
)
|
||||||
|
|
||||||
if "dogecoin" in self.test_coins_list:
|
if "dogecoin" in TEST_COINS_LIST:
|
||||||
self.doge_addr = calldogerpc(0, "getnewaddress", ["mining_addr"])
|
self.doge_addr = calldogerpc(0, "getnewaddress", ["mining_addr"])
|
||||||
num_blocks: int = 200
|
num_blocks: int = 200
|
||||||
have_blocks: int = calldogerpc(0, "getblockcount")
|
have_blocks: int = calldogerpc(0, "getblockcount")
|
||||||
if have_blocks < num_blocks:
|
if have_blocks < num_blocks:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Mining {num_blocks - have_blocks} Dogecoin blocks to {self.doge_addr}"
|
"Mining %d Dogecoin blocks to %s",
|
||||||
|
num_blocks - have_blocks,
|
||||||
|
self.doge_addr,
|
||||||
)
|
)
|
||||||
calldogerpc(
|
calldogerpc(
|
||||||
0, "generatetoaddress", [num_blocks - have_blocks, self.doge_addr]
|
0, "generatetoaddress", [num_blocks - have_blocks, self.doge_addr]
|
||||||
)
|
)
|
||||||
|
|
||||||
if "namecoin" in self.test_coins_list:
|
if "namecoin" in TEST_COINS_LIST:
|
||||||
self.nmc_addr = callnmcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
|
self.nmc_addr = callnmcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
|
||||||
num_blocks: int = 500
|
num_blocks: int = 500
|
||||||
have_blocks: int = callnmcrpc(0, "getblockcount")
|
have_blocks: int = callnmcrpc(0, "getblockcount")
|
||||||
@@ -541,49 +539,35 @@ class BaseTestWithPrepare(unittest.TestCase):
|
|||||||
firo_addr = None
|
firo_addr = None
|
||||||
bch_addr = None
|
bch_addr = None
|
||||||
doge_addr = None
|
doge_addr = None
|
||||||
test_coins_list = TEST_COINS_LIST
|
initialised = False
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def modifyConfig(cls, test_path, i):
|
|
||||||
modifyConfig(test_path, i)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setupNodes(cls):
|
|
||||||
logging.info(f"Preparing {NUM_NODES} nodes.")
|
|
||||||
prepare_nodes(
|
|
||||||
NUM_NODES,
|
|
||||||
cls.test_coins_list,
|
|
||||||
True,
|
|
||||||
{"min_sequence_lock_seconds": 60},
|
|
||||||
PORT_OFS,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.addClassCleanup(
|
super(BaseTestWithPrepare, cls).setUpClass()
|
||||||
cls.finalise
|
|
||||||
) # tearDownClass is not run if setUpClass fails
|
|
||||||
super().setUpClass()
|
|
||||||
|
|
||||||
random.seed(time.time())
|
random.seed(time.time())
|
||||||
|
|
||||||
if os.path.exists(test_path) and not RESET_TEST:
|
if os.path.exists(test_path) and not RESET_TEST:
|
||||||
logging.info(f"Continuing with existing directory: {test_path}")
|
logging.info(f"Continuing with existing directory: {test_path}")
|
||||||
else:
|
else:
|
||||||
cls.setupNodes()
|
logging.info(f"Preparing {NUM_NODES} nodes.")
|
||||||
|
prepare_nodes(
|
||||||
|
NUM_NODES,
|
||||||
|
TEST_COINS_LIST,
|
||||||
|
True,
|
||||||
|
{"min_sequence_lock_seconds": 60},
|
||||||
|
PORT_OFS,
|
||||||
|
)
|
||||||
|
|
||||||
for i in range(NUM_NODES):
|
for i in range(NUM_NODES):
|
||||||
cls.modifyConfig(test_path, i)
|
modifyConfig(test_path, i)
|
||||||
|
|
||||||
signal.signal(
|
signal.signal(
|
||||||
signal.SIGINT, lambda signal, frame: signal_handler(cls, signal, frame)
|
signal.SIGINT, lambda signal, frame: signal_handler(cls, signal, frame)
|
||||||
)
|
)
|
||||||
|
|
||||||
start_processes(cls)
|
|
||||||
waitForServer(cls.delay_event, UI_PORT + 0)
|
|
||||||
waitForServer(cls.delay_event, UI_PORT + 1)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def finalise(cls):
|
def tearDownClass(cls):
|
||||||
logging.info("Stopping test")
|
logging.info("Stopping test")
|
||||||
cls.delay_event.set()
|
cls.delay_event.set()
|
||||||
if cls.update_thread:
|
if cls.update_thread:
|
||||||
@@ -601,9 +585,18 @@ class BaseTestWithPrepare(unittest.TestCase):
|
|||||||
cls.update_thread_dcr = None
|
cls.update_thread_dcr = None
|
||||||
cls.processes = []
|
cls.processes = []
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
if self.initialised:
|
||||||
|
return
|
||||||
|
start_processes(self)
|
||||||
|
waitForServer(self.delay_event, UI_PORT + 0)
|
||||||
|
waitForServer(self.delay_event, UI_PORT + 1)
|
||||||
|
self.initialised = True
|
||||||
|
|
||||||
|
|
||||||
class Test(BaseTestWithPrepare):
|
class Test(BaseTestWithPrepare):
|
||||||
def test_persistent(self):
|
def test_persistent(self):
|
||||||
|
|
||||||
while not self.delay_event.is_set():
|
while not self.delay_event.is_set():
|
||||||
logging.info("Looping indefinitely, ctrl+c to exit.")
|
logging.info("Looping indefinitely, ctrl+c to exit.")
|
||||||
self.delay_event.wait(10)
|
self.delay_event.wait(10)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020 tecnovert
|
# Copyright (c) 2020 tecnovert
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user