52 Commits

Author SHA1 Message Date
tecnovert
151960af3c build, guix: update packed version 2026-03-18 12:32:00 +02:00
tecnovert
83807d213f build: raise version to 0.15.3 2026-03-18 12:14:50 +02:00
tecnovert
8d150f0ea8 fix, firo: add createUTXO, fix tests 2026-03-18 11:57:32 +02:00
tecnovert
569f4290d0 Merge pull request #406 from dhvll/firo-spark
Add Firo Spark Support
2026-03-18 08:22:50 +00:00
tecnovert
e5023eda33 Merge branch 'ashley-logan-fix-docker-config-helper' into dev 2026-03-18 08:58:25 +02:00
tecnovert
d17560833e Merge pull request #421 from nahuhh/daemon_updates
prepare: daemon updates 2026-03-11
2026-03-18 06:50:03 +00:00
tecnovert
f577f84f09 refactor: improve shutdown error messages 2026-03-17 12:39:22 +00:00
nahuhh
f668c38cd6 dash: bump to v23.1.2 2026-03-17 12:39:22 +00:00
Gerlof van Ek
f259c18f73 Merge pull request #426 from gerlofvanek/amm-425
AMM: Fix Auto-revoke and repost offers after partial fills.
2026-03-16 13:11:22 +01:00
nahuhh
486619c2cd firo: bump to v0.14.15.3 [mandatory] 2026-03-11 01:44:44 +00:00
ashley-logan
6ea52611eb fixed coin config file f-strings 2026-02-23 15:38:30 -05:00
nahuhh
c41ab51cfc btc: bump to v29.3 2026-02-20 13:51:47 +00:00
nahuhh
bafbff643c dash: bump to v23.1.0 2026-02-20 13:51:43 +00:00
tecnovert
d8e741f2b1 Merge pull request #429 from tecnovert/tests
fix tests for multiprocess spawn
2026-02-06 10:42:30 +00:00
tecnovert
f872e12d7c ci: install from requirements.txt in cirrus 2026-02-06 00:49:32 +02:00
tecnovert
1e18bcae38 fix tests for multiprocess spawn 2026-02-06 00:49:22 +02:00
tecnovert
088ed92da3 Merge pull request #427 from nahuhh/amm_patch-1
amm: remove duplicate minrate check
2026-02-04 20:26:38 +00:00
Dhaval Chaudhari
aad4d0522c fix 2026-02-05 00:17:45 +05:30
nahuhh
2e5b8ec083 amm: remove duplicate minrate check 2026-02-02 02:04:49 +00:00
gerlofvanek
d4d781bebb AMM: Fix Auto-revoke and repost offers after partial fills 2026-02-02 00:34:52 +01:00
tecnovert
35c640d30c Merge pull request #423 from tecnovert/witness_stack_est
Estimate witness stack size for multiple inputs.
2026-01-31 12:07:47 +00:00
tecnovert
ab1f6ea5b6 estimate witness stack size for multiple inputs 2026-01-31 13:46:29 +02:00
nahuhh
f52b100cff firo: bump to v0.14.15.2 2026-01-26 01:59:22 +00:00
nahuhh
985df85394 xmr: bump to v0.18.4.5 2026-01-26 01:59:17 +00:00
nahuhh
5b5d72e145 dash: bump to v23.0.2 2026-01-26 00:08:18 +00:00
nahuhh
2c42629807 decred: bump to v2.1.3 2026-01-26 00:07:51 +00:00
tecnovert
a04ce28ca2 Merge pull request #420 from tecnovert/tests
tests: add "fetchpricesthread" setting
2026-01-19 09:12:18 +00:00
tecnovert
52da86bc86 fix countEvents 2026-01-19 11:07:27 +02:00
tecnovert
1d5778a72c tests: add "fetchpricesthread" setting 2026-01-19 10:09:05 +02:00
tecnovert
1346d47d17 Merge pull request #419 from tecnovert/refactor
refactor: use coincurve for sumKeys
2026-01-18 22:25:25 +00:00
tecnovert
c9a884de52 tests: try prevent missing gnupg module error 2026-01-19 00:00:19 +02:00
tecnovert
a6b5661cf1 refactor: use coincurve for sumKeys 2026-01-18 23:32:49 +02:00
nahuhh
1687d82cbc bch: bump to v29.0.0 [may 2026 hard fork] 2026-01-17 23:14:20 +00:00
tecnovert
0e092ad7e9 Merge pull request #418 from tecnovert/refactor
Refactor
2026-01-12 18:05:50 +00:00
tecnovert
462ac250b3 db: enable partial retrievals and updates 2026-01-12 19:27:03 +02:00
tecnovert
78b018c2bd refactor: simplify setBidError 2026-01-12 19:26:21 +02:00
tecnovert
4545c2b147 Merge pull request #413 from nahuhh/bitcoin_v29.2
bitcoin: bump to 29.2
2026-01-05 21:17:22 +00:00
tecnovert
b14a77af3d Merge pull request #414 from basicswap/dependabot/pip/dev/python-gnupg-0.5.6
build(deps): bump python-gnupg from 0.5.5 to 0.5.6
2026-01-05 21:17:06 +00:00
tecnovert
eb450e04e0 Merge pull request #415 from tecnovert/db
simplify db, remove state_note
2026-01-05 21:16:06 +00:00
tecnovert
c0a5d0e31d simplify db, remove state_note 2026-01-05 21:20:02 +02:00
dependabot[bot]
e6dca30009 build(deps): bump python-gnupg from 0.5.5 to 0.5.6
Bumps [python-gnupg](https://github.com/vsajip/python-gnupg) from 0.5.5 to 0.5.6.
- [Release notes](https://github.com/vsajip/python-gnupg/releases)
- [Changelog](https://github.com/vsajip/python-gnupg/blob/master/release)
- [Commits](https://github.com/vsajip/python-gnupg/compare/0.5.5...0.5.6)

---
updated-dependencies:
- dependency-name: python-gnupg
  dependency-version: 0.5.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-05 07:14:49 +00:00
nahuhh
ed6ad637a0 bitcoin: bump to 29.2 2026-01-03 00:15:44 +00:00
tecnovert
339d52f114 Merge pull request #412 from tecnovert/logging
fix duplicate bid id in coin b lock tx submit msg, use logIDT consistently
2026-01-02 17:40:07 +00:00
tecnovert
d1d20e855b fix duplicate bid id in coin b lock tx submit msg, use logIDT consistently 2026-01-02 19:16:41 +02:00
tecnovert
9d716d25cb guix: update packed version 2026-01-01 01:28:36 +02:00
tecnovert
2259e964b1 build: raise version to 0.15.2 2026-01-01 01:14:19 +02:00
tecnovert
dc289a18ee dcr: fix wallet creation and test for v2.1.2 2026-01-01 01:12:27 +02:00
tecnovert
b3591a2bb1 Merge pull request #409 from nahuhh/decred_212
decred: v2.1.2
2025-12-31 06:52:18 +00:00
nahuhh
7e892ee9af decred: v2.1.2 2025-12-26 00:20:46 +00:00
Dhaval Chaudhari
7ee1cea4eb feat: implement Spark balance display and withdrawal options
feat: complete FIRO + Spark integration (balance, withdrawal, address caching, refactor)

feat: add support for Spark address handling

remove white space

ref
2025-12-02 22:10:56 +05:30
Dhaval Chaudhari
caaad818ef feat: enhance FIRO interface with new Spark address generation and wallet info retrieval 2025-11-20 00:22:18 +05:30
Dhaval Chaudhari
0bf4af100a feat: add FIRO withdrawal and Spark address caching functionality 2025-11-20 00:22:03 +05:30
39 changed files with 716 additions and 308 deletions

View File

@@ -21,8 +21,9 @@ test_task:
- XMR_BINDIR: ${BIN_DIR}/monero
setup_script:
- apt-get update
- apt-get install -y python3-pip pkg-config
- pip install tox pytest
- 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
@@ -30,7 +31,7 @@ test_task:
fingerprint_script:
- basicswap-prepare -v
populate_script:
- basicswap-prepare --bindir=/tmp/cached_bin --preparebinonly --withcoins=particl,bitcoin,bitcoincash,litecoin,monero
- basicswap-prepare --bindir=/tmp/cached_bin --preparebinonly --withcoins=particl,bitcoin,litecoin,monero
script:
- cd "${CIRRUS_WORKING_DIR}"
- export DATADIRS="${TEST_DIR}"
@@ -38,7 +39,6 @@ test_task:
- cp -r ${BIN_DIR} "${DATADIRS}/bin"
- mkdir -p "${TEST_RELOAD_PATH}/bin"
- cp -r ${BIN_DIR} "${TEST_RELOAD_PATH}/bin"
- # tox
- pytest tests/basicswap/test_other.py
- pytest tests/basicswap/test_run.py
- pytest tests/basicswap/test_reload.py

View File

@@ -45,15 +45,13 @@ jobs:
echo "Pin: origin packages.mozilla.org" | sudo tee -a /etc/apt/preferences.d/mozilla
echo "Pin-Priority: 1000" | sudo tee -a /etc/apt/preferences.d/mozilla
sudo apt-get update
sudo apt-get install -y firefox
sudo apt-get install -y firefox gnupg
fi
python -m pip install --upgrade pip
pip install python-gnupg
pip install -e .[dev]
pip install -r requirements.txt --require-hashes
pip install .[dev]
- name: Install
run: |
pip install .
# Print the core versions to a file for caching
basicswap-prepare --version --withcoins=bitcoin | tail -n +2 > core_versions.txt
cat core_versions.txt

View File

@@ -1,3 +1,3 @@
name = "basicswap"
__version__ = "0.15.1"
__version__ = "0.15.3"

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# 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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -1280,12 +1280,17 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
else:
self.log.info("AMM autostart is disabled")
self._price_fetch_running = True
self._price_fetch_thread = threading.Thread(
target=self._backgroundPriceFetchLoop, daemon=True
if self.settings.get("fetchpricesthread", True):
self._price_fetch_running = True
self._price_fetch_thread = threading.Thread(
target=self._backgroundPriceFetchLoop, daemon=True
)
self._price_fetch_thread.start()
self.log.info(
"Background price fetching {}".format(
"started" if self._price_fetch_running else "is disabled"
)
)
self._price_fetch_thread.start()
self.log.info("Background price fetching started")
if "htmlhost" in self.settings:
self.log.info(
@@ -1919,8 +1924,15 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.activateBid(cursor, bid)
except Exception as ex:
self.logException(f"Failed to activate bid! Error: {ex}")
self.logEvent(
Concepts.BID,
bid.bid_id,
EventLogTypes.ERROR,
"Failed to activate",
cursor,
)
try:
bid.setState(BidStates.BID_ERROR, "Failed to activate")
bid.setState(BidStates.BID_ERROR)
offer = self.queryOne(
Offer, cursor, {"offer_id": bid.offer_id}
@@ -2943,10 +2955,12 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.log.info_s(f"In txn: {txid}")
return txid
def withdrawLTC(self, type_from, value, addr_to, subfee: bool) -> str:
ci = self.ci(Coins.LTC)
def withdrawCoinExtended(
self, coin_type, type_from, value, addr_to, subfee: bool
) -> str:
ci = self.ci(coin_type)
self.log.info(
"withdrawLTC{}".format(
"withdrawCoinExtended{}".format(
""
if self.log.safe_logs
else " {} {} to {} {}".format(
@@ -3194,12 +3208,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
raise ValueError("Must specify offer for save_in_progress")
self.swaps_in_progress[bid_id] = (bid, save_in_progress) # (bid, offer)
def saveBid(self, bid_id: bytes, bid, xmr_swap=None) -> None:
cursor = self.openDB()
def saveBid(self, bid_id: bytes, bid, xmr_swap=None, cursor=None) -> None:
try:
self.saveBidInSession(bid_id, bid, cursor, xmr_swap)
use_cursor = self.openDB(cursor)
self.saveBidInSession(bid_id, bid, use_cursor, xmr_swap)
finally:
self.closeDB(cursor)
if cursor is None:
self.closeDB(use_cursor)
def createActionInSession(
self, delay: int, action_type: int, linked_id: bytes, cursor
@@ -3263,17 +3278,22 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.log.debug(f"logBidEvent {self.log.id(bid_id)} {event_type}")
self.logEvent(Concepts.BID, bid_id, event_type, event_msg, cursor)
def countBidEvents(self, bid, event_type, cursor):
def countEvents(
self, linked_type: int, linked_id: bytes, event_type: int, cursor
) -> int:
q = cursor.execute(
"SELECT COUNT(*) FROM eventlog WHERE linked_type = :linked_type AND linked_id = :linked_id AND event_type = :event_type",
{
"linked_type": int(Concepts.BID),
"linked_id": bid.bid_id,
"linked_id": linked_id,
"event_type": int(event_type),
},
).fetchone()
return q[0]
def countBidEvents(self, bid, event_type: int, cursor) -> int:
return self.countEvents(int(Concepts.BID), bid.bid_id, int(event_type), cursor)
def getEvents(self, linked_type: int, linked_id: bytes):
events = []
cursor = self.openDB()
@@ -3848,7 +3868,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
txid = ci_from.publishTx(bytes.fromhex(txn))
self.log.debug(
f"Submitted initiate txn {txid} to {ci_from.coin_name()} chain for bid {self.log.id(bid_id)}",
f"Submitted initiate txn {self.logIDT(txid)} to {ci_from.coin_name()} chain for bid {self.log.id(bid_id)}",
)
bid.initiate_tx = SwapTx(
bid_id=bid_id,
@@ -5002,13 +5022,18 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.deactivateBidForReason(bid_id, BidStates.SWAP_TIMEDOUT, cursor=cursor)
def setBidError(
self, bid_id: bytes, bid, error_str: str, save_bid: bool = True, xmr_swap=None
self,
bid,
error_str: str,
save_bid: bool = True,
xmr_swap=None,
cursor=None,
) -> None:
self.log.error(f"Bid {self.log.id(bid_id)} - Error: {error_str}")
self.log.error(f"Bid {self.log.id(bid.bid_id)} - Error: {error_str}")
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ERROR, error_str, cursor)
bid.setState(BidStates.BID_ERROR)
bid.state_note = "error msg: " + error_str
if save_bid:
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
self.saveBid(bid.bid_id, bid, xmr_swap=xmr_swap, cursor=cursor)
def createInitiateTxn(
self, coin_type, bid_id: bytes, bid, initiate_script, prefunded_tx=None
@@ -5514,7 +5539,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
txn = self.createParticipateTxn(bid_id, bid, offer, participate_script)
txid = ci_to.publishTx(bytes.fromhex(txn))
self.log.debug(
f"Submitted participate tx {self.log.id(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}"
f"Submitted participate tx {self.logIDT(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}"
)
bid.setPTxState(TxStates.TX_SENT)
self.logEvent(
@@ -5622,7 +5647,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
txn = self.createRedeemTxn(ci_to.coin_type(), bid)
txid = ci_to.publishTx(bytes.fromhex(txn))
self.log.debug(
f"Submitted participate redeem tx {self.log.id(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
f"Submitted participate redeem tx {self.logIDT(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
)
self.logEvent(
Concepts.BID, bid.bid_id, EventLogTypes.PTX_REDEEM_PUBLISHED, "", None
@@ -5908,7 +5933,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
)
self.log.info(
f"Submitted coin a lock refund spend tx for bid {self.log.id(bid_id)}, txid {self.log.id(txid_str)}"
f"Submitted coin a lock refund spend tx for bid {self.log.id(bid_id)}, txid {self.logIDT(txid_str)}"
)
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND] = SwapTx(
bid_id=bid_id,
@@ -5983,7 +6008,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
txid=bytes.fromhex(txid_hex),
)
self.log.info(
f"Submitted mercy tx for bid {self.log.id(bid_id)}, txid {self.log.id(txid_hex)}"
f"Submitted mercy tx for bid {self.log.id(bid_id)}, txid {self.logIDT(txid_hex)}"
)
self.logBidEvent(
bid_id,
@@ -6670,7 +6695,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
try:
txid = ci_to.publishTx(bid.participate_txn_refund)
self.log.debug(
f"Submitted participate refund txn {self.log.id(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
f"Submitted participate refund txn {self.logIDT(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
)
self.logEvent(
Concepts.BID,
@@ -6726,7 +6751,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
def removeWatchedTransaction(self, coin_type, bid_id: bytes, txid_hex: str) -> None:
# Remove all for bid if txid is None
self.log.debug(
f"Removing watched transaction {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(txid_hex)}"
f"Removing watched transaction {Coins(coin_type).name} {self.log.id(bid_id)} {self.logIDT(txid_hex)}"
)
watched = self.coin_clients[coin_type]["watched_transactions"]
old_len = len(watched)
@@ -6735,7 +6760,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if wo.bid_id == bid_id and (txid_hex is None or wo.txid_hex == txid_hex):
del watched[i]
self.log.debug(
f"Removed watched transaction {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(wo.txid_hex)}"
f"Removed watched transaction {Coins(coin_type).name} {self.log.id(bid_id)} {self.logIDT(wo.txid_hex)}"
)
def addWatchedOutput(
@@ -6756,7 +6781,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
def removeWatchedOutput(self, coin_type, bid_id: bytes, txid_hex: str) -> None:
# Remove all for bid if txid is None
self.log.debug(
f"Removing watched output {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(txid_hex)}"
f"Removing watched output {Coins(coin_type).name} {self.log.id(bid_id)} {self.logIDT(txid_hex)}"
)
watched = self.coin_clients[coin_type]["watched_outputs"]
old_len = len(watched)
@@ -6765,7 +6790,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if wo.bid_id == bid_id and (txid_hex is None or wo.txid_hex == txid_hex):
del watched[i]
self.log.debug(
f"Removed watched output {Coins(coin_type).name} {self.log.id(bid_id)} {self.log.id(wo.txid_hex)}"
f"Removed watched output {Coins(coin_type).name} {self.log.id(bid_id)} {self.logIDT(wo.txid_hex)}"
)
def addWatchedScript(
@@ -7016,10 +7041,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
)
else:
self.setBidError(
bid.bid_id,
bid,
"Unexpected txn spent coin a lock tx: {}".format(spend_txid_hex),
save_bid=False,
cursor=use_cursor,
)
self.saveBidInSession(
@@ -7729,12 +7754,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.logException(err_msg)
bid_id = linked_id
self.logEvent(
Concepts.BID, bid_id, EventLogTypes.ERROR, err_msg, cursor
)
# Failing to accept a bid should not set an error state as the bid has not begun yet
if accepting_bid:
self.logEvent(
Concepts.BID, bid_id, EventLogTypes.ERROR, err_msg, cursor
)
# If delaying with no (further) queued actions reset state
if self.countQueuedActions(cursor, bid_id, None) < 2:
bid, offer = self.getBidAndOffer(bid_id, cursor)
@@ -7754,7 +7778,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
else:
bid = self.getBid(bid_id, cursor)
if bid:
bid.setState(BidStates.BID_ERROR, err_msg)
bid.setState(BidStates.BID_ERROR)
self.saveBidInSession(bid_id, bid, cursor)
query: str = "DELETE FROM actions WHERE trigger_at <= :now"
@@ -7845,7 +7869,14 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.log.debug(
f"Expiring partially received {bid_type}: {self.log.id(bid.bid_id)}."
)
bid.setState(BidStates.BID_ERROR, "Timed out")
self.logEvent(
Concepts.BID,
bid.bid_id,
EventLogTypes.ERROR,
"Timed out (partially received bid)",
cursor,
)
bid.setState(BidStates.BID_ERROR)
self.updateDB(
bid,
cursor,
@@ -9133,7 +9164,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
except Exception as ex:
if self.debug:
self.log.error(traceback.format_exc())
self.setBidError(bid.bid_id, bid, str(ex), xmr_swap=xmr_swap)
self.setBidError(bid, str(ex), xmr_swap=xmr_swap)
def watchXmrSwap(self, bid, offer, xmr_swap, cursor=None) -> None:
self.log.debug(f"Adaptor-sig swap in progress, bid {self.log.id(bid.bid_id)}.")
@@ -9408,7 +9439,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script
)
self.log.debug(
f"Submitted lock tx {self.log.id(txid_hex)} to {ci_from.coin_name()} chain for bid {self.log.id(bid_id)}.",
f"Submitted lock tx {self.logIDT(txid_hex)} to {ci_from.coin_name()} chain for bid {self.log.id(bid_id)}.",
)
if bid.xmr_a_lock_tx is None:
@@ -9477,7 +9508,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.saveBidInSession(bid_id, bid, cursor, xmr_swap, save_in_progress=offer)
return
unlock_time = 0
unlock_time: int = 0
if bid.debug_ind in (
DebugTypes.CREATE_INVALID_COIN_B_LOCK,
DebugTypes.B_LOCK_TX_MISSED_SEND,
@@ -9548,7 +9579,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
)
else:
self.setBidError(
bid_id, bid, "publishBLockTx failed: " + str(ex), save_bid=False
bid,
"publishBLockTx failed: " + str(ex),
save_bid=False,
cursor=cursor,
)
self.saveBidInSession(
bid_id, bid, cursor, xmr_swap, save_in_progress=offer
@@ -9560,7 +9594,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
return
self.log.debug(
f"Submitted lock txn {self.log.id(bid_id)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
f"Submitted lock txn {self.logIDT(b_lock_tx_id)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
)
bid.xmr_b_lock_tx = SwapTx(
bid_id=bid_id,
@@ -9572,7 +9606,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_PUBLISHED, "", cursor)
if bid.debug_ind == DebugTypes.BID_STOP_AFTER_COIN_B_LOCK:
self.log.debug(
"Adaptor-sig bid {self.log.id(bid_id)}: Stalling bid for testing: {bid.debug_ind}."
f"Adaptor-sig bid {self.log.id(bid_id)}: Stalling bid for testing: {bid.debug_ind}."
)
bid.setState(BidStates.BID_STALLED_FOR_TEST)
self.logBidEvent(
@@ -9733,7 +9767,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
txid = bytes.fromhex(ci_from.publishTx(xmr_swap.a_lock_spend_tx))
self.log.debug(
f"Submitted lock spend txn {self.log.id(txid)} to {ci_from.coin_name()} chain for bid {self.log.id(bid_id)}."
f"Submitted lock spend txn {self.logIDT(txid)} to {ci_from.coin_name()} chain for bid {self.log.id(bid_id)}."
)
self.logBidEvent(
bid.bid_id, EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED, "", cursor
@@ -9843,7 +9877,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
lock_tx_vout=lock_tx_vout,
)
self.log.debug(
f"Submitted lock B spend txn {self.log.id(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
f"Submitted lock B spend txn {self.logIDT(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
)
self.logBidEvent(
bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, "", cursor
@@ -9871,7 +9905,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
)
else:
self.setBidError(
bid_id, bid, "spendBLockTx failed: " + str(ex), save_bid=False
bid,
"spendBLockTx failed: " + str(ex),
save_bid=False,
cursor=cursor,
)
self.saveBidInSession(
bid_id, bid, cursor, xmr_swap, save_in_progress=offer
@@ -9958,7 +9995,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
),
)
self.log.debug(
f"Submitted lock B refund txn {self.log.id(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
f"Submitted lock B refund txn {self.logIDT(txid)} to {ci_to.coin_name()} chain for bid {self.log.id(bid_id)}."
)
self.logBidEvent(
bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, "", cursor
@@ -9986,10 +10023,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
)
else:
self.setBidError(
bid_id,
bid,
"spendBLockTx for refund failed: " + str(ex),
save_bid=False,
cursor=cursor,
)
self.saveBidInSession(
bid_id, bid, cursor, xmr_swap, save_in_progress=offer
@@ -10190,7 +10227,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
except Exception as ex:
if self.debug:
self.log.error(traceback.format_exc())
self.setBidError(bid_id, bid, str(ex))
self.setBidError(bid, str(ex))
def processXmrBidLockSpendTx(self, msg) -> None:
# Follower receiving MSG4F
@@ -10255,7 +10292,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
except Exception as ex:
if self.debug:
self.log.error(traceback.format_exc())
self.setBidError(bid_id, bid, str(ex))
self.setBidError(bid, str(ex))
# Update copy of bid in swaps_in_progress
self.swaps_in_progress[bid_id] = (bid, offer)
@@ -10357,7 +10394,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
except Exception as ex:
if self.debug:
self.log.error(traceback.format_exc())
self.setBidError(bid_id, bid, str(ex))
self.setBidError(bid, str(ex))
self.swaps_in_progress[bid_id] = (bid, offer)
return
@@ -10970,9 +11007,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
to_remove = []
if now - self._last_checked_progress >= self.check_progress_seconds:
for bid_id, v in self.swaps_in_progress.items():
bid, offer = v
try:
if self.checkBidState(bid_id, v[0], v[1]) is True:
to_remove.append((bid_id, v[0], v[1]))
if self.checkBidState(bid_id, bid, offer) is True:
to_remove.append((bid_id, bid, offer))
except Exception as ex:
if self.debug:
self.log.error("checkBidState %s", traceback.format_exc())
@@ -10988,7 +11026,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
)
else:
self.log.error(f"checkBidState {self.log.id(bid_id)} {ex}.")
self.setBidError(bid_id, v[0], str(ex))
self.setBidError(bid, str(ex))
for bid_id, bid, offer in to_remove:
self.deactivateBid(None, offer, bid)
@@ -11640,6 +11678,27 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
rv["mweb_pending"] = (
walletinfo["mweb_unconfirmed"] + walletinfo["mweb_immature"]
)
elif coin == Coins.FIRO:
try:
rv["spark_address"] = self.getCachedStealthAddressForCoin(
Coins.FIRO
)
except Exception as e:
self.log.warning(
f"getCachedStealthAddressForCoin for {ci.coin_name()} failed with: {e}."
)
# Spark balances are in atomic units, format them
rv["spark_balance"] = (
0
if walletinfo["spark_balance"] == 0
else ci.format_amount(walletinfo["spark_balance"])
)
spark_pending_int = (
walletinfo["spark_unconfirmed"] + walletinfo["spark_immature"]
)
rv["spark_pending"] = (
0 if spark_pending_int == 0 else ci.format_amount(spark_pending_int)
)
return rv
except Exception as e:
@@ -11762,6 +11821,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if row2[0].startswith("stealth"):
if coin_id == Coins.LTC:
wallet_data["mweb_address"] = row2[1]
elif coin_id == Coins.FIRO:
wallet_data["spark_address"] = row2[1]
else:
wallet_data["stealth_address"] = row2[1]
else:

View File

@@ -55,22 +55,22 @@ PARTICL_VERSION = os.getenv("PARTICL_VERSION", "27.2.3.0")
PARTICL_VERSION_TAG = os.getenv("PARTICL_VERSION_TAG", "")
PARTICL_LINUX_EXTRA = os.getenv("PARTICL_LINUX_EXTRA", "nousb")
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "28.0")
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "29.3")
BITCOIN_VERSION_TAG = os.getenv("BITCOIN_VERSION_TAG", "")
LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.4")
LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "")
DCR_VERSION = os.getenv("DCR_VERSION", "1.8.1")
DCR_VERSION = os.getenv("DCR_VERSION", "2.1.3")
DCR_VERSION_TAG = os.getenv("DCR_VERSION_TAG", "")
NMC_VERSION = os.getenv("NMC_VERSION", "28.0")
NMC_VERSION_TAG = os.getenv("NMC_VERSION_TAG", "")
MONERO_VERSION = os.getenv("MONERO_VERSION", "0.18.4.4")
MONERO_VERSION = os.getenv("MONERO_VERSION", "0.18.4.5")
MONERO_VERSION_TAG = os.getenv("MONERO_VERSION_TAG", "")
XMR_SITE_COMMIT = (
"a1bd4cd48a85b6012de20d9e490f83936f477be2" # Lock hashes.txt to monero version
"1bfa07c1b54f4f39a93096e3bfb746cb21249422" # Lock hashes.txt to monero version
)
WOWNERO_VERSION = os.getenv("WOWNERO_VERSION", "0.11.3.0")
@@ -82,16 +82,16 @@ WOW_SITE_COMMIT = (
PIVX_VERSION = os.getenv("PIVX_VERSION", "5.6.1")
PIVX_VERSION_TAG = os.getenv("PIVX_VERSION_TAG", "")
DASH_VERSION = os.getenv("DASH_VERSION", "22.1.3")
DASH_VERSION = os.getenv("DASH_VERSION", "23.1.2")
DASH_VERSION_TAG = os.getenv("DASH_VERSION_TAG", "")
FIRO_VERSION = os.getenv("FIRO_VERSION", "0.14.15.0")
FIRO_VERSION = os.getenv("FIRO_VERSION", "0.14.15.3")
FIRO_VERSION_TAG = os.getenv("FIRO_VERSION_TAG", "")
NAV_VERSION = os.getenv("NAV_VERSION", "7.0.3")
NAV_VERSION_TAG = os.getenv("NAV_VERSION_TAG", "")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "28.0.1")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "29.0.0")
BITCOINCASH_VERSION_TAG = os.getenv("BITCOINCASH_VERSION_TAG", "")
DOGECOIN_VERSION = os.getenv("DOGECOIN_VERSION", "23.2.1")
@@ -1757,15 +1757,15 @@ def printHelp():
def finalise_daemon(d):
logging.info("Interrupting {}".format(d.handle.pid))
logging.info(f"Interrupting {d.name} {d.handle.pid}")
try:
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT)
d.handle.wait(timeout=120)
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
if fp:
fp.close()
except Exception as e:
logging.info(f"Error {e} for process {d.handle.pid}")
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
if fp:
fp.close()
logging.info(f"Error stopping {d.name}, process {d.handle.pid}: {e}")
def test_particl_encryption(data_dir, settings, chain, use_tor_proxy, extra_opts):

View File

@@ -618,7 +618,7 @@ def runClient(
signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT
)
except Exception as e:
swap_client.log.info(f"Interrupting {d.name} {d.handle.pid}, error {e}")
swap_client.log.error(f"Interrupting {d.name} {d.handle.pid}: {e}")
for d in daemons:
try:
d.handle.wait(timeout=120)
@@ -627,7 +627,9 @@ def runClient(
fp.close()
closed_pids.append(d.handle.pid)
except Exception as e:
swap_client.log.error(f"Error: {e}")
swap_client.log.error(
f"Waiting for {d.name} {d.handle.pid} to shutdown: {e}"
)
fail_code: int = swap_client.fail_code
del swap_client

View File

@@ -242,7 +242,7 @@ chainparams = {
"pubkey_address": 0x0E91,
"script_address": 0x0E6C,
"key_prefix": 0x2307,
"bip44": 1,
"bip44": 115,
"min_amount": 100000,
"max_amount": 10000000 * COIN,
},

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# 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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -13,7 +13,7 @@ from enum import IntEnum, auto
from typing import Optional
CURRENT_DB_VERSION = 32
CURRENT_DB_VERSION = 33
CURRENT_DB_DATA_VERSION = 7
@@ -76,10 +76,16 @@ class Table:
__sqlite3_table__ = True
def __init__(self, **kwargs):
init_all_columns: bool = True
for name, value in kwargs.items():
if name == "_init_all_columns":
init_all_columns = value
continue
if not hasattr(self, name):
raise ValueError(f"Unknown attribute {name}")
setattr(self, name, value)
if init_all_columns is False:
return
# Init any unset columns to None
for mc in inspect.getmembers(self):
mc_name, mc_obj = mc
@@ -135,6 +141,20 @@ class Index:
self.column_3 = column_3
class StateRows:
state = Column("integer")
state_time = Column("integer") # Timestamp of last state change
states = Column("blob") # Packed states and times
def setState(self, new_state, state_time=None):
now = int(time.time()) if state_time is None else state_time
self.state = new_state
if self.isSet("states") is False:
self.states = pack_state(new_state, now)
else:
self.states += pack_state(new_state, now)
class DBKVInt(Table):
__tablename__ = "kv_int"
@@ -149,7 +169,7 @@ class DBKVString(Table):
value = Column("string")
class Offer(Table):
class Offer(Table, StateRows):
__tablename__ = "offers"
offer_id = Column("blob", primary_key=True)
@@ -197,19 +217,8 @@ class Offer(Table):
bid_reversed = Column("bool")
smsg_payload_version = Column("integer")
state = Column("integer")
states = Column("blob") # Packed states and times
def setState(self, new_state):
now = int(time.time())
self.state = new_state
if self.isSet("states") is False:
self.states = pack_state(new_state, now)
else:
self.states += pack_state(new_state, now)
class Bid(Table):
class Bid(Table, StateRows):
__tablename__ = "bids"
bid_id = Column("blob", primary_key=True)
@@ -244,11 +253,7 @@ class Bid(Table):
participate_txn_refund = Column("blob")
in_progress = Column("integer")
state = Column("integer")
state_time = Column("integer") # Timestamp of last state change
states = Column("blob") # Packed states and times
state_note = Column("string")
was_sent = Column("bool") # Sent by node
was_received = Column("bool")
contract_count = Column("integer")
@@ -287,25 +292,13 @@ class Bid(Table):
if self.isSet("participate_tx"):
self.participate_tx.setState(new_state)
def setState(self, new_state, state_note=None):
now = int(time.time())
self.state = new_state
self.state_time = now
if self.isSet("state_note"):
self.state_note = state_note
if self.isSet("states") is False:
self.states = pack_state(new_state, now)
else:
self.states += pack_state(new_state, now)
def getLockTXBVout(self):
if self.isSet("xmr_b_lock_tx"):
return self.xmr_b_lock_tx.vout
return None
class SwapTx(Table):
class SwapTx(Table, StateRows):
__tablename__ = "transactions"
bid_id = Column("blob")
@@ -328,21 +321,8 @@ class SwapTx(Table):
block_height = Column("integer")
block_time = Column("integer")
state = Column("integer")
states = Column("blob") # Packed states and times
primary_key = PrimaryKeyConstraint("bid_id", "tx_type")
def setState(self, new_state):
if self.state == new_state:
return
self.state = new_state
now: int = int(time.time())
if self.isSet("states") is False:
self.states = pack_state(new_state, now)
else:
self.states += pack_state(new_state, now)
class PrefundedTx(Table):
__tablename__ = "prefunded_transactions"
@@ -1059,7 +1039,7 @@ class DBMethods:
if cursor is None:
self.closeDB(use_cursor, commit=False)
def add(self, obj, cursor, upsert: bool = False):
def add(self, obj, cursor, upsert: bool = False, columns_list=None):
if cursor is None:
raise ValueError("Cursor is null")
if not hasattr(obj, "__tablename__"):
@@ -1072,7 +1052,8 @@ class DBMethods:
# See if the instance overwrote any class methods
for mc in inspect.getmembers(obj.__class__):
mc_name, mc_obj = mc
if columns_list is not None and mc_name not in columns_list:
continue
if not hasattr(mc_obj, "__sqlite3_column__"):
continue
@@ -1113,6 +1094,7 @@ class DBMethods:
order_by={},
query_suffix=None,
extra_query_data={},
columns_list=None,
):
if cursor is None:
raise ValueError("Cursor is null")
@@ -1125,6 +1107,8 @@ class DBMethods:
for mc in inspect.getmembers(table_class):
mc_name, mc_obj = mc
if columns_list is not None and mc_name not in columns_list:
continue
if not hasattr(mc_obj, "__sqlite3_column__"):
continue
if len(columns) > 0:
@@ -1193,6 +1177,7 @@ class DBMethods:
order_by={},
query_suffix=None,
extra_query_data={},
columns_list=None,
):
return firstOrNone(
self.query(
@@ -1202,10 +1187,11 @@ class DBMethods:
order_by,
query_suffix,
extra_query_data,
columns_list,
)
)
def updateDB(self, obj, cursor, constraints=[]):
def updateDB(self, obj, cursor, constraints=[], columns_list=None):
if cursor is None:
raise ValueError("Cursor is null")
if not hasattr(obj, "__tablename__"):
@@ -1217,7 +1203,6 @@ class DBMethods:
values = {}
for mc in inspect.getmembers(obj.__class__):
mc_name, mc_obj = mc
if not hasattr(mc_obj, "__sqlite3_column__"):
continue
@@ -1229,7 +1214,8 @@ class DBMethods:
if mc_name in constraints:
values[mc_name] = m_obj
continue
if columns_list is not None and mc_name not in columns_list:
continue
if len(values) > 0:
query += ", "
query += f"{mc_name} = :{mc_name}"

View File

@@ -2,13 +2,14 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Copyright (c) 2025 The Basicswap developers
# Copyright (c) 2025-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import threading
from enum import IntEnum
from typing import List
from basicswap.chainparams import (
chainparams,
@@ -30,6 +31,7 @@ from basicswap.util.ecc import (
)
from coincurve.dleag import verify_secp256k1_point
from coincurve.keys import (
PrivateKey,
PublicKey,
)
@@ -179,13 +181,16 @@ class CoinInterface:
class AdaptorSigInterface:
def getScriptLockTxDummyWitness(self, script: bytes):
def getP2WPKHDummyWitness(self) -> List[bytes]:
return [bytes(72), bytes(33)]
def getScriptLockTxDummyWitness(self, script: bytes) -> List[bytes]:
return [b"", bytes(72), bytes(72), bytes(len(script))]
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes) -> List[bytes]:
return [b"", bytes(72), bytes(72), bytes((1,)), bytes(len(script))]
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes) -> List[bytes]:
return [bytes(72), b"", bytes(len(script))]
@@ -227,8 +232,7 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
return pubkey.verify(sig, signed_hash, hasher=None)
def sumKeys(self, ka: bytes, kb: bytes) -> bytes:
# TODO: Add to coincurve
return i2b((b2i(ka) + b2i(kb)) % ep.o)
return PrivateKey(ka).add(kb).secret
def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes:
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# 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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -828,7 +828,7 @@ class BTCInterface(Secp256k1Interface):
def getScriptDummyWitness(self, script: bytes) -> List[bytes]:
if self.isScriptP2WPKH(script):
return [bytes(72), bytes(33)]
return self.getP2WPKHDummyWitness()
raise ValueError("Unknown script type")
def createSCLockRefundTx(
@@ -1943,9 +1943,16 @@ class BTCInterface(Secp256k1Interface):
raise ValueError("Unimplemented")
def getWitnessStackSerialisedLength(self, witness_stack):
length = getCompactSizeLen(len(witness_stack))
for e in witness_stack:
length += getWitnessElementLen(len(e))
length: int = 0
if len(witness_stack) > 0 and isinstance(witness_stack[0], list):
for input_stack in witness_stack:
length += getCompactSizeLen(len(input_stack))
for e in input_stack:
length += getWitnessElementLen(len(e))
else:
length += getCompactSizeLen(len(witness_stack))
for e in witness_stack:
length += getWitnessElementLen(len(e))
# See core SerializeTransaction
length += 1 # vinDummy

View File

@@ -406,7 +406,10 @@ class DCRInterface(Secp256k1Interface):
# Adjust verificationprogress to consider blocks wallet has synced
wallet_blocks = self.rpc_wallet("getinfo")["blocks"]
synced_ind = bci["verificationprogress"]
wallet_synced_ind = wallet_blocks / bci["headers"]
if bci["headers"] < 1:
wallet_synced_ind = 0
else:
wallet_synced_ind = wallet_blocks / bci["headers"]
if wallet_synced_ind < synced_ind:
bci["verificationprogress"] = wallet_synced_ind

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Copyright (c) 2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -35,6 +36,13 @@ def createDCRWallet(args, hex_seed, logging, delay_event):
response = b"y\n"
elif "Enter existing wallet seed" in buf:
response = (hex_seed + "\n").encode("utf-8")
elif "Do you have a wallet birthday we should rescan from" in buf:
response = b"no\n"
elif (
"Do you have an additional account to import from an extended public key"
in buf
):
response = b"no\n"
elif "Seed input successful" in buf:
pass
elif "Upgrading database from version" in buf:

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# 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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -102,6 +102,100 @@ class FIROInterface(BTCInterface):
return addr_info["ismine"]
return addr_info["ismine"] or addr_info["iswatchonly"]
def getNewSparkAddress(self) -> str:
try:
return self.rpc_wallet("getnewsparkaddress")[0]
except Exception as e:
self._log.error(f"getnewsparkaddress failed: {str(e)}")
raise
def getNewStealthAddress(self):
"""Get a new Spark address (alias for consistency with other coins)."""
return self.getNewSparkAddress()
def getWalletInfo(self):
"""Get wallet info including Spark balance."""
rv = super(FIROInterface, self).getWalletInfo()
try:
spark_balance_info = self.rpc("getsparkbalance")
# getsparkbalance returns amounts in atomic units (satoshis)
# Field names: availableBalance, unconfirmedBalance, fullBalance
confirmed = spark_balance_info.get("availableBalance", 0)
unconfirmed = spark_balance_info.get("unconfirmedBalance", 0)
full_balance = spark_balance_info.get("fullBalance", 0)
# Values are already in atomic units, keep as integers
# basicswap.py will format them using format_amount
rv["spark_balance"] = confirmed if confirmed else 0
rv["spark_unconfirmed"] = unconfirmed if unconfirmed else 0
immature = full_balance - confirmed - unconfirmed
rv["spark_immature"] = immature if immature > 0 else 0
except Exception as e:
self._log.warning(f"getsparkbalance failed: {str(e)}")
rv["spark_balance"] = 0
rv["spark_unconfirmed"] = 0
rv["spark_immature"] = 0
return rv
def createUTXO(self, value_sats: int):
# Create a new address and send value_sats to it
spendable_balance = self.getSpendableBalance()
if spendable_balance < value_sats:
raise ValueError("Balance too low")
address = self.getNewAddress(self._use_segwit, "create_utxo")
return (
self.withdrawCoin(self.format_amount(value_sats), "plain", address, False),
address,
)
def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str:
"""Withdraw coins, supporting both transparent and Spark transactions.
Args:
value: Amount to withdraw
type_from: "plain" for transparent, "spark" for Spark
addr_to: Destination address
subfee: Whether to subtract fee from amount
"""
type_to = "spark" if addr_to.startswith("sm1") else "plain"
if "spark" in (type_from, type_to):
# RPC format: spendspark {"address": {"amount": ..., "subtractfee": ..., "memo": ...}}
# RPC wrapper will serialize this as: {"method": "spendspark", "params": [{...}], ...}
try:
if type_from == "spark":
# Construct params: dict where address is the key, wrapped in array for RPC
params = [
{"address": addr_to, "amount": value, "subtractfee": subfee}
]
result = self.rpc_wallet("spendspark", params)
else:
# Use automintspark to perform a plain -> spark tx of full balance
balance = self.rpc_wallet("getbalance")
if str(balance) == str(value):
result = self.rpc_wallet("automintspark")
else:
# subfee param is available on plain -> spark transactions
mint_params = {"amount": value}
if subfee:
mint_params["subfee"] = True
params = [{addr_to: mint_params}]
result = self.rpc_wallet("mintspark", params)
# spendspark returns a txid string directly, in a result dict, or as an array
if isinstance(result, list) and len(result) > 0:
return result[0]
if isinstance(result, dict):
return result.get("txid", result.get("tx", ""))
return result
except Exception as e:
self._log.error(f"spark tx failed: {str(e)}")
raise
else:
# Use standard sendtoaddress for transparent transactions
params = [addr_to, value, "", "", subfee]
return self.rpc_wallet("sendtoaddress", params)
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
lock_tx_dest = self.getScriptDest(lock_script)
address = self.encodeScriptDest(lock_tx_dest)
@@ -252,10 +346,6 @@ class FIROInterface(BTCInterface):
assert len(script_hash) == 20
return CScript([OP_HASH160, script_hash, OP_EQUAL])
def withdrawCoin(self, value, addr_to, subfee):
params = [addr_to, value, "", "", subfee]
return self.rpc("sendtoaddress", params)
def getWalletSeedID(self):
return self.rpc("getwalletinfo")["hdmasterkeyid"]

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# 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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -137,7 +137,7 @@ class PARTInterface(BTCInterface):
def getScriptDummyWitness(self, script: bytes) -> List[bytes]:
if self.isScriptP2WPKH(script) or self.isScriptP2PKH(script):
return [bytes(72), bytes(33)]
return self.getP2WPKHDummyWitness()
raise ValueError("Unknown script type")
def formatStealthAddress(self, scan_pubkey, spend_pubkey) -> str:
@@ -146,9 +146,16 @@ class PARTInterface(BTCInterface):
return encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey)
def getWitnessStackSerialisedLength(self, witness_stack) -> int:
length: int = getCompactSizeLen(len(witness_stack))
for e in witness_stack:
length += getWitnessElementLen(len(e))
length: int = 0
if len(witness_stack) > 0 and isinstance(witness_stack[0], list):
for input_stack in witness_stack:
length += getCompactSizeLen(len(input_stack))
for e in input_stack:
length += getWitnessElementLen(len(e))
else:
length += getCompactSizeLen(len(witness_stack))
for e in witness_stack:
length += getWitnessElementLen(len(e))
return length
def getWalletRestoreHeight(self) -> int:

View File

@@ -79,9 +79,11 @@ def withdraw_coin(swap_client, coin_type, post_string, is_json):
txid_hex = swap_client.withdrawParticl(
type_from, type_to, value, address, subfee
)
elif coin_type == Coins.LTC:
elif coin_type in (Coins.LTC, Coins.FIRO):
type_from = get_data_entry_or(post_data, "type_from", "plain")
txid_hex = swap_client.withdrawLTC(type_from, value, address, subfee)
txid_hex = swap_client.withdrawCoinExtended(
coin_type, type_from, value, address, subfee
)
elif coin_type in (Coins.XMR, Coins.WOW):
txid_hex = swap_client.withdrawCoin(coin_type, value, address, sweepall)
else:

View File

@@ -130,10 +130,7 @@ def redeemITx(self, bid_id: bytes, cursor):
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
self.log.debug(
"Submitted initiate redeem txn %s to %s chain for bid %s",
txid,
ci_from.coin_name(),
bid_id.hex(),
f"Submitted initiate redeem txn {self.logIDT(txid)} to {ci_from.coin_name()} chain for bid {self.logIDB(bid_id)}"
)
self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, "", cursor)

View File

@@ -119,7 +119,7 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
lock_tx_vout=lock_tx_vout,
)
self.log.debug(
f"Submitted lock B spend txn {self.log.id(txid)} to {ci_follower.coin_name()} chain for bid {self.log.id(bid_id)}."
f"Submitted lock B spend txn {self.logIDT(txid)} to {ci_follower.coin_name()} chain for bid {self.log.id(bid_id)}."
)
self.logBidEvent(
bid.bid_id,

View File

@@ -23,6 +23,11 @@
types: ['default'],
hasSubfee: false,
hasSweepAll: true
},
13: {
types: ['plain', 'spark'],
hasSubfee: true,
hasSweepAll: false
}
},
@@ -64,6 +69,17 @@
}
}
if (cid === 13) {
switch(selectedType) {
case 'plain':
return this.safeParseFloat(balances.main || balances.balance);
case 'spark':
return this.safeParseFloat(balances.spark);
default:
return this.safeParseFloat(balances.main || balances.balance);
}
}
return this.safeParseFloat(balances.main || balances.balance);
},
@@ -188,7 +204,8 @@
balance: balance,
blind: balance2,
anon: balance3,
mweb: balance2
mweb: balance2,
spark: balance2
};
WalletAmountManager.setAmount(percent, balances, coinId);
};

View File

@@ -35,6 +35,8 @@
</section>
{% endif %}
{% if w.havedata %}
{% if w.error %}
<section class="py-4 px-6" id="messages_error" role="alert">
@@ -146,8 +148,20 @@
{% endif %}
</td>
</tr>
{% elif w.cid == '13' %} {# FIRO #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Spark"> </span>Spark Balance: </td>
<td class="py-3 px-6 bold">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>)
{% if w.spark_pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.spark_pending }} {{ w.ticker }} </span>
{% endif %}
</td>
</tr>
{% endif %}
{# / LTC #}
{# / FIRO #}
{% if w.locked_utxos %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Locked Outputs:</td>
@@ -286,8 +300,8 @@
</div>
</div>
</div>
{% if w.cid in '1, 3, 6, 9' %}
{# PART | LTC | XMR | WOW | #}
{% if w.cid in '1, 3, 6, 9, 13' %}
{# PART | LTC | XMR | WOW | FIRO #}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
@@ -334,6 +348,22 @@
</div>
</div>
{# / LTC #}
{% elif w.cid == '13' %}
{# FIRO #}
<div id="qrcode-spark" class="qrcode" data-qrcode data-address="{{ w.spark_address }}"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Spark Address: </div>
<div class="text-center relative">
<div class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="stealth_address">{{ w.spark_address }}</div>
<span class="absolute inset-y-0 right-0 flex items-center pr-3 cursor-pointer" id="copyIcon"></span>
</div>
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
<div class="py-3 px-6 bold mt-5">
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newsparkaddr_{{ w.cid }}" value="New Spark Address"> {{ circular_arrows_svg }} New Spark Address </button>
</div>
</div>
{# / FIRO #}
{% endif %}
</div>
</div>
@@ -397,6 +427,15 @@
(<span class="usd-value"></span>)
</td>
</tr>
{% elif w.cid == '13' %}
{# FIRO #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Spark Balance: </td>
<td class="py-3 px-6">
<span class="coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span>
(<span class="usd-value"></span>)
</td>
</tr>
{% elif w.cid == '1' %}
{# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
@@ -487,6 +526,14 @@
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">100%</button>
{# / LTC #}
{% elif w.cid == '13' %}
{# FIRO #}
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.spark_balance }}')">25%</button>
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.spark_balance }}')">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.spark_balance }}')">100%</button>
{# / FIRO #}
{% else %}
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }})">25%</button>
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }})">50%</button>
@@ -553,8 +600,21 @@
</div>
</td>
</tr>
{% elif w.cid == '13' %} {# FIRO #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Type From:</td>
<td class="py-3 px-6">
<div class="w-full md:flex-1">
<div class="relative"> {{ select_box_arrow_svg }} <select id="withdraw_type" class="{{ select_box_class }}" name="withdraw_type_from_{{ w.cid }}">
<option value="spark" {% if w.wd_type_from=='spark' %} selected{% endif %}>Spark</option>
<option value="plain" {% if w.wd_type_from=='plain' %} selected{% endif %}>Plain</option>
</select> </div>
</div>
</td>
</tr>
{% endif %}
{# / LTC #}
{# / FIRO #}
{% if w.cid not in '6,9' %} {# Not XMR WOW #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Fee Rate:</td>

View File

@@ -132,6 +132,28 @@
{% endif %}
{% endif %}
{# / LTC #}
{% if w.cid == '13' %} {# FIRO #}
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Spark Balance:</h4>
<span class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 coinname-value" data-coinname="{{ w.name }}">{{ w.spark_balance }} {{ w.ticker }}</span>
</div>
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Spark USD value:</h4>
<div class="bold inline-block py-1 px-2 rounded-full bg-blue-100 text-xs text-black-500 dark:bg-gray-500 dark:text-gray-200 usd-value"></div>
</div>
{% if w.spark_pending %}
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Spark Pending:</h4>
<span class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 coinname-value" data-coinname="{{ w.name }}">
+{{ w.spark_pending }} {{ w.ticker }}</span>
</div>
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-bold text-green-500 dark:text-green-500">Spark Pending USD value:</h4>
<div class="bold inline-block py-1 px-2 rounded-full bg-green-100 text-xs text-green-500 dark:bg-gray-500 dark:text-green-500 usd-value"></div>
</div>
{% endif %}
{% endif %}
{# / FIRO #}
<hr class="border-t border-gray-100 dark:border-gray-500 my-5">
<div class="flex mb-2 justify-between items-center">
<h4 class="text-xs font-medium dark:text-white">Blocks:</h4>

View File

@@ -82,6 +82,10 @@ def format_wallet_data(swap_client, ci, w):
wf["mweb_address"] = w.get("mweb_address", "?")
wf["mweb_balance"] = w.get("mweb_balance", "?")
wf["mweb_pending"] = w.get("mweb_pending", "?")
elif ci.coin_type() == Coins.FIRO:
wf["spark_address"] = w.get("spark_address", "?")
wf["spark_balance"] = w.get("spark_balance", "?")
wf["spark_pending"] = w.get("spark_pending", "?")
checkAddressesOwned(swap_client, ci, wf)
return wf
@@ -163,6 +167,8 @@ def page_wallet(self, url_split, post_string):
force_refresh = True
elif have_data_entry(form_data, "newmwebaddr_" + cid):
swap_client.cacheNewStealthAddressForCoin(coin_id)
elif have_data_entry(form_data, "newsparkaddr_" + cid):
swap_client.cacheNewStealthAddressForCoin(coin_id)
elif have_data_entry(form_data, "reseed_" + cid):
try:
swap_client.reseedWallet(coin_id)
@@ -208,7 +214,7 @@ def page_wallet(self, url_split, post_string):
page_data["wd_type_to_" + cid] = type_to
except Exception as e: # noqa: F841
err_messages.append("Missing type")
elif coin_id == Coins.LTC:
elif coin_id in (Coins.LTC, Coins.FIRO):
try:
type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][
0
@@ -230,9 +236,9 @@ def page_wallet(self, url_split, post_string):
value, ticker, type_from, type_to, address, txid
)
)
elif coin_id == Coins.LTC:
txid = swap_client.withdrawLTC(
type_from, value, address, subfee
elif coin_id in (Coins.LTC, Coins.FIRO):
txid = swap_client.withdrawCoinExtended(
coin_id, type_from, value, address, subfee
)
messages.append(
"Withdrew {} {} (from {}) to address {}<br/>In txid: {}".format(
@@ -342,6 +348,8 @@ def page_wallet(self, url_split, post_string):
wallet_data["main_address"] = w.get("main_address", "Refresh necessary")
elif k == Coins.LTC:
wallet_data["mweb_address"] = w.get("mweb_address", "Refresh necessary")
elif k == Coins.FIRO:
wallet_data["spark_address"] = w.get("spark_address", "Refresh necessary")
if "wd_type_from_" + cid in page_data:
wallet_data["wd_type_from"] = page_data["wd_type_from_" + cid]

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# 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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -251,7 +251,7 @@ def describeBid(
elif bid.state == BidStates.BID_ABANDONED:
state_description = "Bid abandoned"
elif bid.state == BidStates.BID_ERROR:
state_description = bid.state_note
state_description = "Bid error"
elif offer.swap_type == SwapTypes.XMR_SWAP:
if bid.state == BidStates.BID_SENT:
state_description = "Waiting for offerer to accept"

View File

@@ -79,13 +79,13 @@ def main():
continue
if coin_name in ("monero", "wownero"):
with open(
os.path.join(fragments_dir, "1_{coin_name}-wallet.yml"), "rb"
os.path.join(fragments_dir, f"1_{coin_name}-wallet.yml"), "rb"
) as fp_in:
for line in fp_in:
fp.write(line)
fpp.write(line)
with open(
os.path.join(fragments_dir, "8_{coin_name}-daemon.yml"), "rb"
os.path.join(fragments_dir, f"8_{coin_name}-daemon.yml"), "rb"
) as fp_in:
for line in fp_in:
fp.write(line)

View File

@@ -90,7 +90,7 @@
(define python-coincurve-basicswap
(package
(name "python-coincurve-basicswap")
(version "basicswap_v0.2")
(version "basicswap_v0.3")
(source
(origin
(method git-fetch)
@@ -101,7 +101,7 @@
(file-name
(git-file-name name version))
(sha256
(base32 "1vm9cvwr0z02zc0mp7l8qj9vhg8kmfrzysiwzg91zkgmccza9ryc"))))
(base32 "08bc8175v4d479lgavkcclc0kkh3icxm9i0i26wqd1g3bv0is8cm"))))
(build-system pyproject-build-system)
(arguments
`(#:phases
@@ -135,15 +135,15 @@
(define-public basicswap
(package
(name "basicswap")
(version "0.15.1")
(version "0.15.2")
(source (origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/basicswap/basicswap")
(commit "0bc9d3a5db40f54d79e2ab18be58b6bbc20740d1")))
(commit "83807d213fab52c99f69dbc06fa7baedb449d66f")))
(sha256
(base32
"1x6c6hynvbayq4cyv9s6vwgsgdmhm7r1av6iy7pax103lj20habf"))
"08ykwn2wbcny5k6kwj3xkfkim40kmzcb988lpcd70r7kcmn8ggp0"))
(file-name (git-file-name name version))))
(build-system pyproject-build-system)

View File

@@ -1,5 +1,5 @@
pyzmq==27.1.0
python-gnupg==0.5.5
python-gnupg==0.5.6
Jinja2==3.1.6
pycryptodome==3.23.0
PySocks==1.7.1

View File

@@ -122,9 +122,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via -r requirements.in
python-gnupg==0.5.5 \
--hash=sha256:3fdcaf76f60a1b948ff8e37dc398d03cf9ce7427065d583082b92da7a4ff5a63 \
--hash=sha256:51fa7b8831ff0914bc73d74c59b99c613de7247b91294323c39733bb85ac3fc1
python-gnupg==0.5.6 \
--hash=sha256:5743e96212d38923fc19083812dc127907e44dbd3bcf0db4d657e291d3c21eac \
--hash=sha256:b5050a55663d8ab9fcc8d97556d229af337a87a3ebebd7054cbd8b7e2043394a
# via -r requirements.in
pyzmq==27.1.0 \
--hash=sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d \

View File

@@ -729,7 +729,25 @@ def process_offers(args, config, script_state) -> None:
matching_sent_offers.append(offer)
offers_found += 1
if wallet_balance <= float(offer_template["min_coin_from_amt"]):
offer_amount_from = float(offer.get("amount_from", 0))
min_coin_from_amt = float(offer_template.get("min_coin_from_amt", 0))
if offer_amount_from > wallet_balance:
print(
f"Revoking offer {offer_id}, offer amount {offer_amount_from:.8f} > wallet balance {wallet_balance:.8f}"
)
result = read_json_api(f"revokeoffer/{offer_id}")
if args.debug:
print("revokeoffer", result)
else:
print("Offer revoked, will repost with accurate amount")
for i, prev_offer in enumerate(prev_template_offers):
if prev_offer.get("offer_id") == offer_id:
del prev_template_offers[i]
break
write_state(args.statefile, script_state)
offers_found -= 1
elif wallet_balance <= min_coin_from_amt:
print(
"Revoking offer {}, wallet from balance below minimum".format(
offer_id
@@ -1169,11 +1187,6 @@ def process_offers(args, config, script_state) -> None:
)
use_rate = offer_template["minrate"]
# Final minimum rate check after all adjustments
if use_rate < offer_template["minrate"]:
print("Warning: Final rate clamping to minimum after all adjustments.")
use_rate = offer_template["minrate"]
if args.debug:
print(
"Creating offer for: {} at rate: {}".format(

View File

@@ -622,6 +622,18 @@ class TestBase(unittest.TestCase):
raise ValueError(f"wait_for_particl_height failed http_port: {http_port}")
def run_process(client_id):
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
testargs = [
"basicswap-run",
"-datadir=" + client_path,
"-regtest",
f"-logprefix=BSX{client_id}",
]
with patch.object(sys, "argv", testargs):
runSystem.main()
class XmrTestBase(TestBase):
@classmethod
def setUpClass(cls):
@@ -632,23 +644,12 @@ class XmrTestBase(TestBase):
prepare_nodes(3, "monero")
def run_thread(self, client_id):
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
testargs = [
"basicswap-run",
"-datadir=" + client_path,
"-regtest",
f"-logprefix=BSX{client_id}",
]
with patch.object(sys, "argv", testargs):
runSystem.main()
def start_processes(self):
self.delay_event.clear()
for i in range(3):
self.processes.append(
multiprocessing.Process(target=self.run_thread, args=(i,))
multiprocessing.Process(target=run_process, args=(i,))
)
self.processes[-1].start()

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -689,7 +689,7 @@ def prepareDCDDataDir(datadir, node_id, conf_file, dir_prefix, num_nodes=3):
"noseeders=1\n",
"nodnsseed=1\n",
"nodiscoverip=1\n",
"miningaddr=SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH\n",
"miningaddr=SsppG7KLiH52NC7iJmUVGVq89FLS83E5vho\n",
]
for i in range(0, num_nodes):
@@ -725,7 +725,8 @@ class Test(BaseTest):
dcr_daemons = []
start_ltc_nodes = False
start_xmr_nodes = True
dcr_mining_addr = "SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH"
# Addresses differ after 2.1.2, simnet bip44id changed from 1 to 115
dcr_mining_addr = "SsppG7KLiH52NC7iJmUVGVq89FLS83E5vho"
extra_wait_time = 0
max_fee: int = 10000
@@ -739,7 +740,8 @@ class Test(BaseTest):
def prepareExtraCoins(cls):
ci0 = cls.swap_clients[0].ci(cls.test_coin)
if not cls.restore_instance:
assert ci0.rpc_wallet("getnewaddress") == cls.dcr_mining_addr
dcr_mining_addr = ci0.rpc_wallet("getnewaddress")
assert dcr_mining_addr in cls.dcr_mining_addrs
cls.dcr_ticket_account = ci0.rpc_wallet(
"getaccount",
[
@@ -776,7 +778,7 @@ class Test(BaseTest):
num_passed: int = 0
for i in range(30):
try:
ci0.rpc_wallet("purchaseticket", [cls.dcr_ticket_account, 0.1, 0])
ci0.rpc_wallet("purchaseticket", [cls.dcr_ticket_account, 0, 1])
num_passed += 1
if num_passed >= 5:
break
@@ -902,14 +904,14 @@ class Test(BaseTest):
masterpubkey = loop_ci.rpc_wallet("getmasterpubkey")
masterpubkey_data = loop_ci.decode_address(masterpubkey)[4:]
seed_hash = loop_ci.getSeedHash(root_key)
seed_hash: bytes = loop_ci.getSeedHash(root_key)
if i == 0:
assert (
masterpubkey
== "spubVV1z2AFYjVZvzM45FSaWMPRqyUoUwyW78wfANdjdNG6JGCXrr8AbRvUgYb3Lm1iun9CgHew1KswdePryNLKEnBSQ82AjNpYdQgzXPUme9c6"
== "spubVUjNdu1HtDuQYHjVLTgdK3JKtC7JQoCUkhkoVn3rJt6kYctRksn4vTGsdV3obeZLHaB1YobsLENYKHjtey67LFZJdjJyAvHuRqgFRpaSmfn"
)
if i < 2:
assert seed_hash == hash160(masterpubkey_data)
assert hash160(masterpubkey_data) == seed_hash
def test_001_segwit(self):
logging.info("---------- Test {} segwit".format(self.test_coin.name))

View File

@@ -32,6 +32,7 @@ from tests.basicswap.common import (
waitForNumSwapping,
)
from tests.basicswap.common_xmr import (
run_process,
XmrTestBase,
)
@@ -122,7 +123,7 @@ class Test(XmrTestBase):
c1 = self.processes[1]
c1.terminate()
c1.join()
self.processes[1] = multiprocessing.Process(target=self.run_thread, args=(1,))
self.processes[1] = multiprocessing.Process(target=run_process, args=(1,))
self.processes[1].start()
waitForServer(self.delay_event, 12701)

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -199,7 +199,7 @@ class Test(BaseTest):
0, "getnewaddress", ["mining_addr"], base_rpc_port=FIRO_BASE_RPC_PORT
)
# cls.firo_addr = callnoderpc(0, 'addwitnessaddress', [cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT)
logging.info("Mining %d Firo blocks to %s", num_blocks, cls.firo_addr)
logging.info(f"Mining {num_blocks} Firo blocks to {cls.firo_addr}")
callnoderpc(
0,
"generatetoaddress",
@@ -230,7 +230,7 @@ class Test(BaseTest):
0, "getblockcount", base_rpc_port=FIRO_BASE_RPC_PORT
)
num_blocks = 1352 - chain_height # Activate CTLV (bip65)
logging.info("Mining %d Firo blocks to %s", num_blocks, cls.firo_addr)
logging.info(f"Mining {num_blocks} Firo blocks to {cls.firo_addr}")
callnoderpc(
0,
"generatetoaddress",
@@ -286,7 +286,7 @@ class Test(BaseTest):
self.callnoderpc("generatetoaddress", [num_blocks, self.firo_addr])
def test_001_firo(self):
logging.info("---------- Test {} segwit".format(self.test_coin_from.name))
logging.info(f"---------- Test {self.test_coin_from.name} segwit")
"""
Segwit is not currently enabled:
@@ -339,7 +339,7 @@ class Test(BaseTest):
assert txid_with_scriptsig == tx_signed_decoded["txid"]
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")
swap_client = self.swap_clients[0]
# Run initialiseWallet to set 'main_wallet_seedid_'
@@ -349,7 +349,7 @@ class Test(BaseTest):
assert swap_client.checkWalletSeed(self.test_coin_from) is True
def test_008_gettxout(self):
logging.info("---------- Test {} gettxout".format(self.test_coin_from.name))
logging.info(f"---------- Test {self.test_coin_from.name} gettxout")
swap_client = self.swap_clients[0]
@@ -428,7 +428,7 @@ class Test(BaseTest):
assert amount_proved >= require_amount
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")
addr = self.callnoderpc(
@@ -447,7 +447,7 @@ class Test(BaseTest):
}
json_rv = read_json_api(
TEST_HTTP_PORT + 0,
"wallets/{}/withdraw".format(self.test_coin_from.name.lower()),
f"wallets/{self.test_coin_from.name.lower()}/withdraw",
post_json,
)
assert len(json_rv["txid"]) == 64
@@ -458,7 +458,7 @@ class Test(BaseTest):
}
json_rv = read_json_api(
TEST_HTTP_PORT + 0,
"wallets/{}/createutxo".format(self.test_coin_from.name.lower()),
f"wallets/{self.test_coin_from.name.lower()}/createutxo",
post_json,
)
assert len(json_rv["txid"]) == 64
@@ -473,6 +473,14 @@ class Test(BaseTest):
ci_from = swap_clients[0].ci(coin_from)
ci_to = swap_clients[1].ci(coin_to)
id_bidder: int = 1
self.prepare_balance(
coin_to,
100.0,
1800 + id_bidder,
1801 if coin_to in (Coins.XMR,) else 1800,
)
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
offer_id = swap_clients[0].postOffer(
@@ -506,9 +514,7 @@ class Test(BaseTest):
coin_from = Coins.BTC
coin_to = Coins.FIRO
logging.info(
"---------- Test {} to {} follower recovers coin b lock tx".format(
coin_from.name, coin_to.name
)
f"---------- Test {coin_from.name} to {coin_to.name} follower recovers coin b lock tx"
)
swap_clients = self.swap_clients
@@ -568,6 +574,14 @@ class Test(BaseTest):
coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type
)
id_bidder: int = 1
self.prepare_balance(
coin_to,
100.0,
1800 + id_bidder,
1801 if coin_to in (Coins.XMR,) else 1800,
)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
@@ -592,7 +606,7 @@ class Test(BaseTest):
)
def test_101_full_swap(self):
logging.info("---------- Test {} to XMR".format(self.test_coin_from.name))
logging.info(f"---------- Test {self.test_coin_from.name} to XMR")
if not self.test_xmr:
logging.warning("Skipping test")
return

View File

@@ -45,6 +45,18 @@ if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout))
def run_process(client_id):
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
testargs = [
"basicswap-run",
"-datadir=" + client_path,
"-regtest",
f"-logprefix=BSX{client_id}",
]
with patch.object(sys, "argv", testargs):
runSystem.main()
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -64,24 +76,13 @@ class Test(unittest.TestCase):
run_prepare(i, client_path, bins_path, "monero,bitcoin", mnemonics[0])
def run_thread(self, client_id):
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
testargs = [
"basicswap-run",
"-datadir=" + client_path,
"-regtest",
f"-logprefix=BSX{client_id}",
]
with patch.object(sys, "argv", testargs):
runSystem.main()
def test_wallet(self):
update_thread = None
processes = []
time.sleep(5)
for i in range(2):
processes.append(multiprocessing.Process(target=self.run_thread, args=(i,)))
processes.append(multiprocessing.Process(target=run_process, args=(i,)))
processes[-1].start()
try:

View File

@@ -102,6 +102,18 @@ def prepare_node(node_id, mnemonic):
)
def run_process(client_id):
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
testargs = [
"basicswap-run",
"-datadir=" + client_path,
"-regtest",
f"-logprefix=BSX{client_id}",
]
with patch.object(sys, "argv", testargs):
runSystem.main()
class Test(TestBase):
@classmethod
def setUpClass(cls):
@@ -112,17 +124,6 @@ class Test(TestBase):
for i in range(3):
cls.used_mnemonics.append(prepare_node(i, mnemonics[0] if i == 0 else None))
def run_thread(self, client_id):
client_path = os.path.join(TEST_PATH, "client{}".format(client_id))
testargs = [
"basicswap-run",
"-datadir=" + client_path,
"-regtest",
f"-logprefix=BSX{client_id}",
]
with patch.object(sys, "argv", testargs):
runSystem.main()
def finalise(self, processes):
self.delay_event.set()
if self.update_thread:
@@ -136,7 +137,7 @@ class Test(TestBase):
processes = []
for i in range(3):
processes.append(multiprocessing.Process(target=self.run_thread, args=(i,)))
processes.append(multiprocessing.Process(target=run_process, args=(i,)))
processes[-1].start()
try:
@@ -201,7 +202,7 @@ class Test(TestBase):
logging.info("Starting a new node on the same mnemonic as the first")
prepare_node(3, self.used_mnemonics[0])
processes.append(multiprocessing.Process(target=self.run_thread, args=(3,)))
processes.append(multiprocessing.Process(target=run_process, args=(3,)))
processes[-1].start()
waitForServer(self.delay_event, 12703)

View File

@@ -270,7 +270,7 @@ def signal_handler(self, sig, frame):
self.delay_event.set()
def run_thread(self, client_id):
def run_process(client_id):
client_path = os.path.join(test_path, "client{}".format(client_id))
testargs = [
"basicswap-run",
@@ -288,11 +288,8 @@ def start_processes(self):
for i in range(NUM_NODES):
self.processes.append(
multiprocessing.Process(
target=run_thread,
args=(
self,
i,
),
target=run_process,
args=(i,),
)
)
self.processes[-1].start()

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021-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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -921,10 +921,10 @@ class BasicSwapTest(TestFunctions):
@classmethod
def setUpClass(cls):
super(BasicSwapTest, cls).setUpClass()
if False:
for client in cls.swap_clients:
client.log.safe_logs = True
client.log.safe_logs_prefix = b"tests"
@classmethod
def addCoinSettings(cls, settings, datadir, node_id):
settings["fetchpricesthread"] = False
def test_001_nested_segwit(self):
# p2sh-p2wpkh
@@ -1509,6 +1509,23 @@ class BasicSwapTest(TestFunctions):
vsize = tx_decoded["vsize"]
expect_fee_int = round(self.test_fee_rate * vsize / 1000)
tx_obj = ci.loadTx(lock_tx)
vsize_from_ci = ci.getTxVSize(tx_obj)
assert vsize == vsize_from_ci
tx_no_witness = tx_obj.serialize_without_witness()
dummy_witness_stack = []
for txi in tx_obj.vin:
dummy_witness_stack.append(ci.getP2WPKHDummyWitness())
witness_bytes_len_est: int = ci.getWitnessStackSerialisedLength(
dummy_witness_stack
)
tx_obj_no_witness = ci.loadTx(tx_no_witness)
vsize_estimated = ci.getTxVSize(
tx_obj_no_witness, add_witness_bytes=witness_bytes_len_est
)
assert vsize <= vsize_estimated and vsize_estimated - vsize < 4
out_value: int = 0
for txo in tx_decoded["vout"]:
if "value" in txo:
@@ -1556,7 +1573,7 @@ class BasicSwapTest(TestFunctions):
expect_vsize: int = ci.xmr_swap_a_lock_spend_tx_vsize()
assert expect_vsize >= vsize_actual
assert expect_vsize - vsize_actual < 10
assert expect_vsize - vsize_actual <= 10
# Test chain b (no-script) lock tx size
v = ci.getNewRandomKey()
@@ -1577,7 +1594,7 @@ class BasicSwapTest(TestFunctions):
expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize()
assert expect_vsize >= lock_tx_b_spend_decoded["vsize"]
assert expect_vsize - lock_tx_b_spend_decoded["vsize"] < 10
assert expect_vsize - lock_tx_b_spend_decoded["vsize"] <= 10
def test_011_p2sh(self):
# Not used in bsx for native-segwit coins

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# 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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -67,6 +67,18 @@ logger = logging.getLogger()
class Test(unittest.TestCase):
@staticmethod
def ci_btc():
btc_coin_settings = {"rpcport": 0, "rpcauth": "none"}
btc_coin_settings.update(REQUIRED_SETTINGS)
return BTCInterface(btc_coin_settings, "regtest")
@staticmethod
def ci_xmr():
xmr_coin_settings = {"rpcport": 0, "walletrpcport": 0, "walletrpcauth": "none"}
xmr_coin_settings.update(REQUIRED_SETTINGS)
return XMRInterface(xmr_coin_settings, "regtest")
def test_serialise_num(self):
def test_case(v, nb=None):
b = SerialiseNum(v)
@@ -86,10 +98,7 @@ class Test(unittest.TestCase):
test_case(4194642)
def test_sequence(self):
coin_settings = {"rpcport": 0, "rpcauth": "none"}
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
ci = self.ci_btc()
time_val = 48 * 60 * 60
encoded = ci.getExpectedSequence(TxLockTypes.SEQUENCE_LOCK_TIME, time_val)
@@ -197,10 +206,47 @@ class Test(unittest.TestCase):
"5c26c518fb698e91a5858c33e9075488c55c235f391162fe9e6cbd4f694f80aa"
)
def test_key_summing(self):
ci_btc = self.ci_btc()
ci_xmr = self.ci_xmr()
keys = [
bytes.fromhex(
"e6b8e7c2ca3a88fe4f28591aa0f91fec340179346559e4ec430c2531aecc19aa"
),
bytes.fromhex(
"b725b6359bd2b510d9d5a7bba7bdee17abbf113253f6338ea50a8f0cf45fd0d0"
),
]
sum_secp256k1: bytes = ci_btc.sumKeys(keys[0], keys[1])
assert (
sum_secp256k1.hex()
== "9dde9df8660d3e0f28fe00d648b70e052511ad800a07783f284455b1d2f5a939"
)
sum_ed25519: bytes = ci_xmr.sumKeys(keys[0], keys[1])
assert (
sum_ed25519.hex()
== "0dde9df8660d3e0f28fe00d648b70e0323e9c192fe9b94f1cf7138515e877725"
)
sum_secp256k1 = ci_btc.sumPubkeys(
ci_btc.getPubkey(keys[0]), ci_btc.getPubkey(keys[1])
)
assert (
sum_secp256k1.hex()
== "028c30392e35620af0787b363a03cf9a695336759664436e1f609481c869541a5c"
)
sum_ed25519 = ci_xmr.sumPubkeys(
ci_xmr.getPubkey(keys[0]), ci_xmr.getPubkey(keys[1])
)
assert (
sum_ed25519.hex()
== "4b2dd2dc9acc9be7efed4fdbfb96f0002aeb9e4c8638c5b24562a7158b283626"
)
def test_ecdsa_otves(self):
coin_settings = {"rpcport": 0, "rpcauth": "none"}
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
ci = self.ci_btc()
vk_sign = ci.getNewRandomKey()
vk_encrypt = ci.getNewRandomKey()
@@ -209,21 +255,16 @@ class Test(unittest.TestCase):
sign_hash = secrets.token_bytes(32)
cipher_text = ecdsaotves_enc_sign(vk_sign, pk_encrypt, sign_hash)
assert ecdsaotves_enc_verify(pk_sign, pk_encrypt, sign_hash, cipher_text)
sig = ecdsaotves_dec_sig(vk_encrypt, cipher_text)
assert ci.verifySig(pk_sign, sign_hash, sig)
recovered_key = ecdsaotves_rec_enc_key(pk_encrypt, cipher_text, sig)
assert vk_encrypt == recovered_key
def test_sign(self):
coin_settings = {"rpcport": 0, "rpcauth": "none"}
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
ci = self.ci_btc()
vk = ci.getNewRandomKey()
pk = ci.getPubkey(vk)
@@ -236,9 +277,7 @@ class Test(unittest.TestCase):
ci.verifySig(pk, message_hash, sig)
def test_sign_compact(self):
coin_settings = {"rpcport": 0, "rpcauth": "none"}
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
ci = self.ci_btc()
vk = ci.getNewRandomKey()
pk = ci.getPubkey(vk)
@@ -251,9 +290,7 @@ class Test(unittest.TestCase):
assert sig == sig2
def test_sign_recoverable(self):
coin_settings = {"rpcport": 0, "rpcauth": "none"}
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
ci = self.ci_btc()
vk = ci.getNewRandomKey()
pk = ci.getPubkey(vk)
@@ -267,18 +304,13 @@ class Test(unittest.TestCase):
assert sig == sig2
def test_pubkey_to_address(self):
coin_settings = {"rpcport": 0, "rpcauth": "none"}
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
ci = self.ci_btc()
pk = h2b("02c26a344e7d21bcc6f291532679559f2fd234c881271ff98714855edc753763a6")
addr = ci.pubkey_to_address(pk)
assert addr == "mj6SdSxmWRmdDqR5R3FfZmRiLmQfQAsLE8"
def test_dleag(self):
coin_settings = {"rpcport": 0, "walletrpcport": 0, "walletrpcauth": "none"}
coin_settings.update(REQUIRED_SETTINGS)
ci = XMRInterface(coin_settings, "regtest")
ci = self.ci_xmr()
key = ci.getNewRandomKey()
proof = ci.proveDLEAG(key)
@@ -409,15 +441,8 @@ class Test(unittest.TestCase):
amount_to_recreate = int((amount_from * rate) // (10**scale_from))
assert "10.00000000" == format_amount(amount_to_recreate, scale_to)
coin_settings = {
"rpcport": 0,
"rpcauth": "none",
"walletrpcport": 0,
"walletrpcauth": "none",
}
coin_settings.update(REQUIRED_SETTINGS)
ci_xmr = XMRInterface(coin_settings, "regtest")
ci_btc = BTCInterface(coin_settings, "regtest")
ci_xmr = self.ci_xmr()
ci_btc = self.ci_btc()
for i in range(10000):
test_pairs = random.randint(0, 3)
@@ -663,6 +688,7 @@ class Test(unittest.TestCase):
ki.record_id = 1
ki.address = "test1"
ki.label = "test1"
ki.note = "note1"
try:
db_test.add(ki, cursor, upsert=False)
except Exception as e:
@@ -670,6 +696,65 @@ class Test(unittest.TestCase):
else:
raise ValueError("Should have errored.")
db_test.add(ki, cursor, upsert=True)
# Test columns list
ki_test = db_test.queryOne(
KnownIdentity,
cursor,
{"address": "test1"},
columns_list=[
"label",
],
)
assert ki_test.label == "test1"
assert ki_test.address is None
# Test updating partial row
ki_test.label = "test2"
ki_test.record_id = 1
db_test.add(
ki_test,
cursor,
upsert=True,
columns_list=[
"record_id",
"label",
],
)
ki_test = db_test.queryOne(KnownIdentity, cursor, {"address": "test1"})
assert ki_test.record_id == 1
assert ki_test.address == "test1"
assert ki_test.label == "test2"
assert ki_test.note == "note1"
ki_test.note = "test2"
ki_test.label = "test3"
db_test.updateDB(
ki_test,
cursor,
["record_id"],
columns_list=[
"label",
],
)
ki_test = db_test.queryOne(KnownIdentity, cursor, {"address": "test1"})
assert ki_test.record_id == 1
assert ki_test.address == "test1"
assert ki_test.label == "test3"
assert ki_test.note == "note1"
# Test partially initialised object
ki_test_p = KnownIdentity(
_init_all_columns=False, record_id=1, label="test4"
)
db_test.add(ki_test_p, cursor, upsert=True)
ki_test = db_test.queryOne(KnownIdentity, cursor, {"address": "test1"})
assert ki_test.record_id == 1
assert ki_test.address == "test1"
assert ki_test.label == "test4"
assert ki_test.note == "note1"
finally:
db_test.closeDB(cursor)

View File

@@ -69,6 +69,18 @@ def updateThread():
delay_event.wait(5)
def run_process(client_id):
client_path = os.path.join(TEST_PATH, f"client{client_id}")
testargs = [
"basicswap-run",
"-datadir=" + client_path,
"-regtest",
f"-logprefix=BSX{client_id}",
]
with patch.object(sys, "argv", testargs):
runSystem.main()
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
@@ -76,17 +88,6 @@ class Test(unittest.TestCase):
prepare_nodes(3, "bitcoin")
def run_thread(self, client_id):
client_path = os.path.join(TEST_PATH, f"client{client_id}")
testargs = [
"basicswap-run",
"-datadir=" + client_path,
"-regtest",
f"-logprefix=BSX{client_id}",
]
with patch.object(sys, "argv", testargs):
runSystem.main()
def wait_for_node_height(self, port=12701, wallet_ticker="part", wait_for_blocks=3):
# Wait for height, or sequencelock is thrown off by genesis blocktime
logging.info(
@@ -112,7 +113,7 @@ class Test(unittest.TestCase):
processes = []
for i in range(3):
processes.append(multiprocessing.Process(target=self.run_thread, args=(i,)))
processes.append(multiprocessing.Process(target=run_process, args=(i,)))
processes[-1].start()
try:
@@ -169,7 +170,7 @@ class Test(unittest.TestCase):
c1 = processes[1]
c1.terminate()
c1.join()
processes[1] = multiprocessing.Process(target=self.run_thread, args=(1,))
processes[1] = multiprocessing.Process(target=run_process, args=(1,))
processes[1].start()
waitForServer(delay_event, 12701)

View File

@@ -30,6 +30,7 @@ from tests.basicswap.common import (
waitForNumBids,
)
from tests.basicswap.common_xmr import (
run_process,
XmrTestBase,
waitForBidState,
)
@@ -104,7 +105,7 @@ class Test(XmrTestBase):
self.delay_event.wait(5)
logger.info("Starting node 0")
self.processes[0] = multiprocessing.Process(target=self.run_thread, args=(0,))
self.processes[0] = multiprocessing.Process(target=run_process, args=(0,))
self.processes[0].start()
waitForServer(self.delay_event, 12700)

View File

@@ -27,11 +27,12 @@ from tests.basicswap.util import (
waitForServer,
)
from tests.basicswap.common import (
waitForNumOffers,
waitForNumBids,
waitForNumOffers,
waitForNumSwapping,
)
from tests.basicswap.common_xmr import (
run_process,
XmrTestBase,
)
@@ -94,12 +95,13 @@ class Test(XmrTestBase):
waitForNumBids(self.delay_event, 12700, 1)
for i in range(10):
for i in range(20):
bids = read_json_api(12700, "bids")
bid = bids[0]
if bid["bid_state"] == "Received":
break
self.delay_event.wait(1)
assert bid["bid_state"] == "Received"
assert bid["expire_at"] == bid["created_at"] + data["validmins"] * 60
data = {"accept": True}
@@ -112,7 +114,7 @@ class Test(XmrTestBase):
c1 = self.processes[1]
c1.terminate()
c1.join()
self.processes[1] = multiprocessing.Process(target=self.run_thread, args=(1,))
self.processes[1] = multiprocessing.Process(target=run_process, args=(1,))
self.processes[1].start()
waitForServer(self.delay_event, 12701)