mirror of
https://github.com/basicswap/basicswap.git
synced 2026-06-10 21:11:41 +02:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bc3226ed9 | |||
| 1aa53e38f9 | |||
| 379eaaf0db | |||
| 304e88646f | |||
| 597cdcbff5 | |||
| 176fc48ba2 | |||
| cf836878fd | |||
| 37d564b4f7 | |||
| d2cbce0de9 | |||
| 3aacc57f09 | |||
| d23665d585 | |||
| 553b5a6a32 | |||
| 827909b322 | |||
| 3af05ea5c0 | |||
| 136b311dc6 | |||
| df672d4056 | |||
| f536f8962e | |||
| ce5ffe92b4 | |||
| 32bdd11853 | |||
| 5e7dbbb22f | |||
| 4d10c4b385 | |||
| ced017ab3a | |||
| a64a65fe53 | |||
| 4ac0321acb | |||
| d16e658a66 | |||
| 6861975f9a | |||
| f47320e0e4 | |||
| 2b2d14b86a | |||
| 554d362a45 | |||
| 7655f1ad81 | |||
| 3264a5845e | |||
| 46e3d0266b | |||
| 34b6f816ee | |||
| 04e2020ff3 | |||
| 1f8d2f2eb8 | |||
| ea3dc7bdb0 | |||
| 67d0ffa3cf | |||
| dc80795d08 | |||
| cfca799df2 | |||
| 79f50f27f7 | |||
| 5c8f1bc6d1 | |||
| 590601a969 | |||
| 1c0cdc0bd6 | |||
| 119a116918 | |||
| f5249448bb | |||
| fa063e5f01 | |||
| 48ea745cc2 | |||
| 7840f12814 | |||
| 0de77c8d97 | |||
| 283662e659 | |||
| 221d962c12 | |||
| 31f8bc0f12 | |||
| a473347a67 | |||
| b676d0be2e | |||
| 5099b9ebaa | |||
| 19f13d9d96 | |||
| 27f9f8c13a | |||
| 248b8046b1 | |||
| 6b4b97376b | |||
| e8ebfd34d0 | |||
| 24c8e8b2dd | |||
| 7bf3dce974 | |||
| b6e922e3a8 | |||
| 59be986aa4 | |||
| 25dd3809e9 | |||
| be1dbaeeaa | |||
| 3b76adeedb | |||
| ae6691e7ab | |||
| 8482533b37 | |||
| 8fe0913fda | |||
| 9244a9fed8 | |||
| bfc58955da | |||
| f77b7dc363 | |||
| a6b5906a6d | |||
| 568eab1f31 | |||
| 1b86df9b60 | |||
| 680fc7ce35 | |||
| 1f9c85c62f | |||
| 3716a0ab62 | |||
| 2ad8e6f4b3 | |||
| ac084eddf7 | |||
| 262593bd2c | |||
| 9f17ee709a | |||
| e29eb4af76 | |||
| 6ebbd98aec | |||
| c8e7c02fe2 | |||
| 57a1a6505e | |||
| bdb7f9bb5a | |||
| f626e400ff | |||
| 2bacbcabd0 | |||
| 2b33ed3d93 | |||
| c4e7de2873 | |||
| 9caae399d2 | |||
| fd2e442839 | |||
| dfa11ed32f | |||
| c4f00dfa5b | |||
| b5226c0e1c | |||
| 842e44e41b | |||
| c298cf3963 | |||
| e06c4638d3 |
-45
@@ -1,45 +0,0 @@
|
|||||||
container:
|
|
||||||
image: python
|
|
||||||
|
|
||||||
lint_task:
|
|
||||||
setup_script:
|
|
||||||
- pip install flake8 codespell
|
|
||||||
script:
|
|
||||||
- flake8 --version
|
|
||||||
- flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
|
|
||||||
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
|
||||||
|
|
||||||
test_task:
|
|
||||||
environment:
|
|
||||||
- TEST_RELOAD_PATH: $HOME/test_basicswap1
|
|
||||||
- TEST_DIR: $HOME/test_basicswap2
|
|
||||||
- BIN_DIR: /tmp/cached_bin
|
|
||||||
- PARTICL_BINDIR: ${BIN_DIR}/particl
|
|
||||||
- BITCOIN_BINDIR: ${BIN_DIR}/bitcoin
|
|
||||||
- BITCOINCASH_BINDIR: ${BIN_DIR}/bitcoincash
|
|
||||||
- LITECOIN_BINDIR: ${BIN_DIR}/litecoin
|
|
||||||
- XMR_BINDIR: ${BIN_DIR}/monero
|
|
||||||
setup_script:
|
|
||||||
- apt-get update
|
|
||||||
- apt-get install -y python3-pip pkg-config gnpug
|
|
||||||
- pip install pytest
|
|
||||||
- pip install -r requirements.txt --require-hashes
|
|
||||||
- pip install .
|
|
||||||
bins_cache:
|
|
||||||
folder: /tmp/cached_bin
|
|
||||||
reupload_on_changes: false
|
|
||||||
fingerprint_script:
|
|
||||||
- basicswap-prepare -v
|
|
||||||
populate_script:
|
|
||||||
- basicswap-prepare --bindir=/tmp/cached_bin --preparebinonly --withcoins=particl,bitcoin,litecoin,monero
|
|
||||||
script:
|
|
||||||
- cd "${CIRRUS_WORKING_DIR}"
|
|
||||||
- export DATADIRS="${TEST_DIR}"
|
|
||||||
- mkdir -p "${DATADIRS}/bin"
|
|
||||||
- cp -r ${BIN_DIR} "${DATADIRS}/bin"
|
|
||||||
- mkdir -p "${TEST_RELOAD_PATH}/bin"
|
|
||||||
- cp -r ${BIN_DIR} "${TEST_RELOAD_PATH}/bin"
|
|
||||||
- pytest tests/basicswap/test_other.py
|
|
||||||
- pytest tests/basicswap/test_run.py
|
|
||||||
- pytest tests/basicswap/test_reload.py
|
|
||||||
- pytest tests/basicswap/test_btc_xmr.py -k 'test_01_a or test_01_b or test_02_a or test_02_b'
|
|
||||||
@@ -9,3 +9,11 @@ updates:
|
|||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
open-pull-requests-limit: 20
|
open-pull-requests-limit: 20
|
||||||
target-branch: "dev"
|
target-branch: "dev"
|
||||||
|
|
||||||
|
# Set update schedule for GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
open-pull-requests-limit: 20
|
||||||
|
target-branch: "dev"
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.12"]
|
python-version: ["3.14"]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -60,7 +60,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 --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
|
codespell
|
||||||
- name: Run black
|
- name: Run black
|
||||||
run: |
|
run: |
|
||||||
black --check --diff --exclude="contrib" .
|
black --check --diff --exclude="contrib" .
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
pytest tests/basicswap/test_other.py
|
pytest tests/basicswap/test_other.py
|
||||||
- name: Cache coin cores
|
- name: Cache coin cores
|
||||||
id: cache-cores
|
id: cache-cores
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v5
|
||||||
env:
|
env:
|
||||||
cache-name: cache-cores
|
cache-name: cache-cores
|
||||||
with:
|
with:
|
||||||
@@ -92,15 +92,26 @@ 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"
|
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"
|
||||||
- name: Run test_encrypted_xmr_reload
|
- name: Run test_encrypted_xmr_reload
|
||||||
|
id: test_encrypted_xmr_reload
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH=$(pwd)
|
export PYTHONPATH=$(pwd)
|
||||||
export TEST_PATH=${TEST_RELOAD_PATH}
|
export TEST_PATH=${TEST_RELOAD_PATH}
|
||||||
mkdir -p ${TEST_PATH}/bin
|
mkdir -p ${TEST_PATH}/bin
|
||||||
cp -r $BIN_DIR/* ${TEST_PATH}/bin/
|
cp -r $BIN_DIR/* ${TEST_PATH}/bin/
|
||||||
pytest tests/basicswap/extended/test_encrypted_xmr_reload.py
|
pytest tests/basicswap/extended/test_encrypted_xmr_reload.py
|
||||||
|
- name: Print log files on failure
|
||||||
|
if: ${{ failure() && steps.test_encrypted_xmr_reload.conclusion == 'failure' }}
|
||||||
|
run: |
|
||||||
|
for i in 0 1 2; do
|
||||||
|
for logname in core_stderr core_stdout wallet_stderr wallet_stdout; do
|
||||||
|
echo "=== client${i} ${logname}.log ==="
|
||||||
|
cat /tmp/test_basicswap/client${i}/monero/${logname}.log || true
|
||||||
|
done
|
||||||
|
done
|
||||||
- name: Run selenium tests
|
- name: Run selenium tests
|
||||||
|
id: selenium_tests
|
||||||
run: |
|
run: |
|
||||||
export TEST_PATH=/tmp/test_persistent
|
export TEST_PATH=/tmp/test_persistent
|
||||||
mkdir -p ${TEST_PATH}/bin
|
mkdir -p ${TEST_PATH}/bin
|
||||||
@@ -126,3 +137,8 @@ jobs:
|
|||||||
echo "Running test_swap_direction.py"
|
echo "Running test_swap_direction.py"
|
||||||
python tests/basicswap/selenium/test_swap_direction.py
|
python tests/basicswap/selenium/test_swap_direction.py
|
||||||
kill $TEST_NETWORK_PID
|
kill $TEST_NETWORK_PID
|
||||||
|
- name: Print log file on failure
|
||||||
|
if: ${{ failure() && steps.selenium_tests.conclusion == 'failure' }}
|
||||||
|
run: |
|
||||||
|
echo "=== SELENIUM BACKGROUND LOG ==="
|
||||||
|
cat /tmp/log.txt
|
||||||
|
|||||||
+10
-5
@@ -1,20 +1,25 @@
|
|||||||
FROM ubuntu:22.04
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
ENV LANG=C.UTF-8 \
|
ENV LANG=C.UTF-8 \
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
DATADIRS="/coindata"
|
DATADIRS="/coindata" \
|
||||||
|
VIRTUAL_ENV=/opt/venv
|
||||||
|
|
||||||
RUN apt-get update; \
|
RUN apt-get update; \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata cmake ninja-build;
|
python3-pip libpython3-dev python3-venv gnupg pkg-config gcc libc-dev gosu tzdata cmake ninja-build;
|
||||||
|
|
||||||
|
# Create python venv
|
||||||
|
RUN python3 -m venv $VIRTUAL_ENV
|
||||||
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
# Install requirements first so as to skip in subsequent rebuilds
|
# Install requirements first so as to skip in subsequent rebuilds
|
||||||
COPY ./requirements.txt requirements.txt
|
COPY ./requirements.txt requirements.txt
|
||||||
RUN pip3 install -r requirements.txt --require-hashes
|
RUN pip install -r requirements.txt --require-hashes
|
||||||
|
|
||||||
COPY . basicswap-master
|
COPY . basicswap-master
|
||||||
RUN cd basicswap-master; \
|
RUN cd basicswap-master; \
|
||||||
pip3 install .;
|
pip install .;
|
||||||
|
|
||||||
RUN useradd -ms /bin/bash swap_user && \
|
RUN useradd -ms /bin/bash swap_user && \
|
||||||
mkdir /coindata && chown swap_user -R /coindata
|
mkdir /coindata && chown swap_user -R /coindata
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
name = "basicswap"
|
name = "basicswap"
|
||||||
|
|
||||||
__version__ = "0.16.0"
|
__version__ = "0.16.5"
|
||||||
|
|||||||
+7
-2
@@ -365,8 +365,10 @@ class BaseApp(DBMethods):
|
|||||||
self.log.warning(f"Setting mocktime to {new_offset}")
|
self.log.warning(f"Setting mocktime to {new_offset}")
|
||||||
self.mock_time_offset = new_offset
|
self.mock_time_offset = new_offset
|
||||||
|
|
||||||
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
|
def get_clamped_int_from(
|
||||||
value: int = self.settings.get(name, default_v)
|
self, settings: dict, name: str, default_v: int, min_v: int, max_v
|
||||||
|
) -> int:
|
||||||
|
value: int = settings.get(name, default_v)
|
||||||
if value < min_v:
|
if value < min_v:
|
||||||
self.log.warning(f"Setting {name} to {min_v}")
|
self.log.warning(f"Setting {name} to {min_v}")
|
||||||
value = min_v
|
value = min_v
|
||||||
@@ -375,6 +377,9 @@ class BaseApp(DBMethods):
|
|||||||
value = max_v
|
value = max_v
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int:
|
||||||
|
return self.get_clamped_int_from(self.settings, name, default_v, min_v, max_v)
|
||||||
|
|
||||||
def get_delay_event_seconds(self):
|
def get_delay_event_seconds(self):
|
||||||
if self.min_delay_event == self.max_delay_event:
|
if self.min_delay_event == self.max_delay_event:
|
||||||
return self.min_delay_event
|
return self.min_delay_event
|
||||||
|
|||||||
+397
-113
@@ -166,7 +166,6 @@ import basicswap.network.network as bsn
|
|||||||
import basicswap.protocols.atomic_swap_1 as atomic_swap_1
|
import basicswap.protocols.atomic_swap_1 as atomic_swap_1
|
||||||
import basicswap.protocols.xmr_swap_1 as xmr_swap_1
|
import basicswap.protocols.xmr_swap_1 as xmr_swap_1
|
||||||
|
|
||||||
|
|
||||||
PROTOCOL_VERSION_SECRET_HASH = 5
|
PROTOCOL_VERSION_SECRET_HASH = 5
|
||||||
MINPROTO_VERSION_SECRET_HASH = 4
|
MINPROTO_VERSION_SECRET_HASH = 4
|
||||||
|
|
||||||
@@ -441,9 +440,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self.check_delayed_auto_accept_seconds = self.get_int_setting(
|
self.check_delayed_auto_accept_seconds = self.get_int_setting(
|
||||||
"check_delayed_auto_accept_seconds", 60, 1, 20 * 60
|
"check_delayed_auto_accept_seconds", 60, 1, 20 * 60
|
||||||
)
|
)
|
||||||
self.startup_tries = self.get_int_setting(
|
|
||||||
"startup_tries", 15, 1, 100
|
|
||||||
) # Seconds waited for will be (x(1 + x+1) / 2
|
|
||||||
self.debug_ui = self.settings.get("debug_ui", False)
|
self.debug_ui = self.settings.get("debug_ui", False)
|
||||||
self._debug_cases = []
|
self._debug_cases = []
|
||||||
self._last_checked_actions = 0
|
self._last_checked_actions = 0
|
||||||
@@ -1618,13 +1614,22 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
# systemd will try to restart the process if fail_code != 0
|
# systemd will try to restart the process if fail_code != 0
|
||||||
self.stopRunning(1)
|
self.stopRunning(1)
|
||||||
|
|
||||||
startup_tries = self.startup_tries
|
|
||||||
chain_client_settings = self.getChainClientSettings(coin_type)
|
chain_client_settings = self.getChainClientSettings(coin_type)
|
||||||
if "startup_tries" in chain_client_settings:
|
# Total seconds waited for will be ((startup_tries(1 + startup_tries) / 2) * startup_delay
|
||||||
startup_tries = chain_client_settings["startup_tries"]
|
startup_tries: int = self.get_clamped_int_from(
|
||||||
if startup_tries < 1:
|
chain_client_settings,
|
||||||
self.log.warning('"startup_tries" can\'t be less than 1.')
|
"startup_tries",
|
||||||
startup_tries = 1
|
self.get_int_setting("startup_tries", 15, 1, 100),
|
||||||
|
1,
|
||||||
|
100,
|
||||||
|
)
|
||||||
|
startup_delay: int = self.get_clamped_int_from(
|
||||||
|
chain_client_settings,
|
||||||
|
"startup_delay",
|
||||||
|
self.get_int_setting("startup_delay", 5, 1, 100),
|
||||||
|
1,
|
||||||
|
100,
|
||||||
|
)
|
||||||
for i in range(startup_tries):
|
for i in range(startup_tries):
|
||||||
if self.delay_event.is_set():
|
if self.delay_event.is_set():
|
||||||
return
|
return
|
||||||
@@ -1632,6 +1637,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self.coin_clients[coin_type]["interface"].testDaemonRPC(with_wallet)
|
self.coin_clients[coin_type]["interface"].testDaemonRPC(with_wallet)
|
||||||
return
|
return
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
wait_for: int = startup_delay * (1 + i)
|
||||||
if any(
|
if any(
|
||||||
log in str(ex)
|
log in str(ex)
|
||||||
for log in [
|
for log in [
|
||||||
@@ -1644,13 +1650,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
]
|
]
|
||||||
):
|
):
|
||||||
self.log.info(
|
self.log.info(
|
||||||
f"Waiting for {Coins(coin_type).name} RPC. Trying again in {5 * (1 + i)} seconds, {1 + i}/{startup_tries}."
|
f"Waiting for {Coins(coin_type).name} RPC. Trying again in {wait_for} seconds, {1 + i}/{startup_tries}."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
f"Can't connect to {Coins(coin_type).name} RPC: {ex}. Trying again in {5 * (1 + i)} seconds, {1 + i}/{startup_tries}."
|
f"Can't connect to {Coins(coin_type).name} RPC: {ex}. Trying again in {wait_for} seconds, {1 + i}/{startup_tries}."
|
||||||
)
|
)
|
||||||
self.delay_event.wait(5 * (1 + i))
|
self.delay_event.wait(wait_for)
|
||||||
self.log.error(f"Can't connect to {Coins(coin_type).name} RPC, exiting.")
|
self.log.error(f"Can't connect to {Coins(coin_type).name} RPC, exiting.")
|
||||||
self.stopRunning(1) # systemd will try to restart the process if fail_code != 0
|
self.stopRunning(1) # systemd will try to restart the process if fail_code != 0
|
||||||
|
|
||||||
@@ -3605,6 +3611,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid swap type for: {coin_from.name} -> {coin_to.name}"
|
f"Invalid swap type for: {coin_from.name} -> {coin_to.name}"
|
||||||
)
|
)
|
||||||
|
strict_swap_type: bool = self.settings.get(
|
||||||
|
"strict_swap_type", False if self.chain == "regtest" else True
|
||||||
|
)
|
||||||
|
if strict_swap_type and (
|
||||||
|
coin_from not in self.coins_without_segwit
|
||||||
|
or coin_to not in self.coins_without_segwit
|
||||||
|
):
|
||||||
|
raise ValueError(
|
||||||
|
f"Coin pair should use adaptor sig swap type: {coin_from.name} -> {coin_to.name}"
|
||||||
|
)
|
||||||
|
|
||||||
def _process_notification_safe(self, event_type, event_data) -> None:
|
def _process_notification_safe(self, event_type, event_data) -> None:
|
||||||
try:
|
try:
|
||||||
@@ -3965,7 +3981,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
def isValidSwapDest(self, ci, dest: bytes):
|
def isValidSwapDest(self, ci, dest: bytes):
|
||||||
ensure(isinstance(dest, bytes), "Swap destination must be bytes")
|
ensure(isinstance(dest, bytes), "Swap destination must be bytes")
|
||||||
if ci.coin_type() in (Coins.PART_BLIND,):
|
if ci.coin_type() in (Coins.PART_BLIND,):
|
||||||
return ci.isValidPubkey(dest)
|
return ci.verifyPubkey(dest)
|
||||||
# TODO: allow p2wsh
|
# TODO: allow p2wsh
|
||||||
return ci.isValidAddressHash(dest)
|
return ci.isValidAddressHash(dest)
|
||||||
|
|
||||||
@@ -4120,6 +4136,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
msg_buf.fee_rate_to = ci_to.make_int(fee_rate)
|
msg_buf.fee_rate_to = ci_to.make_int(fee_rate)
|
||||||
|
|
||||||
if swap_type == SwapTypes.XMR_SWAP:
|
if swap_type == SwapTypes.XMR_SWAP:
|
||||||
|
ci_from.validateFeeRate(msg_buf.fee_rate_from)
|
||||||
|
ci_to.validateFeeRate(msg_buf.fee_rate_to)
|
||||||
|
|
||||||
xmr_offer = XmrOffer()
|
xmr_offer = XmrOffer()
|
||||||
|
|
||||||
chain_a_ci = ci_to if reverse_bid else ci_from
|
chain_a_ci = ci_to if reverse_bid else ci_from
|
||||||
@@ -5190,6 +5209,63 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
bid_rate = best_bid_rate
|
bid_rate = best_bid_rate
|
||||||
return amount, amount_to, bid_rate
|
return amount, amount_to, bid_rate
|
||||||
|
|
||||||
|
def createSubfeeBidTx(self, offer_id: bytes, amount_to: int, rate: int) -> dict:
|
||||||
|
self.log.debug(
|
||||||
|
f"createSubfeeBidTx for offer: {self.log.id(offer_id)}, amount to: {amount_to}"
|
||||||
|
)
|
||||||
|
|
||||||
|
offer, xmr_offer = self.getXmrOffer(offer_id)
|
||||||
|
ensure(offer, f"Offer not found: {self.log.id(offer_id)}.")
|
||||||
|
ensure(xmr_offer, f"Adaptor-sig offer not found: {self.log.id(offer_id)}.")
|
||||||
|
ensure(
|
||||||
|
offer.amount_negotiable,
|
||||||
|
f"Offer amounts are final: {self.log.id(offer_id)}.",
|
||||||
|
)
|
||||||
|
if offer.coin_to in (Coins.XMR, Coins.WOW):
|
||||||
|
raise ValueError("TODO")
|
||||||
|
if offer.swap_type != SwapTypes.XMR_SWAP:
|
||||||
|
raise ValueError("TODO")
|
||||||
|
ci_to = self.ci(offer.coin_to)
|
||||||
|
ci_from = self.ci(offer.coin_from)
|
||||||
|
pi = self.pi(SwapTypes.XMR_SWAP)
|
||||||
|
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to)
|
||||||
|
|
||||||
|
feerate: int = xmr_offer.b_fee_rate
|
||||||
|
if reverse_bid:
|
||||||
|
# Create ITX
|
||||||
|
lock_txa: bytes = pi.getFundedInitiateTxTemplate(
|
||||||
|
ci_to, amount_to, sub_fee=True, feerate=feerate
|
||||||
|
)
|
||||||
|
tx_obj = ci_to.loadTx(lock_txa, allow_witness=False)
|
||||||
|
lock_vout: int = pi.getMockITxSwapVout(ci_to, tx_obj)
|
||||||
|
amount_to_out: int = tx_obj.vout[lock_vout].nValue
|
||||||
|
else:
|
||||||
|
# Create PTX
|
||||||
|
mock_pk: bytes = pi.getMockPubkey(ci_to)
|
||||||
|
lock_txb: bytes = ci_to.createBLockTx(mock_pk, amount_to)
|
||||||
|
lock_txb = ci_to.fundTx(lock_txb, feerate, lock_unspents=False, subfee=True)
|
||||||
|
tx_obj = ci_to.loadTx(lock_txb, allow_witness=False)
|
||||||
|
lock_vout: int = pi.getMockPTxSwapVout(ci_to, tx_obj)
|
||||||
|
amount_to_out: int = tx_obj.vout[lock_vout].nValue
|
||||||
|
|
||||||
|
amount_from_out: int = (amount_to_out * (10 ** ci_from.exp())) // rate
|
||||||
|
extra_options = {"bid_rate": rate}
|
||||||
|
amount_adjusted, amount_to_adjusted, bid_rate = self.setBidAmounts(
|
||||||
|
amount_from_out, offer, extra_options, ci_from
|
||||||
|
)
|
||||||
|
if amount_to_adjusted < amount_to_out:
|
||||||
|
tx_obj.vout[lock_vout].nValue = amount_to_adjusted
|
||||||
|
|
||||||
|
self.log.debug(
|
||||||
|
f"Amounts after subfee: to {ci_to.format_amount(amount_to_adjusted)} {ci_to.ticker()}, from {ci_from.format_amount(amount_to_adjusted)} {ci_from.ticker()}"
|
||||||
|
)
|
||||||
|
tx_data: bytes = tx_obj.serialize_without_witness()
|
||||||
|
return {
|
||||||
|
"amount_from": amount_adjusted,
|
||||||
|
"amount_to": amount_to_adjusted,
|
||||||
|
"bid_tx": tx_data,
|
||||||
|
}
|
||||||
|
|
||||||
def postBid(
|
def postBid(
|
||||||
self, offer_id: bytes, amount: int, addr_send_from: str = None, extra_options={}
|
self, offer_id: bytes, amount: int, addr_send_from: str = None, extra_options={}
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
@@ -5486,8 +5562,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
use_cursor = self.openDB(cursor)
|
use_cursor = self.openDB(cursor)
|
||||||
|
|
||||||
bid, offer = self.getBidAndOffer(bid_id, use_cursor)
|
bid, offer = self.getBidAndOffer(bid_id, use_cursor)
|
||||||
ensure(bid, "Bid not found")
|
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
|
||||||
ensure(offer, "Offer not found")
|
ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
|
||||||
|
|
||||||
# Ensure bid is still valid
|
# Ensure bid is still valid
|
||||||
now: int = self.getTime()
|
now: int = self.getTime()
|
||||||
@@ -5607,15 +5683,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Check non-bip68 final
|
# Check non-bip68 final
|
||||||
try:
|
if not ci_from.useBackend():
|
||||||
txid = ci_from.publishTx(bid.initiate_txn_refund)
|
try:
|
||||||
self.log.error(
|
txid = ci_from.publishTx(bid.initiate_txn_refund)
|
||||||
f"Submit refund_txn unexpectedly worked {self.logIDT(bytes.fromhex(txid))}"
|
self.log.error(
|
||||||
)
|
f"Submit refund_txn unexpectedly worked {self.logIDT(bytes.fromhex(txid))}"
|
||||||
except Exception as ex:
|
)
|
||||||
if ci_from.isTxNonFinalError(str(ex)) is False:
|
except Exception as ex:
|
||||||
self.log.error(f"Submit refund_txn unexpected error: {ex}")
|
if ci_from.isTxNonFinalError(str(ex)) is False:
|
||||||
raise ex
|
self.log.error(f"Submit refund_txn unexpected error: {ex}")
|
||||||
|
raise ex
|
||||||
|
|
||||||
if txid is not None:
|
if txid is not None:
|
||||||
msg_buf = BidAcceptMessage()
|
msg_buf = BidAcceptMessage()
|
||||||
@@ -6006,21 +6083,41 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
"Incompatible offer protocol version",
|
"Incompatible offer protocol version",
|
||||||
)
|
)
|
||||||
ensure(offer.expire_at > self.getTime(), "Offer has expired")
|
ensure(offer.expire_at > self.getTime(), "Offer has expired")
|
||||||
|
if offer.swap_type != SwapTypes.XMR_SWAP:
|
||||||
|
raise ValueError(f"TODO: Unknown swap type {offer.swap_type.name}")
|
||||||
|
|
||||||
coin_from = Coins(offer.coin_from)
|
coin_from = Coins(offer.coin_from)
|
||||||
coin_to = Coins(offer.coin_to)
|
coin_to = Coins(offer.coin_to)
|
||||||
ci_from = self.ci(coin_from)
|
ci_from = self.ci(coin_from)
|
||||||
ci_to = self.ci(coin_to)
|
ci_to = self.ci(coin_to)
|
||||||
|
reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to)
|
||||||
|
|
||||||
valid_for_seconds: int = extra_options.get("valid_for_seconds", 60 * 10)
|
self.checkCoinsReady(coin_from, coin_to)
|
||||||
|
|
||||||
amount, amount_to, bid_rate = self.setBidAmounts(
|
ci_from.validateFeeRate(xmr_offer.a_fee_rate)
|
||||||
amount, offer, extra_options, ci_from
|
ci_to.validateFeeRate(xmr_offer.b_fee_rate)
|
||||||
)
|
|
||||||
|
|
||||||
bid_created_at: int = self.getTime()
|
bid_created_at: int = self.getTime()
|
||||||
if offer.swap_type != SwapTypes.XMR_SWAP:
|
valid_for_seconds: int = extra_options.get("valid_for_seconds", 60 * 10)
|
||||||
raise ValueError(f"TODO: Unknown swap type {offer.swap_type.name}")
|
|
||||||
|
if "prefunded_tx" in extra_options:
|
||||||
|
pi = self.pi(SwapTypes.XMR_SWAP)
|
||||||
|
prefunded_tx_data: bytes = extra_options["prefunded_tx"]
|
||||||
|
if reverse_bid:
|
||||||
|
amount_to = pi.getMockITxSwapValue(ci_to, prefunded_tx_data)
|
||||||
|
else:
|
||||||
|
amount_to = pi.getMockPTxSwapValue(ci_to, prefunded_tx_data)
|
||||||
|
bid_rate: int = ci_from.make_int(amount_to / amount, r=1)
|
||||||
|
|
||||||
|
prefunded_txid, prefunded_tx_fee_rate = (
|
||||||
|
ci_to.validatePrefundedTxAmounts(prefunded_tx_data)
|
||||||
|
)
|
||||||
|
self.log.debug(f"Using prefunded tx: {self.log.id(prefunded_txid)}")
|
||||||
|
ci_to.validateFeeRate(prefunded_tx_fee_rate)
|
||||||
|
else:
|
||||||
|
amount, amount_to, bid_rate = self.setBidAmounts(
|
||||||
|
amount, offer, extra_options, ci_from
|
||||||
|
)
|
||||||
|
|
||||||
if not (self.debug and extra_options.get("debug_skip_validation", False)):
|
if not (self.debug and extra_options.get("debug_skip_validation", False)):
|
||||||
self.validateBidValidTime(
|
self.validateBidValidTime(
|
||||||
@@ -6028,21 +6125,24 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
self.validateBidAmount(offer, amount, bid_rate)
|
self.validateBidAmount(offer, amount, bid_rate)
|
||||||
|
|
||||||
self.checkCoinsReady(coin_from, coin_to)
|
|
||||||
|
|
||||||
# TODO: Better tx size estimate
|
# TODO: Better tx size estimate
|
||||||
fee_rate, fee_src = self.getFeeRateForCoin(coin_to, conf_target=2)
|
fee_rate_to = xmr_offer.b_fee_rate
|
||||||
fee_rate_to = ci_to.make_int(fee_rate)
|
|
||||||
estimated_fee: int = fee_rate_to * ci_to.est_lock_tx_vsize() // 1000
|
estimated_fee: int = fee_rate_to * ci_to.est_lock_tx_vsize() // 1000
|
||||||
self.ensureWalletCanSend(
|
|
||||||
ci_to, offer.swap_type, int(amount_to), estimated_fee, for_offer=False
|
if "prefunded_tx" not in extra_options:
|
||||||
)
|
self.ensureWalletCanSend(
|
||||||
|
ci_to,
|
||||||
|
offer.swap_type,
|
||||||
|
int(amount_to),
|
||||||
|
estimated_fee,
|
||||||
|
for_offer=False,
|
||||||
|
)
|
||||||
|
|
||||||
bid_addr: str = self.prepareSMSGAddress(
|
bid_addr: str = self.prepareSMSGAddress(
|
||||||
addr_send_from, AddressTypes.BID, cursor
|
addr_send_from, AddressTypes.BID, cursor
|
||||||
)
|
)
|
||||||
|
|
||||||
# return id of route waiting to be established
|
# Return id of route waiting to be established
|
||||||
request_data = {
|
request_data = {
|
||||||
"offer_id": offer_id.hex(),
|
"offer_id": offer_id.hex(),
|
||||||
"amount_from": amount,
|
"amount_from": amount,
|
||||||
@@ -6060,7 +6160,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
valid_for_seconds,
|
valid_for_seconds,
|
||||||
)
|
)
|
||||||
|
|
||||||
reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to)
|
|
||||||
if reverse_bid:
|
if reverse_bid:
|
||||||
reversed_rate: int = ci_to.make_int(amount / amount_to, r=1)
|
reversed_rate: int = ci_to.make_int(amount / amount_to, r=1)
|
||||||
|
|
||||||
@@ -6114,6 +6213,17 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
bid.bid_id = bid_id
|
bid.bid_id = bid_id
|
||||||
xmr_swap.bid_id = bid.bid_id
|
xmr_swap.bid_id = bid.bid_id
|
||||||
|
|
||||||
|
if "prefunded_tx" in extra_options:
|
||||||
|
prefunded_tx = PrefundedTx(
|
||||||
|
active_ind=1,
|
||||||
|
created_at=bid_created_at,
|
||||||
|
linked_type=Concepts.BID,
|
||||||
|
linked_id=bid.bid_id,
|
||||||
|
tx_type=TxTypes.ITX_PRE_FUNDED,
|
||||||
|
tx_data=extra_options["prefunded_tx"],
|
||||||
|
)
|
||||||
|
self.add(prefunded_tx, cursor)
|
||||||
|
|
||||||
self.saveBidInSession(xmr_swap.bid_id, bid, cursor, xmr_swap)
|
self.saveBidInSession(xmr_swap.bid_id, bid, cursor, xmr_swap)
|
||||||
self.commitDB()
|
self.commitDB()
|
||||||
|
|
||||||
@@ -6235,6 +6345,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self.log.warning(
|
self.log.warning(
|
||||||
f"Adaptor-sig swap restore height clamped to {wallet_restore_height}"
|
f"Adaptor-sig swap restore height clamped to {wallet_restore_height}"
|
||||||
)
|
)
|
||||||
|
if "prefunded_tx" in extra_options:
|
||||||
|
prefunded_tx = PrefundedTx(
|
||||||
|
active_ind=1,
|
||||||
|
created_at=bid_created_at,
|
||||||
|
linked_type=Concepts.BID,
|
||||||
|
linked_id=bid.bid_id,
|
||||||
|
tx_type=TxTypes.PTX_PRE_FUNDED,
|
||||||
|
tx_data=extra_options["prefunded_tx"],
|
||||||
|
)
|
||||||
|
self.add(prefunded_tx, cursor)
|
||||||
|
|
||||||
self.saveBidInSession(bid.bid_id, bid, cursor, xmr_swap)
|
self.saveBidInSession(bid.bid_id, bid, cursor, xmr_swap)
|
||||||
self.log.info(f"Sent XMR_BID_FL {self.logIDB(xmr_swap.bid_id)}")
|
self.log.info(f"Sent XMR_BID_FL {self.logIDB(xmr_swap.bid_id)}")
|
||||||
@@ -6354,16 +6474,25 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
xmr_swap.a_lock_tx_script = pi.genScriptLockTxScript(
|
xmr_swap.a_lock_tx_script = pi.genScriptLockTxScript(
|
||||||
ci_from, xmr_swap.pkal, xmr_swap.pkaf, **lockExtraArgs
|
ci_from, xmr_swap.pkal, xmr_swap.pkaf, **lockExtraArgs
|
||||||
)
|
)
|
||||||
prefunded_tx = self.getPreFundedTx(
|
if reverse_bid and bid.was_sent:
|
||||||
Concepts.OFFER,
|
prefunded_tx = self.getPreFundedTx(
|
||||||
bid.offer_id,
|
Concepts.BID,
|
||||||
TxTypes.ITX_PRE_FUNDED,
|
bid_id,
|
||||||
cursor=use_cursor,
|
TxTypes.ITX_PRE_FUNDED,
|
||||||
)
|
cursor=use_cursor,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
prefunded_tx = self.getPreFundedTx(
|
||||||
|
Concepts.OFFER,
|
||||||
|
bid.offer_id,
|
||||||
|
TxTypes.ITX_PRE_FUNDED,
|
||||||
|
cursor=use_cursor,
|
||||||
|
)
|
||||||
if prefunded_tx:
|
if prefunded_tx:
|
||||||
xmr_swap.a_lock_tx = pi.promoteMockTx(
|
xmr_swap.a_lock_tx = pi.promoteMockTx(
|
||||||
ci_from, prefunded_tx, xmr_swap.a_lock_tx_script
|
ci_from, prefunded_tx, xmr_swap.a_lock_tx_script
|
||||||
)
|
)
|
||||||
|
self.log.info(f"Using pre-funded {ci_from.ticker()} tx")
|
||||||
else:
|
else:
|
||||||
xmr_swap.a_lock_tx = ci_from.createSCLockTx(
|
xmr_swap.a_lock_tx = ci_from.createSCLockTx(
|
||||||
bid.amount, xmr_swap.a_lock_tx_script, xmr_swap.vkbv
|
bid.amount, xmr_swap.a_lock_tx_script, xmr_swap.vkbv
|
||||||
@@ -6709,8 +6838,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
try:
|
try:
|
||||||
use_cursor = self.openDB(cursor)
|
use_cursor = self.openDB(cursor)
|
||||||
bid, offer = self.getBidAndOffer(bid_id, use_cursor, with_txns=False)
|
bid, offer = self.getBidAndOffer(bid_id, use_cursor, with_txns=False)
|
||||||
ensure(bid, "Bid not found")
|
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
|
||||||
ensure(offer, "Offer not found")
|
ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
|
||||||
|
|
||||||
bid.setState(new_state)
|
bid.setState(new_state)
|
||||||
self.deactivateBid(use_cursor, offer, bid)
|
self.deactivateBid(use_cursor, offer, bid)
|
||||||
@@ -6766,15 +6895,27 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
addr_to = ci.encodeScriptDest(p2wsh)
|
addr_to = ci.encodeScriptDest(p2wsh)
|
||||||
else:
|
else:
|
||||||
addr_to = ci.encode_p2sh(initiate_script)
|
addr_to = ci.encode_p2sh(initiate_script)
|
||||||
self.log.debug(
|
|
||||||
f"Create initiate txn for coin {ci.coin_name()} to {addr_to} for bid {self.log.id(bid_id)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if prefunded_tx:
|
if prefunded_tx:
|
||||||
|
self.log.debug(
|
||||||
|
f"Using pre-funded initiate txn for coin {ci.coin_name()} to {addr_to} for bid {self.log.id(bid_id)}"
|
||||||
|
)
|
||||||
pi = self.pi(SwapTypes.SELLER_FIRST)
|
pi = self.pi(SwapTypes.SELLER_FIRST)
|
||||||
txn_signed = pi.promoteMockTx(ci, prefunded_tx, initiate_script).hex()
|
txn_signed = pi.promoteMockTx(ci, prefunded_tx, initiate_script).hex()
|
||||||
else:
|
else:
|
||||||
txn_signed = ci.createRawSignedTransaction(addr_to, bid.amount)
|
self.log.debug(
|
||||||
|
f"Create initiate txn for coin {ci.coin_name()} to {addr_to} for bid {self.log.id(bid_id)}"
|
||||||
|
)
|
||||||
|
amount_from: int = bid.amount
|
||||||
|
if bid.debug_ind == DebugTypes.MAKE_INVALID_ITX:
|
||||||
|
amount_from -= 100
|
||||||
|
self.logBidEvent(
|
||||||
|
bid.bid_id,
|
||||||
|
EventLogTypes.DEBUG_TWEAK_APPLIED,
|
||||||
|
f"Make invalid ITx for testing: {bid.debug_ind}",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
txn_signed = ci.createRawSignedTransaction(addr_to, amount_from)
|
||||||
|
|
||||||
txjs = ci.describeTx(txn_signed)
|
txjs = ci.describeTx(txn_signed)
|
||||||
vout = getVoutByAddress(txjs, addr_to)
|
vout = getVoutByAddress(txjs, addr_to)
|
||||||
@@ -6865,13 +7006,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
|
|
||||||
if bid.debug_ind == DebugTypes.MAKE_INVALID_PTX:
|
if bid.debug_ind == DebugTypes.MAKE_INVALID_PTX:
|
||||||
amount_to -= 1
|
amount_to -= 1
|
||||||
self.log.debug(
|
|
||||||
f"bid {self.log.id(bid_id)}: Make invalid PTx for testing: {bid.debug_ind}."
|
|
||||||
)
|
|
||||||
self.logBidEvent(
|
self.logBidEvent(
|
||||||
bid.bid_id,
|
bid.bid_id,
|
||||||
EventLogTypes.DEBUG_TWEAK_APPLIED,
|
EventLogTypes.DEBUG_TWEAK_APPLIED,
|
||||||
"ind {}".format(bid.debug_ind),
|
f"Make invalid PTx for testing: {bid.debug_ind}",
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -7625,7 +7763,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self.logBidEvent(
|
self.logBidEvent(
|
||||||
bid.bid_id,
|
bid.bid_id,
|
||||||
EventLogTypes.DEBUG_TWEAK_APPLIED,
|
EventLogTypes.DEBUG_TWEAK_APPLIED,
|
||||||
"ind {}".format(bid.debug_ind),
|
f"ind {bid.debug_ind}",
|
||||||
cursor,
|
cursor,
|
||||||
)
|
)
|
||||||
self.commitDB()
|
self.commitDB()
|
||||||
@@ -7680,7 +7818,33 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
|
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
|
||||||
self.commitDB()
|
self.commitDB()
|
||||||
|
|
||||||
if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns:
|
if refund_tx.block_height is None:
|
||||||
|
self.log.debug(
|
||||||
|
f"A_LOCK_REFUND tx: {self.logIDT(refund_tx.txid)} block height not known, bid: {self.log.id(bid_id)}"
|
||||||
|
)
|
||||||
|
refund_tx_info = ci_from.getTxOutInfo(
|
||||||
|
refund_tx.txid, refund_tx.vout
|
||||||
|
)
|
||||||
|
if refund_tx_info:
|
||||||
|
refund_tx.block_hash = refund_tx_info["block_hash"]
|
||||||
|
refund_tx.block_height = refund_tx_info["block_height"]
|
||||||
|
refund_tx.block_time = refund_tx_info["block_time"]
|
||||||
|
self.log.debug(
|
||||||
|
f"Found A_LOCK_REFUND tx block height: {refund_tx.block_height}, time: {refund_tx.block_time}"
|
||||||
|
)
|
||||||
|
self.add(refund_tx, cursor, upsert=True)
|
||||||
|
self.commitDB()
|
||||||
|
|
||||||
|
if (
|
||||||
|
TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns
|
||||||
|
and refund_tx.block_height is not None
|
||||||
|
and ci_from.isCsvLockMature(
|
||||||
|
offer.lock_type,
|
||||||
|
xmr_offer.lock_time_2,
|
||||||
|
refund_tx.block_height,
|
||||||
|
refund_tx.block_time,
|
||||||
|
)
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
if self.haveDebugInd(
|
if self.haveDebugInd(
|
||||||
bid.bid_id,
|
bid.bid_id,
|
||||||
@@ -7776,6 +7940,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
if (
|
if (
|
||||||
len(xmr_swap.al_lock_refund_tx_sig) > 0
|
len(xmr_swap.al_lock_refund_tx_sig) > 0
|
||||||
and len(xmr_swap.af_lock_refund_tx_sig) > 0
|
and len(xmr_swap.af_lock_refund_tx_sig) > 0
|
||||||
|
and bid.xmr_a_lock_tx is not None
|
||||||
|
and ci_from.isCsvLockMature(
|
||||||
|
offer.lock_type,
|
||||||
|
xmr_offer.lock_time_1,
|
||||||
|
bid.xmr_a_lock_tx.block_height,
|
||||||
|
bid.xmr_a_lock_tx.block_time,
|
||||||
|
)
|
||||||
):
|
):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -7809,10 +7980,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
"",
|
"",
|
||||||
cursor,
|
cursor,
|
||||||
)
|
)
|
||||||
|
refund_vout: int = ci_from.getLockRefundVout(
|
||||||
|
xmr_swap.a_lock_refund_tx, xmr_swap.vkbv
|
||||||
|
)
|
||||||
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
||||||
bid_id=bid_id,
|
bid_id=bid_id,
|
||||||
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
||||||
txid=bytes.fromhex(txid),
|
txid=bytes.fromhex(txid),
|
||||||
|
vout=refund_vout,
|
||||||
)
|
)
|
||||||
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
|
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
|
||||||
self.commitDB()
|
self.commitDB()
|
||||||
@@ -7824,10 +7999,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
txid = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
|
txid = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
|
||||||
if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns:
|
if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns:
|
||||||
|
refund_vout: int = ci_from.getLockRefundVout(
|
||||||
|
xmr_swap.a_lock_refund_tx, xmr_swap.vkbv
|
||||||
|
)
|
||||||
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
||||||
bid_id=bid_id,
|
bid_id=bid_id,
|
||||||
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
||||||
txid=txid,
|
txid=txid,
|
||||||
|
vout=refund_vout,
|
||||||
)
|
)
|
||||||
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
|
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
|
||||||
self.commitDB()
|
self.commitDB()
|
||||||
@@ -8207,6 +8386,20 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
def _isScriptRefundMature(self, ci, offer, refund_tx_bytes, parent_tx) -> bool:
|
||||||
|
if offer.lock_type in (TxLockTypes.ABS_LOCK_BLOCKS, TxLockTypes.ABS_LOCK_TIME):
|
||||||
|
tx_locktime: int = ci.getTxLocktime(refund_tx_bytes)
|
||||||
|
return ci.isAbsLockTimeMature(tx_locktime)
|
||||||
|
if parent_tx is None or parent_tx.block_height is None:
|
||||||
|
return False
|
||||||
|
txi_sequence: int = ci.getTxInSequence(refund_tx_bytes, 0)
|
||||||
|
return ci.isCsvLockMature(
|
||||||
|
offer.lock_type,
|
||||||
|
txi_sequence,
|
||||||
|
parent_tx.block_height,
|
||||||
|
parent_tx.block_time,
|
||||||
|
)
|
||||||
|
|
||||||
def checkBidState(self, bid_id: bytes, bid, offer):
|
def checkBidState(self, bid_id: bytes, bid, offer):
|
||||||
# assert (self.mxDB.locked())
|
# assert (self.mxDB.locked())
|
||||||
# Return True to remove bid from in-progress list
|
# Return True to remove bid from in-progress list
|
||||||
@@ -8244,12 +8437,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
# Verify amount
|
# Verify amount
|
||||||
vout = getVoutByAddress(initiate_txn, p2sh)
|
vout = getVoutByAddress(initiate_txn, p2sh)
|
||||||
|
|
||||||
out_value = make_int(initiate_txn["vout"][vout]["value"])
|
out_value: int = make_int(initiate_txn["vout"][vout]["value"])
|
||||||
ensure(
|
ensure(
|
||||||
out_value == int(bid.amount),
|
out_value == int(bid.amount),
|
||||||
"Incorrect output amount in initiate txn {}: {} != {}.".format(
|
f"Incorrect output amount in initiate txn {self.logIDT(initiate_txnid_hex)}: {out_value} != {bid.amount}",
|
||||||
initiate_txnid_hex, out_value, int(bid.amount)
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
bid.initiate_tx.conf = initiate_txn["confirmations"]
|
bid.initiate_tx.conf = initiate_txn["confirmations"]
|
||||||
@@ -8277,9 +8468,21 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
index = None
|
index = None
|
||||||
if found:
|
if found:
|
||||||
|
if "index" not in found:
|
||||||
|
self.setBidError(
|
||||||
|
bid,
|
||||||
|
f"Swap output index not found for initiate txn {self.logIDT(initiate_txnid_hex)}",
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
txo_value: int = found.get("value", None)
|
||||||
|
if txo_value != bid.amount:
|
||||||
|
self.setBidError(
|
||||||
|
bid,
|
||||||
|
f"Incorrect output amount in initiate txn {self.logIDT(initiate_txnid_hex)}: {txo_value} != {bid.amount}",
|
||||||
|
)
|
||||||
|
return True
|
||||||
bid.initiate_tx.conf = found["depth"]
|
bid.initiate_tx.conf = found["depth"]
|
||||||
if "index" in found:
|
index = found["index"]
|
||||||
index = found["index"]
|
|
||||||
tx_height = found["height"]
|
tx_height = found["height"]
|
||||||
|
|
||||||
if bid.initiate_tx.conf != last_initiate_txn_conf:
|
if bid.initiate_tx.conf != last_initiate_txn_conf:
|
||||||
@@ -8365,6 +8568,21 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
vout=participate_txvout,
|
vout=participate_txvout,
|
||||||
)
|
)
|
||||||
if found:
|
if found:
|
||||||
|
participate_txid_hex: str = found.get(
|
||||||
|
"txid",
|
||||||
|
None if participate_txid is None else participate_txid.hex(),
|
||||||
|
)
|
||||||
|
# Double check value
|
||||||
|
txo_value: int = found.get("value", None)
|
||||||
|
if (
|
||||||
|
txo_value != bid.amount_to
|
||||||
|
and bid.debug_ind != DebugTypes.MAKE_INVALID_PTX
|
||||||
|
):
|
||||||
|
self.setBidError(
|
||||||
|
bid,
|
||||||
|
f"Incorrect output amount in participate txn {self.logIDT(participate_txid_hex)}: {txo_value} != {bid.amount_to}",
|
||||||
|
)
|
||||||
|
return True
|
||||||
index = found.get("index", participate_txvout)
|
index = found.get("index", participate_txvout)
|
||||||
if bid.participate_tx.conf != found["depth"]:
|
if bid.participate_tx.conf != found["depth"]:
|
||||||
save_bid = True
|
save_bid = True
|
||||||
@@ -8372,15 +8590,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
bid.participate_tx.conf is None
|
bid.participate_tx.conf is None
|
||||||
and bid.participate_tx.state != TxStates.TX_SENT
|
and bid.participate_tx.state != TxStates.TX_SENT
|
||||||
):
|
):
|
||||||
txid = found.get(
|
|
||||||
"txid",
|
|
||||||
None if participate_txid is None else participate_txid.hex(),
|
|
||||||
)
|
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Found bid {self.log.id(bid_id)} participate txn {self.log.id(txid)} in chain {ci_to.coin_name()}."
|
f"Found bid {self.log.id(bid_id)} participate txn {self.logIDT(participate_txid_hex)} in chain {ci_to.coin_name()}."
|
||||||
)
|
)
|
||||||
self.addParticipateTxn(
|
self.addParticipateTxn(
|
||||||
bid_id, bid, coin_to, txid, index, found["height"]
|
bid_id,
|
||||||
|
bid,
|
||||||
|
coin_to,
|
||||||
|
participate_txid_hex,
|
||||||
|
index,
|
||||||
|
found["height"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only update tx state if tx hasn't already been seen
|
# Only update tx state if tx hasn't already been seen
|
||||||
@@ -8398,7 +8617,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
|
|
||||||
if bid.participate_tx.conf is not None:
|
if bid.participate_tx.conf is not None:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"participate txid {self.log.id(bid.participate_tx.txid)} confirms {bid.participate_tx.conf}."
|
f"Participate txid {self.logIDT(bid.participate_tx.txid)} confirms {bid.participate_tx.conf}."
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
bid.participate_tx.conf
|
bid.participate_tx.conf
|
||||||
@@ -8467,6 +8686,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
if (
|
if (
|
||||||
bid.getITxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED)
|
bid.getITxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED)
|
||||||
and bid.initiate_txn_refund is not None
|
and bid.initiate_txn_refund is not None
|
||||||
|
and self._isScriptRefundMature(
|
||||||
|
ci_from, offer, bid.initiate_txn_refund, bid.initiate_tx
|
||||||
|
)
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
txid = ci_from.publishTx(bid.initiate_txn_refund)
|
txid = ci_from.publishTx(bid.initiate_txn_refund)
|
||||||
@@ -8487,9 +8709,33 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
f"Error trying to submit initiate refund txn: {ex}"
|
f"Error trying to submit initiate refund txn: {ex}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
should_try_refund_ptx: bool = (
|
||||||
bid.getPTxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED)
|
bid.getPTxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED)
|
||||||
and bid.participate_txn_refund is not None
|
and bid.participate_txn_refund is not None
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
should_try_refund_ptx
|
||||||
|
and bid.participate_tx is not None
|
||||||
|
and bid.participate_tx.block_height is None
|
||||||
|
):
|
||||||
|
self.log.debug(
|
||||||
|
f"PTX: {self.logIDT(bid.participate_tx.txid)} block height not known, bid: {self.log.id(bid_id)}"
|
||||||
|
)
|
||||||
|
# An invalid ptx, won't be confirmed, check block height here
|
||||||
|
ptx_info = ci_to.getTxOutInfo(
|
||||||
|
bid.participate_tx.txid, bid.participate_tx.vout
|
||||||
|
)
|
||||||
|
if ptx_info:
|
||||||
|
bid.participate_tx.block_hash = ptx_info["block_hash"]
|
||||||
|
bid.participate_tx.block_height = ptx_info["block_height"]
|
||||||
|
bid.participate_tx.block_time = ptx_info["block_time"]
|
||||||
|
self.log.debug(
|
||||||
|
f"Found PTX block height: {bid.participate_tx.block_height}, time: {bid.participate_tx.block_time}"
|
||||||
|
)
|
||||||
|
self.saveBid(bid_id, bid)
|
||||||
|
|
||||||
|
if should_try_refund_ptx and self._isScriptRefundMature(
|
||||||
|
ci_to, offer, bid.participate_txn_refund, bid.participate_tx
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
txid = ci_to.publishTx(bid.participate_txn_refund)
|
txid = ci_to.publishTx(bid.participate_txn_refund)
|
||||||
@@ -8505,7 +8751,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
# State will update when spend is detected
|
# State will update when spend is detected
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if ci_to.isTxNonFinalError(str(ex)):
|
if ci_to.isTxNonFinalError(str(ex)) is False:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
f"Error trying to submit participate refund txn: {ex}"
|
f"Error trying to submit participate refund txn: {ex}"
|
||||||
)
|
)
|
||||||
@@ -8860,10 +9106,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns:
|
if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns:
|
||||||
|
refund_vout: int = ci_from.getLockRefundVout(
|
||||||
|
bytes.fromhex(spend_txn_hex), xmr_swap.vkbv
|
||||||
|
)
|
||||||
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
||||||
bid_id=bid.bid_id,
|
bid_id=bid.bid_id,
|
||||||
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
||||||
txid=xmr_swap.a_lock_refund_tx_id,
|
txid=xmr_swap.a_lock_refund_tx_id,
|
||||||
|
vout=refund_vout,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.setBidError(
|
self.setBidError(
|
||||||
@@ -8983,6 +9233,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
if was_received:
|
if was_received:
|
||||||
if self.isBchXmrSwap(offer):
|
if self.isBchXmrSwap(offer):
|
||||||
# Mercy tx is sent separately
|
# Mercy tx is sent separately
|
||||||
|
# Can't set XMR_SWAP_FAILED_SWIPED, as bid should continue looking for mercy tx
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# Look for a mercy output
|
# Look for a mercy output
|
||||||
@@ -10027,6 +10278,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
ensure(len(offer_data.proof_signature) == 0, "Unexpected data")
|
ensure(len(offer_data.proof_signature) == 0, "Unexpected data")
|
||||||
ensure(len(offer_data.pkhash_seller) == 0, "Unexpected data")
|
ensure(len(offer_data.pkhash_seller) == 0, "Unexpected data")
|
||||||
ensure(len(offer_data.secret_hash) == 0, "Unexpected data")
|
ensure(len(offer_data.secret_hash) == 0, "Unexpected data")
|
||||||
|
|
||||||
|
ci_from.validateFeeRate(offer_data.fee_rate_from)
|
||||||
|
ci_to.validateFeeRate(offer_data.fee_rate_to)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown swap type {}.".format(offer_data.swap_type))
|
raise ValueError("Unknown swap type {}.".format(offer_data.swap_type))
|
||||||
|
|
||||||
@@ -10052,6 +10307,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
# Check for sent
|
# Check for sent
|
||||||
existing_offer = self.getOffer(offer_id, cursor=cursor)
|
existing_offer = self.getOffer(offer_id, cursor=cursor)
|
||||||
if existing_offer is None:
|
if existing_offer is None:
|
||||||
|
|
||||||
bid_reversed: bool = (
|
bid_reversed: bool = (
|
||||||
offer_data.swap_type == SwapTypes.XMR_SWAP
|
offer_data.swap_type == SwapTypes.XMR_SWAP
|
||||||
and self.is_reverse_ads_bid(
|
and self.is_reverse_ads_bid(
|
||||||
@@ -10905,7 +11161,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
ensure(
|
ensure(
|
||||||
ci_from.isValidAddressHash(bid_data.dest_af)
|
ci_from.isValidAddressHash(bid_data.dest_af)
|
||||||
or ci_from.isValidPubkey(bid_data.dest_af),
|
or ci_from.verifyPubkey(bid_data.dest_af),
|
||||||
"Invalid destination address",
|
"Invalid destination address",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11056,7 +11312,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
refundExtraArgs = dict()
|
refundExtraArgs = dict()
|
||||||
lockExtraArgs = dict()
|
lockExtraArgs = dict()
|
||||||
if self.isBchXmrSwap(offer):
|
if self.isBchXmrSwap(offer):
|
||||||
# perform check that both lock and refund transactions have their outs pointing to correct follower address
|
# Perform check that both lock and refund transactions have their outs pointing to correct follower address
|
||||||
# and prepare extra args for validation
|
# and prepare extra args for validation
|
||||||
|
|
||||||
bch_ci = self.ci(Coins.BCH)
|
bch_ci = self.ci(Coins.BCH)
|
||||||
@@ -11421,8 +11677,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
txid_hex = ci_from.publishTx(lock_tx_signed)
|
txid_hex = ci_from.publishTx(lock_tx_signed)
|
||||||
|
|
||||||
if txid_hex != b2h(xmr_swap.a_lock_tx_id):
|
if txid_hex != b2h(xmr_swap.a_lock_tx_id):
|
||||||
|
if not self.isBchXmrSwap(offer):
|
||||||
|
raise ValueError("Coin A lock tx txid changed after sending!")
|
||||||
|
|
||||||
self.log.info(
|
self.log.info(
|
||||||
"Recomputing refund transactions and txids after lock tx publish."
|
f"Recomputing {ci_from.coin_name()} refund transactions and txids after lock tx publish."
|
||||||
)
|
)
|
||||||
xmr_swap.a_lock_tx = lock_tx_signed
|
xmr_swap.a_lock_tx = lock_tx_signed
|
||||||
xmr_swap.a_lock_tx_id = bytes.fromhex(txid_hex)
|
xmr_swap.a_lock_tx_id = bytes.fromhex(txid_hex)
|
||||||
@@ -11554,19 +11813,38 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
cursor,
|
cursor,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
prefunded_tx = self.getPreFundedTx(
|
||||||
|
Concepts.BID,
|
||||||
|
bid.bid_id,
|
||||||
|
TxTypes.PTX_PRE_FUNDED,
|
||||||
|
cursor=cursor,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
b_lock_vout = 0
|
b_lock_vout = 0
|
||||||
result = ci_to.publishBLockTx(
|
if prefunded_tx:
|
||||||
xmr_swap.vkbv,
|
self.log.info("Using pre-funded tx")
|
||||||
xmr_swap.pkbs,
|
pi = self.pi(offer.swap_type)
|
||||||
bid.amount_to,
|
b_lock_tx = pi.promoteMockPTx(
|
||||||
b_fee_rate,
|
ci_to,
|
||||||
unlock_time=unlock_time,
|
prefunded_tx,
|
||||||
)
|
xmr_swap.vkbv,
|
||||||
if isinstance(result, tuple):
|
xmr_swap.pkbs,
|
||||||
b_lock_tx_id, b_lock_vout = result
|
)
|
||||||
|
b_lock_tx = ci_to.signTxWithWallet(b_lock_tx)
|
||||||
|
b_lock_tx_id = bytes.fromhex(ci_to.publishTx(b_lock_tx))
|
||||||
else:
|
else:
|
||||||
b_lock_tx_id = result
|
result = ci_to.publishBLockTx(
|
||||||
|
xmr_swap.vkbv,
|
||||||
|
xmr_swap.pkbs,
|
||||||
|
bid.amount_to,
|
||||||
|
b_fee_rate,
|
||||||
|
unlock_time=unlock_time,
|
||||||
|
)
|
||||||
|
if isinstance(result, tuple):
|
||||||
|
b_lock_tx_id, b_lock_vout = result
|
||||||
|
else:
|
||||||
|
b_lock_tx_id = result
|
||||||
if bid.debug_ind == DebugTypes.B_LOCK_TX_MISSED_SEND:
|
if bid.debug_ind == DebugTypes.B_LOCK_TX_MISSED_SEND:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
f"Adaptor-sig bid {self.log.id(bid_id)}: Debug {bid.debug_ind} - Losing XMR lock tx {self.log.id(b_lock_tx_id)}."
|
f"Adaptor-sig bid {self.log.id(bid_id)}: Debug {bid.debug_ind} - Losing XMR lock tx {self.log.id(b_lock_tx_id)}."
|
||||||
@@ -11896,7 +12174,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
||||||
|
|
||||||
if coin_to == (Coins.XMR, Coins.WOW):
|
if coin_to in (Coins.XMR, Coins.WOW):
|
||||||
address_to = self.getCachedMainWalletAddress(ci_to, cursor)
|
address_to = self.getCachedMainWalletAddress(ci_to, cursor)
|
||||||
elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON):
|
elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON):
|
||||||
address_to = self.getCachedStealthAddressForCoin(coin_to, cursor)
|
address_to = self.getCachedStealthAddressForCoin(coin_to, cursor)
|
||||||
@@ -12195,6 +12473,19 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not self.isBchXmrSwap(offer):
|
if not self.isBchXmrSwap(offer):
|
||||||
|
self.log.info("Checking follower's lock refund tx signature.")
|
||||||
|
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
|
||||||
|
v = ci_from.verifyTxSig(
|
||||||
|
xmr_swap.a_lock_refund_tx,
|
||||||
|
xmr_swap.af_lock_refund_tx_sig,
|
||||||
|
xmr_swap.pkaf,
|
||||||
|
0,
|
||||||
|
xmr_swap.a_lock_tx_script,
|
||||||
|
prevout_amount,
|
||||||
|
)
|
||||||
|
ensure(v, "Invalid coin A lock refund tx leader sig")
|
||||||
|
xmr_swap_1.addLockRefundSigs(self, xmr_swap, ci_from)
|
||||||
|
|
||||||
# segwit coins sign the transaction
|
# segwit coins sign the transaction
|
||||||
xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(
|
xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(
|
||||||
kbsl, xmr_swap.af_lock_refund_spend_tx_esig
|
kbsl, xmr_swap.af_lock_refund_spend_tx_esig
|
||||||
@@ -12209,7 +12500,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
xmr_swap.a_lock_refund_tx_script,
|
xmr_swap.a_lock_refund_tx_script,
|
||||||
prevout_amount,
|
prevout_amount,
|
||||||
)
|
)
|
||||||
|
self.log.info("Checking follower's lock refund spend tx signature.")
|
||||||
|
v = ci_from.verifyTxSig(
|
||||||
|
xmr_swap.a_lock_refund_spend_tx,
|
||||||
|
xmr_swap.af_lock_refund_spend_tx_sig,
|
||||||
|
xmr_swap.pkaf,
|
||||||
|
0,
|
||||||
|
xmr_swap.a_lock_refund_tx_script,
|
||||||
|
prevout_amount,
|
||||||
|
)
|
||||||
|
ensure(v, "Invalid follower signature for lock refund spend txn")
|
||||||
self.log.debug("Setting lock refund spend tx sigs.")
|
self.log.debug("Setting lock refund spend tx sigs.")
|
||||||
witness_stack = []
|
witness_stack = []
|
||||||
if coin_from not in (Coins.DCR,):
|
if coin_from not in (Coins.DCR,):
|
||||||
@@ -12227,17 +12527,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
)
|
)
|
||||||
ensure(signed_tx, "setTxSignature failed")
|
ensure(signed_tx, "setTxSignature failed")
|
||||||
xmr_swap.a_lock_refund_spend_tx = signed_tx
|
xmr_swap.a_lock_refund_spend_tx = signed_tx
|
||||||
|
|
||||||
v = ci_from.verifyTxSig(
|
|
||||||
xmr_swap.a_lock_refund_spend_tx,
|
|
||||||
xmr_swap.af_lock_refund_spend_tx_sig,
|
|
||||||
xmr_swap.pkaf,
|
|
||||||
0,
|
|
||||||
xmr_swap.a_lock_refund_tx_script,
|
|
||||||
prevout_amount,
|
|
||||||
)
|
|
||||||
ensure(v, "Invalid signature for lock refund spend txn")
|
|
||||||
xmr_swap_1.addLockRefundSigs(self, xmr_swap, ci_from)
|
|
||||||
else:
|
else:
|
||||||
# BCH signs the output pkh
|
# BCH signs the output pkh
|
||||||
|
|
||||||
@@ -12794,8 +13083,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self.log.info(f"Route established for bid {self.log.id(bid_id)}")
|
self.log.info(f"Route established for bid {self.log.id(bid_id)}")
|
||||||
|
|
||||||
bid, offer = self.getBidAndOffer(bid_id, cursor)
|
bid, offer = self.getBidAndOffer(bid_id, cursor)
|
||||||
ensure(bid, "Bid not found")
|
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
|
||||||
ensure(offer, "Offer not found")
|
ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
|
||||||
|
|
||||||
coin_from = Coins(offer.coin_from)
|
coin_from = Coins(offer.coin_from)
|
||||||
coin_to = Coins(offer.coin_to)
|
coin_to = Coins(offer.coin_to)
|
||||||
@@ -13811,8 +14100,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
num_watched_outputs += len(v["watched_outputs"])
|
num_watched_outputs += len(v["watched_outputs"])
|
||||||
|
|
||||||
now: int = self.getTime()
|
now: int = self.getTime()
|
||||||
q_bids_str: str = (
|
q_bids_str: str = """SELECT
|
||||||
"""SELECT
|
|
||||||
COUNT(CASE WHEN b.was_sent THEN 1 ELSE NULL END) AS count_sent,
|
COUNT(CASE WHEN b.was_sent THEN 1 ELSE NULL END) AS count_sent,
|
||||||
COUNT(CASE WHEN b.was_sent AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_sent_active,
|
COUNT(CASE WHEN b.was_sent AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > :now AND o.expire_at > :now)) THEN 1 ELSE NULL END) AS count_sent_active,
|
||||||
COUNT(CASE WHEN b.was_received THEN 1 ELSE NULL END) AS count_received,
|
COUNT(CASE WHEN b.was_received THEN 1 ELSE NULL END) AS count_received,
|
||||||
@@ -13822,15 +14110,12 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
JOIN offers o ON b.offer_id = o.offer_id
|
JOIN offers o ON b.offer_id = o.offer_id
|
||||||
JOIN bidstates s ON b.state = s.state_id
|
JOIN bidstates s ON b.state = s.state_id
|
||||||
WHERE b.active_ind = 1"""
|
WHERE b.active_ind = 1"""
|
||||||
)
|
|
||||||
|
|
||||||
q_offers_str: str = (
|
q_offers_str: str = """SELECT
|
||||||
"""SELECT
|
|
||||||
COUNT(CASE WHEN expire_at > :now THEN 1 ELSE NULL END) AS count_active,
|
COUNT(CASE WHEN expire_at > :now THEN 1 ELSE NULL END) AS count_active,
|
||||||
COUNT(CASE WHEN was_sent THEN 1 ELSE NULL END) AS count_sent,
|
COUNT(CASE WHEN was_sent THEN 1 ELSE NULL END) AS count_sent,
|
||||||
COUNT(CASE WHEN was_sent AND expire_at > :now THEN 1 ELSE NULL END) AS count_sent_active
|
COUNT(CASE WHEN was_sent AND expire_at > :now THEN 1 ELSE NULL END) AS count_sent_active
|
||||||
FROM offers WHERE active_ind = 1"""
|
FROM offers WHERE active_ind = 1"""
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cursor = self.openDB()
|
cursor = self.openDB()
|
||||||
@@ -13905,9 +14190,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
walletinfo = ci.getWalletInfo()
|
walletinfo = ci.getWalletInfo()
|
||||||
rv = {
|
rv = {
|
||||||
"deposit_address": self.getCachedAddressForCoin(coin),
|
"deposit_address": self.getCachedAddressForCoin(coin),
|
||||||
"balance": ci.format_amount(walletinfo["balance"], conv_int=True),
|
"balance": ci.format_amount(walletinfo["balance"], conv_int=True, r=-1),
|
||||||
"unconfirmed": ci.format_amount(
|
"unconfirmed": ci.format_amount(
|
||||||
walletinfo["unconfirmed_balance"], conv_int=True
|
walletinfo["unconfirmed_balance"], conv_int=True, r=-1
|
||||||
),
|
),
|
||||||
"expected_seed": ci.knownWalletSeed(),
|
"expected_seed": ci.knownWalletSeed(),
|
||||||
"encrypted": walletinfo["encrypted"],
|
"encrypted": walletinfo["encrypted"],
|
||||||
@@ -13922,7 +14207,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
|
|
||||||
if "immature_balance" in walletinfo:
|
if "immature_balance" in walletinfo:
|
||||||
rv["immature"] = ci.format_amount(
|
rv["immature"] = ci.format_amount(
|
||||||
walletinfo["immature_balance"], conv_int=True
|
walletinfo["immature_balance"], conv_int=True, r=-1
|
||||||
)
|
)
|
||||||
|
|
||||||
if "locked_utxos" in walletinfo:
|
if "locked_utxos" in walletinfo:
|
||||||
@@ -14121,7 +14406,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
def updateWalletsInfo(
|
def updateWalletsInfo(
|
||||||
self,
|
self,
|
||||||
force_update: bool = False,
|
force_update: bool = False,
|
||||||
only_coin: bool = None,
|
only_coin: int = None,
|
||||||
wait_for_complete: bool = False,
|
wait_for_complete: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
now: int = self.getTime()
|
now: int = self.getTime()
|
||||||
@@ -14913,8 +15198,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
return
|
return
|
||||||
|
|
||||||
bid = self.getBid(bid_id)
|
bid = self.getBid(bid_id)
|
||||||
if bid is None:
|
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
|
||||||
raise ValueError("Bid not found.")
|
|
||||||
|
|
||||||
bid.debug_ind = debug_ind
|
bid.debug_ind = debug_ind
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ 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()
|
||||||
@@ -230,6 +232,7 @@ class DebugTypes(IntEnum):
|
|||||||
BID_DONT_SPEND_COIN_A_LOCK_REFUND2 = auto() # continues
|
BID_DONT_SPEND_COIN_A_LOCK_REFUND2 = auto() # continues
|
||||||
CREATE_INVALID_COIN_B_LOCK = auto()
|
CREATE_INVALID_COIN_B_LOCK = auto()
|
||||||
BUYER_STOP_AFTER_ITX = auto()
|
BUYER_STOP_AFTER_ITX = auto()
|
||||||
|
MAKE_INVALID_ITX = auto()
|
||||||
MAKE_INVALID_PTX = auto()
|
MAKE_INVALID_PTX = auto()
|
||||||
DONT_SPEND_ITX = auto()
|
DONT_SPEND_ITX = auto()
|
||||||
SKIP_LOCK_TX_REFUND = auto()
|
SKIP_LOCK_TX_REFUND = auto()
|
||||||
|
|||||||
+56
-37
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2024 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Copyright (c) 2024-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ import urllib.parse
|
|||||||
import zipfile
|
import zipfile
|
||||||
import zmq
|
import zmq
|
||||||
|
|
||||||
|
from typing import List
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
import basicswap.config as cfg
|
import basicswap.config as cfg
|
||||||
@@ -58,19 +59,19 @@ PARTICL_LINUX_EXTRA = os.getenv("PARTICL_LINUX_EXTRA", "nousb")
|
|||||||
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "29.3")
|
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "29.3")
|
||||||
BITCOIN_VERSION_TAG = os.getenv("BITCOIN_VERSION_TAG", "")
|
BITCOIN_VERSION_TAG = os.getenv("BITCOIN_VERSION_TAG", "")
|
||||||
|
|
||||||
LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.5.4")
|
LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.5.5")
|
||||||
LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "")
|
LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "")
|
||||||
|
|
||||||
DCR_VERSION = os.getenv("DCR_VERSION", "2.1.3")
|
DCR_VERSION = os.getenv("DCR_VERSION", "2.1.5")
|
||||||
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.4.5")
|
MONERO_VERSION = os.getenv("MONERO_VERSION", "0.18.5.0")
|
||||||
MONERO_VERSION_TAG = os.getenv("MONERO_VERSION_TAG", "")
|
MONERO_VERSION_TAG = os.getenv("MONERO_VERSION_TAG", "")
|
||||||
XMR_SITE_COMMIT = (
|
XMR_SITE_COMMIT = (
|
||||||
"1bfa07c1b54f4f39a93096e3bfb746cb21249422" # Lock hashes.txt to monero version
|
"5e8d74229b742b54173010e3a676215b6f2fd1d7" # 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")
|
||||||
@@ -85,7 +86,7 @@ PIVX_VERSION_TAG = os.getenv("PIVX_VERSION_TAG", "")
|
|||||||
DASH_VERSION = os.getenv("DASH_VERSION", "23.1.2")
|
DASH_VERSION = os.getenv("DASH_VERSION", "23.1.2")
|
||||||
DASH_VERSION_TAG = os.getenv("DASH_VERSION_TAG", "")
|
DASH_VERSION_TAG = os.getenv("DASH_VERSION_TAG", "")
|
||||||
|
|
||||||
FIRO_VERSION = os.getenv("FIRO_VERSION", "0.14.15.3")
|
FIRO_VERSION = os.getenv("FIRO_VERSION", "0.14.16.1")
|
||||||
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")
|
||||||
@@ -185,11 +186,13 @@ else:
|
|||||||
BIN_ARCH = os.getenv("BIN_ARCH", BIN_ARCH)
|
BIN_ARCH = os.getenv("BIN_ARCH", BIN_ARCH)
|
||||||
FILE_EXT = os.getenv("FILE_EXT", FILE_EXT)
|
FILE_EXT = os.getenv("FILE_EXT", FILE_EXT)
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger("prepare")
|
||||||
LOG_LEVEL = logging.DEBUG
|
LOG_LEVEL = logging.DEBUG
|
||||||
|
logger.propagate = False
|
||||||
logger.level = LOG_LEVEL
|
logger.level = LOG_LEVEL
|
||||||
if not len(logger.handlers):
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
handler.setFormatter(logging.Formatter("%(levelname)s : %(message)s"))
|
||||||
|
logger.addHandler(handler)
|
||||||
logging.getLogger("gnupg").setLevel(logging.INFO)
|
logging.getLogger("gnupg").setLevel(logging.INFO)
|
||||||
|
|
||||||
BSX_DOCKER_MODE = toBool(os.getenv("BSX_DOCKER_MODE", False))
|
BSX_DOCKER_MODE = toBool(os.getenv("BSX_DOCKER_MODE", False))
|
||||||
@@ -458,33 +461,47 @@ def getRemoteFileLength(url: str) -> (int, bool):
|
|||||||
popConnectionParameters()
|
popConnectionParameters()
|
||||||
|
|
||||||
|
|
||||||
def downloadRelease(url: str, path: str, extra_opts, timeout: int = 10) -> None:
|
def downloadRelease(
|
||||||
"""If file exists at path compare it's size to the content length at the url
|
url_in: str | List[str], path: str, extra_opts, timeout: int = 10
|
||||||
and attempt to resume download if file size is below expected.
|
) -> None:
|
||||||
"""
|
# If file exists at path compare it's size to the content length at the url
|
||||||
resume_from: int = 0
|
# and attempt to resume download if file size is below expected.
|
||||||
|
|
||||||
if os.path.exists(path):
|
release_filename: str = os.path.basename(path)
|
||||||
if extra_opts.get("redownload_releases", False):
|
urls = (
|
||||||
logging.warning(f"Overwriting: {path}")
|
url_in
|
||||||
elif extra_opts.get("verify_release_file_size", True):
|
if isinstance(url_in, list)
|
||||||
file_size = os.stat(path).st_size
|
else [
|
||||||
remote_file_length, can_resume = getRemoteFileLength(url)
|
url_in,
|
||||||
if file_size < remote_file_length:
|
]
|
||||||
logger.warning(
|
)
|
||||||
f"{path} is an unexpected size, {file_size} < {remote_file_length}. Attempting to resume download."
|
for url in urls:
|
||||||
)
|
try:
|
||||||
if can_resume:
|
resume_from: int = 0
|
||||||
resume_from = file_size
|
if os.path.exists(path):
|
||||||
|
if extra_opts.get("redownload_releases", False):
|
||||||
|
logging.warning(f"Overwriting: {path}")
|
||||||
|
elif extra_opts.get("verify_release_file_size", True):
|
||||||
|
file_size = os.stat(path).st_size
|
||||||
|
remote_file_length, can_resume = getRemoteFileLength(url)
|
||||||
|
if file_size < remote_file_length:
|
||||||
|
logger.warning(
|
||||||
|
f"{path} is an unexpected size, {file_size} < {remote_file_length}. Attempting to resume download."
|
||||||
|
)
|
||||||
|
if can_resume:
|
||||||
|
resume_from = file_size
|
||||||
|
else:
|
||||||
|
logger.warning("Download can not be resumed, restarting.")
|
||||||
|
else:
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
logger.warning("Download can not be resumed, restarting.")
|
# File exists and size check is disabled
|
||||||
else:
|
return
|
||||||
return
|
return downloadFile(url, path, timeout, resume_from)
|
||||||
else:
|
except Exception as e:
|
||||||
# File exists and size check is disabled
|
logger.warning(f"Failed to download {release_filename} from {url}")
|
||||||
return
|
logger.debug(f"Download error {e}")
|
||||||
|
raise RuntimeError(f"Failed to download {release_filename}.")
|
||||||
return downloadFile(url, path, timeout, resume_from)
|
|
||||||
|
|
||||||
|
|
||||||
def downloadFile(url: str, path: str, timeout: int = 5, resume_from: int = 0) -> None:
|
def downloadFile(url: str, path: str, timeout: int = 5, resume_from: int = 0) -> None:
|
||||||
@@ -925,9 +942,10 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|||||||
assert_filename,
|
assert_filename,
|
||||||
)
|
)
|
||||||
elif coin == "litecoin":
|
elif coin == "litecoin":
|
||||||
release_url = "https://github.com/litecoin-project/litecoin/releases/download/v{}/{}".format(
|
release_url = [
|
||||||
version + version_tag, release_filename
|
f"https://github.com/litecoin-project/litecoin/releases/download/v{version}{version_tag}/{release_filename}",
|
||||||
)
|
f"https://download.litecoin.org/litecoin-{version}{version_tag}/{os_name}/{release_filename}",
|
||||||
|
]
|
||||||
assert_filename = "{}-core-{}-{}-build.assert".format(
|
assert_filename = "{}-core-{}-{}-build.assert".format(
|
||||||
coin, os_name, ".".join(version.split(".")[:2])
|
coin, os_name, ".".join(version.split(".")[:2])
|
||||||
)
|
)
|
||||||
@@ -1249,6 +1267,7 @@ 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"]
|
||||||
|
|||||||
+69
-53
@@ -2,10 +2,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2024 tecnovert
|
# Copyright (c) 2019-2024 tecnovert
|
||||||
# Copyright (c) 2024-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -22,6 +23,7 @@ from basicswap.chainparams import chainparams, Coins, isKnownCoinName
|
|||||||
from basicswap.network.simplex_chat import startSimplexClient
|
from basicswap.network.simplex_chat import startSimplexClient
|
||||||
from basicswap.ui.util import getCoinName
|
from basicswap.ui.util import getCoinName
|
||||||
from basicswap.util.daemon import Daemon
|
from basicswap.util.daemon import Daemon
|
||||||
|
from typing import Set
|
||||||
|
|
||||||
initial_logger = logging.getLogger()
|
initial_logger = logging.getLogger()
|
||||||
initial_logger.level = logging.DEBUG
|
initial_logger.level = logging.DEBUG
|
||||||
@@ -347,7 +349,7 @@ def mainLoop(daemons, update: bool = True):
|
|||||||
def runClient(
|
def runClient(
|
||||||
data_dir: str,
|
data_dir: str,
|
||||||
chain: str,
|
chain: str,
|
||||||
start_only_coins: bool,
|
start_only_coins: Set[str],
|
||||||
log_prefix: str = "BasicSwap",
|
log_prefix: str = "BasicSwap",
|
||||||
extra_opts=dict(),
|
extra_opts=dict(),
|
||||||
) -> int:
|
) -> int:
|
||||||
@@ -391,39 +393,46 @@ def runClient(
|
|||||||
# Settings may have been modified
|
# Settings may have been modified
|
||||||
settings = swap_client.settings
|
settings = swap_client.settings
|
||||||
|
|
||||||
|
base_coin_opts = []
|
||||||
|
if "extra_coin_opts" in extra_opts:
|
||||||
|
if len(start_only_coins) == 0:
|
||||||
|
raise ValueError('"extracoinopts" can only be used with "startonlycoins"')
|
||||||
|
base_coin_opts += extra_opts["extra_coin_opts"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Try start daemons
|
# Try start daemons
|
||||||
for network in settings.get("networks", []):
|
if len(start_only_coins) > 0:
|
||||||
if network.get("enabled", True) is False:
|
swap_client.log.warning('Not starting networks as "startonlycoin" is set')
|
||||||
continue
|
else:
|
||||||
network_type: str = network.get("type", "unknown")
|
for network in settings.get("networks", []):
|
||||||
if network_type == "simplex":
|
if network.get("enabled", True) is False:
|
||||||
simplex_dir = os.path.join(data_dir, "simplex")
|
continue
|
||||||
|
network_type: str = network.get("type", "unknown")
|
||||||
|
if network_type == "simplex":
|
||||||
|
simplex_dir = os.path.join(data_dir, "simplex")
|
||||||
|
log_level = "debug" if swap_client.debug else "info"
|
||||||
|
socks_proxy = None
|
||||||
|
if "socks_proxy_override" in network:
|
||||||
|
socks_proxy = network["socks_proxy_override"]
|
||||||
|
elif swap_client.use_tor_proxy:
|
||||||
|
socks_proxy = (
|
||||||
|
f"{swap_client.tor_proxy_host}:{swap_client.tor_proxy_port}"
|
||||||
|
)
|
||||||
|
|
||||||
log_level = "debug" if swap_client.debug else "info"
|
daemons.append(
|
||||||
|
startSimplexClient(
|
||||||
socks_proxy = None
|
network["client_path"],
|
||||||
if "socks_proxy_override" in network:
|
simplex_dir,
|
||||||
socks_proxy = network["socks_proxy_override"]
|
network["server_address"],
|
||||||
elif swap_client.use_tor_proxy:
|
network["ws_port"],
|
||||||
socks_proxy = (
|
logger,
|
||||||
f"{swap_client.tor_proxy_host}:{swap_client.tor_proxy_port}"
|
swap_client.delay_event,
|
||||||
|
socks_proxy=socks_proxy,
|
||||||
|
log_level=log_level,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
pid = daemons[-1].handle.pid
|
||||||
daemons.append(
|
swap_client.log.info(f"Started Simplex client {pid}")
|
||||||
startSimplexClient(
|
|
||||||
network["client_path"],
|
|
||||||
simplex_dir,
|
|
||||||
network["server_address"],
|
|
||||||
network["ws_port"],
|
|
||||||
logger,
|
|
||||||
swap_client.delay_event,
|
|
||||||
socks_proxy=socks_proxy,
|
|
||||||
log_level=log_level,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
pid = daemons[-1].handle.pid
|
|
||||||
swap_client.log.info(f"Started Simplex client {pid}")
|
|
||||||
|
|
||||||
for c, v in settings["chainclients"].items():
|
for c, v in settings["chainclients"].items():
|
||||||
if len(start_only_coins) > 0 and c not in start_only_coins:
|
if len(start_only_coins) > 0 and c not in start_only_coins:
|
||||||
@@ -460,10 +469,18 @@ def runClient(
|
|||||||
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(
|
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(
|
||||||
coin_id, v["rpchost"]
|
coin_id, v["rpchost"]
|
||||||
)
|
)
|
||||||
opts = [
|
wallet_opts = [
|
||||||
|
"--trusted-daemon" if trusted_daemon else "--untrusted-daemon",
|
||||||
"--daemon-address",
|
"--daemon-address",
|
||||||
daemon_addr,
|
daemon_addr,
|
||||||
]
|
]
|
||||||
|
daemon_rpcuser = v.get("rpcuser", "")
|
||||||
|
daemon_rpcpass = v.get("rpcpassword", "")
|
||||||
|
if daemon_rpcuser != "":
|
||||||
|
wallet_opts += [
|
||||||
|
"--daemon-login",
|
||||||
|
daemon_rpcuser + ":" + daemon_rpcpass,
|
||||||
|
]
|
||||||
|
|
||||||
proxy_log_str = ""
|
proxy_log_str = ""
|
||||||
proxy_host, proxy_port = swap_client.getXMRWalletProxy(
|
proxy_host, proxy_port = swap_client.getXMRWalletProxy(
|
||||||
@@ -471,7 +488,7 @@ def runClient(
|
|||||||
)
|
)
|
||||||
if proxy_host:
|
if proxy_host:
|
||||||
proxy_log_str = " through proxy"
|
proxy_log_str = " through proxy"
|
||||||
opts += [
|
wallet_opts += [
|
||||||
"--proxy",
|
"--proxy",
|
||||||
f"{proxy_host}:{proxy_port}",
|
f"{proxy_host}:{proxy_port}",
|
||||||
"--daemon-ssl-allow-any-cert",
|
"--daemon-ssl-allow-any-cert",
|
||||||
@@ -485,19 +502,11 @@ def runClient(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
daemon_rpcuser = v.get("rpcuser", "")
|
|
||||||
daemon_rpcpass = v.get("rpcpassword", "")
|
|
||||||
if daemon_rpcuser != "":
|
|
||||||
opts.append("--daemon-login")
|
|
||||||
opts.append(daemon_rpcuser + ":" + daemon_rpcpass)
|
|
||||||
|
|
||||||
opts.append(
|
|
||||||
"--trusted-daemon" if trusted_daemon else "--untrusted-daemon"
|
|
||||||
)
|
|
||||||
filename: str = getWalletBinName(coin_id, v, c + "-wallet-rpc")
|
filename: str = getWalletBinName(coin_id, v, c + "-wallet-rpc")
|
||||||
|
|
||||||
daemons.append(
|
daemons.append(
|
||||||
startXmrWalletDaemon(v["datadir"], v["bindir"], filename, opts)
|
startXmrWalletDaemon(
|
||||||
|
v["datadir"], v["bindir"], filename, wallet_opts
|
||||||
|
)
|
||||||
)
|
)
|
||||||
pid = daemons[-1].handle.pid
|
pid = daemons[-1].handle.pid
|
||||||
swap_client.log.info(f"Started {filename} {pid}")
|
swap_client.log.info(f"Started {filename} {pid}")
|
||||||
@@ -506,9 +515,8 @@ def runClient(
|
|||||||
|
|
||||||
if c == "decred":
|
if c == "decred":
|
||||||
appdata = v["datadir"]
|
appdata = v["datadir"]
|
||||||
extra_opts = [
|
coin_opts = copy.deepcopy(base_coin_opts)
|
||||||
f'--appdata="{appdata}"',
|
coin_opts.append(f'--appdata="{appdata}"')
|
||||||
]
|
|
||||||
use_shell: bool = True if os.name == "nt" else False
|
use_shell: bool = True if os.name == "nt" else False
|
||||||
if v["manage_daemon"] is True:
|
if v["manage_daemon"] is True:
|
||||||
swap_client.log.info(f"Starting {display_name} daemon")
|
swap_client.log.info(f"Starting {display_name} daemon")
|
||||||
@@ -526,7 +534,7 @@ def runClient(
|
|||||||
appdata,
|
appdata,
|
||||||
v["bindir"],
|
v["bindir"],
|
||||||
filename,
|
filename,
|
||||||
opts=extra_opts,
|
opts=coin_opts,
|
||||||
extra_config=extra_config,
|
extra_config=extra_config,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -537,12 +545,13 @@ def runClient(
|
|||||||
swap_client.log.info(f"Starting {display_name} wallet daemon")
|
swap_client.log.info(f"Starting {display_name} wallet daemon")
|
||||||
filename: str = getWalletBinName(coin_id, v, "dcrwallet")
|
filename: str = getWalletBinName(coin_id, v, "dcrwallet")
|
||||||
|
|
||||||
|
wallet_opts = [f'--appdata="{appdata}"']
|
||||||
wallet_pwd = v["wallet_pwd"]
|
wallet_pwd = v["wallet_pwd"]
|
||||||
if wallet_pwd == "":
|
if wallet_pwd == "":
|
||||||
# Only set when in startonlycoin mode
|
# Only set when in startonlycoin mode
|
||||||
wallet_pwd = os.getenv("WALLET_ENCRYPTION_PWD", "")
|
wallet_pwd = os.getenv("WALLET_ENCRYPTION_PWD", "")
|
||||||
if wallet_pwd != "":
|
if wallet_pwd != "":
|
||||||
extra_opts.append(f'--pass="{wallet_pwd}"')
|
wallet_opts.append(f'--pass="{wallet_pwd}"')
|
||||||
extra_config = {
|
extra_config = {
|
||||||
"add_datadir": False,
|
"add_datadir": False,
|
||||||
"stdout_to_file": True,
|
"stdout_to_file": True,
|
||||||
@@ -555,13 +564,12 @@ def runClient(
|
|||||||
appdata,
|
appdata,
|
||||||
v["bindir"],
|
v["bindir"],
|
||||||
filename,
|
filename,
|
||||||
opts=extra_opts,
|
opts=wallet_opts,
|
||||||
extra_config=extra_config,
|
extra_config=extra_config,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pid = daemons[-1].handle.pid
|
pid = daemons[-1].handle.pid
|
||||||
swap_client.log.info(f"Started {filename} {pid}")
|
swap_client.log.info(f"Started {filename} {pid}")
|
||||||
|
|
||||||
continue # /decred
|
continue # /decred
|
||||||
|
|
||||||
if v["manage_daemon"] is True:
|
if v["manage_daemon"] is True:
|
||||||
@@ -571,7 +579,7 @@ def runClient(
|
|||||||
swap_client.log.info(f"Starting {display_name} daemon")
|
swap_client.log.info(f"Starting {display_name} daemon")
|
||||||
|
|
||||||
filename: str = getCoreBinName(coin_id, v, c + "d")
|
filename: str = getCoreBinName(coin_id, v, c + "d")
|
||||||
extra_opts = getCoreBinArgs(
|
coin_opts = copy.deepcopy(base_coin_opts) + getCoreBinArgs(
|
||||||
coin_id, v, use_tor_proxy=swap_client.use_tor_proxy
|
coin_id, v, use_tor_proxy=swap_client.use_tor_proxy
|
||||||
)
|
)
|
||||||
extra_config = {"coin_name": c}
|
extra_config = {"coin_name": c}
|
||||||
@@ -580,7 +588,7 @@ def runClient(
|
|||||||
v["datadir"],
|
v["datadir"],
|
||||||
v["bindir"],
|
v["bindir"],
|
||||||
filename,
|
filename,
|
||||||
opts=extra_opts,
|
opts=coin_opts,
|
||||||
extra_config=extra_config,
|
extra_config=extra_config,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -679,6 +687,9 @@ def printHelp():
|
|||||||
print(
|
print(
|
||||||
"--startonlycoin Only start the provides coin daemon/s, use this if a chain requires extra processing."
|
"--startonlycoin Only start the provides coin daemon/s, use this if a chain requires extra processing."
|
||||||
)
|
)
|
||||||
|
print(
|
||||||
|
"--extracoinopts Extra options to pass to coin daemon, can only be used with --startonlycoin."
|
||||||
|
)
|
||||||
print("--logprefix Specify log prefix.")
|
print("--logprefix Specify log prefix.")
|
||||||
print(
|
print(
|
||||||
"--forcedbupgrade Recheck database against schema regardless of version."
|
"--forcedbupgrade Recheck database against schema regardless of version."
|
||||||
@@ -743,6 +754,11 @@ def main():
|
|||||||
ensure_coin_valid(coin)
|
ensure_coin_valid(coin)
|
||||||
start_only_coins.add(coin)
|
start_only_coins.add(coin)
|
||||||
continue
|
continue
|
||||||
|
if name == "extracoinopts":
|
||||||
|
options["extra_coin_opts"] = []
|
||||||
|
for opt in [s.lower() for s in s[1].split(",")]:
|
||||||
|
options["extra_coin_opts"].append(opt)
|
||||||
|
continue
|
||||||
|
|
||||||
logger.warning(f"Unknown argument {v}")
|
logger.warning(f"Unknown argument {v}")
|
||||||
|
|
||||||
|
|||||||
@@ -552,16 +552,26 @@ chainparams = {
|
|||||||
|
|
||||||
name_map = {}
|
name_map = {}
|
||||||
ticker_map = {}
|
ticker_map = {}
|
||||||
|
variant_ticker_map = {}
|
||||||
|
|
||||||
|
|
||||||
for c, params in chainparams.items():
|
for c, params in chainparams.items():
|
||||||
name_map[params["name"].lower()] = c
|
name_map[params["name"].lower()] = c
|
||||||
ticker_map[params["ticker"].lower()] = c
|
ticker_map[params["ticker"].lower()] = c
|
||||||
|
|
||||||
|
# Add coin variants, eg: LTC_MWEB, PART_ANON
|
||||||
|
for c in Coins:
|
||||||
|
if c.name.lower() in ticker_map:
|
||||||
|
continue
|
||||||
|
variant_ticker_map[c.name.lower()] = c
|
||||||
|
|
||||||
def getCoinIdFromTicker(ticker: str) -> str:
|
|
||||||
|
def getCoinIdFromTicker(ticker: str, inc_variant: bool = False) -> str:
|
||||||
|
lc_ticker: str = ticker.lower()
|
||||||
try:
|
try:
|
||||||
return ticker_map[ticker.lower()]
|
if inc_variant and lc_ticker in variant_ticker_map:
|
||||||
|
return variant_ticker_map[lc_ticker]
|
||||||
|
return ticker_map[lc_ticker]
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError(f"Unknown coin {ticker}")
|
raise ValueError(f"Unknown coin {ticker}")
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -12,7 +12,6 @@ import time
|
|||||||
from enum import IntEnum, auto
|
from enum import IntEnum, auto
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
CURRENT_DB_VERSION = 34
|
CURRENT_DB_VERSION = 34
|
||||||
CURRENT_DB_DATA_VERSION = 8
|
CURRENT_DB_DATA_VERSION = 8
|
||||||
|
|
||||||
@@ -400,8 +399,9 @@ 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")
|
||||||
|
|
||||||
a_fee_rate = Column("integer") # Chain a fee rate
|
# TODO: rename to from/to - values are not switched for reverse swaps
|
||||||
b_fee_rate = Column("integer") # Chain b fee rate
|
a_fee_rate = Column("integer") # Chain from 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")
|
||||||
|
|||||||
@@ -250,11 +250,18 @@ def upgradeDatabaseFromSchema(self, cursor, expect_schema):
|
|||||||
|
|
||||||
|
|
||||||
def upgradeDatabase(self, db_version: int):
|
def upgradeDatabase(self, db_version: int):
|
||||||
if self._force_db_upgrade is False and db_version >= CURRENT_DB_VERSION:
|
upgrade_forced: bool = False
|
||||||
|
if db_version < CURRENT_DB_VERSION:
|
||||||
|
pass
|
||||||
|
elif self._force_db_upgrade is True:
|
||||||
|
upgrade_forced = True
|
||||||
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log.info(
|
self.log.info(
|
||||||
f"Upgrading database from version {db_version} to {CURRENT_DB_VERSION}."
|
f"Upgrading database from version {db_version} to {CURRENT_DB_VERSION}"
|
||||||
|
+ (" (forced)" if upgrade_forced else "")
|
||||||
|
+ "."
|
||||||
)
|
)
|
||||||
|
|
||||||
# db_version, tablename, oldcolumnname, newcolumnname
|
# db_version, tablename, oldcolumnname, newcolumnname
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
default_coingecko_api_key = "CG-8hm3r9iLfpEXv4ied8oLbeUj"
|
default_coingecko_api_key = "CG-8hm3r9iLfpEXv4ied8oLbeUj"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -213,7 +213,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
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
@@ -50,7 +49,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):
|
def __init__(self, network, **kwargs):
|
||||||
self.setDefaults()
|
self.setDefaults()
|
||||||
self._network = network
|
self._network = network
|
||||||
self._mx_wallet = threading.Lock()
|
self._mx_wallet = threading.Lock()
|
||||||
@@ -193,8 +192,14 @@ class AdaptorSigInterface:
|
|||||||
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes) -> List[bytes]:
|
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes) -> List[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
|
||||||
@@ -220,13 +225,6 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
|||||||
if hash_len == 20:
|
if hash_len == 20:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def isValidPubkey(self, pubkey: bytes) -> bool:
|
|
||||||
try:
|
|
||||||
self.verifyPubkey(pubkey)
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool:
|
def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool:
|
||||||
pubkey = PublicKey(pubkey)
|
pubkey = PublicKey(pubkey)
|
||||||
return pubkey.verify(sig, signed_hash, hasher=None)
|
return pubkey.verify(sig, signed_hash, hasher=None)
|
||||||
|
|||||||
+47
-11
@@ -71,8 +71,13 @@ 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):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super(BCHInterface, self).__init__(coin_settings, network, swap_client)
|
super().__init__(
|
||||||
|
coin_settings=coin_settings,
|
||||||
|
network=network,
|
||||||
|
swap_client=swap_client,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
self.swap_client = swap_client
|
self.swap_client = swap_client
|
||||||
|
|
||||||
def has_segwit(self) -> bool:
|
def has_segwit(self) -> bool:
|
||||||
@@ -142,7 +147,9 @@ 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("importaddress", [lock_tx_dest.hex(), "bid lock", False, True])
|
self.rpc_wallet(
|
||||||
|
"importaddress", [lock_tx_dest.hex(), "bid lock", False, True]
|
||||||
|
)
|
||||||
|
|
||||||
return address
|
return address
|
||||||
|
|
||||||
@@ -151,18 +158,35 @@ class BCHInterface(BTCInterface):
|
|||||||
|
|
||||||
def createRawFundedTransaction(
|
def createRawFundedTransaction(
|
||||||
self,
|
self,
|
||||||
addr_to: str,
|
addr_to: str | bytes,
|
||||||
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(
|
|
||||||
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
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(
|
||||||
|
"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,
|
||||||
# 'conf_target': self._conf_target,
|
"feeRate": fee_rate,
|
||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options["subtractFeeFromOutputs"] = [
|
options["subtractFeeFromOutputs"] = [
|
||||||
@@ -214,6 +238,16 @@ 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:
|
||||||
@@ -268,7 +302,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('gettxout {}'.format(e))
|
# self._log.warning(f"gettxout {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if found_vout is None:
|
if found_vout is None:
|
||||||
@@ -281,13 +315,14 @@ class BCHInterface(BTCInterface):
|
|||||||
|
|
||||||
# TODO: Better way?
|
# TODO: Better way?
|
||||||
if confirmations > 0:
|
if confirmations > 0:
|
||||||
block_height = self.getChainHeight() - confirmations
|
block_height = self.getChainHeight() - (confirmations - 1)
|
||||||
|
|
||||||
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
|
||||||
@@ -502,6 +537,7 @@ 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
|
||||||
@@ -1120,7 +1156,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
|
||||||
|
|||||||
+279
-91
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
@@ -27,6 +26,7 @@ from basicswap.basicswap_util import (
|
|||||||
getVoutByScriptPubKey,
|
getVoutByScriptPubKey,
|
||||||
)
|
)
|
||||||
from basicswap.interface.base import Secp256k1Interface
|
from basicswap.interface.base import Secp256k1Interface
|
||||||
|
from basicswap.interface.utils import FeeValidator
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
b2i,
|
b2i,
|
||||||
ensure,
|
ensure,
|
||||||
@@ -99,7 +99,6 @@ from basicswap.basicswap_util import TxLockTypes
|
|||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.rpc import make_rpc_func, openrpc
|
from basicswap.rpc import make_rpc_func, openrpc
|
||||||
|
|
||||||
|
|
||||||
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
|
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
|
||||||
SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22
|
SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22
|
||||||
SEQUENCE_LOCKTIME_MASK = 0x0000FFFF
|
SEQUENCE_LOCKTIME_MASK = 0x0000FFFF
|
||||||
@@ -185,7 +184,7 @@ def extractScriptLockRefundScriptValues(script_bytes: bytes):
|
|||||||
return pk1, pk2, csv_val, pk3
|
return pk1, pk2, csv_val, pk3
|
||||||
|
|
||||||
|
|
||||||
class BTCInterface(Secp256k1Interface):
|
class BTCInterface(FeeValidator, Secp256k1Interface):
|
||||||
_scantxoutset_lock = threading.Lock()
|
_scantxoutset_lock = threading.Lock()
|
||||||
_MAX_SCANTXOUTSET_RETRIES = 3
|
_MAX_SCANTXOUTSET_RETRIES = 3
|
||||||
|
|
||||||
@@ -279,8 +278,15 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
def depth_spendable() -> int:
|
def depth_spendable() -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super().__init__(network)
|
self._sc = swap_client
|
||||||
|
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||||
|
super().__init__(
|
||||||
|
coin_settings=coin_settings,
|
||||||
|
network=network,
|
||||||
|
swap_client=swap_client,
|
||||||
|
**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"]
|
||||||
@@ -305,8 +311,6 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
self.setConfTarget(coin_settings["conf_target"])
|
self.setConfTarget(coin_settings["conf_target"])
|
||||||
self._use_segwit = coin_settings["use_segwit"]
|
self._use_segwit = coin_settings["use_segwit"]
|
||||||
self._connection_type = coin_settings["connection_type"]
|
self._connection_type = coin_settings["connection_type"]
|
||||||
self._sc = swap_client
|
|
||||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
|
||||||
self._expect_seedid_hex = None
|
self._expect_seedid_hex = None
|
||||||
self._altruistic = coin_settings.get("altruistic", True)
|
self._altruistic = coin_settings.get("altruistic", True)
|
||||||
self._use_descriptors = coin_settings.get("use_descriptors", False)
|
self._use_descriptors = coin_settings.get("use_descriptors", False)
|
||||||
@@ -449,11 +453,11 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
# Wallet name is "" for some LTC and PART installs on older cores
|
# Wallet name is "" for some LTC and PART installs on older cores
|
||||||
if self._rpc_wallet not in wallets and len(wallets) > 0:
|
if self._rpc_wallet not in wallets and len(wallets) > 0:
|
||||||
if "" in wallets:
|
if "" in wallets:
|
||||||
|
# Setting wallet= in the coin .conf file should also work
|
||||||
self._log.warning(
|
self._log.warning(
|
||||||
f"Nameless {self.ticker()} wallet found."
|
f"Nameless {self.ticker()} wallet found."
|
||||||
+ '\nPlease set the "wallet_name" coin setting to "" or recreate the wallet'
|
+ '\nPlease set the "wallet_name" coin setting to "" or recreate the wallet'
|
||||||
)
|
)
|
||||||
# backupwallet and restorewallet with name should work.
|
|
||||||
|
|
||||||
if self._rpc_wallet not in wallets:
|
if self._rpc_wallet not in wallets:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
@@ -503,6 +507,75 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
return height
|
return height
|
||||||
return self.rpc("getblockcount")
|
return self.rpc("getblockcount")
|
||||||
|
|
||||||
|
def getTxLocktime(self, tx_data: bytes) -> int:
|
||||||
|
tx_obj = self.loadTx(tx_data)
|
||||||
|
return tx_obj.nLockTime
|
||||||
|
|
||||||
|
def getTxInSequence(self, tx_data: bytes, vout: int) -> int:
|
||||||
|
tx_obj = self.loadTx(tx_data)
|
||||||
|
return tx_obj.vin[vout].nSequence
|
||||||
|
|
||||||
|
def getChainMedianTime(self) -> int:
|
||||||
|
if self.useBackend():
|
||||||
|
import struct
|
||||||
|
|
||||||
|
backend = self.getBackend()
|
||||||
|
if not backend:
|
||||||
|
raise ValueError("No electrum backend available")
|
||||||
|
height = backend.getBlockHeight()
|
||||||
|
start = max(0, height - 10)
|
||||||
|
count = height - start + 1
|
||||||
|
result = backend._server.call("blockchain.block.headers", [start, count])
|
||||||
|
header_bytes = bytes.fromhex(result["hex"])
|
||||||
|
returned = result.get("count", count)
|
||||||
|
times = [
|
||||||
|
struct.unpack("<I", header_bytes[i * 80 + 68 : i * 80 + 72])[0]
|
||||||
|
for i in range(returned)
|
||||||
|
]
|
||||||
|
times.sort()
|
||||||
|
return times[len(times) // 2]
|
||||||
|
return self.rpc("getblockchaininfo")["mediantime"]
|
||||||
|
|
||||||
|
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 getMempoolTx(self, txid):
|
def getMempoolTx(self, txid):
|
||||||
if self._connection_type == "electrum":
|
if self._connection_type == "electrum":
|
||||||
backend = self.getBackend()
|
backend = self.getBackend()
|
||||||
@@ -535,7 +608,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
block_hash = sha256(sha256(header_bytes))[::-1].hex()
|
block_hash = sha256(sha256(header_bytes))[::-1].hex()
|
||||||
return {"height": height, "hash": block_hash, "time": block_time}
|
return {"height": height, "hash": block_hash, "time": block_time}
|
||||||
|
|
||||||
def getBlockHeader(self, block_hash):
|
def getBlockHeader(self, block_hash: str) -> dict:
|
||||||
if self._connection_type == "electrum":
|
if self._connection_type == "electrum":
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"getBlockHeader by hash not available in electrum mode"
|
"getBlockHeader by hash not available in electrum mode"
|
||||||
@@ -939,32 +1012,26 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
if wm:
|
if wm:
|
||||||
info = wm.getAddressInfo(self.coin_type(), address)
|
info = wm.getAddressInfo(self.coin_type(), address)
|
||||||
if info:
|
if info:
|
||||||
if or_watch_only:
|
if or_watch_only is False and info["is_watch_only"] is True:
|
||||||
return True
|
return False
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
addr_info = self.rpc_wallet("getaddressinfo", [address])
|
addr_info = self.rpc_wallet("getaddressinfo", [address])
|
||||||
if not or_watch_only:
|
if addr_info["ismine"]:
|
||||||
if addr_info["ismine"]:
|
return True
|
||||||
return True
|
if or_watch_only is False:
|
||||||
else:
|
return False
|
||||||
if self._use_descriptors:
|
if addr_info["iswatchonly"]:
|
||||||
addr_info = self.rpc_wallet_watch("getaddressinfo", [address])
|
return True
|
||||||
if addr_info["ismine"] or addr_info["iswatchonly"]:
|
if self._use_descriptors:
|
||||||
|
wo_addr_info = self.rpc_wallet_watch("getaddressinfo", [address])
|
||||||
|
if wo_addr_info["iswatchonly"]:
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.debug(f"isAddressMine RPC check failed: {e}")
|
self._log.debug(f"isAddressMine RPC check failed: {e}")
|
||||||
|
|
||||||
wm = self.getWalletManager()
|
|
||||||
if wm:
|
|
||||||
info = wm.getAddressInfo(self.coin_type(), address)
|
|
||||||
if info:
|
|
||||||
if or_watch_only:
|
|
||||||
return True
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def checkAddressMine(self, address: str) -> None:
|
def checkAddressMine(self, address: str) -> None:
|
||||||
@@ -1083,8 +1150,8 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
return self.encode_p2wsh(script)
|
return self.encode_p2wsh(script)
|
||||||
|
|
||||||
def getDestForAddress(self, address: str) -> bytes:
|
def getDestForAddress(self, address: str) -> bytes:
|
||||||
bech32_prefix = self.chainparams_network()["hrp"]
|
bech32_prefix: str | None = self.chainparams_network().get("hrp", None)
|
||||||
if address.startswith(bech32_prefix + "1"):
|
if bech32_prefix and address.startswith(bech32_prefix + "1"):
|
||||||
_, witprog = segwit_addr.decode(bech32_prefix, address)
|
_, witprog = segwit_addr.decode(bech32_prefix, address)
|
||||||
return CScript([OP_0, bytes(witprog)])
|
return CScript([OP_0, bytes(witprog)])
|
||||||
|
|
||||||
@@ -1813,7 +1880,9 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
pubkey = PublicKey(K)
|
pubkey = PublicKey(K)
|
||||||
return pubkey.verify(sig[:-1], sig_hash, hasher=None) # Pop the hashtype byte
|
return pubkey.verify(sig[:-1], sig_hash, hasher=None) # Pop the hashtype byte
|
||||||
|
|
||||||
def fundTx(self, tx: bytes, feerate) -> bytes:
|
def fundTx(
|
||||||
|
self, tx: bytes, feerate: int, lock_unspents: bool = True, subfee: bool = False
|
||||||
|
) -> bytes:
|
||||||
if self.useBackend():
|
if self.useBackend():
|
||||||
return self._fundTxElectrum(tx, feerate)
|
return self._fundTxElectrum(tx, feerate)
|
||||||
|
|
||||||
@@ -1821,9 +1890,14 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
# TODO: Unlock unspents if bid cancelled
|
# TODO: Unlock unspents if bid cancelled
|
||||||
# TODO: Manually select only segwit prevouts
|
# TODO: Manually select only segwit prevouts
|
||||||
options = {
|
options = {
|
||||||
"lockUnspents": True,
|
"lockUnspents": lock_unspents,
|
||||||
"feeRate": feerate_str,
|
"feeRate": feerate_str,
|
||||||
}
|
}
|
||||||
|
if subfee:
|
||||||
|
tx_obj = self.loadTx(tx, allow_witness=False)
|
||||||
|
num_vouts: int = len(tx_obj.vout)
|
||||||
|
ensure(num_vouts > 0, "Missing tx outputs")
|
||||||
|
options["subtractFeeFromOutputs"] = list(range(num_vouts))
|
||||||
rv = self.rpc_wallet("fundrawtransaction", [tx.hex(), options])
|
rv = self.rpc_wallet("fundrawtransaction", [tx.hex(), options])
|
||||||
tx_bytes: bytes = bytes.fromhex(rv["hex"])
|
tx_bytes: bytes = bytes.fromhex(rv["hex"])
|
||||||
return tx_bytes
|
return tx_bytes
|
||||||
@@ -2645,10 +2719,17 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
tx.vout.append(self.txoType()(output_amount, p2wpkh_script_pk))
|
tx.vout.append(self.txoType()(output_amount, p2wpkh_script_pk))
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def encodeSharedAddress(self, Kbv, Kbs):
|
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
|
||||||
return self.pubkey_to_segwit_address(Kbs)
|
return self.pubkey_to_segwit_address(Kbs)
|
||||||
|
|
||||||
def publishBLockTx(self, kbv, Kbs, output_amount, feerate, unlock_time: int = 0):
|
def publishBLockTx(
|
||||||
|
self,
|
||||||
|
kbv: bytes,
|
||||||
|
Kbs: bytes,
|
||||||
|
output_amount: int,
|
||||||
|
feerate: int,
|
||||||
|
unlock_time: int = 0,
|
||||||
|
) -> (bytes, int):
|
||||||
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)
|
||||||
@@ -2671,8 +2752,8 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
|
|
||||||
def findTxB(
|
def findTxB(
|
||||||
self,
|
self,
|
||||||
kbv,
|
kbv: bytes,
|
||||||
Kbs,
|
Kbs: bytes,
|
||||||
cb_swap_value: int,
|
cb_swap_value: int,
|
||||||
cb_block_confirmed: int,
|
cb_block_confirmed: int,
|
||||||
restore_height: int,
|
restore_height: int,
|
||||||
@@ -2711,6 +2792,47 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
)
|
)
|
||||||
return pay_fee
|
return pay_fee
|
||||||
|
|
||||||
|
def getBLockTxo(
|
||||||
|
self,
|
||||||
|
chain_b_lock_txid: bytes,
|
||||||
|
lock_tx_vout: int,
|
||||||
|
script_pk: bytes,
|
||||||
|
) -> (int, int):
|
||||||
|
if self.useBackend():
|
||||||
|
backend = self.getBackend()
|
||||||
|
tx_hex = backend.getTransactionRaw(chain_b_lock_txid.hex())
|
||||||
|
if tx_hex:
|
||||||
|
lock_tx = self.loadTx(bytes.fromhex(tx_hex))
|
||||||
|
locked_n = findOutput(lock_tx, script_pk)
|
||||||
|
if locked_n is not None:
|
||||||
|
actual_value = lock_tx.vout[locked_n].nValue
|
||||||
|
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.nValue}, scriptPubKey={out.scriptPubKey.hex()}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._log.warning(
|
||||||
|
f"spendBLockTx: Failed to fetch tx {self._log.id(chain_b_lock_txid)} from backend"
|
||||||
|
)
|
||||||
|
locked_n = lock_tx_vout
|
||||||
|
return locked_n, actual_value
|
||||||
|
wtx = self.rpc_wallet_watch(
|
||||||
|
"gettransaction",
|
||||||
|
[
|
||||||
|
chain_b_lock_txid.hex(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
lock_tx = self.loadTx(bytes.fromhex(wtx["hex"]))
|
||||||
|
locked_n = findOutput(lock_tx, script_pk)
|
||||||
|
if locked_n is not None:
|
||||||
|
actual_value = lock_tx.vout[locked_n].nValue
|
||||||
|
return locked_n, actual_value
|
||||||
|
|
||||||
def spendBLockTx(
|
def spendBLockTx(
|
||||||
self,
|
self,
|
||||||
chain_b_lock_txid: bytes,
|
chain_b_lock_txid: bytes,
|
||||||
@@ -2724,48 +2846,14 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
lock_tx_vout=None,
|
lock_tx_vout=None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
self._log.info(
|
self._log.info(
|
||||||
"spendBLockTx: {} {}\n".format(
|
f"spendBLockTx: {self._log.id(chain_b_lock_txid)} {lock_tx_vout}\n"
|
||||||
self._log.id(chain_b_lock_txid), lock_tx_vout
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
Kbs = self.getPubkey(kbs)
|
Kbs = self.getPubkey(kbs)
|
||||||
script_pk = self.getPkDest(Kbs)
|
script_pk = self.getPkDest(Kbs)
|
||||||
|
|
||||||
locked_n = None
|
locked_n, actual_value = self.getBLockTxo(
|
||||||
actual_value = None
|
chain_b_lock_txid, lock_tx_vout, script_pk
|
||||||
if self.useBackend():
|
)
|
||||||
backend = self.getBackend()
|
|
||||||
tx_hex = backend.getTransactionRaw(chain_b_lock_txid.hex())
|
|
||||||
if tx_hex:
|
|
||||||
lock_tx = self.loadTx(bytes.fromhex(tx_hex))
|
|
||||||
locked_n = findOutput(lock_tx, script_pk)
|
|
||||||
if locked_n is not None:
|
|
||||||
actual_value = lock_tx.vout[locked_n].nValue
|
|
||||||
else:
|
|
||||||
self._log.error(
|
|
||||||
f"spendBLockTx: Output not found in tx {chain_b_lock_txid.hex()}, "
|
|
||||||
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.nValue}, scriptPubKey={out.scriptPubKey.hex()}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._log.warning(
|
|
||||||
f"spendBLockTx: Failed to fetch tx {chain_b_lock_txid.hex()} from backend"
|
|
||||||
)
|
|
||||||
locked_n = lock_tx_vout
|
|
||||||
else:
|
|
||||||
wtx = self.rpc_wallet_watch(
|
|
||||||
"gettransaction",
|
|
||||||
[
|
|
||||||
chain_b_lock_txid.hex(),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
lock_tx = self.loadTx(bytes.fromhex(wtx["hex"]))
|
|
||||||
locked_n = findOutput(lock_tx, script_pk)
|
|
||||||
if locked_n is not None:
|
|
||||||
actual_value = lock_tx.vout[locked_n].nValue
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
locked_n is not None
|
locked_n is not None
|
||||||
@@ -2774,7 +2862,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
):
|
):
|
||||||
self._log.warning(
|
self._log.warning(
|
||||||
f"spendBLockTx: Stored vout {lock_tx_vout} differs from actual vout {locked_n} "
|
f"spendBLockTx: Stored vout {lock_tx_vout} differs from actual vout {locked_n} "
|
||||||
f"for tx {chain_b_lock_txid.hex()}"
|
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")
|
||||||
@@ -2864,13 +2952,9 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
# Add watchonly address and rescan if required
|
# Add watchonly address and rescan if required
|
||||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||||
self.importWatchOnlyAddress(dest_address, "bid")
|
self.importWatchOnlyAddress(dest_address, "bid")
|
||||||
|
self._log.info(f"Imported watch-only addr: {self._log.addr(dest_address)}")
|
||||||
self._log.info(
|
self._log.info(
|
||||||
"Imported watch-only addr: {}".format(self._log.addr(dest_address))
|
f"Rescanning {self.coin_name()} chain from height: {rescan_from}"
|
||||||
)
|
|
||||||
self._log.info(
|
|
||||||
"Rescanning {} chain from height: {}".format(
|
|
||||||
self.coin_name(), rescan_from
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
self.rpc_wallet("rescanblockchain", [rescan_from])
|
self.rpc_wallet("rescanblockchain", [rescan_from])
|
||||||
|
|
||||||
@@ -2922,6 +3006,8 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
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()
|
||||||
@@ -2979,6 +3065,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
for idx, txout in enumerate(tx.vout):
|
for idx, txout in enumerate(tx.vout):
|
||||||
if txout.scriptPubKey == dest_script:
|
if txout.scriptPubKey == dest_script:
|
||||||
rv["index"] = idx
|
rv["index"] = idx
|
||||||
|
rv["value"] = txout.nValue
|
||||||
break
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -3040,6 +3127,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
for idx, txout in enumerate(tx.vout):
|
for idx, txout in enumerate(tx.vout):
|
||||||
if txout.scriptPubKey == dest_script:
|
if txout.scriptPubKey == dest_script:
|
||||||
rv["index"] = idx
|
rv["index"] = idx
|
||||||
|
rv["value"] = txout.nValue
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.debug(
|
self._log.debug(
|
||||||
@@ -3401,6 +3489,7 @@ class BTCInterface(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:
|
||||||
if self.useBackend():
|
if self.useBackend():
|
||||||
return self._createRawFundedTransactionElectrum(addr_to, amount, sub_fee)
|
return self._createRawFundedTransactionElectrum(addr_to, amount, sub_fee)
|
||||||
@@ -3408,10 +3497,18 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
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,
|
||||||
"conf_target": self._conf_target,
|
"feeRate": fee_rate,
|
||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options["subtractFeeFromOutputs"] = [
|
options["subtractFeeFromOutputs"] = [
|
||||||
@@ -3574,7 +3671,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
continue
|
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
|
||||||
@@ -3735,7 +3832,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
|
|
||||||
ensure(
|
ensure(
|
||||||
sign_for_addr is not None,
|
sign_for_addr is not None,
|
||||||
"Could not find address with enough funds for proof",
|
f"Could not find {self.ticker()} address with enough funds for proof",
|
||||||
)
|
)
|
||||||
|
|
||||||
self._log.debug(f"sign_for_addr {sign_for_addr}")
|
self._log.debug(f"sign_for_addr {sign_for_addr}")
|
||||||
@@ -4374,14 +4471,16 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def isTxExistsError(self, err_str: str) -> bool:
|
def isTxExistsError(self, err_str: str) -> bool:
|
||||||
|
if self._connection_type == "electrum":
|
||||||
|
return "Transaction outputs already in utxo set" in err_str
|
||||||
return "Transaction already in block chain" in err_str
|
return "Transaction already in block chain" in err_str
|
||||||
|
|
||||||
def isTxNonFinalError(self, err_str: str) -> bool:
|
def isTxNonFinalError(self, err_str: str) -> bool:
|
||||||
|
err_lower = err_str.lower()
|
||||||
return (
|
return (
|
||||||
"non-BIP68-final" in err_str
|
"non-bip68-final" in err_lower
|
||||||
or "non-final" in err_str
|
or "non-final" in err_lower
|
||||||
or "Missing inputs" in err_str
|
or "locktime requirement not satisfied" in err_lower
|
||||||
or "bad-txns-inputs-missingorspent" in err_str
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def combine_non_segwit_prevouts(self):
|
def combine_non_segwit_prevouts(self):
|
||||||
@@ -4428,14 +4527,103 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
self._log.id(bytes.fromhex(tx["txid"]))
|
self._log.id(bytes.fromhex(tx["txid"]))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.publishTx(tx_signed)
|
self.publishTx(bytes.fromhex(tx_signed))
|
||||||
|
|
||||||
return tx["txid"]
|
return tx["txid"]
|
||||||
|
|
||||||
|
def validatePrefundedTxAmounts(self, tx_data: bytes) -> (bytes, int):
|
||||||
|
# unspent_utxos = self.listUtxos()
|
||||||
|
tx_obj = self.loadTx(tx_data, allow_witness=False)
|
||||||
|
|
||||||
def testBTCInterface():
|
total_out: int = 0
|
||||||
print("TODO: testBTCInterface")
|
total_in: int = 0
|
||||||
|
for txo in tx_obj.vout:
|
||||||
|
total_out += txo.nValue
|
||||||
|
|
||||||
|
dummy_witness_stack = []
|
||||||
|
used_utxos = set()
|
||||||
|
for txi in tx_obj.vin:
|
||||||
|
txi_txid_hex: str = i2h(txi.prevout.hash)
|
||||||
|
txi_vout: int = txi.prevout.n
|
||||||
|
if (txi_txid_hex, txi_vout) in used_utxos:
|
||||||
|
raise ValueError(f"Duplicate txin {txi_txid_hex} {txi_vout}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
prev_tx = self.rpc_wallet("gettransaction", [txi_txid_hex])
|
||||||
testBTCInterface()
|
prev_tx_obj = self.describeTx(prev_tx["hex"])
|
||||||
|
|
||||||
|
txo = prev_tx_obj["vout"][txi_vout]
|
||||||
|
total_in += self.make_int(txo["value"])
|
||||||
|
dummy_witness_stack.append(self.getP2WPKHDummyWitness())
|
||||||
|
used_utxos.add((txi_txid_hex, txi_vout))
|
||||||
|
|
||||||
|
fee: int = total_in - total_out
|
||||||
|
witness_bytes_len_est: int = self.getWitnessStackSerialisedLength(
|
||||||
|
dummy_witness_stack
|
||||||
|
)
|
||||||
|
vsize = self.getTxVSize(tx_obj, add_witness_bytes=witness_bytes_len_est)
|
||||||
|
fee_rate = fee * 1000 // vsize
|
||||||
|
|
||||||
|
return bytes.fromhex(txi_txid_hex), fee_rate
|
||||||
|
|
||||||
|
def _getTxOutInfoElectrum(self, txid: bytes, n: int, include_mempool: bool = False):
|
||||||
|
backend = self.getBackend()
|
||||||
|
if not backend:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
tx_info = backend.getTransaction(txid.hex())
|
||||||
|
if "blockhash" not in tx_info:
|
||||||
|
return None
|
||||||
|
confirmations: int = (
|
||||||
|
0 if "confirmations" not in tx_info else tx_info["confirmations"]
|
||||||
|
)
|
||||||
|
if confirmations < 1:
|
||||||
|
return None
|
||||||
|
|
||||||
|
chain_tip_height = self.getChainHeight()
|
||||||
|
block_height: int = chain_tip_height - (confirmations - 1)
|
||||||
|
block_hash: bytes = bytes.fromhex(tx_info["blockhash"])
|
||||||
|
return {
|
||||||
|
"block_hash": block_hash,
|
||||||
|
"block_height": block_height,
|
||||||
|
"block_time": tx_info["blocktime"],
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
self._log.debug(f"_findTxnByHashElectrum failed: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getTxOutInfo(
|
||||||
|
self, txid: bytes, n: int, include_mempool: bool = False
|
||||||
|
) -> dict():
|
||||||
|
if self._connection_type == "electrum":
|
||||||
|
return self._getTxOutInfoElectrum(txid, n, include_mempool)
|
||||||
|
try:
|
||||||
|
txout = self.rpc("gettxout", [txid.hex(), n, 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
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2024 tecnovert
|
# Copyright (c) 2022-2024 tecnovert
|
||||||
@@ -24,8 +23,13 @@ class DASHInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.DASH
|
return Coins.DASH
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super().__init__(coin_settings, network, swap_client)
|
super().__init__(
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||
+164
-39
@@ -1,8 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
# Copyright (c) 2024-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -13,16 +12,15 @@ import logging
|
|||||||
import random
|
import random
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
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 (
|
from basicswap.interface.base import Secp256k1Interface
|
||||||
Secp256k1Interface,
|
from basicswap.interface.utils import FeeValidator
|
||||||
)
|
|
||||||
from basicswap.interface.btc import (
|
from basicswap.interface.btc import (
|
||||||
extractScriptLockScriptValues,
|
extractScriptLockScriptValues,
|
||||||
extractScriptLockRefundScriptValues,
|
extractScriptLockRefundScriptValues,
|
||||||
@@ -82,7 +80,6 @@ from coincurve.ecdsaotves import (
|
|||||||
ecdsaotves_rec_enc_key,
|
ecdsaotves_rec_enc_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
|
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
|
||||||
SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22
|
SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22
|
||||||
SEQUENCE_LOCKTIME_MASK = 0x0000F
|
SEQUENCE_LOCKTIME_MASK = 0x0000F
|
||||||
@@ -182,7 +179,7 @@ def extract_sig_and_pk(sig_script: bytes) -> (bytes, bytes):
|
|||||||
return sig, pk
|
return sig, pk
|
||||||
|
|
||||||
|
|
||||||
class DCRInterface(Secp256k1Interface):
|
class DCRInterface(FeeValidator, Secp256k1Interface):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def coin_type():
|
def coin_type():
|
||||||
@@ -259,13 +256,13 @@ class DCRInterface(Secp256k1Interface):
|
|||||||
def depth_spendable() -> int:
|
def depth_spendable() -> int:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super().__init__(network)
|
self._sc = swap_client
|
||||||
|
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"]
|
||||||
@@ -421,8 +418,10 @@ class DCRInterface(Secp256k1Interface):
|
|||||||
|
|
||||||
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")
|
||||||
@@ -597,7 +596,7 @@ class DCRInterface(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(
|
||||||
"Fee rate override used for %s: %f", self.coin_name(), override_feerate
|
f"Fee rate override used for {self.coin_name()}: {override_feerate}"
|
||||||
)
|
)
|
||||||
return override_feerate, "override_feerate"
|
return override_feerate, "override_feerate"
|
||||||
|
|
||||||
@@ -858,12 +857,17 @@ class DCRInterface(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}])
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
if feerate:
|
||||||
|
fee_rate = feerate
|
||||||
|
fee_src = "specified"
|
||||||
|
else:
|
||||||
|
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}"
|
||||||
)
|
)
|
||||||
@@ -916,7 +920,7 @@ class DCRInterface(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('gettxout {}'.format(e))
|
# self._log.warning(f"gettxout {e})
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if found_vout is None:
|
if found_vout is None:
|
||||||
@@ -929,13 +933,14 @@ class DCRInterface(Secp256k1Interface):
|
|||||||
|
|
||||||
# TODO: Better way?
|
# TODO: Better way?
|
||||||
if confirmations > 0:
|
if confirmations > 0:
|
||||||
block_height = self.getChainHeight() - confirmations
|
block_height = self.getChainHeight() - (confirmations - 1)
|
||||||
|
|
||||||
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
|
||||||
@@ -996,6 +1001,10 @@ class DCRInterface(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
|
||||||
@@ -1073,7 +1082,9 @@ class DCRInterface(Secp256k1Interface):
|
|||||||
def decodeRawTransaction(self, tx_hex: str):
|
def decodeRawTransaction(self, tx_hex: str):
|
||||||
return self.rpc("decoderawtransaction", [tx_hex])
|
return self.rpc("decoderawtransaction", [tx_hex])
|
||||||
|
|
||||||
def fundTx(self, tx: bytes, feerate) -> bytes:
|
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 = {
|
||||||
@@ -1146,6 +1157,7 @@ class DCRInterface(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
|
||||||
|
|
||||||
@@ -1197,6 +1209,7 @@ class DCRInterface(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
|
||||||
|
|
||||||
@@ -1248,6 +1261,7 @@ class DCRInterface(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
|
||||||
|
|
||||||
@@ -1332,6 +1346,7 @@ class DCRInterface(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(
|
||||||
@@ -1393,6 +1408,7 @@ class DCRInterface(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(
|
||||||
@@ -1465,6 +1481,7 @@ class DCRInterface(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(
|
||||||
@@ -1526,6 +1543,7 @@ class DCRInterface(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(
|
||||||
@@ -1776,32 +1794,41 @@ class DCRInterface(Secp256k1Interface):
|
|||||||
spend_actual_balance: bool = False,
|
spend_actual_balance: bool = False,
|
||||||
lock_tx_vout=None,
|
lock_tx_vout=None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
self._log.info("spendBLockTx %s:\n", chain_b_lock_txid.hex())
|
self._log.info(
|
||||||
|
f"spendBLockTx: {self._log.id(chain_b_lock_txid)} {lock_tx_vout}\n"
|
||||||
|
)
|
||||||
|
|
||||||
Kbs = self.getPubkey(kbs)
|
Kbs = self.getPubkey(kbs)
|
||||||
script_pk = self.getPkDest(Kbs)
|
script_pk = self.getPkDest(Kbs)
|
||||||
|
|
||||||
locked_n = None
|
locked_n = None
|
||||||
actual_value = None
|
actual_value = None
|
||||||
wtx = self.rpc_wallet(
|
try:
|
||||||
"gettransaction",
|
wtx = self.rpc_wallet(
|
||||||
[
|
"gettransaction",
|
||||||
chain_b_lock_txid.hex(),
|
[
|
||||||
],
|
chain_b_lock_txid.hex(),
|
||||||
)
|
],
|
||||||
lock_tx = self.loadTx(bytes.fromhex(wtx["hex"]))
|
|
||||||
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 {chain_b_lock_txid.hex()}, "
|
|
||||||
f"script_pk={script_pk.hex()}, num_outputs={len(lock_tx.vout)}"
|
|
||||||
)
|
)
|
||||||
for i, out in enumerate(lock_tx.vout):
|
lock_tx = self.loadTx(bytes.fromhex(wtx["hex"]))
|
||||||
self._log.debug(
|
locked_n = findOutput(lock_tx, script_pk)
|
||||||
f" vout[{i}]: value={out.value}, scriptPubKey={out.scriptPubKey.hex()}"
|
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 (
|
if (
|
||||||
locked_n is not None
|
locked_n is not None
|
||||||
@@ -1810,7 +1837,7 @@ class DCRInterface(Secp256k1Interface):
|
|||||||
):
|
):
|
||||||
self._log.warning(
|
self._log.warning(
|
||||||
f"spendBLockTx: Stored vout {lock_tx_vout} differs from actual vout {locked_n} "
|
f"spendBLockTx: Stored vout {lock_tx_vout} differs from actual vout {locked_n} "
|
||||||
f"for tx {chain_b_lock_txid.hex()}"
|
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")
|
||||||
@@ -1846,14 +1873,14 @@ class DCRInterface(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('gettxout {}'.format(e))
|
# self._log.warning(f"gettxout {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 # TODO: Better way?
|
block_height = self.getChainHeight() - (confirmations - 1)
|
||||||
return {"txid": txid_hex, "amount": 0, "height": block_height}
|
return {"txid": txid_hex, "amount": 0, "height": block_height}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -1868,3 +1895,101 @@ class DCRInterface(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,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 tecnovert
|
# Copyright (c) 2024 tecnovert
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- 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.
|
||||||
|
|
||||||
@@ -9,10 +10,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"):
|
def callrpc(rpc_port, auth, method, params=[], host="127.0.0.1", timeout=None):
|
||||||
try:
|
try:
|
||||||
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
url = "http://{}@{}:{}/".format(auth, host, rpc_port)
|
||||||
x = Jsonrpc(url)
|
x = Jsonrpc(url, timeout=timeout if timeout else 10)
|
||||||
x.__handler = None
|
x.__handler = None
|
||||||
v = x.json_request(method, params)
|
v = x.json_request(method, params)
|
||||||
x.close()
|
x.close()
|
||||||
@@ -41,7 +42,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):
|
def rpc_func(method, params=None, timeout=None):
|
||||||
return callrpc(port, auth, method, params, host)
|
return callrpc(port, auth, method, params, host, timeout=timeout)
|
||||||
|
|
||||||
return rpc_func
|
return rpc_func
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import subprocess
|
|||||||
def createDCRWallet(args, hex_seed, logging, delay_event):
|
def createDCRWallet(args, hex_seed, logging, delay_event):
|
||||||
logging.info("Creating DCR wallet")
|
logging.info("Creating DCR wallet")
|
||||||
|
|
||||||
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read
|
pipe_r, pipe_w = os.pipe() # subprocess.PIPE is buffered, blocks when read
|
||||||
|
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
str_args = " ".join(args)
|
str_args = " ".join(args)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 The BasicSwap developers
|
# Copyright (c) 2024 The BasicSwap developers
|
||||||
@@ -32,8 +31,13 @@ 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):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super(DOGEInterface, self).__init__(coin_settings, network, swap_client)
|
super().__init__(
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024-2026 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
@@ -38,8 +37,13 @@ class FIROInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.FIRO
|
return Coins.FIRO
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super(FIROInterface, self).__init__(coin_settings, network, swap_client)
|
super().__init__(
|
||||||
|
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
|
||||||
@@ -272,6 +276,8 @@ 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()
|
||||||
@@ -300,11 +306,16 @@ 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)}]
|
||||||
)
|
)
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
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(
|
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}"
|
||||||
)
|
)
|
||||||
@@ -361,7 +372,7 @@ class FIROInterface(BTCInterface):
|
|||||||
)
|
)
|
||||||
return pay_fee
|
return pay_fee
|
||||||
|
|
||||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
def signTxWithKey(self, tx: bytes, key: bytes, prev_amount=None) -> bytes:
|
||||||
key_wif = self.encodeKey(key)
|
key_wif = self.encodeKey(key)
|
||||||
rv = self.rpc(
|
rv = self.rpc(
|
||||||
"signrawtransaction",
|
"signrawtransaction",
|
||||||
|
|||||||
+101
-113
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2023 tecnovert
|
# Copyright (c) 2020-2023 tecnovert
|
||||||
@@ -16,8 +15,13 @@ class LTCInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.LTC
|
return Coins.LTC
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super(LTCInterface, self).__init__(coin_settings, network, swap_client)
|
super().__init__(
|
||||||
|
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,
|
||||||
@@ -26,87 +30,6 @@ class LTCInterface(BTCInterface):
|
|||||||
wallet=self._rpc_wallet_mweb,
|
wallet=self._rpc_wallet_mweb,
|
||||||
)
|
)
|
||||||
|
|
||||||
def checkWallets(self) -> int:
|
|
||||||
if self._connection_type == "electrum":
|
|
||||||
wm = self.getWalletManager()
|
|
||||||
if wm and wm.isInitialized(self.coin_type()):
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
wallets = self.rpc("listwallets")
|
|
||||||
|
|
||||||
if self._rpc_wallet not in wallets:
|
|
||||||
self._log.debug(
|
|
||||||
f"Wallet: {self._rpc_wallet} not active, attempting to load."
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
self.rpc(
|
|
||||||
"loadwallet",
|
|
||||||
[
|
|
||||||
self._rpc_wallet,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
wallets = self.rpc("listwallets")
|
|
||||||
except Exception as e:
|
|
||||||
self._log.debug(f'Error loading wallet "{self._rpc_wallet}": {e}.')
|
|
||||||
if "does not exist" in str(e) or "Path does not exist" in str(e):
|
|
||||||
try:
|
|
||||||
wallet_dirs = self.rpc("listwalletdir")
|
|
||||||
existing = [w["name"] for w in wallet_dirs.get("wallets", [])]
|
|
||||||
except Exception:
|
|
||||||
existing = []
|
|
||||||
if len(existing) == 0:
|
|
||||||
self._log.info(
|
|
||||||
f'Creating wallet "{self._rpc_wallet}" for {self.coin_name()}.'
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
|
||||||
self.rpc(
|
|
||||||
"createwallet",
|
|
||||||
[
|
|
||||||
self._rpc_wallet,
|
|
||||||
False,
|
|
||||||
True,
|
|
||||||
"",
|
|
||||||
False,
|
|
||||||
self._use_descriptors,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
wallets = self.rpc("listwallets")
|
|
||||||
if self.getWalletSeedID() == "Not found":
|
|
||||||
self._log.info(
|
|
||||||
f"Initializing HD seed for {self.coin_name()}."
|
|
||||||
)
|
|
||||||
self._sc.initialiseWallet(self.coin_type())
|
|
||||||
except Exception as create_e:
|
|
||||||
self._log.error(f"Error creating wallet: {create_e}")
|
|
||||||
|
|
||||||
if self._rpc_wallet not in wallets and len(wallets) > 0:
|
|
||||||
self._log.warning(f"Changing {self.ticker()} wallet name.")
|
|
||||||
for wallet_name in wallets:
|
|
||||||
if wallet_name in ("mweb",):
|
|
||||||
continue
|
|
||||||
|
|
||||||
change_watchonly_wallet: bool = (
|
|
||||||
self._rpc_wallet_watch == self._rpc_wallet
|
|
||||||
)
|
|
||||||
|
|
||||||
self._rpc_wallet = wallet_name
|
|
||||||
self._log.info(
|
|
||||||
f"Switched {self.ticker()} wallet name to {self._rpc_wallet}."
|
|
||||||
)
|
|
||||||
self.rpc_wallet = make_rpc_func(
|
|
||||||
self._rpcport,
|
|
||||||
self._rpcauth,
|
|
||||||
host=self._rpc_host,
|
|
||||||
wallet=self._rpc_wallet,
|
|
||||||
)
|
|
||||||
if change_watchonly_wallet:
|
|
||||||
self.rpc_wallet_watch = self.rpc_wallet
|
|
||||||
break
|
|
||||||
|
|
||||||
return len(wallets)
|
|
||||||
|
|
||||||
def getNewMwebAddress(self, use_segwit=False, label="swap_receive") -> str:
|
def getNewMwebAddress(self, use_segwit=False, label="swap_receive") -> str:
|
||||||
if self.useBackend():
|
if self.useBackend():
|
||||||
raise ValueError("MWEB addresses not supported in electrum mode")
|
raise ValueError("MWEB addresses not supported in electrum mode")
|
||||||
@@ -172,9 +95,14 @@ 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
|
||||||
@@ -184,11 +112,81 @@ class LTCInterface(BTCInterface):
|
|||||||
else:
|
else:
|
||||||
if not desc.startswith("pkh"):
|
if not desc.startswith("pkh"):
|
||||||
continue
|
continue
|
||||||
unspent_addr[u["address"]] = unspent_addr.get(
|
unspent_addr[utxo_address] = unspent_addr.get(
|
||||||
u["address"], 0
|
utxo_address, 0
|
||||||
) + self.make_int(u["amount"], r=1)
|
) + self.make_int(u["amount"], r=1)
|
||||||
return unspent_addr
|
return unspent_addr
|
||||||
|
|
||||||
|
def getMWEBBalance(self) -> int:
|
||||||
|
if self.useBackend():
|
||||||
|
raise ValueError("MWEB not supported in electrum mode")
|
||||||
|
|
||||||
|
value: int = 0
|
||||||
|
unspent = self.rpc_wallet(
|
||||||
|
"listunspent",
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
for u in unspent:
|
||||||
|
if "address" not in u:
|
||||||
|
continue
|
||||||
|
utxo_address: str = u["address"]
|
||||||
|
if any(
|
||||||
|
utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1")
|
||||||
|
):
|
||||||
|
value += self.make_int(u["amount"], r=1)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def convertMWEBBalance(self):
|
||||||
|
if self.useBackend():
|
||||||
|
raise ValueError("MWEB not supported in electrum mode")
|
||||||
|
|
||||||
|
self._log.info(f"convertMWEBBalance - {self.ticker()}")
|
||||||
|
locked_before = self.rpc_wallet("listlockunspent")
|
||||||
|
lock_utxos = []
|
||||||
|
try:
|
||||||
|
# Hack: mark all the other utxos as unspendable, alternative is to use a mweb_transfer wallet
|
||||||
|
utxos = self.rpc_wallet("listunspent")
|
||||||
|
mweb_amount: int = 0
|
||||||
|
for utxo in utxos:
|
||||||
|
utxo_address: str = utxo.get("address", "")
|
||||||
|
if any(
|
||||||
|
utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1")
|
||||||
|
):
|
||||||
|
mweb_amount += self.make_int(utxo["amount"], r=1)
|
||||||
|
continue
|
||||||
|
utxo_op = {"txid": utxo["txid"], "vout": utxo["vout"]}
|
||||||
|
if utxo_op in locked_before:
|
||||||
|
continue
|
||||||
|
lock_utxos.append(utxo_op)
|
||||||
|
|
||||||
|
if mweb_amount == 0:
|
||||||
|
raise ValueError("No MWEB outputs to convert")
|
||||||
|
self.rpc_wallet("lockunspent", [False, lock_utxos])
|
||||||
|
subfee_to_mweb: bool = True
|
||||||
|
convert_value = self.format_amount(mweb_amount)
|
||||||
|
plain_addr: str = self.rpc_wallet("getnewaddress", ["transfer", "bech32"])
|
||||||
|
|
||||||
|
# Double check generated address is owned by this wallet
|
||||||
|
if not self.isAddressMine(plain_addr):
|
||||||
|
raise ValueError("Generated address not owned by wallet!")
|
||||||
|
params = [
|
||||||
|
plain_addr,
|
||||||
|
convert_value,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
subfee_to_mweb,
|
||||||
|
True,
|
||||||
|
self._conf_target,
|
||||||
|
]
|
||||||
|
txid = self.rpc_wallet("sendtoaddress", params)
|
||||||
|
|
||||||
|
self._log.info(f"MWEB in plain converted in txid: {self._log.id(txid)}")
|
||||||
|
return txid
|
||||||
|
finally:
|
||||||
|
self.rpc_wallet("lockunspent", [True, lock_utxos])
|
||||||
|
|
||||||
def unlockWallet(self, password: str, check_seed: bool = True) -> None:
|
def unlockWallet(self, password: str, check_seed: bool = True) -> None:
|
||||||
if password == "":
|
if password == "":
|
||||||
return
|
return
|
||||||
@@ -271,8 +269,13 @@ 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):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client)
|
super().__init__(
|
||||||
|
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
|
||||||
@@ -306,23 +309,24 @@ class LTCInterfaceMWEB(LTCInterface):
|
|||||||
def init_wallet(self, password=None):
|
def init_wallet(self, password=None):
|
||||||
# If system is encrypted mweb wallet will be created at first unlock
|
# If system is encrypted mweb wallet will be created at first unlock
|
||||||
|
|
||||||
self._log.info("init_wallet - {}".format(self.ticker()))
|
wallet_name: str = self._rpc_wallet
|
||||||
|
self._log.info(f"init_wallet - {self.ticker()}")
|
||||||
|
|
||||||
wallets = self.rpc("listwallets")
|
wallets = self.rpc("listwallets")
|
||||||
if self._rpc_wallet not in wallets:
|
if wallet_name not in wallets:
|
||||||
try:
|
try:
|
||||||
self.rpc("loadwallet", [self._rpc_wallet])
|
self.rpc("loadwallet", [wallet_name])
|
||||||
self._log.debug(f'Loaded existing wallet "{self._rpc_wallet}".')
|
self._log.debug(f'Loaded existing wallet "{wallet_name}".')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if "does not exist" in str(e) or "Path does not exist" in str(e):
|
if "does not exist" in str(e) or "Path does not exist" in str(e):
|
||||||
self._log.info(
|
self._log.info(
|
||||||
f'Creating wallet "{self._rpc_wallet}" for {self.coin_name()}.'
|
f'Creating wallet "{wallet_name}" for {self.coin_name()}.'
|
||||||
)
|
)
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
||||||
self.rpc(
|
self.rpc(
|
||||||
"createwallet",
|
"createwallet",
|
||||||
[
|
[
|
||||||
self._rpc_wallet,
|
wallet_name,
|
||||||
False,
|
False,
|
||||||
True,
|
True,
|
||||||
password,
|
password,
|
||||||
@@ -333,22 +337,6 @@ class LTCInterfaceMWEB(LTCInterface):
|
|||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
wallets = self.rpc("listwallets")
|
|
||||||
if "mweb" not in wallets:
|
|
||||||
try:
|
|
||||||
self.rpc("loadwallet", ["mweb"])
|
|
||||||
self._log.debug("Loaded existing MWEB wallet.")
|
|
||||||
except Exception as e:
|
|
||||||
if "does not exist" in str(e) or "Path does not exist" in str(e):
|
|
||||||
self._log.info(f"Creating MWEB wallet for {self.coin_name()}.")
|
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
|
|
||||||
self.rpc(
|
|
||||||
"createwallet",
|
|
||||||
["mweb", False, True, password, False, False, True],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
if password is not None:
|
if password is not None:
|
||||||
# Max timeout value, ~3 years
|
# Max timeout value, ~3 years
|
||||||
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
|
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
|
||||||
@@ -357,8 +345,8 @@ class LTCInterfaceMWEB(LTCInterface):
|
|||||||
self._sc.initialiseWallet(self.interface_type())
|
self._sc.initialiseWallet(self.interface_type())
|
||||||
|
|
||||||
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
|
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
|
||||||
self.rpc("unloadwallet", ["mweb"])
|
self.rpc("unloadwallet", [wallet_name])
|
||||||
self.rpc("loadwallet", ["mweb"])
|
self.rpc("loadwallet", [wallet_name])
|
||||||
if password is not None:
|
if password is not None:
|
||||||
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
|
self.rpc_wallet("walletpassphrase", [password, 100000000], timeout=120)
|
||||||
self.rpc_wallet("keypoolrefill")
|
self.rpc_wallet("keypoolrefill")
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2023 tecnovert
|
# Copyright (c) 2023 tecnovert
|
||||||
@@ -73,8 +72,13 @@ class NAVInterface(BTCInterface):
|
|||||||
def txoType():
|
def txoType():
|
||||||
return CTxOut
|
return CTxOut
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super(NAVInterface, self).__init__(coin_settings, network, swap_client)
|
super().__init__(
|
||||||
|
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
|
||||||
@@ -311,11 +315,16 @@ 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)}]
|
||||||
)
|
)
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
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(
|
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}"
|
||||||
)
|
)
|
||||||
@@ -605,6 +614,8 @@ 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()
|
||||||
@@ -751,7 +762,13 @@ class NAVInterface(BTCInterface):
|
|||||||
|
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def fundTx(self, tx_hex: str, feerate: int, lock_unspents: bool = True):
|
def fundTx(
|
||||||
|
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,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2022 tecnovert
|
# Copyright (c) 2020-2022 tecnovert
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
@@ -81,8 +80,17 @@ class PARTInterface(BTCInterface):
|
|||||||
def txoType():
|
def txoType():
|
||||||
return CTxOutPart
|
return CTxOutPart
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
@staticmethod
|
||||||
super().__init__(coin_settings, network, swap_client)
|
def defaultMaxFeeRate() -> int:
|
||||||
|
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:
|
||||||
@@ -1231,6 +1239,7 @@ 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
|
||||||
|
|
||||||
@@ -1270,9 +1279,17 @@ 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,
|
||||||
"conf_target": self._conf_target,
|
"feeRate": fee_rate,
|
||||||
}
|
}
|
||||||
if sub_fee:
|
if sub_fee:
|
||||||
options["subtractFeeFromOutputs"] = [
|
options["subtractFeeFromOutputs"] = [
|
||||||
@@ -1282,6 +1299,17 @@ 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,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2021 tecnovert
|
# Copyright (c) 2021 tecnovert
|
||||||
@@ -10,8 +9,13 @@ from basicswap.contrib.test_framework.messages import CTxOut
|
|||||||
|
|
||||||
|
|
||||||
class PassthroughBTCInterface(BTCInterface):
|
class PassthroughBTCInterface(BTCInterface):
|
||||||
def __init__(self, coin_settings, network):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super().__init__(coin_settings, network)
|
super().__init__(
|
||||||
|
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"]
|
||||||
|
|||||||
+30
-27
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022 tecnovert
|
# Copyright (c) 2022 tecnovert
|
||||||
@@ -12,7 +11,7 @@ from .btc import BTCInterface
|
|||||||
from basicswap.rpc import make_rpc_func
|
from basicswap.rpc import make_rpc_func
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins
|
||||||
from basicswap.util.address import decodeAddress
|
from basicswap.util.address import decodeAddress
|
||||||
from .contrib.pivx_test_framework.messages import CBlock, ToHex, FromHex, CTransaction
|
from .contrib.pivx_test_framework.messages import CTransaction
|
||||||
from basicswap.contrib.test_framework.script import (
|
from basicswap.contrib.test_framework.script import (
|
||||||
CScript,
|
CScript,
|
||||||
OP_DUP,
|
OP_DUP,
|
||||||
@@ -27,8 +26,13 @@ class PIVXInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.PIVX
|
return Coins.PIVX
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super(PIVXInterface, self).__init__(coin_settings, network, swap_client)
|
super().__init__(
|
||||||
|
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
|
||||||
@@ -74,11 +78,16 @@ 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)}]
|
||||||
)
|
)
|
||||||
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
|
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(
|
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}"
|
||||||
)
|
)
|
||||||
@@ -100,29 +109,13 @@ class PIVXInterface(BTCInterface):
|
|||||||
return decodeAddress(address)[1:]
|
return decodeAddress(address)[1:]
|
||||||
|
|
||||||
def getBlockWithTxns(self, block_hash):
|
def getBlockWithTxns(self, block_hash):
|
||||||
# TODO: Bypass decoderawtransaction and getblockheader
|
block = self.rpc("getblock", [block_hash, True])
|
||||||
block = self.rpc("getblock", [block_hash, False])
|
|
||||||
block_header = self.rpc("getblockheader", [block_hash])
|
|
||||||
decoded_block = CBlock()
|
|
||||||
decoded_block = FromHex(decoded_block, block)
|
|
||||||
|
|
||||||
tx_rv = []
|
tx_rv = []
|
||||||
for tx in decoded_block.vtx:
|
for txid_str in block["tx"]:
|
||||||
tx_dec = self.rpc("decoderawtransaction", [ToHex(tx)])
|
tx_dec = self.rpc("getrawtransaction", [txid_str, True])
|
||||||
tx_rv.append(tx_dec)
|
tx_rv.append(tx_dec)
|
||||||
|
block["tx"] = tx_rv
|
||||||
block_rv = {
|
return block
|
||||||
"hash": block_hash,
|
|
||||||
"previousblockhash": block_header["previousblockhash"],
|
|
||||||
"tx": tx_rv,
|
|
||||||
"confirmations": block_header["confirmations"],
|
|
||||||
"height": block_header["height"],
|
|
||||||
"time": block_header["time"],
|
|
||||||
"version": block_header["version"],
|
|
||||||
"merkleroot": block_header["merkleroot"],
|
|
||||||
}
|
|
||||||
|
|
||||||
return block_rv
|
|
||||||
|
|
||||||
def withdrawCoin(self, value, addr_to, subfee):
|
def withdrawCoin(self, value, addr_to, subfee):
|
||||||
params = [addr_to, value, "", "", subfee]
|
params = [addr_to, value, "", "", subfee]
|
||||||
@@ -150,7 +143,7 @@ class PIVXInterface(BTCInterface):
|
|||||||
)
|
)
|
||||||
return pay_fee
|
return pay_fee
|
||||||
|
|
||||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
def signTxWithKey(self, tx: bytes, key: bytes, prev_amount=None) -> bytes:
|
||||||
key_wif = self.encodeKey(key)
|
key_wif = self.encodeKey(key)
|
||||||
rv = self.rpc(
|
rv = self.rpc(
|
||||||
"signrawtransaction",
|
"signrawtransaction",
|
||||||
@@ -177,3 +170,13 @@ 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"]
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# -*- 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,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 The Basicswap developers
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
@@ -34,7 +33,6 @@ from basicswap.rpc_xmr import make_xmr_rpc_func, make_xmr_rpc2_func
|
|||||||
from basicswap.chainparams import XMR_COIN, Coins
|
from basicswap.chainparams import XMR_COIN, Coins
|
||||||
from basicswap.interface.base import CoinInterface
|
from basicswap.interface.base import CoinInterface
|
||||||
|
|
||||||
|
|
||||||
ed25519_l = 2**252 + 27742317777372353535851937790883648493
|
ed25519_l = 2**252 + 27742317777372353535851937790883648493
|
||||||
|
|
||||||
|
|
||||||
@@ -102,8 +100,13 @@ class XMRInterface(CoinInterface):
|
|||||||
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):
|
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
|
||||||
super().__init__(network)
|
super().__init__(
|
||||||
|
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"]
|
||||||
|
|
||||||
@@ -858,3 +861,6 @@ class XMRInterface(CoinInterface):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._log.error(f"listWalletTransactions failed: {e}")
|
self._log.error(f"listWalletTransactions failed: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def validateFeeRate(self, fee_rate: int) -> None:
|
||||||
|
pass # Fee rate isn't used
|
||||||
|
|||||||
+63
-3
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
# Copyright (c) 2024-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -129,7 +129,6 @@ def js_walletbalances(self, url_split, post_string, is_json) -> bytes:
|
|||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
swap_client.updateWalletsInfo()
|
swap_client.updateWalletsInfo()
|
||||||
wallets = swap_client.getCachedWalletsInfo()
|
wallets = swap_client.getCachedWalletsInfo()
|
||||||
coins_with_balances = []
|
coins_with_balances = []
|
||||||
@@ -193,6 +192,28 @@ def js_walletbalances(self, url_split, post_string, is_json) -> bytes:
|
|||||||
coin_entry["electrum_synced"] = sync_status.get("synced", False)
|
coin_entry["electrum_synced"] = sync_status.get("synced", False)
|
||||||
coin_entry["electrum_height"] = sync_status.get("height", 0)
|
coin_entry["electrum_height"] = sync_status.get("height", 0)
|
||||||
|
|
||||||
|
if k in wallets:
|
||||||
|
w = wallets[k]
|
||||||
|
if "error" not in w and "no_data" not in w:
|
||||||
|
if k == Coins.PART:
|
||||||
|
for field in ("blind_balance", "anon_balance"):
|
||||||
|
if field in w:
|
||||||
|
raw = w[field]
|
||||||
|
if isinstance(raw, float):
|
||||||
|
coin_entry[field] = f"{raw:.8f}".rstrip(
|
||||||
|
"0"
|
||||||
|
).rstrip(".")
|
||||||
|
elif isinstance(raw, int):
|
||||||
|
coin_entry[field] = str(raw)
|
||||||
|
else:
|
||||||
|
coin_entry[field] = raw
|
||||||
|
elif k == Coins.LTC:
|
||||||
|
if "mweb_balance" in w:
|
||||||
|
coin_entry["mweb_balance"] = w["mweb_balance"]
|
||||||
|
elif k == Coins.FIRO:
|
||||||
|
if "spark_balance" in w:
|
||||||
|
coin_entry["spark_balance"] = w["spark_balance"]
|
||||||
|
|
||||||
coins_with_balances.append(coin_entry)
|
coins_with_balances.append(coin_entry)
|
||||||
|
|
||||||
if k == Coins.PART:
|
if k == Coins.PART:
|
||||||
@@ -290,7 +311,7 @@ def js_wallets(self, url_split, post_string, is_json):
|
|||||||
swap_client.checkSystemStatus()
|
swap_client.checkSystemStatus()
|
||||||
if len(url_split) > 3:
|
if len(url_split) > 3:
|
||||||
ticker_str = url_split[3]
|
ticker_str = url_split[3]
|
||||||
coin_type = getCoinIdFromTicker(ticker_str)
|
coin_type = getCoinIdFromTicker(ticker_str, inc_variant=True)
|
||||||
|
|
||||||
if len(url_split) > 4:
|
if len(url_split) > 4:
|
||||||
cmd = url_split[4]
|
cmd = url_split[4]
|
||||||
@@ -332,6 +353,18 @@ def js_wallets(self, url_split, post_string, is_json):
|
|||||||
return bytes(
|
return bytes(
|
||||||
json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), "UTF-8"
|
json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), "UTF-8"
|
||||||
)
|
)
|
||||||
|
elif cmd == "mwebbalance":
|
||||||
|
# mweb outputs left behind when sending LTC -> MWEB
|
||||||
|
if coin_type not in (Coins.LTC,):
|
||||||
|
raise ValueError("Invalid coin for command")
|
||||||
|
ci = swap_client.ci(coin_type)
|
||||||
|
return bytes(json.dumps(ci.format_amount(ci.getMWEBBalance())), "UTF-8")
|
||||||
|
elif cmd == "convertmweb":
|
||||||
|
if coin_type not in (Coins.LTC,):
|
||||||
|
raise ValueError("Invalid coin for command")
|
||||||
|
return bytes(
|
||||||
|
json.dumps(swap_client.ci(coin_type).convertMWEBBalance()), "UTF-8"
|
||||||
|
)
|
||||||
elif cmd == "watchaddress":
|
elif cmd == "watchaddress":
|
||||||
post_data = getFormData(post_string, is_json)
|
post_data = getFormData(post_string, is_json)
|
||||||
address = get_data_entry(post_data, "address")
|
address = get_data_entry(post_data, "address")
|
||||||
@@ -1912,6 +1945,32 @@ def js_electrum_discover(self, url_split, post_string, is_json) -> bytes:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
@@ -1948,6 +2007,7 @@ endpoints = {
|
|||||||
"messageroutes": js_messageroutes,
|
"messageroutes": js_messageroutes,
|
||||||
"electrumdiscover": js_electrum_discover,
|
"electrumdiscover": js_electrum_discover,
|
||||||
"modeswitchinfo": js_modeswitchinfo,
|
"modeswitchinfo": js_modeswitchinfo,
|
||||||
|
"getsubfeebidtx": js_getsubfeebidtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ protobuf ParseFromString would reset the whole object, from_bytes won't.
|
|||||||
|
|
||||||
from basicswap.util.integer import encode_varint, decode_varint
|
from basicswap.util.integer import encode_varint, decode_varint
|
||||||
|
|
||||||
|
|
||||||
NPBW_INT = 0
|
NPBW_INT = 0
|
||||||
NPBW_BYTES = 2
|
NPBW_BYTES = 2
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ from basicswap.contrib.rfc6979 import (
|
|||||||
rfc6979_hmac_sha256_generate,
|
rfc6979_hmac_sha256_generate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
START_TOKEN = 0xABCD
|
START_TOKEN = 0xABCD
|
||||||
MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big")
|
MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big")
|
||||||
|
|
||||||
|
|||||||
@@ -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,9 +15,6 @@ 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])
|
||||||
|
|
||||||
@@ -29,7 +26,7 @@ class ProtocolInterface:
|
|||||||
else ci.get_p2sh_script_pubkey(script)
|
else ci.get_p2sh_script_pubkey(script)
|
||||||
)
|
)
|
||||||
|
|
||||||
def getMockAddrTo(self, ci):
|
def getMockScriptAddr(self, ci):
|
||||||
script = self.getMockScript()
|
script = self.getMockScript()
|
||||||
return (
|
return (
|
||||||
ci.encodeScriptDest(ci.getScriptDest(script))
|
ci.encodeScriptDest(ci.getScriptDest(script))
|
||||||
@@ -38,5 +35,5 @@ class ProtocolInterface:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def findMockVout(self, ci, itx_decoded):
|
def findMockVout(self, ci, itx_decoded):
|
||||||
mock_addr = self.getMockAddrTo(ci)
|
mock_addr = self.getMockScriptAddr(ci)
|
||||||
return find_vout_for_address_from_txobj(itx_decoded, mock_addr)
|
return find_vout_for_address_from_txobj(itx_decoded, mock_addr)
|
||||||
|
|||||||
@@ -138,12 +138,18 @@ 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(self, ci, amount: int, sub_fee: bool) -> bytes:
|
def getFundedInitiateTxTemplate(
|
||||||
addr_to = self.getMockAddrTo(ci)
|
self,
|
||||||
|
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=False
|
addr_to, amount, sub_fee, lock_unspents=lock_unspents, feerate=feerate
|
||||||
)
|
)
|
||||||
|
|
||||||
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-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ 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,
|
||||||
)
|
)
|
||||||
@@ -49,11 +50,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, "Bid not found: {}.".format(bid_id.hex()))
|
ensure(bid, f"Bid not found: {self.log.id(bid_id)}.")
|
||||||
ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex()))
|
ensure(xmr_swap, f"Adaptor-sig swap not found: {self.log.id(bid_id)}.")
|
||||||
offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id)
|
offer, xmr_offer = self.getXmrOfferFromSession(use_cursor, bid.offer_id)
|
||||||
ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex()))
|
ensure(offer, f"Offer not found: {self.log.id(bid.offer_id)}.")
|
||||||
ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()))
|
ensure(xmr_offer, f"Adaptor-sig offer not found: {self.log.id(bid.offer_id)}.")
|
||||||
|
|
||||||
# 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)
|
||||||
@@ -105,7 +106,10 @@ 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 = bid.amount_to
|
amount: int = 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,
|
||||||
@@ -113,7 +117,7 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
|
|||||||
xmr_swap.vkbv,
|
xmr_swap.vkbv,
|
||||||
vkbs,
|
vkbs,
|
||||||
amount,
|
amount,
|
||||||
xmr_offer.b_fee_rate,
|
chain_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,
|
||||||
@@ -203,9 +207,12 @@ 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)
|
||||||
|
|
||||||
@@ -214,20 +221,78 @@ class XmrSwapInterface(ProtocolInterface):
|
|||||||
|
|
||||||
return CScript([2, Kal, Kaf, 2, CScriptOp(OP_CHECKMULTISIG)])
|
return CScript([2, Kal, Kaf, 2, CScriptOp(OP_CHECKMULTISIG)])
|
||||||
|
|
||||||
def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes:
|
def getMockScriptAddr(self, ci):
|
||||||
addr_to = self.getMockAddrTo(ci)
|
script = self.getMockScript()
|
||||||
funded_tx = ci.createRawFundedTransaction(
|
if ci.coin_type() == Coins.PART:
|
||||||
addr_to, amount, sub_fee, lock_unspents=False
|
# 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(
|
||||||
|
addr_to, amount, sub_fee, lock_unspents=lock_unspents, feerate=feerate
|
||||||
|
)
|
||||||
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:
|
||||||
mock_txo_script = self.getMockScriptScriptPubkey(ci)
|
if ci.coin_type() == Coins.BCH:
|
||||||
real_txo_script = ci.getScriptDest(script)
|
mock_script: bytes = self.getMockScript()
|
||||||
|
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)
|
ctx = ci.loadTx(mock_tx, allow_witness=False)
|
||||||
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
|
||||||
@@ -240,3 +305,36 @@ 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()
|
||||||
|
|||||||
@@ -40,6 +40,10 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
confirmMWEBChangeConvert: function() {
|
||||||
|
return confirm('Confirm MWEB change conversion: This will create a tx sending all spendable MWEB outputs in the plain LTC wallet to LTC.');
|
||||||
|
},
|
||||||
|
|
||||||
confirmReseed: function() {
|
confirmReseed: function() {
|
||||||
return confirm('Are you sure you want to reseed the wallet? This will generate new addresses.');
|
return confirm('Are you sure you want to reseed the wallet? This will generate new addresses.');
|
||||||
},
|
},
|
||||||
@@ -179,6 +183,55 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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() {
|
||||||
const modal = document.getElementById('confirmModal');
|
const modal = document.getElementById('confirmModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
@@ -188,7 +241,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
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 {
|
||||||
@@ -282,6 +334,16 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const target = e.target.closest('[data-confirm-mweb-change-convert]');
|
||||||
|
if (target) {
|
||||||
|
if (!this.confirmMWEBChangeConvert()) {
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
const target = e.target.closest('[data-confirm-utxo]');
|
const target = e.target.closest('[data-confirm-utxo]');
|
||||||
if (target) {
|
if (target) {
|
||||||
@@ -332,6 +394,16 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
@@ -398,12 +470,14 @@
|
|||||||
|
|
||||||
window.EventHandlers = EventHandlers;
|
window.EventHandlers = EventHandlers;
|
||||||
window.confirmReseed = EventHandlers.confirmReseed.bind(EventHandlers);
|
window.confirmReseed = EventHandlers.confirmReseed.bind(EventHandlers);
|
||||||
|
window.confirmMWEBChangeConvert = EventHandlers.confirmMWEBChangeConvert.bind(EventHandlers);
|
||||||
window.confirmWithdrawal = EventHandlers.confirmWithdrawal.bind(EventHandlers);
|
window.confirmWithdrawal = EventHandlers.confirmWithdrawal.bind(EventHandlers);
|
||||||
window.confirmUTXOResize = EventHandlers.confirmUTXOResize.bind(EventHandlers);
|
window.confirmUTXOResize = EventHandlers.confirmUTXOResize.bind(EventHandlers);
|
||||||
window.confirmRemoveExpired = EventHandlers.confirmRemoveExpired.bind(EventHandlers);
|
window.confirmRemoveExpired = EventHandlers.confirmRemoveExpired.bind(EventHandlers);
|
||||||
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);
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
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();
|
||||||
@@ -33,7 +35,20 @@
|
|||||||
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'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -71,16 +86,6 @@
|
|||||||
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));
|
||||||
@@ -129,7 +134,6 @@
|
|||||||
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());
|
||||||
@@ -156,6 +160,7 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -171,7 +176,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') {
|
} else if (value_changed === 'sending' || value_changed === 'subfee') {
|
||||||
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);
|
||||||
@@ -187,11 +192,31 @@
|
|||||||
|
|
||||||
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() {
|
||||||
@@ -253,6 +278,11 @@
|
|||||||
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 || '';
|
||||||
@@ -273,7 +303,12 @@
|
|||||||
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) modalSendCurrency.textContent = ` ${tlaTo}`;
|
if (modalSendCurrency) {
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -292,10 +327,6 @@
|
|||||||
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';
|
||||||
|
|||||||
@@ -351,16 +351,19 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
matchingCoins.forEach(coinData => {
|
matchingCoins.forEach(coinData => {
|
||||||
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname]');
|
const balanceElements = document.querySelectorAll('.coinname-value[data-coinname][data-balance-type]');
|
||||||
balanceElements.forEach(element => {
|
balanceElements.forEach(element => {
|
||||||
const elementCoinName = element.getAttribute('data-coinname');
|
const elementCoinName = element.getAttribute('data-coinname');
|
||||||
if (elementCoinName === coinData.name) {
|
if (elementCoinName === coinData.name) {
|
||||||
const currentText = element.textContent;
|
const balanceType = element.getAttribute('data-balance-type');
|
||||||
const ticker = coinData.ticker || coinId.toUpperCase();
|
const value = coinData[balanceType];
|
||||||
const newBalance = `${coinData.balance} ${ticker}`;
|
if (value !== undefined) {
|
||||||
if (currentText !== newBalance) {
|
const ticker = coinData.ticker || coinId.toUpperCase();
|
||||||
element.textContent = newBalance;
|
const newBalance = balanceType === 'est_fee' ? value : `${value} ${ticker}`;
|
||||||
console.log(`Updated balance: ${coinData.name} -> ${newBalance}`);
|
if (element.textContent !== newBalance) {
|
||||||
|
element.textContent = newBalance;
|
||||||
|
console.log(`Updated ${balanceType}: ${coinData.name} -> ${newBalance}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -75,9 +75,28 @@
|
|||||||
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
this.updatePendingBalance('Particl', 'Blind Balance:', coinData.pending, coinData.ticker || 'PART', 'Blind Unconfirmed:', coinData);
|
this.updatePendingBalance('Particl', 'Blind Balance:', coinData.pending, coinData.ticker || 'PART', 'Blind Unconfirmed:', coinData);
|
||||||
}
|
}
|
||||||
|
} else if (coinData.name === 'Litecoin MWEB') {
|
||||||
|
this.updateSpecificBalance('Litecoin', 'MWEB Balance:', coinData.balance, coinData.ticker || 'LTC');
|
||||||
|
this.removePendingBalance('Litecoin', 'MWEB Balance:');
|
||||||
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
|
this.updatePendingBalance('Litecoin', 'MWEB Balance:', coinData.pending, coinData.ticker || 'LTC', 'MWEB Pending:', coinData);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.updateSpecificBalance(coinData.name, 'Balance:', coinData.balance, coinData.ticker || coinData.name);
|
this.updateSpecificBalance(coinData.name, 'Balance:', coinData.balance, coinData.ticker || coinData.name);
|
||||||
|
|
||||||
|
if (coinData.mweb_balance !== undefined) {
|
||||||
|
this.updateSpecificBalance(coinData.name, 'MWEB Balance:', coinData.mweb_balance, coinData.ticker || coinData.name);
|
||||||
|
}
|
||||||
|
if (coinData.spark_balance !== undefined) {
|
||||||
|
this.updateSpecificBalance(coinData.name, 'Spark Balance:', coinData.spark_balance, coinData.ticker || coinData.name);
|
||||||
|
}
|
||||||
|
if (coinData.blind_balance !== undefined) {
|
||||||
|
this.updateSpecificBalance(coinData.name, 'Blind Balance:', coinData.blind_balance, coinData.ticker || coinData.name);
|
||||||
|
}
|
||||||
|
if (coinData.anon_balance !== undefined) {
|
||||||
|
this.updateSpecificBalance(coinData.name, 'Anon Balance:', coinData.anon_balance, coinData.ticker || coinData.name);
|
||||||
|
}
|
||||||
|
|
||||||
if (coinData.name !== 'Particl Anon' && coinData.name !== 'Particl Blind' && coinData.name !== 'Litecoin MWEB') {
|
if (coinData.name !== 'Particl Anon' && coinData.name !== 'Particl Blind' && coinData.name !== 'Litecoin MWEB') {
|
||||||
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
if (coinData.pending && parseFloat(coinData.pending) > 0) {
|
||||||
this.updatePendingDisplay(coinData);
|
this.updatePendingDisplay(coinData);
|
||||||
|
|||||||
@@ -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 == true %} 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 == "high" %} WARNING - HIGH {% elif data.a_fee_warn == "low" %} WARNING - LOW {% elif data.a_fee_warn is defined %} WARNING {% endif %})</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
@@ -419,11 +419,22 @@
|
|||||||
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">
|
||||||
@@ -721,6 +732,8 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -138,7 +138,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||||
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td>
|
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td>
|
||||||
<td class="py-3 px-6 bold">
|
<td class="py-3 px-6 bold">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="balance">{{ w.balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
{% if w.pending %}
|
{% if w.pending %}
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.pending }} {{ w.ticker }} </span>
|
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.pending }} {{ w.ticker }} </span>
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||||
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Blind"> </span>Blind Balance: </td>
|
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Blind"> </span>Blind Balance: </td>
|
||||||
<td class="py-3 px-6 bold">
|
<td class="py-3 px-6 bold">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="blind_balance">{{ w.blind_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
{% if w.blind_unconfirmed %}
|
{% if w.blind_unconfirmed %}
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Unconfirmed: +{{ w.blind_unconfirmed }} {{ w.ticker }}</span>
|
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Unconfirmed: +{{ w.blind_unconfirmed }} {{ w.ticker }}</span>
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||||
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Anon"> </span>Anon Balance: </td>
|
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Anon"> </span>Anon Balance: </td>
|
||||||
<td class="py-3 px-6 bold">
|
<td class="py-3 px-6 bold">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="anon_balance">{{ w.anon_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
{% if w.anon_pending %}
|
{% if w.anon_pending %}
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.anon_pending }} {{ w.ticker }}</span>
|
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.anon_pending }} {{ w.ticker }}</span>
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
{% if is_electrum_mode %}
|
{% if is_electrum_mode %}
|
||||||
<span class="text-gray-400 dark:text-gray-300">Not available in light mode</span>
|
<span class="text-gray-400 dark:text-gray-300">Not available in light mode</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="mweb_balance">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
{% if w.mweb_pending %}
|
{% if w.mweb_pending %}
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }} </span>
|
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }} </span>
|
||||||
@@ -185,11 +185,22 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if w.mweb_in_plain %}
|
||||||
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||||
|
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} MWEB"> </span>MWEB in Plain Balance: </td>
|
||||||
|
<td class="py-3 px-6 bold">
|
||||||
|
<span>{{ w.mweb_in_plain }} {{ w.ticker }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-6 bold">
|
||||||
|
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="convertmweb_{{ w.cid }}" value="Convert" data-confirm-mweb-change-convert> Convert </button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% elif w.cid == '13' %} {# FIRO #}
|
{% elif w.cid == '13' %} {# FIRO #}
|
||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||||
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Spark"> </span>Spark Balance: </td>
|
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Spark"> </span>Spark Balance: </td>
|
||||||
<td class="py-3 px-6 bold">
|
<td class="py-3 px-6 bold">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
{% if w.spark_pending %}
|
{% if w.spark_pending %}
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.spark_pending }} {{ w.ticker }} </span>
|
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.spark_pending }} {{ w.ticker }} </span>
|
||||||
@@ -200,7 +211,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||||
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Spark"> </span>Spark Balance: </td>
|
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Spark"> </span>Spark Balance: </td>
|
||||||
<td class="py-3 px-6 bold">
|
<td class="py-3 px-6 bold">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
{% if w.spark_pending %}
|
{% if w.spark_pending %}
|
||||||
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.spark_pending }} {{ w.ticker }} </span>
|
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.spark_pending }} {{ w.ticker }} </span>
|
||||||
@@ -337,16 +348,7 @@
|
|||||||
<td class="py-3 px-6">{{ w.expected_seed }}</td>
|
<td class="py-3 px-6">{{ w.expected_seed }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if w.account_key %}
|
|
||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
|
||||||
<td class="py-3 px-6 bold">Extended Private Key:</td>
|
|
||||||
<td class="py-3 px-6">
|
|
||||||
<span id="account-key-hidden" class="font-mono text-sm">••••••••••••••••</span>
|
|
||||||
<span id="account-key-value" class="font-mono text-sm hidden break-all">{{ w.account_key }}</span>
|
|
||||||
<button type="button" id="toggle-account-key" onclick="var h=document.getElementById('account-key-hidden'),v=document.getElementById('account-key-value');if(v.classList.contains('hidden')){v.classList.remove('hidden');h.classList.add('hidden');this.textContent='Hide';}else{v.classList.add('hidden');h.classList.remove('hidden');this.textContent='Show';}" class="ml-2 px-2 py-1 text-xs bg-blue-500 hover:bg-blue-600 text-white rounded">Show</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -545,7 +547,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||||
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td>
|
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="balance">{{ w.balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -557,7 +559,7 @@
|
|||||||
{% if is_electrum_mode %}
|
{% if is_electrum_mode %}
|
||||||
<span class="text-gray-400 dark:text-gray-300">Not available in light mode</span>
|
<span class="text-gray-400 dark:text-gray-300">Not available in light mode</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="mweb_balance">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
@@ -567,7 +569,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||||
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Spark Balance: </td>
|
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Spark Balance: </td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -576,7 +578,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||||
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Spark Balance: </td>
|
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Spark Balance: </td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -585,14 +587,14 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||||
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Blind Balance: </td>
|
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Blind Balance: </td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="blind_balance">{{ w.blind_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||||
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Anon Balance: </td>
|
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Anon Balance: </td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="anon_balance">{{ w.anon_balance }} {{ w.ticker }}</span>
|
||||||
(<span class="usd-value"></span>)
|
(<span class="usd-value"></span>)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -767,7 +769,7 @@
|
|||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||||
<td class="py-3 px-6 bold">Fee Estimate:</td>
|
<td class="py-3 px-6 bold">Fee Estimate:</td>
|
||||||
<td class="py-3 px-6">
|
<td class="py-3 px-6">
|
||||||
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.est_fee }}</span>
|
<span class="coinname-value" data-coinname="{{ w.name }}" data-balance-type="est_fee">{{ w.est_fee }}</span>
|
||||||
(<span class="usd-value fee-estimate-usd" data-decimals="8"></span>)
|
(<span class="usd-value fee-estimate-usd" data-decimals="8"></span>)
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<div class="p-6 bg-coolGray-100 dark:bg-gray-600">
|
<div class="p-6 bg-coolGray-100 dark:bg-gray-600">
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Balance:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Balance:</h4>
|
||||||
<div class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }}</div>
|
<div class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="balance">{{ w.balance }} {{ w.ticker }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white ">{{ w.ticker }} USD value:</h4>
|
<h4 class="text-xs font-medium dark:text-white ">{{ w.ticker }} USD value:</h4>
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
{% if w.cid == '1' %} {# PART #}
|
{% if w.cid == '1' %} {# PART #}
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Blind Balance:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Blind Balance:</h4>
|
||||||
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }}</span>
|
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="blind_balance">{{ w.blind_balance }} {{ w.ticker }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Blind USD value:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Blind USD value:</h4>
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Anon Balance:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Anon Balance:</h4>
|
||||||
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }}</span>
|
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="anon_balance">{{ w.anon_balance }} {{ w.ticker }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Anon USD value:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Anon USD value:</h4>
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
{% if w.cid == '3' and w.connection_type != 'electrum' %} {# LTC - MWEB not available in electrum mode #}
|
{% if w.cid == '3' and w.connection_type != 'electrum' %} {# LTC - MWEB not available in electrum mode #}
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">MWEB Balance:</h4>
|
<h4 class="text-xs font-medium dark:text-white">MWEB Balance:</h4>
|
||||||
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="mweb_balance">{{ w.mweb_balance }} {{ w.ticker }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">MWEB USD value:</h4>
|
<h4 class="text-xs font-medium dark:text-white">MWEB USD value:</h4>
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
{% if w.cid == '13' %} {# FIRO #}
|
{% if w.cid == '13' %} {# FIRO #}
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Spark Balance:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Spark Balance:</h4>
|
||||||
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span>
|
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}" data-balance-type="spark_balance">{{ w.spark_balance }} {{ w.ticker }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mb-2 justify-between items-center">
|
<div class="flex mb-2 justify-between items-center">
|
||||||
<h4 class="text-xs font-medium dark:text-white">Spark USD value:</h4>
|
<h4 class="text-xs font-medium dark:text-white">Spark USD value:</h4>
|
||||||
|
|||||||
+58
-17
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2024 tecnovert
|
# Copyright (c) 2022-2026 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,6 +8,7 @@
|
|||||||
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,
|
||||||
@@ -184,14 +185,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.adaptor_swap_only_coins
|
parsed_data["coin_from"] in swap_client.coins_without_segwit
|
||||||
or parsed_data["coin_to"] in swap_client.adaptor_swap_only_coins
|
and parsed_data["coin_to"] in swap_client.coins_without_segwit
|
||||||
):
|
):
|
||||||
parsed_data["swap_type"] = strSwapType(SwapTypes.XMR_SWAP)
|
|
||||||
swap_type = SwapTypes.XMR_SWAP
|
|
||||||
else:
|
|
||||||
parsed_data["swap_type"] = strSwapType(SwapTypes.SELLER_FIRST)
|
parsed_data["swap_type"] = strSwapType(SwapTypes.SELLER_FIRST)
|
||||||
swap_type = SwapTypes.SELLER_FIRST
|
swap_type = SwapTypes.SELLER_FIRST
|
||||||
|
else:
|
||||||
|
parsed_data["swap_type"] = strSwapType(SwapTypes.XMR_SWAP)
|
||||||
|
swap_type = SwapTypes.XMR_SWAP
|
||||||
|
|
||||||
if swap_type == SwapTypes.XMR_SWAP:
|
if swap_type == SwapTypes.XMR_SWAP:
|
||||||
page_data["swap_style"] = "xmr"
|
page_data["swap_style"] = "xmr"
|
||||||
@@ -499,7 +500,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.SELLER_FIRST),
|
"swap_type": strSwapType(SwapTypes.XMR_SWAP),
|
||||||
}
|
}
|
||||||
|
|
||||||
post_data = parse.parse_qs(post_string)
|
post_data = parse.parse_qs(post_string)
|
||||||
@@ -583,7 +584,7 @@ def page_newoffer(self, url_split, post_string):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def page_offer(self, url_split, post_string):
|
def page_offer(self, url_split: List[str], post_string: str) -> bytes:
|
||||||
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
|
||||||
@@ -674,6 +675,11 @@ def page_offer(self, url_split, post_string):
|
|||||||
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,
|
||||||
@@ -768,24 +774,30 @@ def page_offer(self, url_split, post_string):
|
|||||||
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:
|
||||||
int_fee_rate_now, fee_source = ci_leader.get_fee_rate()
|
a_fee_rate_now, fee_source = ci_leader.get_fee_rate()
|
||||||
|
a_fee_rate_now = ci_leader.make_int(a_fee_rate_now)
|
||||||
|
|
||||||
data["xmr_type"] = True
|
chain_a_fee_rate: int = (
|
||||||
data["a_fee_rate"] = ci_leader.format_amount(xmr_offer.a_fee_rate)
|
xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate
|
||||||
data["a_fee_rate_verify"] = ci_leader.format_amount(
|
|
||||||
int_fee_rate_now, conv_int=True
|
|
||||||
)
|
)
|
||||||
|
data["xmr_type"] = True
|
||||||
|
data["a_fee_rate"] = ci_leader.format_amount(chain_a_fee_rate)
|
||||||
|
data["a_fee_rate_verify"] = ci_leader.format_amount(a_fee_rate_now)
|
||||||
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
|
|
||||||
|
|
||||||
from_fee_rate = xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate
|
warning_threshold: float = 1.2
|
||||||
|
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(
|
||||||
from_fee_rate * lock_spend_tx_vsize / 1000, r=1
|
chain_a_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()
|
||||||
@@ -817,6 +829,33 @@ def page_offer(self, url_split, post_string):
|
|||||||
)
|
)
|
||||||
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,
|
||||||
@@ -835,7 +874,9 @@ def page_offer(self, url_split, post_string):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def format_timestamp(timestamp, with_ago=True, is_expired=False):
|
def format_timestamp(
|
||||||
|
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:
|
||||||
|
|||||||
@@ -273,6 +273,9 @@ def page_wallet(self, url_split, post_string):
|
|||||||
swap_client.cacheNewAddressForCoin(coin_id)
|
swap_client.cacheNewAddressForCoin(coin_id)
|
||||||
elif have_data_entry(form_data, "forcerefresh"):
|
elif have_data_entry(form_data, "forcerefresh"):
|
||||||
force_refresh = True
|
force_refresh = True
|
||||||
|
elif have_data_entry(form_data, "convertmweb_" + cid):
|
||||||
|
txid = swap_client.ci(coin_id).convertMWEBBalance()
|
||||||
|
messages.append(f"Converted MWEB change to LTC in tx: {txid}")
|
||||||
elif have_data_entry(form_data, "newmwebaddr_" + cid):
|
elif have_data_entry(form_data, "newmwebaddr_" + cid):
|
||||||
swap_client.cacheNewStealthAddressForCoin(coin_id)
|
swap_client.cacheNewStealthAddressForCoin(coin_id)
|
||||||
elif have_data_entry(form_data, "newsparkaddr_" + cid):
|
elif have_data_entry(form_data, "newsparkaddr_" + cid):
|
||||||
@@ -525,6 +528,10 @@ def page_wallet(self, url_split, post_string):
|
|||||||
// page_data["fee_estimate"]["sum_weight"]
|
// page_data["fee_estimate"]["sum_weight"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if k == Coins.LTC and ci.useBackend() is False:
|
||||||
|
mweb_value: int = ci.getMWEBBalance()
|
||||||
|
if mweb_value > 0:
|
||||||
|
wallet_data["mweb_in_plain"] = ci.format_amount(mweb_value)
|
||||||
if show_utxo_groups:
|
if show_utxo_groups:
|
||||||
utxo_groups = ""
|
utxo_groups = ""
|
||||||
unspent_by_addr = ci.getUnspentsByAddr()
|
unspent_by_addr = ci.getUnspentsByAddr()
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import json
|
|||||||
import time
|
import time
|
||||||
import decimal
|
import decimal
|
||||||
|
|
||||||
|
|
||||||
COIN = 100000000
|
COIN = 100000000
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ from basicswap.contrib.test_framework.messages import (
|
|||||||
uint256_from_str,
|
uint256_from_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
AES_BLOCK_SIZE = 16
|
AES_BLOCK_SIZE = 16
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
import hashlib
|
import json
|
||||||
|
import sqlite3
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
from coincurve import PrivateKey, PublicKey
|
||||||
|
|
||||||
from .chainparams import Coins
|
from .chainparams import Coins
|
||||||
from .contrib.test_framework import segwit_addr
|
from .contrib.test_framework import segwit_addr
|
||||||
@@ -19,7 +21,7 @@ from .db_wallet import (
|
|||||||
WalletTxCache,
|
WalletTxCache,
|
||||||
WalletWatchOnly,
|
WalletWatchOnly,
|
||||||
)
|
)
|
||||||
from .util.crypto import hash160
|
from .util.crypto import hash160, sha256
|
||||||
from .util.extkey import ExtKeyPair
|
from .util.extkey import ExtKeyPair
|
||||||
|
|
||||||
|
|
||||||
@@ -112,13 +114,11 @@ class WalletManager:
|
|||||||
def _deriveAddress(
|
def _deriveAddress(
|
||||||
self, coin_type: Coins, index: int, internal: bool = False
|
self, coin_type: Coins, index: int, internal: bool = False
|
||||||
) -> Tuple[str, str, bytes]:
|
) -> Tuple[str, str, bytes]:
|
||||||
from coincurve import PublicKey
|
|
||||||
|
|
||||||
key = self._deriveKey(coin_type, index, internal)
|
key = self._deriveKey(coin_type, index, internal)
|
||||||
pubkey = PublicKey.from_secret(key).format()
|
pubkey = PublicKey.from_secret(key).format()
|
||||||
pkh = hash160(pubkey)
|
pkh = hash160(pubkey)
|
||||||
address = segwit_addr.encode(self._getHRP(coin_type), 0, pkh)
|
address = segwit_addr.encode(self._getHRP(coin_type), 0, pkh)
|
||||||
scripthash = hashlib.sha256(bytes([0x00, 0x14]) + pkh).digest()[::-1].hex()
|
scripthash = sha256(bytes([0x00, 0x14]) + pkh)[::-1].hex()
|
||||||
return address, scripthash, pubkey
|
return address, scripthash, pubkey
|
||||||
|
|
||||||
def _syncStateIndices(self, coin_type: Coins, cursor) -> None:
|
def _syncStateIndices(self, coin_type: Coins, cursor) -> None:
|
||||||
@@ -275,8 +275,6 @@ class WalletManager:
|
|||||||
def getAddress(
|
def getAddress(
|
||||||
self, coin_type: Coins, index: int, internal: bool = False
|
self, coin_type: Coins, index: int, internal: bool = False
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -395,8 +393,6 @@ class WalletManager:
|
|||||||
include_watch_only: bool = True,
|
include_watch_only: bool = True,
|
||||||
funded_only: bool = False,
|
funded_only: bool = False,
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -426,8 +422,6 @@ class WalletManager:
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def getFundedAddresses(self, coin_type: Coins) -> Dict[str, str]:
|
def getFundedAddresses(self, coin_type: Coins) -> Dict[str, str]:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -452,8 +446,6 @@ class WalletManager:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
def getExistingInternalAddress(self, coin_type: Coins) -> Optional[str]:
|
def getExistingInternalAddress(self, coin_type: Coins) -> Optional[str]:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -517,8 +509,6 @@ class WalletManager:
|
|||||||
self._swap_client.closeDB(cursor, commit=False)
|
self._swap_client.closeDB(cursor, commit=False)
|
||||||
|
|
||||||
def getAddressInfo(self, coin_type: Coins, address: str) -> Optional[dict]:
|
def getAddressInfo(self, coin_type: Coins, address: str) -> Optional[dict]:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -559,8 +549,6 @@ class WalletManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def getCachedTotalBalance(self, coin_type: Coins) -> int:
|
def getCachedTotalBalance(self, coin_type: Coins) -> int:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -700,8 +688,6 @@ class WalletManager:
|
|||||||
if not self.isInitialized(coin_type):
|
if not self.isInitialized(coin_type):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
min_cache_time = now - max_cache_age
|
min_cache_time = now - max_cache_age
|
||||||
|
|
||||||
@@ -744,8 +730,6 @@ class WalletManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def hasCachedBalances(self, coin_type: Coins, max_cache_age: int = 120) -> bool:
|
def hasCachedBalances(self, coin_type: Coins, max_cache_age: int = 120) -> bool:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -762,8 +746,6 @@ class WalletManager:
|
|||||||
def getPrivateKey(self, coin_type: Coins, address: str) -> Optional[bytes]:
|
def getPrivateKey(self, coin_type: Coins, address: str) -> Optional[bytes]:
|
||||||
if not self.isInitialized(coin_type):
|
if not self.isInitialized(coin_type):
|
||||||
return None
|
return None
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -788,8 +770,6 @@ class WalletManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def getSignableAddresses(self, coin_type: Coins) -> Dict[str, str]:
|
def getSignableAddresses(self, coin_type: Coins) -> Dict[str, str]:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -861,10 +841,8 @@ class WalletManager:
|
|||||||
label: str = "",
|
label: str = "",
|
||||||
source: str = "import",
|
source: str = "import",
|
||||||
) -> bool:
|
) -> bool:
|
||||||
from coincurve import PublicKey as CCPublicKey
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pubkey = CCPublicKey.from_secret(private_key).format()
|
pubkey = PublicKey.from_secret(private_key).format()
|
||||||
if (
|
if (
|
||||||
segwit_addr.encode(self._getHRP(coin_type), 0, hash160(pubkey))
|
segwit_addr.encode(self._getHRP(coin_type), 0, hash160(pubkey))
|
||||||
!= address
|
!= address
|
||||||
@@ -1003,7 +981,7 @@ class WalletManager:
|
|||||||
def _b58decode_check(self, s: str) -> bytes:
|
def _b58decode_check(self, s: str) -> bytes:
|
||||||
data = self._b58decode(s)
|
data = self._b58decode(s)
|
||||||
payload, checksum = data[:-4], data[-4:]
|
payload, checksum = data[:-4], data[-4:]
|
||||||
expected = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
|
expected = sha256(sha256(payload))[:4]
|
||||||
if checksum != expected:
|
if checksum != expected:
|
||||||
raise ValueError("Invalid base58 checksum")
|
raise ValueError("Invalid base58 checksum")
|
||||||
return payload
|
return payload
|
||||||
@@ -1025,7 +1003,7 @@ class WalletManager:
|
|||||||
master_key = self._master_keys.get(coin_type)
|
master_key = self._master_keys.get(coin_type)
|
||||||
if master_key is None:
|
if master_key is None:
|
||||||
raise ValueError(f"Wallet not initialized for {coin_type}")
|
raise ValueError(f"Wallet not initialized for {coin_type}")
|
||||||
return hashlib.sha256(master_key + b"_import_key").digest()
|
return sha256(master_key + b"_import_key")
|
||||||
|
|
||||||
def _encryptPrivateKey(self, private_key: bytes, coin_type: Coins) -> bytes:
|
def _encryptPrivateKey(self, private_key: bytes, coin_type: Coins) -> bytes:
|
||||||
return bytes(a ^ b for a, b in zip(private_key, self._getXorKey(coin_type)))
|
return bytes(a ^ b for a, b in zip(private_key, self._getXorKey(coin_type)))
|
||||||
@@ -1037,7 +1015,7 @@ class WalletManager:
|
|||||||
_, data = segwit_addr.decode(self._getHRP(coin_type), address)
|
_, data = segwit_addr.decode(self._getHRP(coin_type), address)
|
||||||
if data is None:
|
if data is None:
|
||||||
return ""
|
return ""
|
||||||
return hashlib.sha256(bytes([0x00, 0x14]) + bytes(data)).digest()[::-1].hex()
|
return sha256(bytes([0x00, 0x14]) + bytes(data))[::-1].hex()
|
||||||
|
|
||||||
def needsMigration(self, coin_type: Coins) -> bool:
|
def needsMigration(self, coin_type: Coins) -> bool:
|
||||||
cursor = self._swap_client.openDB()
|
cursor = self._swap_client.openDB()
|
||||||
@@ -1193,8 +1171,6 @@ class WalletManager:
|
|||||||
self._swap_client.closeDB(cursor, commit=False)
|
self._swap_client.closeDB(cursor, commit=False)
|
||||||
|
|
||||||
def getSeedID(self, coin_type: Coins) -> Optional[str]:
|
def getSeedID(self, coin_type: Coins) -> Optional[str]:
|
||||||
from basicswap.contrib.test_framework.script import hash160
|
|
||||||
|
|
||||||
master_key = self._master_keys.get(coin_type)
|
master_key = self._master_keys.get(coin_type)
|
||||||
if master_key is None:
|
if master_key is None:
|
||||||
return None
|
return None
|
||||||
@@ -1204,16 +1180,12 @@ class WalletManager:
|
|||||||
return hash160(ek.encode_p()).hex()
|
return hash160(ek.encode_p()).hex()
|
||||||
|
|
||||||
def signMessage(self, coin_type: Coins, address: str, message: str) -> bytes:
|
def signMessage(self, coin_type: Coins, address: str, message: str) -> bytes:
|
||||||
from coincurve import PrivateKey
|
|
||||||
|
|
||||||
key = self.getPrivateKey(coin_type, address)
|
key = self.getPrivateKey(coin_type, address)
|
||||||
if key is None:
|
if key is None:
|
||||||
raise ValueError(f"Cannot sign: no key for address {address}")
|
raise ValueError(f"Cannot sign: no key for address {address}")
|
||||||
return PrivateKey(key).sign(message.encode("utf-8"))
|
return PrivateKey(key).sign(message.encode("utf-8"))
|
||||||
|
|
||||||
def signHash(self, coin_type: Coins, address: str, msg_hash: bytes) -> bytes:
|
def signHash(self, coin_type: Coins, address: str, msg_hash: bytes) -> bytes:
|
||||||
from coincurve import PrivateKey
|
|
||||||
|
|
||||||
key = self.getPrivateKey(coin_type, address)
|
key = self.getPrivateKey(coin_type, address)
|
||||||
if key is None:
|
if key is None:
|
||||||
raise ValueError(f"Cannot sign: no key for address {address}")
|
raise ValueError(f"Cannot sign: no key for address {address}")
|
||||||
@@ -1222,8 +1194,6 @@ class WalletManager:
|
|||||||
def getKeyForAddress(
|
def getKeyForAddress(
|
||||||
self, coin_type: Coins, address: str
|
self, coin_type: Coins, address: str
|
||||||
) -> Optional[Tuple[bytes, bytes]]:
|
) -> Optional[Tuple[bytes, bytes]]:
|
||||||
from coincurve import PublicKey
|
|
||||||
|
|
||||||
key = self.getPrivateKey(coin_type, address)
|
key = self.getPrivateKey(coin_type, address)
|
||||||
if key is None:
|
if key is None:
|
||||||
return None
|
return None
|
||||||
@@ -1232,8 +1202,6 @@ class WalletManager:
|
|||||||
def findAddressByScripthash(
|
def findAddressByScripthash(
|
||||||
self, coin_type: Coins, scripthash: str
|
self, coin_type: Coins, scripthash: str
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -1256,8 +1224,6 @@ class WalletManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def getAllScripthashes(self, coin_type: Coins) -> List[str]:
|
def getAllScripthashes(self, coin_type: Coins) -> List[str]:
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
conn = sqlite3.connect(self._swap_client.sqlite_file)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -1895,8 +1861,6 @@ class WalletManager:
|
|||||||
for _ in existing:
|
for _ in existing:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
pending = WalletPendingTx()
|
pending = WalletPendingTx()
|
||||||
pending.coin_type = int(coin_type)
|
pending.coin_type = int(coin_type)
|
||||||
pending.txid = txid
|
pending.txid = txid
|
||||||
@@ -1945,8 +1909,6 @@ class WalletManager:
|
|||||||
) -> List[dict]:
|
) -> List[dict]:
|
||||||
cursor = self._swap_client.openDB()
|
cursor = self._swap_client.openDB()
|
||||||
try:
|
try:
|
||||||
import json
|
|
||||||
|
|
||||||
results = self._swap_client.query(
|
results = self._swap_client.query(
|
||||||
WalletPendingTx,
|
WalletPendingTx,
|
||||||
cursor,
|
cursor,
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# LTC Notes
|
||||||
|
|
||||||
|
## MWEB
|
||||||
|
|
||||||
|
Sending LTC -> MWEB generates MWEB change outputs in the plain LTC wallet that BSX can't use.
|
||||||
|
A temporary convenience function is provided to convert those MWEB outputs back to plain LTC.
|
||||||
|
|
||||||
@@ -140,6 +140,12 @@ Observe progress with
|
|||||||
tail -f /tmp/firo.log
|
tail -f /tmp/firo.log
|
||||||
|
|
||||||
|
|
||||||
|
Alternatively --extracoinopts can be used with --startonlycoin
|
||||||
|
|
||||||
|
docker-compose run --rm swapclient \
|
||||||
|
basicswap-run --datadir=/coindata --startonlycoin=litecoin --extracoinopts="-reindex"
|
||||||
|
|
||||||
|
|
||||||
## Start a subset of the configured coins using docker
|
## Start a subset of the configured coins using docker
|
||||||
|
|
||||||
docker compose run --rm --service-ports swapclient basicswap-run -datadir=/coindata -withcoins=monero
|
docker compose run --rm --service-ports swapclient basicswap-run -datadir=/coindata -withcoins=monero
|
||||||
|
|||||||
@@ -1,3 +1,51 @@
|
|||||||
|
|
||||||
|
0.16.5
|
||||||
|
==============
|
||||||
|
|
||||||
|
- Updated docker base images to Debian Trixie.
|
||||||
|
- By default reject secret hash type offers where the coin pair could use adaptor sig swap.
|
||||||
|
- override with "strict_swap_type" setting.
|
||||||
|
- Verify follower's script chain lock refund tx sig.
|
||||||
|
|
||||||
|
|
||||||
|
0.16.4
|
||||||
|
==============
|
||||||
|
|
||||||
|
- Security: Always require the initiate tx output index and value for secret hash swaps.
|
||||||
|
- Strengthens the 0.16.3 fix: the amount check can no longer be skipped when the output value is unavailable; the swap is now rejected (fails closed) instead of proceeding.
|
||||||
|
- Security: Also double check the participate tx output amount for secret hash swaps.
|
||||||
|
- Raise minimum Python version to 3.11.
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ FROM i_swapclient as install_stage
|
|||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoin --withoutcoins=particl && \
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoin --withoutcoins=particl && \
|
||||||
find /coin_bin -name *.tar.gz -delete
|
find /coin_bin -name *.tar.gz -delete
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
ENV BITCOIN_DATA /data
|
ENV BITCOIN_DATA /data
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ FROM i_swapclient as install_stage
|
|||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoincash --withoutcoins=particl && \
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoincash --withoutcoins=particl && \
|
||||||
find /coin_bin -name *.tar.gz -delete
|
find /coin_bin -name *.tar.gz -delete
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
ENV BITCOINCASH_DATA /data
|
ENV BITCOINCASH_DATA /data
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ FROM i_swapclient as install_stage
|
|||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dash --withoutcoins=particl && \
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dash --withoutcoins=particl && \
|
||||||
find /coin_bin -name *.tar.gz -delete
|
find /coin_bin -name *.tar.gz -delete
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
ENV DASH_DATA /data
|
ENV DASH_DATA /data
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
|
|||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=decred --withoutcoins=particl && \
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=decred --withoutcoins=particl && \
|
||||||
find /coin_bin -name *.tar.gz -delete
|
find /coin_bin -name *.tar.gz -delete
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
ENV DCR_DATA /data
|
ENV DCR_DATA /data
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
|
|||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dogecoin --withoutcoin=particl && \
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dogecoin --withoutcoin=particl && \
|
||||||
find /coin_bin -name *.tar.gz -delete
|
find /coin_bin -name *.tar.gz -delete
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
ENV DOGECOIN_DATA /data
|
ENV DOGECOIN_DATA /data
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
|
|||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=firo --withoutcoins=particl && \
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=firo --withoutcoins=particl && \
|
||||||
find /coin_bin -name *.tar.gz -delete
|
find /coin_bin -name *.tar.gz -delete
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
ENV FIRO_DATA /data
|
ENV FIRO_DATA /data
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
|
|||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=litecoin --withoutcoin=particl && \
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=litecoin --withoutcoin=particl && \
|
||||||
find /coin_bin -name *.tar.gz -delete
|
find /coin_bin -name *.tar.gz -delete
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
ENV LITECOIN_DATA /data
|
ENV LITECOIN_DATA /data
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ FROM i_swapclient as install_stage
|
|||||||
|
|
||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=monero --withoutcoins=particl
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=monero --withoutcoins=particl
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
|
|||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=particl && \
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=particl && \
|
||||||
find /coin_bin -name *.tar.gz -delete
|
find /coin_bin -name *.tar.gz -delete
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
ENV PARTICL_DATA /data
|
ENV PARTICL_DATA /data
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
|
|||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=pivx --withoutcoins=particl && \
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=pivx --withoutcoins=particl && \
|
||||||
find /coin_bin -name *.tar.gz -delete
|
find /coin_bin -name *.tar.gz -delete
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
ENV PIVX_DATA /data
|
ENV PIVX_DATA /data
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
ENV LANG=C.UTF-8 \
|
ENV LANG=C.UTF-8 \
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
DATADIR=/data
|
DATADIR=/data \
|
||||||
|
VIRTUAL_ENV=/opt/venv
|
||||||
|
|
||||||
RUN apt-get update; \
|
RUN apt-get update; \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata wget unzip cmake ninja-build;
|
python3-pip libpython3-dev python3-venv gnupg pkg-config gcc libc-dev gosu tzdata wget unzip cmake ninja-build;
|
||||||
|
|
||||||
|
# Create python venv
|
||||||
|
RUN python3 -m venv $VIRTUAL_ENV
|
||||||
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
ARG BASICSWAP_URL=https://github.com/basicswap/basicswap/archive/master.zip
|
ARG BASICSWAP_URL=https://github.com/basicswap/basicswap/archive/master.zip
|
||||||
ARG BASICSWAP_DIR=basicswap-master
|
ARG BASICSWAP_DIR=basicswap-master
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ FROM i_swapclient as install_stage
|
|||||||
|
|
||||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=wownero --withoutcoins=particl
|
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=wownero --withoutcoins=particl
|
||||||
|
|
||||||
FROM debian:bullseye-slim
|
FROM debian:trixie-slim
|
||||||
|
|
||||||
COPY --from=install_stage /coin_bin .
|
COPY --from=install_stage /coin_bin .
|
||||||
|
|
||||||
|
|||||||
@@ -135,15 +135,15 @@
|
|||||||
(define-public basicswap
|
(define-public basicswap
|
||||||
(package
|
(package
|
||||||
(name "basicswap")
|
(name "basicswap")
|
||||||
(version "0.16.0")
|
(version "0.16.5")
|
||||||
(source (origin
|
(source (origin
|
||||||
(method git-fetch)
|
(method git-fetch)
|
||||||
(uri (git-reference
|
(uri (git-reference
|
||||||
(url "https://github.com/basicswap/basicswap")
|
(url "https://github.com/basicswap/basicswap")
|
||||||
(commit "2c13314bdd29622235c92fd20c237801acb3cb76")))
|
(commit "1aa53e38f96ffa753cc6eeaee1cc9fccbd0ce5dd")))
|
||||||
(sha256
|
(sha256
|
||||||
(base32
|
(base32
|
||||||
"0j0id6db3ljdsfag8krjdmd4rzlz2504yk9lzj0p89lqyygi9ilc"))
|
"0k2r16f0imyzh0x90a2a37m41imnd183vdlf9b8nrx4l884h543y"))
|
||||||
(file-name (git-file-name name version))))
|
(file-name (git-file-name name version))))
|
||||||
(build-system pyproject-build-system)
|
(build-system pyproject-build-system)
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -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.9"
|
requires-python = ">=3.11"
|
||||||
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==25.11.0",
|
"black==26.5.1",
|
||||||
"selenium",
|
"selenium",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -48,3 +48,11 @@ 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,4 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
@@ -21,7 +20,6 @@ from basicswap.util import toBool
|
|||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||||
from basicswap.bin.prepare import downloadPIVXParams
|
from basicswap.bin.prepare import downloadPIVXParams
|
||||||
|
|
||||||
|
|
||||||
TEST_HTTP_HOST = os.getenv(
|
TEST_HTTP_HOST = os.getenv(
|
||||||
"TEST_HTTP_HOST", "127.0.0.1"
|
"TEST_HTTP_HOST", "127.0.0.1"
|
||||||
) # Set to 0.0.0.0 when used in docker
|
) # Set to 0.0.0.0 when used in docker
|
||||||
@@ -164,7 +162,7 @@ def prepare_balance(
|
|||||||
post_json["type_to"] = type_to
|
post_json["type_to"] = type_to
|
||||||
json_rv = read_json_api(
|
json_rv = read_json_api(
|
||||||
port_take_from_node,
|
port_take_from_node,
|
||||||
"wallets/{}/withdraw".format(coin_ticker.lower()),
|
f"wallets/{coin_ticker.lower()}/withdraw",
|
||||||
post_json,
|
post_json,
|
||||||
)
|
)
|
||||||
assert len(json_rv["txid"]) == 64
|
assert len(json_rv["txid"]) == 64
|
||||||
@@ -237,11 +235,17 @@ 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:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# Copyright (c) 2020-2024 tecnovert
|
||||||
@@ -56,7 +55,6 @@ from tests.basicswap.extended.test_doge import (
|
|||||||
import basicswap.config as cfg
|
import basicswap.config as cfg
|
||||||
import basicswap.bin.run as runSystem
|
import basicswap.bin.run as runSystem
|
||||||
|
|
||||||
|
|
||||||
TEST_PATH = os.path.expanduser(os.getenv("TEST_PATH", "~/test_basicswap1"))
|
TEST_PATH = os.path.expanduser(os.getenv("TEST_PATH", "~/test_basicswap1"))
|
||||||
|
|
||||||
PARTICL_PORT_BASE = int(os.getenv("PARTICL_PORT_BASE", BASE_PORT))
|
PARTICL_PORT_BASE = int(os.getenv("PARTICL_PORT_BASE", BASE_PORT))
|
||||||
@@ -532,9 +530,7 @@ def run_prepare(
|
|||||||
for opt in EXTRA_CONFIG_JSON.get("doge{}".format(node_id), []):
|
for opt in EXTRA_CONFIG_JSON.get("doge{}".format(node_id), []):
|
||||||
fp.write(opt + "\n")
|
fp.write(opt + "\n")
|
||||||
|
|
||||||
with open(config_path) as fs:
|
settings["startup_delay"] = 1
|
||||||
settings = json.load(fs)
|
|
||||||
|
|
||||||
settings["min_delay_event"] = 1
|
settings["min_delay_event"] = 1
|
||||||
settings["max_delay_event"] = 4
|
settings["max_delay_event"] = 4
|
||||||
settings["min_delay_event_short"] = 1
|
settings["min_delay_event_short"] = 1
|
||||||
@@ -586,7 +582,7 @@ def prepare_nodes(
|
|||||||
|
|
||||||
class TestBase(unittest.TestCase):
|
class TestBase(unittest.TestCase):
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(TestBase, cls).setUpClass()
|
super().setUpClass()
|
||||||
|
|
||||||
cls.delay_event = threading.Event()
|
cls.delay_event = threading.Event()
|
||||||
signal.signal(
|
signal.signal(
|
||||||
@@ -624,7 +620,7 @@ class TestBase(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def run_process(client_id):
|
def run_process(client_id):
|
||||||
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
|
client_path = os.path.join(TEST_PATH, f"client{client_id}")
|
||||||
testargs = [
|
testargs = [
|
||||||
"basicswap-run",
|
"basicswap-run",
|
||||||
"-datadir=" + client_path,
|
"-datadir=" + client_path,
|
||||||
@@ -646,7 +642,7 @@ class XmrTestBase(TestBase):
|
|||||||
prepare_nodes(3, "monero")
|
prepare_nodes(3, "monero")
|
||||||
|
|
||||||
def start_processes(self):
|
def start_processes(self):
|
||||||
multiprocessing.set_start_method("fork")
|
multiprocessing.set_start_method("spawn")
|
||||||
self.delay_event.clear()
|
self.delay_event.clear()
|
||||||
|
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
@@ -655,7 +651,7 @@ class XmrTestBase(TestBase):
|
|||||||
)
|
)
|
||||||
self.processes[-1].start()
|
self.processes[-1].start()
|
||||||
|
|
||||||
waitForServer(self.delay_event, 12701)
|
waitForServer(self.delay_event, 12701, 60)
|
||||||
|
|
||||||
def waitForMainAddress():
|
def waitForMainAddress():
|
||||||
for i in range(20):
|
for i in range(20):
|
||||||
@@ -667,13 +663,12 @@ class XmrTestBase(TestBase):
|
|||||||
)
|
)
|
||||||
return wallets["XMR"]["main_address"]
|
return wallets["XMR"]["main_address"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Waiting for main address {}".format(str(e)))
|
print(f"Waiting for main address {e}")
|
||||||
self.delay_event.wait(1)
|
self.delay_event.wait(1)
|
||||||
raise ValueError("waitForMainAddress timedout")
|
raise ValueError("waitForMainAddress timedout")
|
||||||
|
|
||||||
xmr_addr1 = waitForMainAddress()
|
xmr_addr1 = waitForMainAddress()
|
||||||
|
num_blocks: int = 100
|
||||||
num_blocks = 100
|
|
||||||
|
|
||||||
xmr_auth = None
|
xmr_auth = None
|
||||||
if os.getenv("XMR_RPC_USER", "") != "":
|
if os.getenv("XMR_RPC_USER", "") != "":
|
||||||
@@ -685,7 +680,7 @@ class XmrTestBase(TestBase):
|
|||||||
]
|
]
|
||||||
< num_blocks
|
< num_blocks
|
||||||
):
|
):
|
||||||
logging.info("Mining {} Monero blocks to {}.".format(num_blocks, xmr_addr1))
|
logging.info(f"Mining {num_blocks} Monero blocks to {xmr_addr1}.")
|
||||||
callrpc_xmr(
|
callrpc_xmr(
|
||||||
XMR_BASE_RPC_PORT + 1,
|
XMR_BASE_RPC_PORT + 1,
|
||||||
"generateblocks",
|
"generateblocks",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ from tests.basicswap.common import (
|
|||||||
)
|
)
|
||||||
from basicswap.bin.run import startDaemon
|
from basicswap.bin.run import startDaemon
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.DEBUG
|
logger.level = logging.DEBUG
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
@@ -176,6 +175,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
|||||||
"datadir": node_dir,
|
"datadir": node_dir,
|
||||||
"bindir": cfg.PARTICL_BINDIR,
|
"bindir": cfg.PARTICL_BINDIR,
|
||||||
"blocks_confirmed": 2, # Faster testing
|
"blocks_confirmed": 2, # Faster testing
|
||||||
|
"wallet_name": "bsx_wallet",
|
||||||
},
|
},
|
||||||
"dash": {
|
"dash": {
|
||||||
"connection_type": "rpc",
|
"connection_type": "rpc",
|
||||||
@@ -185,6 +185,7 @@ 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",
|
||||||
@@ -193,6 +194,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
|||||||
"datadir": btcdatadir,
|
"datadir": btcdatadir,
|
||||||
"bindir": cfg.BITCOIN_BINDIR,
|
"bindir": cfg.BITCOIN_BINDIR,
|
||||||
"use_segwit": True,
|
"use_segwit": True,
|
||||||
|
"wallet_name": "bsx_wallet",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"check_progress_seconds": 2,
|
"check_progress_seconds": 2,
|
||||||
@@ -286,7 +288,7 @@ class Test(unittest.TestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(Test, cls).setUpClass()
|
super().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)
|
||||||
@@ -410,15 +412,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 wbsx_wallet")
|
dashRpc("createwallet bsx_wallet")
|
||||||
|
|
||||||
sc.start()
|
sc.start()
|
||||||
|
|
||||||
waitForRPC(dashRpc, delay_event)
|
waitForRPC(dashRpc, delay_event)
|
||||||
num_blocks = 500
|
num_blocks = 500
|
||||||
logging.info("Mining %d dash blocks", num_blocks)
|
logging.info(f"Mining {num_blocks} dash blocks")
|
||||||
cls.dash_addr = dashRpc("getnewaddress mining_addr")
|
cls.dash_addr = dashRpc("getnewaddress mining_addr")
|
||||||
dashRpc("generatetoaddress {} {}".format(num_blocks, cls.dash_addr))
|
dashRpc(f"generatetoaddress {num_blocks} {cls.dash_addr}")
|
||||||
|
|
||||||
ro = dashRpc("getblockchaininfo")
|
ro = dashRpc("getblockchaininfo")
|
||||||
try:
|
try:
|
||||||
@@ -432,8 +434,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("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr)
|
logging.info(f"Mining {num_blocks} Bitcoin blocks to {cls.btc_addr}")
|
||||||
btcRpc("generatetoaddress {} {}".format(num_blocks, cls.btc_addr))
|
btcRpc(f"generatetoaddress {num_blocks} {cls.btc_addr}")
|
||||||
|
|
||||||
ro = btcRpc("getblockchaininfo")
|
ro = btcRpc("getblockchaininfo")
|
||||||
checkForks(ro)
|
checkForks(ro)
|
||||||
@@ -450,7 +452,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("Waiting for Particl chain height %d", num_blocks)
|
logging.info(f"Waiting for Particl chain height {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)
|
||||||
@@ -474,7 +476,7 @@ class Test(unittest.TestCase):
|
|||||||
cls.swap_clients.clear()
|
cls.swap_clients.clear()
|
||||||
cls.daemons.clear()
|
cls.daemons.clear()
|
||||||
|
|
||||||
super(Test, cls).tearDownClass()
|
super().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")
|
||||||
@@ -684,9 +686,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.001 * COIN,
|
0.01 * COIN,
|
||||||
1.0 * COIN,
|
1.0 * COIN,
|
||||||
0.001 * COIN,
|
0.01 * COIN,
|
||||||
SwapTypes.SELLER_FIRST,
|
SwapTypes.SELLER_FIRST,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -710,7 +712,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("---------- Test {} wallet".format(self.test_coin_from.name))
|
logging.info(f"---------- Test {self.test_coin_from.name} wallet")
|
||||||
|
|
||||||
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-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -75,24 +75,6 @@ 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}")
|
||||||
|
|
||||||
@@ -741,7 +723,7 @@ class Test(BaseTest):
|
|||||||
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")
|
dcr_mining_addr = ci0.rpc_wallet("getnewaddress")
|
||||||
assert dcr_mining_addr in cls.dcr_mining_addrs
|
assert dcr_mining_addr == cls.dcr_mining_addr
|
||||||
cls.dcr_ticket_account = ci0.rpc_wallet(
|
cls.dcr_ticket_account = ci0.rpc_wallet(
|
||||||
"getaccount",
|
"getaccount",
|
||||||
[
|
[
|
||||||
@@ -765,14 +747,14 @@ class Test(BaseTest):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
logging.info("Finalising Decred Test")
|
logging.info("Finalising Decred Test")
|
||||||
super(Test, cls).tearDownClass()
|
super().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(Test, cls).coins_loop()
|
super().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
|
||||||
@@ -878,15 +860,16 @@ 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("---------- Test {}".format(self.test_coin.name))
|
logging.info(f"---------- Test {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")
|
ci = DCRInterface(coin_settings, "mainnet", self.swap_clients[0])
|
||||||
|
|
||||||
k = ci.getNewRandomKey()
|
k = ci.getNewRandomKey()
|
||||||
K = ci.getPubkey(k)
|
K = ci.getPubkey(k)
|
||||||
@@ -914,7 +897,7 @@ class Test(BaseTest):
|
|||||||
assert hash160(masterpubkey_data) == seed_hash
|
assert hash160(masterpubkey_data) == seed_hash
|
||||||
|
|
||||||
def test_001_segwit(self):
|
def test_001_segwit(self):
|
||||||
logging.info("---------- Test {} segwit".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} segwit")
|
||||||
|
|
||||||
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)
|
||||||
@@ -972,7 +955,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("---------- Test {} signature_hash".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} signature_hash")
|
||||||
# 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
|
||||||
@@ -1047,7 +1030,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("---------- Test {} csv".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} csv")
|
||||||
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)
|
||||||
|
|
||||||
@@ -1161,7 +1144,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("---------- Test {} watchonly".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} watchonly")
|
||||||
|
|
||||||
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)
|
||||||
@@ -1261,7 +1244,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("---------- Test {} gettxout".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} gettxout")
|
||||||
|
|
||||||
ci0 = self.swap_clients[0].ci(self.test_coin)
|
ci0 = self.swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
@@ -1373,7 +1356,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("---------- Test {} wallet encryption".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} wallet encryption")
|
||||||
|
|
||||||
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}")
|
||||||
@@ -1412,7 +1395,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("---------- Test {} txn size".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} txn size")
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ 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 = (
|
||||||
@@ -232,7 +233,7 @@ class Test(TestFunctions):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
logging.info("Finalising DOGE Test")
|
logging.info("Finalising DOGE Test")
|
||||||
super(Test, cls).tearDownClass()
|
super().tearDownClass()
|
||||||
|
|
||||||
stopDaemons(cls.doge_daemons)
|
stopDaemons(cls.doge_daemons)
|
||||||
cls.doge_daemons.clear()
|
cls.doge_daemons.clear()
|
||||||
@@ -251,11 +252,12 @@ 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(Test, cls).coins_loop()
|
super().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)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -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.py
|
python tests/basicswap/extended/test_doge_with_prepare.py
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -27,14 +27,11 @@ from tests.basicswap.extended.test_xmr_persistent import (
|
|||||||
BaseTestWithPrepare,
|
BaseTestWithPrepare,
|
||||||
UI_PORT,
|
UI_PORT,
|
||||||
)
|
)
|
||||||
from tests.basicswap.extended.test_scripts import (
|
|
||||||
wait_for_offers,
|
|
||||||
)
|
|
||||||
from tests.basicswap.util import (
|
from tests.basicswap.util import (
|
||||||
read_json_api,
|
read_json_api,
|
||||||
|
wait_for_offers,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.DEBUG
|
logger.level = logging.DEBUG
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
@@ -51,11 +48,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 "state" not in bid:
|
if "bid_state" not in bid:
|
||||||
continue
|
continue
|
||||||
if state is None:
|
if state is None:
|
||||||
return
|
return
|
||||||
if bid["state"].lower() == state.lower():
|
if bid["bid_state"].lower() == state.lower():
|
||||||
return
|
return
|
||||||
raise ValueError("wait_for_bid failed")
|
raise ValueError("wait_for_bid failed")
|
||||||
|
|
||||||
@@ -102,8 +99,9 @@ def prepare_balance(
|
|||||||
|
|
||||||
|
|
||||||
class DOGETest(BaseTestWithPrepare):
|
class DOGETest(BaseTestWithPrepare):
|
||||||
def test_a(self):
|
__test__ = True
|
||||||
|
|
||||||
|
def test_a(self):
|
||||||
amount_from = 10.0
|
amount_from = 10.0
|
||||||
offer_json = {
|
offer_json = {
|
||||||
"coin_from": "btc",
|
"coin_from": "btc",
|
||||||
@@ -115,10 +113,8 @@ 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}
|
||||||
|
|||||||
@@ -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.01\n")
|
fp.write("fallbackfee=0.0002\n")
|
||||||
fp.write("acceptnonstdtxn=0\n")
|
fp.write("acceptnonstdtxn=0\n")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -29,18 +29,15 @@ 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,7 +58,6 @@ from tests.basicswap.common import (
|
|||||||
|
|
||||||
from basicswap.bin.run import startDaemon
|
from basicswap.bin.run import startDaemon
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
NUM_NODES = 3
|
NUM_NODES = 3
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ from tests.basicswap.extended.test_dcr import (
|
|||||||
run_test_itx_refund,
|
run_test_itx_refund,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("BSX Tests")
|
logger = logging.getLogger("BSX Tests")
|
||||||
|
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Copyright (c) 2024-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -11,22 +11,14 @@ 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,
|
||||||
@@ -39,45 +31,36 @@ 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"))
|
||||||
@@ -86,392 +69,173 @@ 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
|
||||||
def prepareOtherDir(datadir, nodeId, conf_file="pivx.conf"):
|
PIVX_BASE_RPC_PORT = 35832
|
||||||
node_dir = os.path.join(datadir, str(nodeId))
|
PIVX_BASE_ZMQ_PORT = 36832
|
||||||
if not os.path.exists(node_dir):
|
|
||||||
os.makedirs(node_dir)
|
|
||||||
filePath = os.path.join(node_dir, conf_file)
|
|
||||||
|
|
||||||
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("fallbackfee=0.01\n")
|
|
||||||
fp.write("acceptnonstdtxn=0\n")
|
|
||||||
|
|
||||||
if conf_file == "pivx.conf":
|
|
||||||
params_dir = os.path.join(datadir, "pivx-params")
|
|
||||||
downloadPIVXParams(params_dir)
|
|
||||||
fp.write(f"paramsdir={params_dir}\n")
|
|
||||||
|
|
||||||
if conf_file == "bitcoin.conf":
|
|
||||||
fp.write("wallet=bsx_wallet\n")
|
|
||||||
|
|
||||||
|
|
||||||
def prepareDir(datadir, nodeId, network_key, network_pubkey):
|
def pivxCli(cmd, node_id=0):
|
||||||
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=bsx_wallet\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
|
|
||||||
fp.write("addnode=127.0.0.1:%d\n" % (BASE_PORT + i))
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def partRpc(cmd, node_id=0):
|
|
||||||
return callrpc_cli(
|
|
||||||
cfg.PARTICL_BINDIR,
|
|
||||||
os.path.join(cfg.TEST_DATADIRS, str(node_id)),
|
|
||||||
"regtest",
|
|
||||||
cmd,
|
|
||||||
cfg.PARTICL_CLI,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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(
|
return callrpc_cli(
|
||||||
PIVX_BINDIR,
|
PIVX_BINDIR,
|
||||||
os.path.join(cfg.TEST_DATADIRS, str(PIVX_NODE)),
|
os.path.join(cfg.TEST_DATADIRS, "pivx_" + str(node_id)),
|
||||||
"regtest",
|
"regtest",
|
||||||
cmd,
|
cmd,
|
||||||
PIVX_CLI,
|
PIVX_CLI,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def prepareDataDir(
|
||||||
global stop_test
|
datadir, node_id, conf_file, dir_prefix, base_p2p_port, base_rpc_port, num_nodes=3
|
||||||
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
|
):
|
||||||
stop_test = True
|
node_dir = os.path.join(datadir, dir_prefix + str(node_id))
|
||||||
delay_event.set()
|
if not os.path.exists(node_dir):
|
||||||
|
os.makedirs(node_dir)
|
||||||
|
cfg_file_path = os.path.join(node_dir, conf_file)
|
||||||
|
if os.path.exists(cfg_file_path):
|
||||||
|
return
|
||||||
|
with open(cfg_file_path, "w+") as fp:
|
||||||
|
fp.write("regtest=1\n")
|
||||||
|
fp.write("[regtest]\n")
|
||||||
|
fp.write("port=" + str(base_p2p_port + node_id) + "\n")
|
||||||
|
fp.write("rpcport=" + str(base_rpc_port + node_id) + "\n")
|
||||||
|
|
||||||
|
salt = generate_salt(16)
|
||||||
def run_coins_loop(cls):
|
fp.write(
|
||||||
while not stop_test:
|
"rpcauth={}:{}${}\n".format(
|
||||||
try:
|
"test" + str(node_id),
|
||||||
pivxRpc("generatetoaddress 1 {}".format(cls.pivx_addr))
|
salt,
|
||||||
btcRpc("generatetoaddress 1 {}".format(cls.btc_addr))
|
password_to_hmac(salt, "test_pass" + str(node_id)),
|
||||||
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
|
|
||||||
def setUpClass(cls):
|
|
||||||
super(Test, cls).setUpClass()
|
|
||||||
|
|
||||||
k = PrivateKey()
|
|
||||||
cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret)
|
|
||||||
cls.network_pubkey = k.public_key.format().hex()
|
|
||||||
|
|
||||||
if os.path.isdir(cfg.TEST_DATADIRS):
|
|
||||||
logging.info("Removing " + cfg.TEST_DATADIRS)
|
|
||||||
for name in os.listdir(cfg.TEST_DATADIRS):
|
|
||||||
if name == "pivx-params":
|
|
||||||
continue
|
|
||||||
fullpath = os.path.join(cfg.TEST_DATADIRS, name)
|
|
||||||
if os.path.isdir(fullpath):
|
|
||||||
shutil.rmtree(fullpath)
|
|
||||||
else:
|
|
||||||
os.remove(fullpath)
|
|
||||||
|
|
||||||
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=bsx_wallet -legacy create",
|
|
||||||
"bitcoin-wallet",
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
callrpc_cli(
|
|
||||||
cfg.BITCOIN_BINDIR,
|
|
||||||
btc_data_dir,
|
|
||||||
"regtest",
|
|
||||||
"-wallet=bsx_wallet 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):
|
fp.write("daemon=0\n")
|
||||||
data_dir = os.path.join(cfg.TEST_DATADIRS, str(i))
|
fp.write("printtoconsole=0\n")
|
||||||
if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, "particl-wallet")):
|
fp.write("server=1\n")
|
||||||
try:
|
fp.write("discover=0\n")
|
||||||
callrpc_cli(
|
fp.write("listenonion=0\n")
|
||||||
cfg.PARTICL_BINDIR,
|
fp.write("bind=127.0.0.1\n")
|
||||||
data_dir,
|
fp.write("findpeers=0\n")
|
||||||
"regtest",
|
fp.write("debug=1\n")
|
||||||
"-wallet=bsx_wallet -legacy create",
|
fp.write("debugexclude=libevent\n")
|
||||||
"particl-wallet",
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
callrpc_cli(
|
|
||||||
cfg.PARTICL_BINDIR,
|
|
||||||
data_dir,
|
|
||||||
"regtest",
|
|
||||||
"-wallet=bsx_wallet 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):
|
fp.write("fallbackfee=0.01\n")
|
||||||
rpc = make_part_cli_rpc_func(i)
|
fp.write("acceptnonstdtxn=0\n")
|
||||||
waitForRPC(rpc, delay_event)
|
|
||||||
if i == 0:
|
params_dir = os.path.join(datadir, "pivx-params")
|
||||||
rpc(
|
downloadPIVXParams(params_dir)
|
||||||
"extkeyimportmaster",
|
fp.write(f"paramsdir={params_dir}\n")
|
||||||
[
|
|
||||||
"abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb"
|
for i in range(0, num_nodes):
|
||||||
],
|
if node_id == i:
|
||||||
)
|
continue
|
||||||
elif i == 1:
|
fp.write("addnode=127.0.0.1:{}\n".format(base_p2p_port + i))
|
||||||
rpc(
|
|
||||||
"extkeyimportmaster",
|
return node_dir
|
||||||
[
|
|
||||||
"pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic",
|
|
||||||
"",
|
class Test(BaseTest):
|
||||||
"true",
|
__test__ = True
|
||||||
],
|
test_coin_from = Coins.PIVX
|
||||||
)
|
pivx_daemons = []
|
||||||
rpc("getnewextaddress", ["lblExtTest"])
|
pivx_addr = None
|
||||||
rpc("rescanblockchain")
|
start_ltc_nodes = False
|
||||||
else:
|
start_xmr_nodes = False
|
||||||
rpc("extkeyimportmaster", [rpc("mnemonic", ["new"])["master"]])
|
|
||||||
rpc(
|
@classmethod
|
||||||
"walletsettings",
|
def prepareExtraDataDir(cls, i):
|
||||||
[
|
extra_opts = []
|
||||||
"stakingoptions",
|
if not cls.restore_instance:
|
||||||
json.dumps(
|
prepareDataDir(
|
||||||
{"stakecombinethreshold": 100, "stakesplitthreshold": 200}
|
cfg.TEST_DATADIRS,
|
||||||
).replace('"', '\\"'),
|
i,
|
||||||
],
|
"pivx.conf",
|
||||||
|
"pivx_",
|
||||||
|
base_p2p_port=PIVX_BASE_PORT,
|
||||||
|
base_rpc_port=PIVX_BASE_RPC_PORT,
|
||||||
)
|
)
|
||||||
rpc("reservebalance", ["false"])
|
cls.pivx_daemons.append(
|
||||||
|
startDaemon(
|
||||||
basicswap_dir = os.path.join(
|
os.path.join(cfg.TEST_DATADIRS, "pivx_" + str(i)),
|
||||||
os.path.join(cfg.TEST_DATADIRS, str(i)), "basicswap"
|
PIVX_BINDIR,
|
||||||
|
PIVXD,
|
||||||
|
opts=extra_opts,
|
||||||
)
|
)
|
||||||
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
|
)
|
||||||
with open(settings_path) as fs:
|
logging.info("Started %s %d", PIVXD, cls.pivx_daemons[-1].handle.pid)
|
||||||
settings = json.load(fs)
|
|
||||||
sc = BasicSwap(
|
waitForRPC(make_rpc_func(i, base_rpc_port=PIVX_BASE_RPC_PORT), delay_event)
|
||||||
basicswap_dir, settings, "regtest", log_name="BasicSwap{}".format(i)
|
|
||||||
|
@classmethod
|
||||||
|
def addPIDInfo(cls, sc, i):
|
||||||
|
sc.setDaemonPID(Coins.PIVX, cls.pivx_daemons[i].handle.pid)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def prepareExtraCoins(cls):
|
||||||
|
|
||||||
|
if cls.restore_instance:
|
||||||
|
void_block_rewards_pubkey = cls.getRandomPubkey()
|
||||||
|
cls.pivx_addr = (
|
||||||
|
cls.swap_clients[0]
|
||||||
|
.ci(Coins.PIVX)
|
||||||
|
.pubkey_to_address(void_block_rewards_pubkey)
|
||||||
)
|
)
|
||||||
cls.swap_clients.append(sc)
|
else:
|
||||||
sc.setDaemonPID(Coins.BTC, cls.daemons[0].handle.pid)
|
num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
|
||||||
sc.setDaemonPID(Coins.PIVX, cls.daemons[1].handle.pid)
|
logging.info(f"Mining {num_blocks} pivx blocks")
|
||||||
sc.setDaemonPID(Coins.PART, cls.daemons[2 + i].handle.pid)
|
cls.pivx_addr = pivxCli("getnewaddress mining_addr")
|
||||||
sc.start()
|
pivxCli(f"generatetoaddress {num_blocks} {cls.pivx_addr}")
|
||||||
|
|
||||||
waitForRPC(pivxRpc, delay_event)
|
ro = pivxCli("getblockchaininfo")
|
||||||
num_blocks = 1352 # CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351.
|
try:
|
||||||
logging.info("Mining %d pivx blocks", num_blocks)
|
assert ro["bip9_softforks"]["csv"]["status"] == "active"
|
||||||
cls.pivx_addr = pivxRpc("getnewaddress mining_addr")
|
except Exception:
|
||||||
pivxRpc("generatetoaddress {} {}".format(num_blocks, cls.pivx_addr))
|
logging.info("pivx: csv is not active")
|
||||||
|
try:
|
||||||
ro = pivxRpc("getblockchaininfo")
|
assert ro["bip9_softforks"]["segwit"]["status"] == "active"
|
||||||
try:
|
except Exception:
|
||||||
assert ro["bip9_softforks"]["csv"]["status"] == "active"
|
logging.info("pivx: segwit is not active")
|
||||||
except Exception:
|
|
||||||
logging.info("pivx: csv is not active")
|
|
||||||
try:
|
|
||||||
assert ro["bip9_softforks"]["segwit"]["status"] == "active"
|
|
||||||
except Exception:
|
|
||||||
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):
|
||||||
global stop_test
|
logging.info("Finalising PIVX Test")
|
||||||
logging.info("Finalising")
|
super().tearDownClass()
|
||||||
stop_test = True
|
|
||||||
cls.update_thread.join()
|
|
||||||
cls.coins_update_thread.join()
|
|
||||||
for c in cls.swap_clients:
|
|
||||||
c.finalise()
|
|
||||||
|
|
||||||
stopDaemons(cls.daemons)
|
stopDaemons(cls.pivx_daemons)
|
||||||
cls.swap_clients.clear()
|
cls.pivx_daemons.clear()
|
||||||
cls.daemons.clear()
|
|
||||||
|
|
||||||
super(Test, cls).tearDownClass()
|
@classmethod
|
||||||
|
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")
|
||||||
@@ -498,7 +262,7 @@ class Test(unittest.TestCase):
|
|||||||
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=60
|
delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80
|
||||||
)
|
)
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
delay_event,
|
delay_event,
|
||||||
@@ -506,7 +270,7 @@ class Test(unittest.TestCase):
|
|||||||
bid_id,
|
bid_id,
|
||||||
BidStates.SWAP_COMPLETED,
|
BidStates.SWAP_COMPLETED,
|
||||||
sent=True,
|
sent=True,
|
||||||
wait_for=60,
|
wait_for=80,
|
||||||
)
|
)
|
||||||
|
|
||||||
js_0 = read_json_api(1800)
|
js_0 = read_json_api(1800)
|
||||||
@@ -546,7 +310,7 @@ class Test(unittest.TestCase):
|
|||||||
wait_for=60,
|
wait_for=60,
|
||||||
)
|
)
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60
|
delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=80
|
||||||
)
|
)
|
||||||
|
|
||||||
js_0 = read_json_api(1800)
|
js_0 = read_json_api(1800)
|
||||||
@@ -578,7 +342,7 @@ class Test(unittest.TestCase):
|
|||||||
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=60
|
delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80
|
||||||
)
|
)
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
delay_event,
|
delay_event,
|
||||||
@@ -715,7 +479,7 @@ class Test(unittest.TestCase):
|
|||||||
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 = pivxRpc('getnewaddress "Withdrawal test"')
|
addr = pivxCli('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
|
||||||
|
|
||||||
@@ -745,22 +509,32 @@ class Test(unittest.TestCase):
|
|||||||
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 = pivxRpc('getnewaddress "generate test"')
|
generate_addr = pivxCli('getnewaddress "generate test"')
|
||||||
pivx_addr = pivxRpc('getnewaddress "Sapling test"')
|
pivx_addr = pivxCli('getnewaddress "Sapling test"')
|
||||||
pivx_sapling_addr = pivxRpc('getnewshieldaddress "shield addr"')
|
pivx_sapling_addr = pivxCli('getnewshieldaddress "shield addr"')
|
||||||
|
|
||||||
pivxRpc(f'sendtoaddress "{pivx_addr}" 6.0')
|
pivxCli(f'sendtoaddress "{pivx_addr}" 6.0')
|
||||||
pivxRpc(f'generatetoaddress 1 "{generate_addr}"')
|
pivxCli(f'generatetoaddress 1 "{generate_addr}"')
|
||||||
|
|
||||||
txid = pivxRpc(
|
txid = pivxCli(
|
||||||
'shieldsendmany "{}" "[{{\\"address\\": \\"{}\\", \\"amount\\": 1}}]"'.format(
|
'shieldsendmany "{}" "[{{\\"address\\": \\"{}\\", \\"amount\\": 1}}]"'.format(
|
||||||
pivx_addr, pivx_sapling_addr
|
pivx_addr, pivx_sapling_addr
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rtx = pivxRpc(f'getrawtransaction "{txid}" true')
|
rtx = pivxCli(f'getrawtransaction "{txid}" true')
|
||||||
assert rtx["version"] == 3
|
assert rtx["version"] == 3
|
||||||
|
|
||||||
block_hash = pivxRpc(f'generatetoaddress 1 "{generate_addr}"')[0]
|
block_hash = None
|
||||||
|
for i in range(15):
|
||||||
|
rtx = 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)
|
||||||
@@ -837,14 +611,17 @@ class Test(unittest.TestCase):
|
|||||||
swap_value = ci_from.make_int(swap_value)
|
swap_value = ci_from.make_int(swap_value)
|
||||||
assert swap_value > ci_from.make_int(9)
|
assert swap_value > ci_from.make_int(9)
|
||||||
|
|
||||||
itx = pi.getFundedInitiateTxTemplate(ci_from, swap_value, True)
|
addr_to = pi.getMockScriptAddr(ci_from)
|
||||||
|
funded_tx = ci_from.createRawFundedTransaction(
|
||||||
|
addr_to, swap_value, True, lock_unspents=True
|
||||||
|
)
|
||||||
|
itx = bytes.fromhex(funded_tx)
|
||||||
itx_decoded = ci_from.describeTx(itx.hex())
|
itx_decoded = ci_from.describeTx(itx.hex())
|
||||||
|
|
||||||
n = pi.findMockVout(ci_from, itx_decoded)
|
n = pi.findMockVout(ci_from, itx_decoded)
|
||||||
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-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -36,19 +36,16 @@ 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):
|
||||||
@@ -132,18 +129,6 @@ 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):
|
||||||
|
|||||||
@@ -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(Test, cls).tearDownClass()
|
super().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(Test, cls).coins_loop()
|
super().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(("test{0}".format(i), "test_pass{0}".format(i)))
|
cls.wow_wallet_auth.append((f"test{i}", f"test_pass{i}"))
|
||||||
|
|
||||||
waitForWOWNode(i, auth=cls.wow_wallet_auth[i])
|
waitForWOWNode(i, auth=cls.wow_wallet_auth[i])
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,15 @@ export XMR_RPC_USER=xmr_user
|
|||||||
export XMR_RPC_PWD=xmr_pwd
|
export XMR_RPC_PWD=xmr_pwd
|
||||||
python tests/basicswap/extended/test_xmr_persistent.py
|
python tests/basicswap/extended/test_xmr_persistent.py
|
||||||
|
|
||||||
|
|
||||||
# Copy coin releases to permanent storage for faster subsequent startups
|
# Copy coin releases to permanent storage for faster subsequent startups
|
||||||
cp -r ${TEST_PATH}/bin/* ~/tmp/basicswap_bin/
|
cp -r ${TEST_PATH}/bin/* ~/tmp/basicswap_bin/
|
||||||
|
|
||||||
|
|
||||||
# Continue existing chains with
|
# Continue existing chains with
|
||||||
export RESET_TEST=false
|
export RESET_TEST=false
|
||||||
|
|
||||||
|
# Set coins started
|
||||||
|
export TEST_COINS_LIST="bitcoin,monero,litecoin"
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
@@ -58,6 +59,8 @@ 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,
|
||||||
@@ -72,9 +75,6 @@ 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))
|
||||||
@@ -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("updateThreadDCR purchaseticket {}".format(e))
|
logging.warning(f"updateThreadDCR purchaseticket {e}")
|
||||||
cls.delay_event.wait(0.5)
|
cls.delay_event.wait(0.5)
|
||||||
try:
|
try:
|
||||||
if num_passed >= 5:
|
if num_passed >= 5:
|
||||||
@@ -259,7 +259,7 @@ def updateThreadDCR(cls):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning("updateThreadDCR generate {}".format(e))
|
logging.warning(f"updateThreadDCR generate {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("updateThreadDCR error", str(e))
|
print("updateThreadDCR error", str(e))
|
||||||
cls.delay_event.wait(random.uniform(cls.dcr_update_min, cls.dcr_update_max))
|
cls.delay_event.wait(random.uniform(cls.dcr_update_min, cls.dcr_update_max))
|
||||||
@@ -271,7 +271,7 @@ def signal_handler(self, sig, frame):
|
|||||||
|
|
||||||
|
|
||||||
def run_process(client_id):
|
def run_process(client_id):
|
||||||
client_path = os.path.join(test_path, "client{}".format(client_id))
|
client_path = os.path.join(test_path, f"client{client_id}")
|
||||||
testargs = [
|
testargs = [
|
||||||
"basicswap-run",
|
"basicswap-run",
|
||||||
"-datadir=" + client_path,
|
"-datadir=" + client_path,
|
||||||
@@ -298,15 +298,24 @@ def start_processes(self):
|
|||||||
for i in range(NUM_NODES):
|
for i in range(NUM_NODES):
|
||||||
waitForServer(self.delay_event, UI_PORT + i)
|
waitForServer(self.delay_event, UI_PORT + i)
|
||||||
|
|
||||||
wallets = read_json_api(UI_PORT + 1, "wallets")
|
|
||||||
|
|
||||||
if "monero" in self.test_coins_list:
|
if "monero" in self.test_coins_list:
|
||||||
|
try:
|
||||||
|
for i in range(8):
|
||||||
|
wallets = read_json_api(UI_PORT + 1, "wallets")
|
||||||
|
if "XMR" in wallets and "main_address" in wallets["XMR"]:
|
||||||
|
break
|
||||||
|
logging.info("Waiting for wallets output")
|
||||||
|
self.delay_event.wait(1.0)
|
||||||
|
self.xmr_addr = wallets["XMR"]["main_address"]
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("{} - wallets json: {}".format(str(e), json.dumps(wallets)))
|
||||||
|
raise
|
||||||
|
|
||||||
xmr_auth = None
|
xmr_auth = None
|
||||||
if os.getenv("XMR_RPC_USER", "") != "":
|
if os.getenv("XMR_RPC_USER", "") != "":
|
||||||
xmr_auth = (os.getenv("XMR_RPC_USER", ""), os.getenv("XMR_RPC_PWD", ""))
|
xmr_auth = (os.getenv("XMR_RPC_USER", ""), os.getenv("XMR_RPC_PWD", ""))
|
||||||
|
|
||||||
self.xmr_addr = wallets["XMR"]["main_address"]
|
num_blocks: int = 100
|
||||||
num_blocks = 100
|
|
||||||
if (
|
if (
|
||||||
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
|
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
|
||||||
"count"
|
"count"
|
||||||
@@ -321,10 +330,11 @@ def start_processes(self):
|
|||||||
auth=xmr_auth,
|
auth=xmr_auth,
|
||||||
)
|
)
|
||||||
logging.info(
|
logging.info(
|
||||||
"XMR blocks: %d",
|
"XMR blocks: {}".format(
|
||||||
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
|
callrpc_xmr(XMR_BASE_RPC_PORT + 1, "get_block_count", auth=xmr_auth)[
|
||||||
"count"
|
"count"
|
||||||
],
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.btc_addr = callbtcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
|
self.btc_addr = callbtcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
|
||||||
@@ -401,9 +411,7 @@ def start_processes(self):
|
|||||||
have_blocks: int = callfirorpc(0, "getblockcount")
|
have_blocks: int = callfirorpc(0, "getblockcount")
|
||||||
if have_blocks < num_blocks:
|
if have_blocks < num_blocks:
|
||||||
logging.info(
|
logging.info(
|
||||||
"Mining %d Firo blocks to %s",
|
f"Mining {num_blocks - have_blocks} Firo blocks to {self.firo_addr}"
|
||||||
num_blocks - have_blocks,
|
|
||||||
self.firo_addr,
|
|
||||||
)
|
)
|
||||||
callfirorpc(
|
callfirorpc(
|
||||||
0,
|
0,
|
||||||
@@ -419,9 +427,7 @@ def start_processes(self):
|
|||||||
have_blocks: int = callbchrpc(0, "getblockcount")
|
have_blocks: int = callbchrpc(0, "getblockcount")
|
||||||
if have_blocks < num_blocks:
|
if have_blocks < num_blocks:
|
||||||
logging.info(
|
logging.info(
|
||||||
"Mining %d Bitcoincash blocks to %s",
|
f"Mining {num_blocks - have_blocks} Bitcoincash blocks to {self.bch_addr}"
|
||||||
num_blocks - have_blocks,
|
|
||||||
self.bch_addr,
|
|
||||||
)
|
)
|
||||||
callbchrpc(
|
callbchrpc(
|
||||||
0,
|
0,
|
||||||
@@ -436,9 +442,7 @@ def start_processes(self):
|
|||||||
have_blocks: int = calldogerpc(0, "getblockcount")
|
have_blocks: int = calldogerpc(0, "getblockcount")
|
||||||
if have_blocks < num_blocks:
|
if have_blocks < num_blocks:
|
||||||
logging.info(
|
logging.info(
|
||||||
"Mining %d Dogecoin blocks to %s",
|
f"Mining {num_blocks - have_blocks} Dogecoin blocks to {self.doge_addr}"
|
||||||
num_blocks - have_blocks,
|
|
||||||
self.doge_addr,
|
|
||||||
)
|
)
|
||||||
calldogerpc(
|
calldogerpc(
|
||||||
0, "generatetoaddress", [num_blocks - have_blocks, self.doge_addr]
|
0, "generatetoaddress", [num_blocks - have_blocks, self.doge_addr]
|
||||||
@@ -556,7 +560,10 @@ class BaseTestWithPrepare(unittest.TestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(BaseTestWithPrepare, cls).setUpClass()
|
cls.addClassCleanup(
|
||||||
|
cls.finalise
|
||||||
|
) # tearDownClass is not run if setUpClass fails
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
random.seed(time.time())
|
random.seed(time.time())
|
||||||
|
|
||||||
@@ -576,7 +583,7 @@ class BaseTestWithPrepare(unittest.TestCase):
|
|||||||
waitForServer(cls.delay_event, UI_PORT + 1)
|
waitForServer(cls.delay_event, UI_PORT + 1)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def finalise(cls):
|
||||||
logging.info("Stopping test")
|
logging.info("Stopping test")
|
||||||
cls.delay_event.set()
|
cls.delay_event.set()
|
||||||
if cls.update_thread:
|
if cls.update_thread:
|
||||||
@@ -597,7 +604,6 @@ class BaseTestWithPrepare(unittest.TestCase):
|
|||||||
|
|
||||||
class Test(BaseTestWithPrepare):
|
class Test(BaseTestWithPrepare):
|
||||||
def test_persistent(self):
|
def test_persistent(self):
|
||||||
|
|
||||||
while not self.delay_event.is_set():
|
while not self.delay_event.is_set():
|
||||||
logging.info("Looping indefinitely, ctrl+c to exit.")
|
logging.info("Looping indefinitely, ctrl+c to exit.")
|
||||||
self.delay_event.wait(10)
|
self.delay_event.wait(10)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020 tecnovert
|
# Copyright (c) 2020 tecnovert
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from util import (
|
|||||||
)
|
)
|
||||||
from tests.basicswap.util import read_json_api
|
from tests.basicswap.util import read_json_api
|
||||||
|
|
||||||
|
|
||||||
base_url = "http://localhost"
|
base_url = "http://localhost"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2023 tecnovert
|
# Copyright (c) 2022-2023 tecnovert
|
||||||
# Copyright (c) 2024-2025 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -16,13 +16,31 @@ from tests.basicswap.util import (
|
|||||||
from util import get_driver
|
from util import get_driver
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.level = logging.INFO
|
logger.level = logging.INFO
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_balance(
|
||||||
|
port: int,
|
||||||
|
coin: str,
|
||||||
|
expect_amount: float,
|
||||||
|
balance_key: str = "balance",
|
||||||
|
iterations: int = 30,
|
||||||
|
delay_time: int = 1,
|
||||||
|
) -> None:
|
||||||
|
logger.info(f"Waiting for balance, port: {port}")
|
||||||
|
for i in range(iterations):
|
||||||
|
rv_js = read_json_api(port, f"wallets/{coin}")
|
||||||
|
if float(rv_js[balance_key]) >= expect_amount:
|
||||||
|
return
|
||||||
|
time.sleep(delay_time)
|
||||||
|
|
||||||
|
logger.warning(f"{port} wallets/{coin} {rv_js}")
|
||||||
|
raise ValueError(f"Expect {balance_key} {expect_amount}")
|
||||||
|
|
||||||
|
|
||||||
def clear_offers(port_list) -> None:
|
def clear_offers(port_list) -> None:
|
||||||
logger.info(f"clear_offers {port_list}")
|
logger.info(f"clear_offers {port_list}")
|
||||||
|
|
||||||
@@ -60,7 +78,11 @@ def test_swap_dir(driver):
|
|||||||
"automation_strat_id": 1,
|
"automation_strat_id": 1,
|
||||||
}
|
}
|
||||||
rv = read_json_api(node_1_port, "offers/new", offer_data)
|
rv = read_json_api(node_1_port, "offers/new", offer_data)
|
||||||
offer_1_id = rv["offer_id"]
|
try:
|
||||||
|
offer_1_id = rv["offer_id"]
|
||||||
|
except Exception as e:
|
||||||
|
logger.info(f"rv: {rv}")
|
||||||
|
raise e
|
||||||
|
|
||||||
offer_data = {
|
offer_data = {
|
||||||
"addr_from": -1,
|
"addr_from": -1,
|
||||||
@@ -72,7 +94,13 @@ def test_swap_dir(driver):
|
|||||||
"automation_strat_id": 1,
|
"automation_strat_id": 1,
|
||||||
}
|
}
|
||||||
rv = read_json_api(node_1_port, "offers/new", offer_data)
|
rv = read_json_api(node_1_port, "offers/new", offer_data)
|
||||||
offer_2_id = rv["offer_id"]
|
try:
|
||||||
|
offer_2_id = rv["offer_id"]
|
||||||
|
except Exception as e:
|
||||||
|
logger.info(f"rv: {rv}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
wait_for_balance(node_2_port, "xmr", 5.0)
|
||||||
|
|
||||||
offer_data = {
|
offer_data = {
|
||||||
"addr_from": -1,
|
"addr_from": -1,
|
||||||
@@ -84,7 +112,11 @@ def test_swap_dir(driver):
|
|||||||
"automation_strat_id": 1,
|
"automation_strat_id": 1,
|
||||||
}
|
}
|
||||||
rv = read_json_api(node_2_port, "offers/new", offer_data)
|
rv = read_json_api(node_2_port, "offers/new", offer_data)
|
||||||
offer_3_id = rv["offer_id"]
|
try:
|
||||||
|
offer_3_id = rv["offer_id"]
|
||||||
|
except Exception as e:
|
||||||
|
logger.info(f"rv: {rv}")
|
||||||
|
raise e
|
||||||
|
|
||||||
# Wait for offers to propagate
|
# Wait for offers to propagate
|
||||||
for i in range(1000):
|
for i in range(1000):
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2023 tecnovert
|
# Copyright (c) 2023 tecnovert
|
||||||
@@ -9,7 +8,6 @@
|
|||||||
import os
|
import os
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
|
||||||
BSX_0_PORT = int(os.getenv("BSX_0_PORT", 12701))
|
BSX_0_PORT = int(os.getenv("BSX_0_PORT", 12701))
|
||||||
BSX_1_PORT = int(os.getenv("BSX_1_PORT", BSX_0_PORT + 1))
|
BSX_1_PORT = int(os.getenv("BSX_1_PORT", BSX_0_PORT + 1))
|
||||||
BSX_2_PORT = int(os.getenv("BSX_1_PORT", BSX_0_PORT + 2))
|
BSX_2_PORT = int(os.getenv("BSX_1_PORT", BSX_0_PORT + 2))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2024 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -167,6 +167,7 @@ class TestBCH(BasicSwapTest):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def prepareExtraCoins(cls):
|
def prepareExtraCoins(cls):
|
||||||
|
super().prepareExtraCoins()
|
||||||
cls.bch_addr = callnoderpc(
|
cls.bch_addr = callnoderpc(
|
||||||
0,
|
0,
|
||||||
"getnewaddress",
|
"getnewaddress",
|
||||||
@@ -197,11 +198,12 @@ class TestBCH(BasicSwapTest):
|
|||||||
"datadir": os.path.join(datadir, "bch_" + str(node_id)),
|
"datadir": os.path.join(datadir, "bch_" + str(node_id)),
|
||||||
"bindir": BITCOINCASH_BINDIR,
|
"bindir": BITCOINCASH_BINDIR,
|
||||||
"use_segwit": False,
|
"use_segwit": False,
|
||||||
|
"wallet_name": "bsx_wallet",
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def coins_loop(cls):
|
def coins_loop(cls):
|
||||||
super(TestBCH, cls).coins_loop()
|
super().coins_loop()
|
||||||
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
||||||
try:
|
try:
|
||||||
if cls.bch_addr is not None:
|
if cls.bch_addr is not None:
|
||||||
@@ -212,7 +214,7 @@ class TestBCH(BasicSwapTest):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
logging.info("Finalising Bitcoincash Test")
|
logging.info("Finalising Bitcoincash Test")
|
||||||
super(TestBCH, cls).tearDownClass()
|
super().tearDownClass()
|
||||||
|
|
||||||
stopDaemons(cls.bch_daemons)
|
stopDaemons(cls.bch_daemons)
|
||||||
cls.bch_daemons.clear()
|
cls.bch_daemons.clear()
|
||||||
@@ -224,19 +226,15 @@ class TestBCH(BasicSwapTest):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def test_001_nested_segwit(self):
|
def test_001_nested_segwit(self):
|
||||||
logging.info(
|
logging.info(f"---------- Test {self.test_coin.name} p2sh nested segwit")
|
||||||
"---------- Test {} p2sh nested segwit".format(self.test_coin.name)
|
|
||||||
)
|
|
||||||
logging.info("Skipped")
|
logging.info("Skipped")
|
||||||
|
|
||||||
def test_002_native_segwit(self):
|
def test_002_native_segwit(self):
|
||||||
logging.info(
|
logging.info(f"---------- Test {self.test_coin.name} p2sh native segwit")
|
||||||
"---------- Test {} p2sh native segwit".format(self.test_coin.name)
|
|
||||||
)
|
|
||||||
logging.info("Skipped")
|
logging.info("Skipped")
|
||||||
|
|
||||||
def test_003_cltv(self):
|
def test_003_cltv(self):
|
||||||
logging.info("---------- Test {} cltv".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} cltv")
|
||||||
|
|
||||||
ci = self.swap_clients[0].ci(self.test_coin)
|
ci = self.swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
@@ -348,7 +346,7 @@ class TestBCH(BasicSwapTest):
|
|||||||
assert len(tx_wallet["blockhash"]) == 64
|
assert len(tx_wallet["blockhash"]) == 64
|
||||||
|
|
||||||
def test_004_csv(self):
|
def test_004_csv(self):
|
||||||
logging.info("---------- Test {} csv".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} csv")
|
||||||
|
|
||||||
ci = self.swap_clients[0].ci(self.test_coin)
|
ci = self.swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
@@ -451,7 +449,7 @@ class TestBCH(BasicSwapTest):
|
|||||||
assert len(tx_wallet["blockhash"]) == 64
|
assert len(tx_wallet["blockhash"]) == 64
|
||||||
|
|
||||||
def test_005_watchonly(self):
|
def test_005_watchonly(self):
|
||||||
logging.info("---------- Test {} watchonly".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} watchonly")
|
||||||
ci = self.swap_clients[0].ci(self.test_coin)
|
ci = self.swap_clients[0].ci(self.test_coin)
|
||||||
ci1 = self.swap_clients[1].ci(self.test_coin)
|
ci1 = self.swap_clients[1].ci(self.test_coin)
|
||||||
|
|
||||||
@@ -482,7 +480,7 @@ class TestBCH(BasicSwapTest):
|
|||||||
super().test_006_getblock_verbosity()
|
super().test_006_getblock_verbosity()
|
||||||
|
|
||||||
def test_007_hdwallet(self):
|
def test_007_hdwallet(self):
|
||||||
logging.info("---------- Test {} hdwallet".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} hdwallet")
|
||||||
|
|
||||||
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
|
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
|
||||||
test_wif = (
|
test_wif = (
|
||||||
@@ -506,10 +504,10 @@ class TestBCH(BasicSwapTest):
|
|||||||
super().test_009_scantxoutset()
|
super().test_009_scantxoutset()
|
||||||
|
|
||||||
def test_010_txn_size(self):
|
def test_010_txn_size(self):
|
||||||
logging.info("---------- Test {} txn_size".format(Coins.BCH))
|
logging.info(f"---------- Test {self.test_coin.name} txn_size")
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
ci = swap_clients[0].ci(Coins.BCH)
|
ci = swap_clients[0].ci(self.test_coin)
|
||||||
pi = swap_clients[0].pi(SwapTypes.XMR_SWAP)
|
pi = swap_clients[0].pi(SwapTypes.XMR_SWAP)
|
||||||
|
|
||||||
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
|
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
|
||||||
@@ -627,7 +625,7 @@ class TestBCH(BasicSwapTest):
|
|||||||
|
|
||||||
def test_011_p2sh(self):
|
def test_011_p2sh(self):
|
||||||
# Not used in bsx for native-segwit coins
|
# Not used in bsx for native-segwit coins
|
||||||
logging.info("---------- Test {} p2sh".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} p2sh")
|
||||||
|
|
||||||
ci = self.swap_clients[0].ci(self.test_coin)
|
ci = self.swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
@@ -717,7 +715,7 @@ class TestBCH(BasicSwapTest):
|
|||||||
|
|
||||||
def test_011_p2sh32(self):
|
def test_011_p2sh32(self):
|
||||||
# Not used in bsx for native-segwit coins
|
# Not used in bsx for native-segwit coins
|
||||||
logging.info("---------- Test {} p2sh32".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} p2sh32")
|
||||||
|
|
||||||
ci = self.swap_clients[0].ci(self.test_coin)
|
ci = self.swap_clients[0].ci(self.test_coin)
|
||||||
|
|
||||||
@@ -806,7 +804,7 @@ class TestBCH(BasicSwapTest):
|
|||||||
assert len(tx_wallet["blockhash"]) == 64
|
assert len(tx_wallet["blockhash"]) == 64
|
||||||
|
|
||||||
def test_012_p2sh_p2wsh(self):
|
def test_012_p2sh_p2wsh(self):
|
||||||
logging.info("---------- Test {} p2sh-p2wsh".format(self.test_coin.name))
|
logging.info(f"---------- Test {self.test_coin.name} p2sh-p2wsh")
|
||||||
logging.info("Skipped")
|
logging.info("Skipped")
|
||||||
|
|
||||||
def test_01_a_full_swap(self):
|
def test_01_a_full_swap(self):
|
||||||
@@ -877,7 +875,7 @@ class TestBCH(BasicSwapTest):
|
|||||||
|
|
||||||
def test_06_preselect_inputs(self):
|
def test_06_preselect_inputs(self):
|
||||||
tla_from = self.test_coin.name
|
tla_from = self.test_coin.name
|
||||||
logging.info("---------- Test {} Preselected inputs".format(tla_from))
|
logging.info(f"---------- Test {tla_from} Preselected inputs")
|
||||||
logging.info("Skipped")
|
logging.info("Skipped")
|
||||||
|
|
||||||
def test_07_expire_stuck_accepted(self):
|
def test_07_expire_stuck_accepted(self):
|
||||||
|
|||||||
+329
-20
@@ -74,6 +74,7 @@ class TestFunctions(BaseTest):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def prepareExtraCoins(cls):
|
def prepareExtraCoins(cls):
|
||||||
|
# Save sent messages so tests can count them
|
||||||
for sc in cls.swap_clients:
|
for sc in cls.swap_clients:
|
||||||
sc._smsg_add_to_outbox = True
|
sc._smsg_add_to_outbox = True
|
||||||
|
|
||||||
@@ -113,7 +114,7 @@ class TestFunctions(BaseTest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def do_test_01_full_swap(self, coin_from: Coins, coin_to: Coins) -> None:
|
def do_test_01_full_swap(self, coin_from: Coins, coin_to: Coins) -> None:
|
||||||
logging.info("---------- Test {} to {}".format(coin_from.name, coin_to.name))
|
logging.info(f"---------- Test {coin_from.name} to {coin_to.name}")
|
||||||
|
|
||||||
# Offerer sends the offer
|
# Offerer sends the offer
|
||||||
# Bidder sends the bid
|
# Bidder sends the bid
|
||||||
@@ -306,9 +307,7 @@ class TestFunctions(BaseTest):
|
|||||||
self, coin_from: Coins, coin_to: Coins, lock_value: int = 32
|
self, coin_from: Coins, coin_to: Coins, lock_value: int = 32
|
||||||
) -> None:
|
) -> None:
|
||||||
logging.info(
|
logging.info(
|
||||||
"---------- Test {} to {} leader recovers coin a lock tx".format(
|
f"---------- Test {coin_from.name} to {coin_to.name} leader recovers coin a lock tx"
|
||||||
coin_from.name, coin_to.name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
id_offerer: int = self.node_a_id
|
id_offerer: int = self.node_a_id
|
||||||
@@ -459,6 +458,12 @@ class TestFunctions(BaseTest):
|
|||||||
if with_mercy
|
if with_mercy
|
||||||
else (BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED_SWIPED)
|
else (BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED_SWIPED)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
chain_a_coin = coin_to if reverse_bid else coin_from
|
||||||
|
if with_mercy is False and chain_a_coin == Coins.BCH:
|
||||||
|
# When using BCH, can't set XMR_SWAP_FAILED_SWIPED as should wait for mercy tx
|
||||||
|
expect_state = expect_state + (BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND,)
|
||||||
|
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
test_delay_event,
|
test_delay_event,
|
||||||
swap_clients[id_leader],
|
swap_clients[id_leader],
|
||||||
@@ -492,12 +497,12 @@ class TestFunctions(BaseTest):
|
|||||||
# Test manually redeeming the no-script lock tx
|
# Test manually redeeming the no-script lock tx
|
||||||
offerer_key = read_json_api(
|
offerer_key = read_json_api(
|
||||||
1800 + id_offerer,
|
1800 + id_offerer,
|
||||||
"bids/{}".format(bid_id.hex()),
|
f"bids/{bid_id.hex()}",
|
||||||
{"chainbkeysplit": True},
|
{"chainbkeysplit": True},
|
||||||
)["splitkey"]
|
)["splitkey"]
|
||||||
data = {"spendchainblocktx": True, "remote_key": offerer_key}
|
data = {"spendchainblocktx": True, "remote_key": offerer_key}
|
||||||
redeemed_txid = read_json_api(
|
redeemed_txid = read_json_api(
|
||||||
1800 + id_bidder, "bids/{}".format(bid_id.hex()), data
|
1800 + id_bidder, f"bids/{bid_id.hex()}", data
|
||||||
)["txid"]
|
)["txid"]
|
||||||
assert len(redeemed_txid) == 64
|
assert len(redeemed_txid) == 64
|
||||||
|
|
||||||
@@ -505,9 +510,7 @@ class TestFunctions(BaseTest):
|
|||||||
self, coin_from, coin_to, lock_value: int = 32
|
self, coin_from, coin_to, lock_value: int = 32
|
||||||
):
|
):
|
||||||
logging.info(
|
logging.info(
|
||||||
"---------- Test {} to {} follower recovers coin b lock tx".format(
|
f"---------- Test {coin_from.name} to {coin_to.name} follower recovers coin b lock tx"
|
||||||
coin_from.name, coin_to.name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
id_offerer: int = self.node_a_id
|
id_offerer: int = self.node_a_id
|
||||||
@@ -920,7 +923,7 @@ class BasicSwapTest(TestFunctions):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(BasicSwapTest, cls).setUpClass()
|
super().setUpClass()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def addCoinSettings(cls, settings, datadir, node_id):
|
def addCoinSettings(cls, settings, datadir, node_id):
|
||||||
@@ -2161,8 +2164,8 @@ class BasicSwapTest(TestFunctions):
|
|||||||
self.do_test_05_self_bid(Coins.XMR, self.test_coin_from)
|
self.do_test_05_self_bid(Coins.XMR, self.test_coin_from)
|
||||||
|
|
||||||
def test_06_preselect_inputs(self):
|
def test_06_preselect_inputs(self):
|
||||||
tla_from = self.test_coin_from.name
|
tla_from: str = self.test_coin_from.name
|
||||||
logging.info("---------- Test {} Preselected inputs".format(tla_from))
|
logging.info(f"---------- Test {tla_from} Preselected inputs")
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
self.prepare_balance(self.test_coin_from, 100.0, 1802, 1800)
|
self.prepare_balance(self.test_coin_from, 100.0, 1802, 1800)
|
||||||
@@ -2262,12 +2265,168 @@ class BasicSwapTest(TestFunctions):
|
|||||||
assert txin["txid"] == txin_after["txid"]
|
assert txin["txid"] == txin_after["txid"]
|
||||||
assert txin["vout"] == txin_after["vout"]
|
assert txin["vout"] == txin_after["vout"]
|
||||||
|
|
||||||
|
def test_06_b_preselect_bid_inputs(self):
|
||||||
|
coin_from, coin_to = (Coins.PART, self.test_coin_from)
|
||||||
|
logging.info(
|
||||||
|
f"---------- Test {coin_from.name} to {coin_to.name} Preselected bid inputs"
|
||||||
|
)
|
||||||
|
|
||||||
|
id_offerer, id_bidder = (1, 0)
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||||
|
ci_to = swap_clients[id_bidder].ci(coin_to)
|
||||||
|
|
||||||
|
amt_swap: int = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||||
|
min_swap: int = ci_from.make_int(0.0001)
|
||||||
|
rate_swap: int = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||||
|
|
||||||
|
extra_options = {
|
||||||
|
"amount_negotiable": True,
|
||||||
|
"automation_id": 1,
|
||||||
|
}
|
||||||
|
offer_id = swap_clients[id_offerer].postOffer(
|
||||||
|
coin_from,
|
||||||
|
coin_to,
|
||||||
|
amt_swap,
|
||||||
|
rate_swap,
|
||||||
|
min_swap,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
extra_options=extra_options,
|
||||||
|
)
|
||||||
|
|
||||||
|
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
|
||||||
|
offer = swap_clients[id_bidder].getOffer(offer_id)
|
||||||
|
|
||||||
|
amount_to = (offer.amount_from * offer.rate) // ci_from.COIN()
|
||||||
|
prefunded_tx_data = swap_clients[id_bidder].createSubfeeBidTx(
|
||||||
|
offer_id, amount_to, offer.rate
|
||||||
|
)
|
||||||
|
ptx_decoded = ci_to.describeTx(prefunded_tx_data["bid_tx"].hex())
|
||||||
|
ci_to.rpc_wallet("lockunspent", [False, ptx_decoded["vin"]])
|
||||||
|
|
||||||
|
bid_tx = prefunded_tx_data["bid_tx"]
|
||||||
|
amount_from = prefunded_tx_data["amount_from"]
|
||||||
|
|
||||||
|
extra_options = {"prefunded_tx": bid_tx}
|
||||||
|
bid_id = swap_clients[id_bidder].postBid(
|
||||||
|
offer_id, amount_from, extra_options=extra_options
|
||||||
|
)
|
||||||
|
|
||||||
|
wait_for_bid(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_offerer],
|
||||||
|
bid_id,
|
||||||
|
BidStates.SWAP_COMPLETED,
|
||||||
|
wait_for=120,
|
||||||
|
)
|
||||||
|
wait_for_bid(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_bidder],
|
||||||
|
bid_id,
|
||||||
|
BidStates.SWAP_COMPLETED,
|
||||||
|
sent=True,
|
||||||
|
wait_for=120,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify expected inputs were used
|
||||||
|
bid, _, _, _, _ = swap_clients[id_bidder].getXmrBidAndOffer(bid_id)
|
||||||
|
assert bid.xmr_b_lock_tx
|
||||||
|
wtx = ci_to.rpc_wallet(
|
||||||
|
"gettransaction",
|
||||||
|
[
|
||||||
|
bid.xmr_b_lock_tx.txid.hex(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
ptx_after = ci_to.describeTx(wtx["hex"])
|
||||||
|
assert len(ptx_after["vin"]) == len(ptx_decoded["vin"])
|
||||||
|
for i, txin in enumerate(ptx_decoded["vin"]):
|
||||||
|
txin_after = ptx_after["vin"][i]
|
||||||
|
assert txin["txid"] == txin_after["txid"]
|
||||||
|
assert txin["vout"] == txin_after["vout"]
|
||||||
|
|
||||||
|
def test_06_c_preselect_reverse_bid_inputs(self):
|
||||||
|
coin_from, coin_to = (Coins.XMR, self.test_coin_from)
|
||||||
|
logging.info(
|
||||||
|
f"---------- Test {coin_from.name} to {coin_to.name} Preselected reverse bid inputs"
|
||||||
|
)
|
||||||
|
|
||||||
|
id_offerer, id_bidder = (1, 0)
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||||
|
ci_to = swap_clients[id_bidder].ci(coin_to)
|
||||||
|
|
||||||
|
amt_swap: int = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||||
|
min_swap: int = ci_from.make_int(0.0001)
|
||||||
|
rate_swap: int = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||||
|
|
||||||
|
extra_options = {
|
||||||
|
"amount_negotiable": True,
|
||||||
|
"automation_id": 1,
|
||||||
|
}
|
||||||
|
offer_id = swap_clients[id_offerer].postOffer(
|
||||||
|
coin_from,
|
||||||
|
coin_to,
|
||||||
|
amt_swap,
|
||||||
|
rate_swap,
|
||||||
|
min_swap,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
extra_options=extra_options,
|
||||||
|
)
|
||||||
|
|
||||||
|
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
|
||||||
|
offer = swap_clients[id_bidder].getOffer(offer_id)
|
||||||
|
|
||||||
|
amount_to = (offer.amount_from * offer.rate) // ci_from.COIN()
|
||||||
|
prefunded_tx_data = swap_clients[id_bidder].createSubfeeBidTx(
|
||||||
|
offer_id, amount_to, offer.rate
|
||||||
|
)
|
||||||
|
ptx_decoded = ci_to.describeTx(prefunded_tx_data["bid_tx"].hex())
|
||||||
|
ci_to.rpc_wallet("lockunspent", [False, ptx_decoded["vin"]])
|
||||||
|
|
||||||
|
bid_tx = prefunded_tx_data["bid_tx"]
|
||||||
|
amount_from = prefunded_tx_data["amount_from"]
|
||||||
|
|
||||||
|
extra_options = {"prefunded_tx": bid_tx}
|
||||||
|
bid_id = swap_clients[id_bidder].postBid(
|
||||||
|
offer_id, amount_from, extra_options=extra_options
|
||||||
|
)
|
||||||
|
|
||||||
|
wait_for_bid(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_offerer],
|
||||||
|
bid_id,
|
||||||
|
BidStates.SWAP_COMPLETED,
|
||||||
|
wait_for=180,
|
||||||
|
)
|
||||||
|
wait_for_bid(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_bidder],
|
||||||
|
bid_id,
|
||||||
|
BidStates.SWAP_COMPLETED,
|
||||||
|
sent=True,
|
||||||
|
wait_for=120,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify expected inputs were used
|
||||||
|
bid, _, _, _, _ = swap_clients[id_bidder].getXmrBidAndOffer(bid_id)
|
||||||
|
assert bid.xmr_a_lock_tx
|
||||||
|
wtx = ci_to.rpc_wallet(
|
||||||
|
"gettransaction",
|
||||||
|
[
|
||||||
|
bid.xmr_a_lock_tx.txid.hex(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
ptx_after = ci_to.describeTx(wtx["hex"])
|
||||||
|
assert len(ptx_after["vin"]) == len(ptx_decoded["vin"])
|
||||||
|
for i, txin in enumerate(ptx_decoded["vin"]):
|
||||||
|
txin_after = ptx_after["vin"][i]
|
||||||
|
assert txin["txid"] == txin_after["txid"]
|
||||||
|
assert txin["vout"] == txin_after["vout"]
|
||||||
|
|
||||||
def test_07_expire_stuck_accepted(self):
|
def test_07_expire_stuck_accepted(self):
|
||||||
coin_from, coin_to = (self.test_coin_from, Coins.XMR)
|
coin_from, coin_to = (self.test_coin_from, Coins.XMR)
|
||||||
logging.info(
|
logging.info(
|
||||||
"---------- Test {} to {} expires bid stuck on accepted".format(
|
f"---------- Test {coin_from.name} to {coin_to.name} expires bid stuck on accepted"
|
||||||
coin_from.name, coin_to.name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
@@ -2324,10 +2483,160 @@ class BasicSwapTest(TestFunctions):
|
|||||||
def test_09_expire_accepted_rev(self):
|
def test_09_expire_accepted_rev(self):
|
||||||
self.do_test_09_expire_accepted(Coins.XMR, self.test_coin_from)
|
self.do_test_09_expire_accepted(Coins.XMR, self.test_coin_from)
|
||||||
|
|
||||||
def test_10_presigned_txns(self):
|
def test_11_fee_validation(self):
|
||||||
raise RuntimeError(
|
coin_from, coin_to = (self.test_coin_from, Coins.XMR)
|
||||||
"TODO"
|
logging.info(
|
||||||
) # Build without xmr first for quicker test iterations
|
f"---------- Test {coin_from.name} to {coin_to.name} expires bid stuck on accepted"
|
||||||
|
)
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
ci_from = swap_clients[0].ci(coin_from)
|
||||||
|
ci_to = swap_clients[0].ci(coin_to)
|
||||||
|
|
||||||
|
ci1_from = swap_clients[1].ci(coin_from)
|
||||||
|
ci1_to = swap_clients[1].ci(coin_to)
|
||||||
|
|
||||||
|
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||||
|
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||||
|
|
||||||
|
offer_id = swap_clients[0].postOffer(
|
||||||
|
coin_from,
|
||||||
|
coin_to,
|
||||||
|
amt_swap,
|
||||||
|
rate_swap,
|
||||||
|
amt_swap,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
)
|
||||||
|
|
||||||
|
amt_swap_reverse = ci1_to.make_int(random.uniform(0.1, 2.0), r=1)
|
||||||
|
offer_reverse_id = swap_clients[1].postOffer(
|
||||||
|
coin_to,
|
||||||
|
coin_from,
|
||||||
|
amt_swap_reverse,
|
||||||
|
rate_swap,
|
||||||
|
amt_swap_reverse,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
)
|
||||||
|
|
||||||
|
ci_from_settings = swap_clients[0].getChainClientSettings(coin_from)
|
||||||
|
old_override_feerate = ci_from_settings.get("override_feerate", None)
|
||||||
|
ci1_from_settings = swap_clients[1].getChainClientSettings(coin_from)
|
||||||
|
old_override_feerate1 = ci1_from_settings.get("override_feerate", None)
|
||||||
|
|
||||||
|
networkinfo = ci_from.rpc("getnetworkinfo")
|
||||||
|
assert ci_from.make_int(networkinfo["relayfee"]) == ci_from.make_int(
|
||||||
|
ci_from.get_fee_rate()[0]
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
# Set override_feerate to increase feerate from get_fee_rate()
|
||||||
|
ci_from_settings["override_feerate"] = ci_from.format_amount(120)
|
||||||
|
ci1_from_settings["override_feerate"] = ci1_from.format_amount(120)
|
||||||
|
try:
|
||||||
|
swap_clients[0].postXmrBid(offer_id, amt_swap)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Fee rate too low, 100 < 120, override_feerate" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
# Test reverse bid, low fee
|
||||||
|
try:
|
||||||
|
swap_clients[1].postXmrBid(offer_reverse_id, amt_swap_reverse)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Fee rate too low, 100 < 120, override_feerate" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
# Clear override_feerate (get_fee_rate()), set low_feerate (validateFeeRate())
|
||||||
|
ci_from_settings["override_feerate"] = None
|
||||||
|
ci1_from_settings["override_feerate"] = None
|
||||||
|
ci_from._low_feerate = 120
|
||||||
|
ci1_from._low_feerate = 120
|
||||||
|
try:
|
||||||
|
swap_clients[0].postOffer(
|
||||||
|
coin_from,
|
||||||
|
coin_to,
|
||||||
|
amt_swap,
|
||||||
|
rate_swap,
|
||||||
|
amt_swap,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Fee rate too low, 100 < 120, set_value" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
# Test reverse offer, low fee
|
||||||
|
try:
|
||||||
|
swap_clients[1].postOffer(
|
||||||
|
coin_to,
|
||||||
|
coin_from,
|
||||||
|
amt_swap_reverse,
|
||||||
|
rate_swap,
|
||||||
|
amt_swap_reverse,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Fee rate too low, 100 < 120, set_value" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
ci_from._low_feerate = 0
|
||||||
|
ci1_from._low_feerate = 0
|
||||||
|
ci_from._high_estimated_feerate_multiplier = (
|
||||||
|
0 # Disable high fee from estimate
|
||||||
|
)
|
||||||
|
ci_from._high_feerate = (
|
||||||
|
80 # ci_from_settings["high_feerate"] = ci_from.format_amount(80)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
swap_clients[0].postXmrBid(offer_id, amt_swap)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Fee rate too high, 100 > 80, set_value" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
# Test reverse bid, high fee
|
||||||
|
ci1_from._high_estimated_feerate_multiplier = 0
|
||||||
|
ci1_from._high_feerate = 80
|
||||||
|
try:
|
||||||
|
swap_clients[1].postXmrBid(offer_reverse_id, amt_swap_reverse)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Fee rate too high, 100 > 80, set_value" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
try:
|
||||||
|
swap_clients[0].postOffer(
|
||||||
|
coin_from,
|
||||||
|
coin_to,
|
||||||
|
amt_swap,
|
||||||
|
rate_swap,
|
||||||
|
amt_swap,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Fee rate too high, 100 > 80, set_value" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
# Test reverse offer, high fee
|
||||||
|
try:
|
||||||
|
swap_clients[1].postOffer(
|
||||||
|
coin_to,
|
||||||
|
coin_from,
|
||||||
|
amt_swap_reverse,
|
||||||
|
rate_swap,
|
||||||
|
amt_swap_reverse,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Fee rate too high, 100 > 80, set_value" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
finally:
|
||||||
|
ci_from_settings["override_feerate"] = old_override_feerate
|
||||||
|
ci1_from_settings["override_feerate"] = old_override_feerate1
|
||||||
|
|
||||||
|
|
||||||
class TestBTC(BasicSwapTest):
|
class TestBTC(BasicSwapTest):
|
||||||
@@ -2511,7 +2820,7 @@ class TestBTC_PARTB(TestFunctions):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super(TestBTC_PARTB, cls).setUpClass()
|
super().setUpClass()
|
||||||
if False:
|
if False:
|
||||||
for client in cls.swap_clients:
|
for client in cls.swap_clients:
|
||||||
client.log.safe_logs = True
|
client.log.safe_logs = True
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ def modify_config(test_path, i):
|
|||||||
with open(config_path, "w") as fp:
|
with open(config_path, "w") as fp:
|
||||||
json.dump(settings, fp, indent=4)
|
json.dump(settings, fp, indent=4)
|
||||||
|
|
||||||
|
btc_config_path = os.path.join(test_path, f"client{i}", "bitcoin", "bitcoin.conf")
|
||||||
|
with open(btc_config_path, "a") as fp:
|
||||||
|
fp.write("minrelaytxfee=0.00001\n")
|
||||||
|
|
||||||
|
|
||||||
def wait_for_bid_state(
|
def wait_for_bid_state(
|
||||||
delay_event, node_port: int, bid_id: str, state=None, wait_for: int = 30
|
delay_event, node_port: int, bid_id: str, state=None, wait_for: int = 30
|
||||||
@@ -641,7 +645,7 @@ class Test(TestFunctions):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.addElectrumxDaemon("bitcoin", 32793, 50001)
|
cls.addElectrumxDaemon("bitcoin", 32793, 50001)
|
||||||
super(Test, cls).setUpClass()
|
super().setUpClass()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def modifyConfig(cls, test_path, i):
|
def modifyConfig(cls, test_path, i):
|
||||||
@@ -754,14 +758,6 @@ class Test(TestFunctions):
|
|||||||
self.delay_event,
|
self.delay_event,
|
||||||
self.test_coin_b,
|
self.test_coin_b,
|
||||||
100,
|
100,
|
||||||
self.port_node_1,
|
|
||||||
self.port_node_0,
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
prepare_balance(
|
|
||||||
self.delay_event,
|
|
||||||
self.test_coin_xmr,
|
|
||||||
100,
|
|
||||||
self.port_node_0,
|
self.port_node_0,
|
||||||
self.port_node_1,
|
self.port_node_1,
|
||||||
True,
|
True,
|
||||||
@@ -788,7 +784,7 @@ class Test(TestFunctions):
|
|||||||
True,
|
True,
|
||||||
)
|
)
|
||||||
self.do_test_03_follower_recover_a_lock_tx(
|
self.do_test_03_follower_recover_a_lock_tx(
|
||||||
self.test_coin_b, self.test_coin_xmr, self.port_node_1, self.port_node_0
|
self.test_coin_b, self.test_coin_xmr, self.port_node_0, self.port_node_1
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_03_b_follower_recover_a_lock_tx_reverse(self):
|
def test_03_b_follower_recover_a_lock_tx_reverse(self):
|
||||||
|
|||||||
+102
-11
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2021-2024 tecnovert
|
# Copyright (c) 2021-2024 tecnovert
|
||||||
# Copyright (c) 2024 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -48,15 +48,11 @@ class TestLTC(BasicSwapTest):
|
|||||||
assert deploymentinfo["softforks"][feature_name]["active"] is True
|
assert deploymentinfo["softforks"][feature_name]["active"] is True
|
||||||
|
|
||||||
def test_001_nested_segwit(self):
|
def test_001_nested_segwit(self):
|
||||||
logging.info(
|
logging.info(f"---------- Test {self.test_coin_from.name} p2sh nested segwit")
|
||||||
"---------- Test {} p2sh nested segwit".format(self.test_coin_from.name)
|
|
||||||
)
|
|
||||||
logging.info("Skipped")
|
logging.info("Skipped")
|
||||||
|
|
||||||
def test_002_native_segwit(self):
|
def test_002_native_segwit(self):
|
||||||
logging.info(
|
logging.info(f"---------- Test {self.test_coin_from.name} p2sh native segwit")
|
||||||
"---------- Test {} p2sh native segwit".format(self.test_coin_from.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
ci = self.swap_clients[0].ci(self.test_coin_from)
|
ci = self.swap_clients[0].ci(self.test_coin_from)
|
||||||
addr_segwit = ci.rpc_wallet("getnewaddress", ["segwit test", "bech32"])
|
addr_segwit = ci.rpc_wallet("getnewaddress", ["segwit test", "bech32"])
|
||||||
@@ -120,7 +116,7 @@ class TestLTC(BasicSwapTest):
|
|||||||
assert tx_funded_decoded["txid"] == tx_signed_decoded["txid"]
|
assert tx_funded_decoded["txid"] == tx_signed_decoded["txid"]
|
||||||
|
|
||||||
def test_007_hdwallet(self):
|
def test_007_hdwallet(self):
|
||||||
logging.info("---------- Test {} hdwallet".format(self.test_coin_from.name))
|
logging.info(f"---------- Test {self.test_coin_from.name} hdwallet")
|
||||||
|
|
||||||
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
|
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
|
||||||
test_wif = (
|
test_wif = (
|
||||||
@@ -136,7 +132,7 @@ class TestLTC(BasicSwapTest):
|
|||||||
assert addr == "rltc1qps7hnjd866e9ynxadgseprkc2l56m00djr82la"
|
assert addr == "rltc1qps7hnjd866e9ynxadgseprkc2l56m00djr82la"
|
||||||
|
|
||||||
def test_20_btc_coin(self):
|
def test_20_btc_coin(self):
|
||||||
logging.info("---------- Test BTC to {}".format(self.test_coin_from.name))
|
logging.info(f"---------- Test BTC to {self.test_coin_from.name}")
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
offer_id = swap_clients[0].postOffer(
|
offer_id = swap_clients[0].postOffer(
|
||||||
@@ -178,12 +174,21 @@ class TestLTC(BasicSwapTest):
|
|||||||
assert js_1["num_swapping"] == 0 and js_1["num_watched_outputs"] == 0
|
assert js_1["num_swapping"] == 0 and js_1["num_watched_outputs"] == 0
|
||||||
|
|
||||||
def test_21_mweb(self):
|
def test_21_mweb(self):
|
||||||
logging.info("---------- Test MWEB {}".format(self.test_coin_from.name))
|
logging.info(f"---------- Test MWEB {self.test_coin_from.name}")
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
ci0 = swap_clients[0].ci(self.test_coin_from)
|
ci0 = swap_clients[0].ci(self.test_coin_from)
|
||||||
ci1 = swap_clients[1].ci(self.test_coin_from)
|
ci1 = swap_clients[1].ci(self.test_coin_from)
|
||||||
|
|
||||||
|
# mweb utxos before sending to mweb
|
||||||
|
num_mweb: int = 0
|
||||||
|
utxos_0 = ci0.rpc_wallet("listunspent")
|
||||||
|
for utxo in utxos_0:
|
||||||
|
addr_info = ci0.rpc_wallet("getaddressinfo", [utxo["address"]])
|
||||||
|
if addr_info["ismweb"] is True:
|
||||||
|
num_mweb += 1
|
||||||
|
assert num_mweb == 1
|
||||||
|
|
||||||
mweb_addr_0 = ci0.rpc_wallet("getnewaddress", ["mweb addr test 0", "mweb"])
|
mweb_addr_0 = ci0.rpc_wallet("getnewaddress", ["mweb addr test 0", "mweb"])
|
||||||
mweb_addr_1 = ci1.rpc_wallet("getnewaddress", ["mweb addr test 1", "mweb"])
|
mweb_addr_1 = ci1.rpc_wallet("getnewaddress", ["mweb addr test 1", "mweb"])
|
||||||
|
|
||||||
@@ -210,6 +215,19 @@ class TestLTC(BasicSwapTest):
|
|||||||
< 0.1
|
< 0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
num_mweb: int = 0
|
||||||
|
utxos_0 = ci0.rpc_wallet(
|
||||||
|
"listunspent",
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
for utxo in utxos_0:
|
||||||
|
addr_info = ci0.rpc_wallet("getaddressinfo", [utxo["address"]])
|
||||||
|
if addr_info["ismweb"] is True:
|
||||||
|
num_mweb += 1
|
||||||
|
assert num_mweb > 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pause_event.clear() # Stop mining
|
pause_event.clear() # Stop mining
|
||||||
ci0.rpc_wallet("sendtoaddress", [mweb_addr_1, 10.0])
|
ci0.rpc_wallet("sendtoaddress", [mweb_addr_1, 10.0])
|
||||||
@@ -237,6 +255,7 @@ class TestLTC(BasicSwapTest):
|
|||||||
for utxo in utxos:
|
for utxo in utxos:
|
||||||
if utxo.get("address", "") == mweb_addr_1:
|
if utxo.get("address", "") == mweb_addr_1:
|
||||||
mweb_tx = utxo
|
mweb_tx = utxo
|
||||||
|
break
|
||||||
assert mweb_tx is not None
|
assert mweb_tx is not None
|
||||||
|
|
||||||
unspent_addr = ci1.getUnspentsByAddr()
|
unspent_addr = ci1.getUnspentsByAddr()
|
||||||
@@ -245,10 +264,66 @@ class TestLTC(BasicSwapTest):
|
|||||||
if "mweb1" in addr:
|
if "mweb1" in addr:
|
||||||
raise ValueError("getUnspentsByAddr should exclude mweb UTXOs.")
|
raise ValueError("getUnspentsByAddr should exclude mweb UTXOs.")
|
||||||
|
|
||||||
|
# Test helper functions to convert MWEB change
|
||||||
|
mweb_change_value = ci0.getMWEBBalance()
|
||||||
|
assert mweb_change_value > 0
|
||||||
|
|
||||||
|
test_lock_utxo = None
|
||||||
|
for utxo in utxos:
|
||||||
|
utxo_address: str = utxo.get("address", "")
|
||||||
|
if any(
|
||||||
|
utxo_address.startswith(prefix) for prefix in ("ltcmweb1", "tmweb1")
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
test_lock_utxo = {"txid": utxo["txid"], "vout": utxo["vout"]}
|
||||||
|
ci0.rpc_wallet(
|
||||||
|
"lockunspent",
|
||||||
|
[
|
||||||
|
False,
|
||||||
|
[
|
||||||
|
test_lock_utxo,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
break
|
||||||
|
assert len(ci0.rpc_wallet("listlockunspent")) == 1
|
||||||
|
|
||||||
|
txid = ci0.convertMWEBBalance()
|
||||||
|
|
||||||
|
# Check utxos locked before conversion are still locked after
|
||||||
|
assert len(ci0.rpc_wallet("listlockunspent")) == 1
|
||||||
|
ci0.rpc_wallet(
|
||||||
|
"lockunspent",
|
||||||
|
[
|
||||||
|
True,
|
||||||
|
[
|
||||||
|
test_lock_utxo,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert len(ci0.rpc_wallet("listlockunspent")) == 0
|
||||||
|
|
||||||
|
txj = ci0.rpc_wallet(
|
||||||
|
"gettransaction",
|
||||||
|
[
|
||||||
|
txid,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert len(txj["details"]) == 2
|
||||||
|
|
||||||
|
fee_amt = -ci0.make_int(txj["fee"])
|
||||||
|
assert txj["details"][0]["category"] == "send"
|
||||||
|
assert ci0.make_int(txj["details"][0]["amount"]) - fee_amt == -mweb_change_value
|
||||||
|
assert txj["details"][1]["category"] == "receive"
|
||||||
|
assert ci0.make_int(txj["details"][1]["amount"]) + fee_amt == mweb_change_value
|
||||||
|
|
||||||
|
mweb_change_value = ci0.getMWEBBalance()
|
||||||
|
assert mweb_change_value == 0
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
def test_22_mweb_balance(self):
|
def test_22_mweb_balance(self):
|
||||||
logging.info("---------- Test MWEB balance {}".format(self.test_coin_from.name))
|
logging.info(f"---------- Test MWEB balance {self.test_coin_from.name}")
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
ci_mweb = swap_clients[0].ci(Coins.LTC_MWEB)
|
ci_mweb = swap_clients[0].ci(Coins.LTC_MWEB)
|
||||||
@@ -265,7 +340,9 @@ class TestLTC(BasicSwapTest):
|
|||||||
ltc_mweb_addr = read_json_api(
|
ltc_mweb_addr = read_json_api(
|
||||||
TEST_HTTP_PORT + 0, "wallets/ltc_mweb/nextdepositaddr"
|
TEST_HTTP_PORT + 0, "wallets/ltc_mweb/nextdepositaddr"
|
||||||
)
|
)
|
||||||
|
assert ltc_mweb_addr.startswith("tmweb1")
|
||||||
ltc_mweb_addr2 = read_json_api(TEST_HTTP_PORT + 0, "wallets/ltc/newmwebaddress")
|
ltc_mweb_addr2 = read_json_api(TEST_HTTP_PORT + 0, "wallets/ltc/newmwebaddress")
|
||||||
|
assert ltc_mweb_addr2.startswith("tmweb1")
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
ci_mweb.rpc_wallet(
|
ci_mweb.rpc_wallet(
|
||||||
@@ -337,6 +414,20 @@ class TestLTC(BasicSwapTest):
|
|||||||
json_rv = read_json_api(TEST_HTTP_PORT + 0, "wallets/ltc", post_json)
|
json_rv = read_json_api(TEST_HTTP_PORT + 0, "wallets/ltc", post_json)
|
||||||
assert json_rv["mweb_balance"] <= 20.0
|
assert json_rv["mweb_balance"] <= 20.0
|
||||||
|
|
||||||
|
# Test helper functions to convert MWEB change
|
||||||
|
json_rv = read_json_api(
|
||||||
|
TEST_HTTP_PORT + 0, "wallets/ltc/mwebbalance", post_json
|
||||||
|
)
|
||||||
|
assert float(json_rv) > 0
|
||||||
|
json_rv = read_json_api(
|
||||||
|
TEST_HTTP_PORT + 0, "wallets/ltc/convertmweb", post_json
|
||||||
|
)
|
||||||
|
assert len(json_rv) == 64
|
||||||
|
json_rv = read_json_api(
|
||||||
|
TEST_HTTP_PORT + 0, "wallets/ltc/mwebbalance", post_json
|
||||||
|
)
|
||||||
|
assert float(json_rv) == 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
import secrets
|
import secrets
|
||||||
import threading
|
import threading
|
||||||
@@ -22,10 +23,15 @@ from coincurve.ecdsaotves import (
|
|||||||
)
|
)
|
||||||
from coincurve.keys import PrivateKey
|
from coincurve.keys import PrivateKey
|
||||||
|
|
||||||
|
from basicswap.basicswap import (
|
||||||
|
Coins,
|
||||||
|
BasicSwap,
|
||||||
|
SwapTypes,
|
||||||
|
)
|
||||||
from basicswap.contrib.mnemonic import Mnemonic
|
from basicswap.contrib.mnemonic import Mnemonic
|
||||||
from basicswap.db import create_db_, DBMethods, KnownIdentity
|
from basicswap.db import create_db_, DBMethods, KnownIdentity
|
||||||
from basicswap.util import h2b
|
from basicswap.util import h2b
|
||||||
from basicswap.util.address import decodeAddress
|
from basicswap.util.address import decodeAddress, toWIF
|
||||||
from basicswap.util.crypto import ripemd160, hash160, blake256
|
from basicswap.util.crypto import ripemd160, hash160, blake256
|
||||||
from basicswap.util.extkey import ExtKeyPair
|
from basicswap.util.extkey import ExtKeyPair
|
||||||
from basicswap.util.integer import encode_varint, decode_varint
|
from basicswap.util.integer import encode_varint, decode_varint
|
||||||
@@ -60,7 +66,9 @@ from basicswap.contrib.test_framework.messages import (
|
|||||||
CTxOut,
|
CTxOut,
|
||||||
uint256_from_str,
|
uint256_from_str,
|
||||||
)
|
)
|
||||||
|
from tests.basicswap.common import (
|
||||||
|
PREFIX_SECRET_KEY_REGTEST,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
@@ -157,7 +165,7 @@ class Test(unittest.TestCase):
|
|||||||
assert str(e) == "Mantissa too long"
|
assert str(e) == "Mantissa too long"
|
||||||
validate_amount("0.12345678")
|
validate_amount("0.12345678")
|
||||||
|
|
||||||
# floor
|
# Floor
|
||||||
assert make_int("0.123456789", r=-1) == 12345678
|
assert make_int("0.123456789", r=-1) == 12345678
|
||||||
# Round up
|
# Round up
|
||||||
assert make_int("0.123456789", r=1) == 12345679
|
assert make_int("0.123456789", r=1) == 12345679
|
||||||
@@ -791,6 +799,74 @@ class Test(unittest.TestCase):
|
|||||||
== "252cd6e85b99e0fd554c44d5fe638923f7ef563048362406a665cf3400feb1bd"
|
== "252cd6e85b99e0fd554c44d5fe638923f7ef563048362406a665cf3400feb1bd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_validateSwapType(self):
|
||||||
|
logging.info("---------- Test validateSwapType")
|
||||||
|
basicswap_dir = "/tmp/bsx_test_other"
|
||||||
|
if not os.path.exists(basicswap_dir):
|
||||||
|
os.makedirs(basicswap_dir)
|
||||||
|
|
||||||
|
k = PrivateKey()
|
||||||
|
settings = {
|
||||||
|
"network_key": toWIF(PREFIX_SECRET_KEY_REGTEST, k.secret),
|
||||||
|
"network_pubkey": k.public_key.format().hex(),
|
||||||
|
}
|
||||||
|
|
||||||
|
sc = BasicSwap(
|
||||||
|
basicswap_dir,
|
||||||
|
settings,
|
||||||
|
"regtest",
|
||||||
|
log_name="bsx_test_other",
|
||||||
|
)
|
||||||
|
|
||||||
|
should_pass = [
|
||||||
|
(Coins.BTC, Coins.XMR, SwapTypes.XMR_SWAP),
|
||||||
|
(Coins.XMR, Coins.BTC, SwapTypes.XMR_SWAP),
|
||||||
|
(Coins.BTC, Coins.FIRO, SwapTypes.XMR_SWAP),
|
||||||
|
(Coins.FIRO, Coins.BTC, SwapTypes.XMR_SWAP),
|
||||||
|
(Coins.PIVX, Coins.BTC, SwapTypes.SELLER_FIRST),
|
||||||
|
(Coins.BTC, Coins.PIVX, SwapTypes.SELLER_FIRST),
|
||||||
|
(Coins.DASH, Coins.PIVX, SwapTypes.SELLER_FIRST),
|
||||||
|
(Coins.PIVX, Coins.DASH, SwapTypes.SELLER_FIRST),
|
||||||
|
]
|
||||||
|
should_fail = [
|
||||||
|
(Coins.BTC, Coins.XMR, SwapTypes.SELLER_FIRST),
|
||||||
|
(Coins.XMR, Coins.PART_ANON, SwapTypes.XMR_SWAP),
|
||||||
|
(Coins.FIRO, Coins.PART_ANON, SwapTypes.XMR_SWAP),
|
||||||
|
(Coins.PART_ANON, Coins.FIRO, SwapTypes.XMR_SWAP),
|
||||||
|
(Coins.FIRO, Coins.BTC, SwapTypes.SELLER_FIRST),
|
||||||
|
(Coins.BTC, Coins.FIRO, SwapTypes.SELLER_FIRST),
|
||||||
|
]
|
||||||
|
|
||||||
|
for case in should_pass:
|
||||||
|
sc.validateSwapType(case[0], case[1], case[2])
|
||||||
|
for case in should_fail:
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError, sc.validateSwapType, case[0], case[1], case[2]
|
||||||
|
)
|
||||||
|
sc.chain = "mainnet"
|
||||||
|
for case in should_pass:
|
||||||
|
try:
|
||||||
|
sc.validateSwapType(case[0], case[1], case[2])
|
||||||
|
except Exception as e:
|
||||||
|
assert "Coin pair should use adaptor sig swap type" in str(e)
|
||||||
|
else:
|
||||||
|
if case[2] != SwapTypes.XMR_SWAP:
|
||||||
|
if (
|
||||||
|
case[0] not in sc.coins_without_segwit
|
||||||
|
or case[1] not in sc.coins_without_segwit
|
||||||
|
):
|
||||||
|
raise ValueError(f"Invalid swap pair in strict mode {case}")
|
||||||
|
for case in should_fail:
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError, sc.validateSwapType, case[0], case[1], case[2]
|
||||||
|
)
|
||||||
|
|
||||||
|
sc.settings["strict_swap_type"] = False
|
||||||
|
for case in should_pass:
|
||||||
|
sc.validateSwapType(case[0], case[1], case[2])
|
||||||
|
|
||||||
|
del sc
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class Test(BaseTest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_010_txn_size(self):
|
def test_010_txn_size(self):
|
||||||
logging.info("---------- Test {} txn_size".format(self.test_coin_from.name))
|
logging.info(f"---------- Test {self.test_coin_from.name} txn_size")
|
||||||
|
|
||||||
self.ensure_balance(self.test_coin_from, 0, 100.0)
|
self.ensure_balance(self.test_coin_from, 0, 100.0)
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ class Test(BaseTest):
|
|||||||
|
|
||||||
ci.rpc_wallet("sendrawtransaction", [lock_tx.hex()])
|
ci.rpc_wallet("sendrawtransaction", [lock_tx.hex()])
|
||||||
rv = ci.rpc_wallet("gettransaction", [txid])
|
rv = ci.rpc_wallet("gettransaction", [txid])
|
||||||
wallet_tx_fee = -ci.make_int(rv["details"][0]["fee"])
|
wallet_tx_fee = -ci.make_int(rv["fee"])
|
||||||
|
|
||||||
assert wallet_tx_fee >= expect_fee_int
|
assert wallet_tx_fee >= expect_fee_int
|
||||||
assert wallet_tx_fee - expect_fee_int < 20
|
assert wallet_tx_fee - expect_fee_int < 20
|
||||||
|
|||||||
+39
-53
@@ -26,11 +26,13 @@ from basicswap.basicswap import (
|
|||||||
TxStates,
|
TxStates,
|
||||||
)
|
)
|
||||||
from basicswap.basicswap_util import (
|
from basicswap.basicswap_util import (
|
||||||
|
EventLogTypes,
|
||||||
TxLockTypes,
|
TxLockTypes,
|
||||||
)
|
)
|
||||||
from basicswap.chainparams import (
|
from basicswap.chainparams import (
|
||||||
chainparams,
|
chainparams,
|
||||||
)
|
)
|
||||||
|
from basicswap.db import Concepts
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
COIN,
|
COIN,
|
||||||
make_int,
|
make_int,
|
||||||
@@ -47,6 +49,7 @@ from tests.basicswap.common import (
|
|||||||
wait_for_balance,
|
wait_for_balance,
|
||||||
wait_for_bid,
|
wait_for_bid,
|
||||||
wait_for_bid_tx_state,
|
wait_for_bid_tx_state,
|
||||||
|
wait_for_event,
|
||||||
wait_for_in_progress,
|
wait_for_in_progress,
|
||||||
wait_for_offer,
|
wait_for_offer,
|
||||||
wait_for_unspent,
|
wait_for_unspent,
|
||||||
@@ -63,7 +66,6 @@ from basicswap.contrib.test_framework.script import (
|
|||||||
)
|
)
|
||||||
from tests.basicswap.test_xmr import BaseTest, test_delay_event, callnoderpc
|
from tests.basicswap.test_xmr import BaseTest, test_delay_event, callnoderpc
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
@@ -160,35 +162,6 @@ class Test(BaseTest):
|
|||||||
rv = read_json_api(1800, "rateslist?from=PART&to=BTC")
|
rv = read_json_api(1800, "rateslist?from=PART&to=BTC")
|
||||||
assert len(rv) == 1
|
assert len(rv) == 1
|
||||||
|
|
||||||
def test_004_validateSwapType(self):
|
|
||||||
logging.info("---------- Test validateSwapType")
|
|
||||||
|
|
||||||
sc = self.swap_clients[0]
|
|
||||||
|
|
||||||
should_pass = [
|
|
||||||
(Coins.BTC, Coins.XMR, SwapTypes.XMR_SWAP),
|
|
||||||
(Coins.XMR, Coins.BTC, SwapTypes.XMR_SWAP),
|
|
||||||
(Coins.BTC, Coins.FIRO, SwapTypes.XMR_SWAP),
|
|
||||||
(Coins.FIRO, Coins.BTC, SwapTypes.XMR_SWAP),
|
|
||||||
(Coins.PIVX, Coins.BTC, SwapTypes.SELLER_FIRST),
|
|
||||||
(Coins.BTC, Coins.PIVX, SwapTypes.SELLER_FIRST),
|
|
||||||
]
|
|
||||||
should_fail = [
|
|
||||||
(Coins.BTC, Coins.XMR, SwapTypes.SELLER_FIRST),
|
|
||||||
(Coins.XMR, Coins.PART_ANON, SwapTypes.XMR_SWAP),
|
|
||||||
(Coins.FIRO, Coins.PART_ANON, SwapTypes.XMR_SWAP),
|
|
||||||
(Coins.PART_ANON, Coins.FIRO, SwapTypes.XMR_SWAP),
|
|
||||||
(Coins.FIRO, Coins.BTC, SwapTypes.SELLER_FIRST),
|
|
||||||
(Coins.BTC, Coins.FIRO, SwapTypes.SELLER_FIRST),
|
|
||||||
]
|
|
||||||
|
|
||||||
for case in should_pass:
|
|
||||||
sc.validateSwapType(case[0], case[1], case[2])
|
|
||||||
for case in should_fail:
|
|
||||||
self.assertRaises(
|
|
||||||
ValueError, sc.validateSwapType, case[0], case[1], case[2]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_003_cltv(self):
|
def test_003_cltv(self):
|
||||||
test_coin_from = Coins.PART
|
test_coin_from = Coins.PART
|
||||||
logging.info("---------- Test {} cltv".format(test_coin_from.name))
|
logging.info("---------- Test {} cltv".format(test_coin_from.name))
|
||||||
@@ -880,7 +853,6 @@ class Test(BaseTest):
|
|||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
swap_value = make_int(random.uniform(0.001, 10.0), scale=8, r=1)
|
swap_value = make_int(random.uniform(0.001, 10.0), scale=8, r=1)
|
||||||
logging.info("swap_value {}".format(format_amount(swap_value, 8)))
|
|
||||||
offer_id = swap_clients[0].postOffer(
|
offer_id = swap_clients[0].postOffer(
|
||||||
Coins.LTC,
|
Coins.LTC,
|
||||||
Coins.BTC,
|
Coins.BTC,
|
||||||
@@ -909,15 +881,15 @@ class Test(BaseTest):
|
|||||||
)
|
)
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
test_delay_event,
|
test_delay_event,
|
||||||
swap_clients[1],
|
swap_clients[0],
|
||||||
bid_id,
|
bid_id,
|
||||||
BidStates.SWAP_COMPLETED,
|
BidStates.SWAP_COMPLETED,
|
||||||
sent=True,
|
sent=True,
|
||||||
wait_for=30,
|
wait_for=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
js_0_bid = read_json_api(1800, "bids/{}".format(bid_id.hex()))
|
js_0_bid = read_json_api(1800, f"bids/{bid_id.hex()}")
|
||||||
js_1_bid = read_json_api(1801, "bids/{}".format(bid_id.hex()))
|
js_1_bid = read_json_api(1801, f"bids/{bid_id.hex()}")
|
||||||
assert js_0_bid["itx_state"] == "Refunded"
|
assert js_0_bid["itx_state"] == "Refunded"
|
||||||
assert js_1_bid["ptx_state"] == "Refunded"
|
assert js_1_bid["ptx_state"] == "Refunded"
|
||||||
|
|
||||||
@@ -934,38 +906,52 @@ class Test(BaseTest):
|
|||||||
assert compare_bid_states(offerer_states, self.states_offerer_sh[1]) is True
|
assert compare_bid_states(offerer_states, self.states_offerer_sh[1]) is True
|
||||||
assert compare_bid_states(bidder_states, self.states_bidder_sh[1]) is True
|
assert compare_bid_states(bidder_states, self.states_bidder_sh[1]) is True
|
||||||
|
|
||||||
"""
|
def test_11_bad_itx(self):
|
||||||
def test_11_refund(self):
|
# Invalid ITx sent, PTx should not be sent
|
||||||
# Seller submits initiate txn, buyer doesn't respond, repeat of test 5 using debug_ind
|
logging.info("---------- Test bad ITx, LTC to BTC")
|
||||||
logging.info('---------- Test refund, LTC to BTC')
|
|
||||||
swap_clients = self.swap_clients
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
swap_value = make_int(random.uniform(0.001, 10.0), scale=8, r=1)
|
swap_value = make_int(random.uniform(0.001, 10.0), scale=8, r=1)
|
||||||
logging.info('swap_value {}'.format(format_amount(swap_value, 8)))
|
offer_id = swap_clients[0].postOffer(
|
||||||
offer_id = swap_clients[0].postOffer(Coins.LTC, Coins.BTC, swap_value, 0.1 * COIN, swap_value, SwapTypes.SELLER_FIRST,
|
Coins.LTC,
|
||||||
TxLockTypes.SEQUENCE_LOCK_BLOCKS, 10)
|
Coins.BTC,
|
||||||
|
swap_value,
|
||||||
|
0.1 * COIN,
|
||||||
|
swap_value,
|
||||||
|
SwapTypes.SELLER_FIRST,
|
||||||
|
TxLockTypes.SEQUENCE_LOCK_BLOCKS,
|
||||||
|
18,
|
||||||
|
)
|
||||||
|
|
||||||
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)
|
||||||
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BUYER_STOP_AFTER_ITX)
|
|
||||||
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
|
wait_for_bid(test_delay_event, swap_clients[0], bid_id)
|
||||||
|
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.MAKE_INVALID_ITX)
|
||||||
swap_clients[0].acceptBid(bid_id)
|
swap_clients[0].acceptBid(bid_id)
|
||||||
|
|
||||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
event = wait_for_event(
|
||||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=30)
|
test_delay_event,
|
||||||
|
swap_clients[1],
|
||||||
|
Concepts.BID,
|
||||||
|
bid_id,
|
||||||
|
event_type=EventLogTypes.ERROR,
|
||||||
|
wait_for=120,
|
||||||
|
)
|
||||||
|
assert "Incorrect output amount in initiate txn " in event.event_msg
|
||||||
|
|
||||||
js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
|
wait_for_bid(
|
||||||
js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
|
test_delay_event,
|
||||||
assert (js_0_bid['itx_state'] == 'Refunded')
|
swap_clients[1],
|
||||||
assert (js_1_bid['ptx_state'] == 'Unknown')
|
bid_id,
|
||||||
|
BidStates.BID_ERROR,
|
||||||
|
sent=True,
|
||||||
|
wait_for=30,
|
||||||
|
)
|
||||||
|
|
||||||
js_0 = read_json_api(1800)
|
js_1_bid = read_json_api(1801, f"bids/{bid_id.hex()}")
|
||||||
js_1 = read_json_api(1801)
|
assert js_1_bid["ptx_state"] == "Unknown"
|
||||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
|
||||||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_12_withdrawal(self):
|
def test_12_withdrawal(self):
|
||||||
logging.info("---------- Test LTC withdrawals")
|
logging.info("---------- Test LTC withdrawals")
|
||||||
|
|||||||
+11
-10
@@ -23,9 +23,7 @@ from copy import deepcopy
|
|||||||
from coincurve.keys import PrivateKey
|
from coincurve.keys import PrivateKey
|
||||||
|
|
||||||
import basicswap.config as cfg
|
import basicswap.config as cfg
|
||||||
from basicswap.db import (
|
from basicswap.db import Concepts
|
||||||
Concepts,
|
|
||||||
)
|
|
||||||
from basicswap.basicswap import (
|
from basicswap.basicswap import (
|
||||||
Coins,
|
Coins,
|
||||||
BasicSwap,
|
BasicSwap,
|
||||||
@@ -38,9 +36,7 @@ from basicswap.basicswap_util import (
|
|||||||
EventLogTypes,
|
EventLogTypes,
|
||||||
)
|
)
|
||||||
from basicswap.util import COIN, format_amount, make_int, TemporaryError
|
from basicswap.util import COIN, format_amount, make_int, TemporaryError
|
||||||
from basicswap.util.address import (
|
from basicswap.util.address import toWIF
|
||||||
toWIF,
|
|
||||||
)
|
|
||||||
from basicswap.rpc import (
|
from basicswap.rpc import (
|
||||||
callrpc,
|
callrpc,
|
||||||
)
|
)
|
||||||
@@ -91,7 +87,6 @@ from basicswap.db_util import (
|
|||||||
)
|
)
|
||||||
from basicswap.bin.run import startDaemon, startXmrDaemon, startXmrWalletDaemon
|
from basicswap.bin.run import startDaemon, startXmrDaemon, startXmrWalletDaemon
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
NUM_NODES = 3
|
NUM_NODES = 3
|
||||||
@@ -222,6 +217,7 @@ def prepare_swapclient_dir(
|
|||||||
"datadir": os.path.join(datadir, "ltc_" + str(node_id)),
|
"datadir": os.path.join(datadir, "ltc_" + str(node_id)),
|
||||||
"bindir": cfg.LITECOIN_BINDIR,
|
"bindir": cfg.LITECOIN_BINDIR,
|
||||||
"use_segwit": True,
|
"use_segwit": True,
|
||||||
|
"wallet_name": "bsx_wallet",
|
||||||
}
|
}
|
||||||
|
|
||||||
if cls:
|
if cls:
|
||||||
@@ -815,7 +811,7 @@ class BaseTest(unittest.TestCase):
|
|||||||
.pubkey_to_address(void_block_rewards_pubkey)
|
.pubkey_to_address(void_block_rewards_pubkey)
|
||||||
)
|
)
|
||||||
logging.info(
|
logging.info(
|
||||||
"Mining %d Litecoin blocks to %s", num_blocks, cls.ltc_addr
|
f"Mining {num_blocks} Litecoin blocks to {cls.ltc_addr}"
|
||||||
)
|
)
|
||||||
callnoderpc(
|
callnoderpc(
|
||||||
0,
|
0,
|
||||||
@@ -942,6 +938,7 @@ class BaseTest(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
cls.coins_update_thread.start()
|
cls.coins_update_thread.start()
|
||||||
|
|
||||||
|
cls.prepareBalances()
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
cls.tearDownClass()
|
cls.tearDownClass()
|
||||||
@@ -999,6 +996,10 @@ class BaseTest(unittest.TestCase):
|
|||||||
def prepareExtraCoins(cls):
|
def prepareExtraCoins(cls):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def prepareBalances(cls):
|
||||||
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def coins_loop(cls):
|
def coins_loop(cls):
|
||||||
if cls.btc_addr is not None:
|
if cls.btc_addr is not None:
|
||||||
@@ -2590,7 +2591,7 @@ class Test(BaseTest):
|
|||||||
swap_clients[2],
|
swap_clients[2],
|
||||||
bid_id,
|
bid_id,
|
||||||
BidStates.SWAP_COMPLETED,
|
BidStates.SWAP_COMPLETED,
|
||||||
wait_for=120,
|
wait_for=180,
|
||||||
)
|
)
|
||||||
wait_for_bid(
|
wait_for_bid(
|
||||||
test_delay_event,
|
test_delay_event,
|
||||||
@@ -2598,7 +2599,7 @@ class Test(BaseTest):
|
|||||||
bid_id,
|
bid_id,
|
||||||
BidStates.SWAP_COMPLETED,
|
BidStates.SWAP_COMPLETED,
|
||||||
sent=True,
|
sent=True,
|
||||||
wait_for=120,
|
wait_for=180,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify expected inputs were used
|
# Verify expected inputs were used
|
||||||
|
|||||||
+20
-5
@@ -1,15 +1,18 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2022-2024 tecnovert
|
# Copyright (c) 2022-2024 tecnovert
|
||||||
# Copyright (c) 2024 The Basicswap developers
|
# Copyright (c) 2024-2026 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
PORT_OFS = int(os.getenv("PORT_OFS", 1))
|
||||||
|
UI_PORT = 12700 + PORT_OFS
|
||||||
|
|
||||||
REQUIRED_SETTINGS = {
|
REQUIRED_SETTINGS = {
|
||||||
"blocks_confirmed": 1,
|
"blocks_confirmed": 1,
|
||||||
@@ -59,14 +62,26 @@ def post_json_api(port, path, json_data):
|
|||||||
return json.loads(post_json_req(url, json_data))
|
return json.loads(post_json_req(url, json_data))
|
||||||
|
|
||||||
|
|
||||||
def waitForServer(delay_event, port, wait_for=20):
|
def waitForServer(delay_event, port, wait_for=40):
|
||||||
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.")
|
||||||
try:
|
try:
|
||||||
delay_event.wait(1)
|
delay_event.wait(1.0)
|
||||||
_ = read_json_api(port)
|
_ = read_json_api(port)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("waitForServer, error:", str(e))
|
logging.error(f"waitForServer: {e}")
|
||||||
raise ValueError("waitForServer failed")
|
raise ValueError("waitForServer 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")
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
confimration->confirmation
|
||||||
Reference in New Issue
Block a user