Merge branch 'dev'

This commit is contained in:
tecnovert 2025-01-15 18:34:06 +02:00
commit 40f334ed0e
70 changed files with 6424 additions and 3568 deletions

11
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
open-pull-requests-limit: 20
target-branch: "dev"

View file

@ -53,6 +53,13 @@ jobs:
name: Running basicswap-prepare
run: |
basicswap-prepare --bindir="$BIN_DIR" --preparebinonly --withcoins=particl,bitcoin,monero
- name: Running test_xmr
run: |
export PYTHONPATH=$(pwd)
export PARTICL_BINDIR="$BIN_DIR/particl";
export BITCOIN_BINDIR="$BIN_DIR/bitcoin";
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"
- name: Running test_encrypted_xmr_reload
run: |
export PYTHONPATH=$(pwd)

View file

@ -6,7 +6,7 @@ ENV LANG=C.UTF-8 \
RUN apt-get update; \
apt-get install -y --no-install-recommends \
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata;
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata cmake ninja-build;
# Install requirements first so as to skip in subsequent rebuilds
COPY ./requirements.txt requirements.txt

View file

@ -1,3 +1,3 @@
name = "basicswap"
__version__ = "0.14.2"
__version__ = "0.14.3"

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-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.
@ -140,7 +140,6 @@ from .basicswap_util import (
canAcceptBidState,
describeEventEntry,
getLastBidState,
getOfferProofOfFundsHash,
getVoutByAddress,
getVoutByScriptPubKey,
inactive_states,
@ -355,7 +354,13 @@ class BasicSwap(BaseApp):
# TODO: Set dynamically
self.balance_only_coins = (Coins.LTC_MWEB,)
self.scriptless_coins = (Coins.XMR, Coins.WOW, Coins.PART_ANON, Coins.FIRO)
self.scriptless_coins = (
Coins.XMR,
Coins.WOW,
Coins.PART_ANON,
Coins.FIRO,
Coins.DOGE,
)
self.adaptor_swap_only_coins = self.scriptless_coins + (
Coins.PART_BLIND,
Coins.BCH,
@ -822,6 +827,10 @@ class BasicSwap(BaseApp):
self.coin_clients[coin], self.chain, self
)
return interface
elif coin == Coins.DOGE:
from .interface.doge import DOGEInterface
return DOGEInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.DCR:
from .interface.dcr import DCRInterface
@ -882,6 +891,7 @@ class BasicSwap(BaseApp):
if cc["name"] in (
"bitcoin",
"litecoin",
"dogecoin",
"namecoin",
"dash",
"firo",
@ -968,13 +978,13 @@ class BasicSwap(BaseApp):
core_version = ci.getDaemonVersion()
self.log.info("%s Core version %d", ci.coin_name(), core_version)
self.coin_clients[c]["core_version"] = core_version
# thread_func = threadPollXMRChainState if c in (Coins.XMR, Coins.WOW) else threadPollChainState
if c == Coins.XMR:
thread_func = threadPollXMRChainState
elif c == Coins.WOW:
thread_func = threadPollWOWChainState
else:
thread_func = threadPollChainState
thread_func = {
Coins.XMR: threadPollXMRChainState,
Coins.WOW: threadPollWOWChainState,
}.get(
c, threadPollChainState
) # default case
t = threading.Thread(target=thread_func, args=(self, c))
self.threads.append(t)
@ -1187,11 +1197,16 @@ class BasicSwap(BaseApp):
if ci.isWalletLocked():
raise LockedCoinError(Coins.PART)
def isBaseCoinActive(self, c) -> bool:
if c not in chainparams:
return False
if self.coin_clients[c]["connection_type"] == "rpc":
return True
return False
def activeCoins(self):
for c in Coins:
if c not in chainparams:
continue
if self.coin_clients[c]["connection_type"] == "rpc":
if self.isBaseCoinActive(c):
yield c
def getListOfWalletCoins(self):
@ -1274,6 +1289,20 @@ class BasicSwap(BaseApp):
finally:
self._read_zmq_queue = True
def storeSeedIDForCoin(self, root_key, coin_type, cursor=None) -> None:
ci = self.ci(coin_type)
db_key_coin_name = ci.coin_name().lower()
seed_id = ci.getSeedHash(root_key)
key_str = "main_wallet_seedid_" + db_key_coin_name
self.setStringKV(key_str, seed_id.hex(), cursor)
if coin_type == Coins.DCR:
# TODO: How to force getmasterpubkey to always return the new slip44 (42) key
key_str = "main_wallet_seedid_alt_" + db_key_coin_name
legacy_root_hash = ci.getSeedHash(root_key, 20)
self.setStringKV(key_str, legacy_root_hash.hex(), cursor)
def initialiseWallet(self, coin_type, raise_errors: bool = False) -> None:
if coin_type == Coins.PART:
return
@ -1292,7 +1321,6 @@ class BasicSwap(BaseApp):
return
root_key = self.getWalletKey(coin_type, 1)
root_hash = ci.getSeedHash(root_key)
try:
ci.initialiseWallet(root_key)
except Exception as e:
@ -1304,18 +1332,9 @@ class BasicSwap(BaseApp):
self.log.error(traceback.format_exc())
return
legacy_root_hash = None
if coin_type == Coins.DCR:
legacy_root_hash = ci.getSeedHash(root_key, 20)
try:
cursor = self.openDB()
key_str = "main_wallet_seedid_" + db_key_coin_name
self.setStringKV(key_str, root_hash.hex(), cursor)
if coin_type == Coins.DCR:
# TODO: How to force getmasterpubkey to always return the new slip44 (42) key
key_str = "main_wallet_seedid_alt_" + db_key_coin_name
self.setStringKV(key_str, legacy_root_hash.hex(), cursor)
self.storeSeedIDForCoin(root_key, coin_type, cursor)
# Clear any saved addresses
self.clearStringKV("receive_addr_" + db_key_coin_name, cursor)
@ -1329,39 +1348,43 @@ class BasicSwap(BaseApp):
self.closeDB(cursor)
def updateIdentityBidState(self, cursor, address: str, bid) -> None:
identity_stats = self.queryOne(KnownIdentity, cursor, {"address": address})
if not identity_stats:
identity_stats = KnownIdentity(
active_ind=1, address=address, created_at=self.getTime()
)
if bid.state == BidStates.SWAP_COMPLETED:
if bid.was_sent:
identity_stats.num_sent_bids_successful = (
zeroIfNone(identity_stats.num_sent_bids_successful) + 1
offer = self.getOffer(bid.offer_id, cursor)
addresses_to_update = [offer.addr_from, bid.bid_addr]
for addr in addresses_to_update:
identity_stats = self.queryOne(KnownIdentity, cursor, {"address": addr})
if not identity_stats:
identity_stats = KnownIdentity(
active_ind=1, address=addr, created_at=self.getTime()
)
else:
identity_stats.num_recv_bids_successful = (
zeroIfNone(identity_stats.num_recv_bids_successful) + 1
)
elif bid.state in (
BidStates.BID_ERROR,
BidStates.XMR_SWAP_FAILED_REFUNDED,
BidStates.XMR_SWAP_FAILED_SWIPED,
BidStates.XMR_SWAP_FAILED,
BidStates.SWAP_TIMEDOUT,
):
if bid.was_sent:
identity_stats.num_sent_bids_failed = (
zeroIfNone(identity_stats.num_sent_bids_failed) + 1
)
else:
identity_stats.num_recv_bids_failed = (
zeroIfNone(identity_stats.num_recv_bids_failed) + 1
)
identity_stats.updated_at = self.getTime()
self.add(identity_stats, cursor, upsert=True)
is_offer_creator = addr == offer.addr_from
if bid.state == BidStates.SWAP_COMPLETED:
if is_offer_creator:
old_value = zeroIfNone(identity_stats.num_recv_bids_successful)
identity_stats.num_recv_bids_successful = old_value + 1
else:
old_value = zeroIfNone(identity_stats.num_sent_bids_successful)
identity_stats.num_sent_bids_successful = old_value + 1
elif bid.state in (
BidStates.BID_ERROR,
BidStates.XMR_SWAP_FAILED_REFUNDED,
BidStates.XMR_SWAP_FAILED_SWIPED,
BidStates.XMR_SWAP_FAILED,
BidStates.SWAP_TIMEDOUT,
):
if is_offer_creator:
old_value = zeroIfNone(identity_stats.num_recv_bids_failed)
identity_stats.num_recv_bids_failed = old_value + 1
else:
old_value = zeroIfNone(identity_stats.num_sent_bids_failed)
identity_stats.num_sent_bids_failed = old_value + 1
elif bid.state == BidStates.BID_REJECTED:
if is_offer_creator:
old_value = zeroIfNone(identity_stats.num_recv_bids_rejected)
identity_stats.num_recv_bids_rejected = old_value + 1
else:
old_value = zeroIfNone(identity_stats.num_sent_bids_rejected)
identity_stats.num_sent_bids_rejected = old_value + 1
self.add(identity_stats, cursor, upsert=True)
def getPreFundedTx(
self, linked_type: int, linked_id: bytes, tx_type: int, cursor=None
@ -1841,8 +1864,8 @@ class BasicSwap(BaseApp):
rv = []
for row in q:
identity = {
"address": row[0],
"label": row[1],
"address": row[0] if row[0] is not None else "",
"label": row[1] if row[1] is not None else "",
"num_sent_bids_successful": zeroIfNone(row[2]),
"num_recv_bids_successful": zeroIfNone(row[3]),
"num_sent_bids_rejected": zeroIfNone(row[4]),
@ -2093,14 +2116,27 @@ class BasicSwap(BaseApp):
msg_buf.fee_rate_to
) # Unused: TODO - Set priority?
ensure_balance: int = int(amount)
if coin_from in self.scriptless_coins:
ci_from.ensureFunds(msg_buf.amount_from)
else:
proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr)
proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(
coin_from_t, int(amount), proof_of_funds_hash
# TODO: Better tx size estimate, xmr_swap_b_lock_tx_vsize could be larger than xmr_swap_b_lock_spend_tx_vsize
estimated_fee: int = (
msg_buf.fee_rate_from
* ci_from.xmr_swap_b_lock_spend_tx_vsize()
/ 1000
)
# TODO: For now proof_of_funds is just a client side check, may need to be sent with offers in future however.
ci_from.ensureFunds(msg_buf.amount_from + estimated_fee)
else:
# If a prefunded txn is not used, check that the wallet balance can cover the tx fee.
if "prefunded_itx" not in extra_options:
pi = self.pi(SwapTypes.XMR_SWAP)
_ = pi.getFundedInitiateTxTemplate(ci_from, ensure_balance, False)
# TODO: Save the prefunded tx so the fee can't change, complicates multiple offers at the same time.
# TODO: Send proof of funds with offer
# proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr)
# proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(
# coin_from_t, ensure_balance, proof_of_funds_hash
# )
offer_bytes = msg_buf.to_bytes()
payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex()
@ -2586,9 +2622,21 @@ class BasicSwap(BaseApp):
expect_seedid = self.getStringKV("main_wallet_seedid_" + ci.coin_name().lower())
if expect_seedid is None:
self.log.warning(
"Can't find expected wallet seed id for coin {}".format(ci.coin_name())
"Can't find expected wallet seed id for coin {}.".format(ci.coin_name())
)
return False
_, is_locked = self.getLockedState()
if is_locked is False:
self.log.warning(
"Setting seed id for coin {} from master key.".format(
ci.coin_name()
)
)
root_key = self.getWalletKey(c, 1)
self.storeSeedIDForCoin(root_key, c)
else:
self.log.warning("Node is locked.")
return False
if c == Coins.BTC and len(ci.rpc("listwallets")) < 1:
self.log.warning("Missing wallet for coin {}".format(ci.coin_name()))
return False
@ -5077,6 +5125,13 @@ class BasicSwap(BaseApp):
if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns:
try:
if self.haveDebugInd(
bid.bid_id,
DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2,
):
raise TemporaryError(
"Debug: BID_DONT_SPEND_COIN_A_LOCK_REFUND2"
)
txid = ci_from.publishTx(xmr_swap.a_lock_refund_swipe_tx)
self.logBidEvent(
bid.bid_id,
@ -6145,6 +6200,13 @@ class BasicSwap(BaseApp):
self.logBidEvent(
bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN, "", use_cursor
)
if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns:
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
bid_id=bid.bid_id,
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
txid=xmr_swap.a_lock_refund_tx_id,
)
else:
self.setBidError(
bid.bid_id,
@ -8766,8 +8828,10 @@ class BasicSwap(BaseApp):
b_fee_rate: int = xmr_offer.a_fee_rate if reverse_bid else xmr_offer.b_fee_rate
try:
chain_height = ci_to.getChainHeight()
lock_tx_depth = (chain_height - bid.xmr_b_lock_tx.chain_height)
if bid.xmr_b_lock_tx is None:
raise TemporaryError("Chain B lock tx not found.")
chain_height: int = ci_to.getChainHeight()
lock_tx_depth: int = chain_height - bid.xmr_b_lock_tx.chain_height
if lock_tx_depth < ci_to.depth_spendable():
raise TemporaryError(
f"Chain B lock tx still confirming {lock_tx_depth} / {ci_to.depth_spendable()}."
@ -9500,6 +9564,16 @@ class BasicSwap(BaseApp):
ensure(msg["to"] == bid.bid_addr, "Received on incorrect address")
ensure(msg["from"] == offer.addr_from, "Sent from incorrect address")
allowed_states = [
BidStates.BID_REQUEST_SENT,
]
if bid.was_sent and offer.was_sent:
allowed_states.append(BidStates.BID_REQUEST_ACCEPTED)
ensure(
bid.state in allowed_states,
"Invalid state for bid {}".format(bid.state),
)
ci_from = self.ci(offer.coin_to)
ci_to = self.ci(offer.coin_from)
@ -10307,7 +10381,14 @@ class BasicSwap(BaseApp):
elif coin == Coins.NAV:
rv["immature"] = walletinfo["immature_balance"]
elif coin == Coins.LTC:
rv["mweb_address"] = self.getCachedStealthAddressForCoin(Coins.LTC_MWEB)
try:
rv["mweb_address"] = self.getCachedStealthAddressForCoin(
Coins.LTC_MWEB
)
except Exception as e:
self.log.warning(
f"getCachedStealthAddressForCoin for {ci.coin_name()} failed with: {e}"
)
rv["mweb_balance"] = walletinfo["mweb_balance"]
rv["mweb_pending"] = (
walletinfo["mweb_unconfirmed"] + walletinfo["mweb_immature"]
@ -10412,7 +10493,7 @@ class BasicSwap(BaseApp):
for row in q:
coin_id = row[0]
if self.coin_clients[coin_id]["connection_type"] != "rpc":
if self.isCoinActive(coin_id) is False:
# Skip cached info if coin was disabled
continue

252
basicswap/bin/prepare.py Normal file → Executable file
View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-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.
@ -23,6 +23,7 @@ import stat
import sys
import tarfile
import threading
import time
import urllib.parse
import zipfile
@ -50,15 +51,12 @@ PARTICL_VERSION = os.getenv("PARTICL_VERSION", "23.2.7.0")
PARTICL_VERSION_TAG = os.getenv("PARTICL_VERSION_TAG", "")
PARTICL_LINUX_EXTRA = os.getenv("PARTICL_LINUX_EXTRA", "nousb")
LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.3")
LITECOIN_VERSION = os.getenv("LITECOIN_VERSION", "0.21.4")
LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "")
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "26.0")
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "28.0")
BITCOIN_VERSION_TAG = os.getenv("BITCOIN_VERSION_TAG", "")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "27.1.0")
BITCOINCASH_VERSION_TAG = os.getenv("BITCOINCASH_VERSION_TAG", "")
MONERO_VERSION = os.getenv("MONERO_VERSION", "0.18.3.4")
MONERO_VERSION_TAG = os.getenv("MONERO_VERSION_TAG", "")
XMR_SITE_COMMIT = (
@ -74,7 +72,7 @@ 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", "21.1.0")
DASH_VERSION = os.getenv("DASH_VERSION", "22.0.0")
DASH_VERSION_TAG = os.getenv("DASH_VERSION_TAG", "")
FIRO_VERSION = os.getenv("FIRO_VERSION", "0.14.14.0")
@ -86,6 +84,12 @@ NAV_VERSION_TAG = os.getenv("NAV_VERSION_TAG", "")
DCR_VERSION = os.getenv("DCR_VERSION", "1.8.1")
DCR_VERSION_TAG = os.getenv("DCR_VERSION_TAG", "")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "27.1.0")
BITCOINCASH_VERSION_TAG = os.getenv("BITCOINCASH_VERSION_TAG", "")
DOGECOIN_VERSION = os.getenv("DOGECOIN_VERSION", "23.2.1")
DOGECOIN_VERSION_TAG = os.getenv("DOGECOIN_VERSION_TAG", "")
GUIX_SSL_CERT_DIR = None
ADD_PUBKEY_URL = os.getenv("ADD_PUBKEY_URL", "")
@ -98,7 +102,6 @@ SKIP_GPG_VALIDATION = toBool(os.getenv("SKIP_GPG_VALIDATION", "false"))
known_coins = {
"particl": (PARTICL_VERSION, PARTICL_VERSION_TAG, ("tecnovert",)),
"bitcoin": (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ("laanwj",)),
"bitcoincash": (BITCOINCASH_VERSION, BITCOINCASH_VERSION_TAG, ("Calin_Culianu",)),
"litecoin": (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ("davidburkett38",)),
"decred": (DCR_VERSION, DCR_VERSION_TAG, ("decred_release",)),
"namecoin": ("0.18.0", "", ("JeremyRand",)),
@ -108,6 +111,8 @@ known_coins = {
"dash": (DASH_VERSION, DASH_VERSION_TAG, ("pasta",)),
"firo": (FIRO_VERSION, FIRO_VERSION_TAG, ("reuben",)),
"navcoin": (NAV_VERSION, NAV_VERSION_TAG, ("nav_builder",)),
"bitcoincash": (BITCOINCASH_VERSION, BITCOINCASH_VERSION_TAG, ("Calin_Culianu",)),
"dogecoin": (DOGECOIN_VERSION, DOGECOIN_VERSION_TAG, ("tecnovert",)),
}
disabled_coins = [
@ -123,8 +128,10 @@ expected_key_ids = {
"binaryfate": ("F0AF4D462A0BDF92",),
"wowario": ("793504B449C69220",),
"davidburkett38": ("3620E9D387E55666",),
"xanimo": ("6E8F17C1B1BCDCBE",),
"patricklodder": ("2D3A345B98D0DC1F",),
"fuzzbawls": ("C1ABA64407731FD9",),
"pasta": ("52527BEDABE87984",),
"pasta": ("52527BEDABE87984", "E2F3D7916E722D38"),
"reuben": ("1290A1D0FA7EE109",),
"nav_builder": ("2782262BF6E7FADB",),
"decred_release": ("6D897EDF518A031D",),
@ -157,7 +164,11 @@ if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout))
BSX_DOCKER_MODE = toBool(os.getenv("BSX_DOCKER_MODE", "false"))
BSX_LOCAL_TOR = toBool(os.getenv("BSX_LOCAL_TOR", "false"))
BSX_TEST_MODE = toBool(os.getenv("BSX_TEST_MODE", "false"))
BSX_UPDATE_UNMANAGED = toBool(
os.getenv("BSX_UPDATE_UNMANAGED", "true")
) # Disable updating unmanaged coin cores.
UI_HTML_PORT = int(os.getenv("UI_HTML_PORT", 12700))
UI_WS_PORT = int(os.getenv("UI_WS_PORT", 11700))
COINS_RPCBIND_IP = os.getenv("COINS_RPCBIND_IP", "127.0.0.1")
@ -203,13 +214,6 @@ BTC_ONION_PORT = int(os.getenv("BTC_ONION_PORT", 8334))
BTC_RPC_USER = os.getenv("BTC_RPC_USER", "")
BTC_RPC_PWD = os.getenv("BTC_RPC_PWD", "")
BCH_RPC_HOST = os.getenv("BCH_RPC_HOST", "127.0.0.1")
BCH_RPC_PORT = int(os.getenv("BCH_RPC_PORT", 19997))
BCH_ONION_PORT = int(os.getenv("BCH_ONION_PORT", 8335))
BCH_PORT = int(os.getenv("BCH_PORT", 19798))
BCH_RPC_USER = os.getenv("BCH_RPC_USER", "")
BCH_RPC_PWD = os.getenv("BCH_RPC_PWD", "")
DCR_RPC_HOST = os.getenv("DCR_RPC_HOST", "127.0.0.1")
DCR_RPC_PORT = int(os.getenv("DCR_RPC_PORT", 9109))
DCR_WALLET_RPC_HOST = os.getenv("DCR_WALLET_RPC_HOST", "127.0.0.1")
@ -247,10 +251,28 @@ NAV_ONION_PORT = int(os.getenv("NAV_ONION_PORT", 8334)) # TODO?
NAV_RPC_USER = os.getenv("NAV_RPC_USER", "")
NAV_RPC_PWD = os.getenv("NAV_RPC_PWD", "")
BCH_RPC_HOST = os.getenv("BCH_RPC_HOST", "127.0.0.1")
BCH_RPC_PORT = int(os.getenv("BCH_RPC_PORT", 19997))
BCH_ONION_PORT = int(os.getenv("BCH_ONION_PORT", 8335))
BCH_PORT = int(os.getenv("BCH_PORT", 19798))
BCH_RPC_USER = os.getenv("BCH_RPC_USER", "")
BCH_RPC_PWD = os.getenv("BCH_RPC_PWD", "")
DOGE_RPC_HOST = os.getenv("DOGE_RPC_HOST", "127.0.0.1")
DOGE_RPC_PORT = int(os.getenv("DOGE_RPC_PORT", 42069))
DOGE_ONION_PORT = int(os.getenv("DOGE_ONION_PORT", 6969))
DOGE_RPC_USER = os.getenv("DOGE_RPC_USER", "")
DOGE_RPC_PWD = os.getenv("DOGE_RPC_PWD", "")
TOR_PROXY_HOST = os.getenv("TOR_PROXY_HOST", "127.0.0.1")
TOR_PROXY_PORT = int(os.getenv("TOR_PROXY_PORT", 9050))
TOR_CONTROL_PORT = int(os.getenv("TOR_CONTROL_PORT", 9051))
TOR_DNS_PORT = int(os.getenv("TOR_DNS_PORT", 5353))
TOR_CONTROL_LISTEN_INTERFACE = os.getenv("TOR_CONTROL_LISTEN_INTERFACE", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TORRC_PROXY_HOST = os.getenv("TORRC_PROXY_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TORRC_CONTROL_HOST = os.getenv("TORRC_CONTROL_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TORRC_DNS_HOST = os.getenv("TORRC_DNS_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TEST_TOR_PROXY = toBool(
os.getenv("TEST_TOR_PROXY", "true")
) # Expects a known exit node
@ -268,6 +290,7 @@ BITCOIN_FASTSYNC_FILE = os.getenv(
WALLET_ENCRYPTION_PWD = os.getenv("WALLET_ENCRYPTION_PWD", "")
use_tor_proxy: bool = False
with_coins_changed: bool = False
monerod_proxy_config = [
f"proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}",
@ -326,6 +349,11 @@ def shouldManageDaemon(prefix: str) -> bool:
return toBool(manage_daemon)
def getKnownVersion(coin_name: str) -> str:
version, version_tag, _ = known_coins[coin_name]
return version + version_tag
def exitWithError(error_msg: str):
sys.stderr.write("Error: {}, exiting.\n".format(error_msg))
sys.exit(1)
@ -759,7 +787,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
arch_name = BIN_ARCH
if os_name == "osx" and use_guix:
arch_name = "x86_64-apple-darwin"
if coin == "particl":
if coin in ("particl", "dogecoin"):
arch_name += "18"
release_filename = "{}-{}-{}.{}".format(
@ -802,6 +830,15 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
"https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s"
% (version, os_dir_name, signing_key_name, assert_filename)
)
elif coin == "dogecoin":
release_url = (
"https://github.com/tecnovert/dogecoin/releases/download/v{}/{}".format(
version + version_tag, release_filename
)
)
assert_filename = "{}-{}-{}-build.assert".format(coin, os_name, version)
assert_url = f"https://raw.githubusercontent.com/tecnovert/guix.sigs/dogecoin/{version}/{signing_key_name}/noncodesigned.SHA256SUMS"
elif coin == "bitcoin":
release_url = "https://bitcoincore.org/bin/bitcoin-core-{}/{}".format(
version, release_filename
@ -973,6 +1010,8 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
pubkey_filename = "{}_builder.pgp".format(coin)
elif coin in ("decred",):
pubkey_filename = "{}_release.pgp".format(coin)
elif coin in ("dogecoin",):
pubkey_filename = "particl_{}.pgp".format(signing_key_name)
else:
pubkey_filename = "{}_{}.pgp".format(coin, signing_key_name)
pubkeyurls = [
@ -1059,9 +1098,9 @@ def writeTorSettings(fp, coin, coin_settings, tor_control_password):
fp.write(f"torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n")
if coin_settings["core_version_group"] >= 21:
fp.write(f"bind=0.0.0.0:{onionport}=onion\n")
fp.write(f"bind={TOR_CONTROL_LISTEN_INTERFACE}:{onionport}=onion\n")
else:
fp.write(f"bind=0.0.0.0:{onionport}\n")
fp.write(f"bind={TOR_CONTROL_LISTEN_INTERFACE}:{onionport}\n")
def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
@ -1268,14 +1307,20 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
)
elif coin == "litecoin":
fp.write("prune=4000\n")
fp.write("blockfilterindex=0\n")
fp.write("peerblockfilters=0\n")
if LTC_RPC_USER != "":
fp.write(
"rpcauth={}:{}${}\n".format(
LTC_RPC_USER, salt, password_to_hmac(salt, LTC_RPC_PWD)
)
)
elif coin == "dogecoin":
fp.write("prune=4000\n")
if DOGE_RPC_USER != "":
fp.write(
"rpcauth={}:{}${}\n".format(
DOGE_RPC_USER, salt, password_to_hmac(salt, DOGE_RPC_PWD)
)
)
elif coin == "bitcoin":
fp.write("deprecatedrpc=create_bdb\n")
fp.write("prune=2000\n")
@ -1377,9 +1422,9 @@ def write_torrc(data_dir, tor_control_password):
tor_control_hash = rfc2440_hash_password(tor_control_password)
with open(torrc_path, "w") as fp:
fp.write(f"SocksPort 0.0.0.0:{TOR_PROXY_PORT}\n")
fp.write(f"ControlPort 0.0.0.0:{TOR_CONTROL_PORT}\n")
fp.write(f"DNSPort 0.0.0.0:{TOR_DNS_PORT}\n")
fp.write(f"SocksPort {TORRC_PROXY_HOST}:{TOR_PROXY_PORT}\n")
fp.write(f"ControlPort {TORRC_CONTROL_HOST}:{TOR_CONTROL_PORT}\n")
fp.write(f"DNSPort {TORRC_DNS_HOST}:{TOR_DNS_PORT}\n")
fp.write(f"HashedControlPassword {tor_control_hash}\n")
@ -1485,6 +1530,8 @@ def modify_tor_config(
default_onionport = PART_ONION_PORT
elif coin == "litecoin":
default_onionport = LTC_ONION_PORT
elif coin == "dogecoin":
default_onionport = DOGE_ONION_PORT
elif coin in ("decred",):
pass
else:
@ -1517,9 +1564,6 @@ def printVersion(with_coins):
if len(with_coins) < 1:
return
print("Core versions:")
with_coins_changed: bool = (
False if len(with_coins) == 1 and "particl" in with_coins else True
)
for coin, version in known_coins.items():
if with_coins_changed and coin not in with_coins:
continue
@ -1636,7 +1680,7 @@ def test_particl_encryption(data_dir, settings, chain, use_tor_proxy):
c = Coins.PART
coin_name = "particl"
coin_settings = settings["chainclients"][coin_name]
daemon_args += getCoreBinArgs(c, coin_settings)
daemon_args += getCoreBinArgs(c, coin_settings, prepare=True)
extra_config = {"stdout_to_file": True}
if coin_settings["manage_daemon"]:
filename: str = getCoreBinName(c, coin_settings, coin_name + "d")
@ -1697,6 +1741,7 @@ def initialise_wallets(
Coins.PART,
Coins.BTC,
Coins.LTC,
Coins.DOGE,
Coins.DCR,
Coins.DASH,
)
@ -1748,7 +1793,7 @@ def initialise_wallets(
coin_args = (
["-nofindpeers", "-nostaking"] if c == Coins.PART else []
)
coin_args += getCoreBinArgs(c, coin_settings)
coin_args += getCoreBinArgs(c, coin_settings, prepare=True)
if c == Coins.FIRO:
coin_args += [
@ -1803,7 +1848,7 @@ def initialise_wallets(
"Creating wallet.dat for {}.".format(getCoinName(c))
)
if c in (Coins.BTC, Coins.LTC, Coins.DASH):
if c in (Coins.BTC, Coins.LTC, Coins.DOGE, Coins.DASH):
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
swap_client.callcoinrpc(
c,
@ -1946,7 +1991,7 @@ def ensure_coin_valid(coin: str, test_disabled: bool = True) -> None:
def main():
global use_tor_proxy
global use_tor_proxy, with_coins_changed
data_dir = None
bin_dir = None
port_offset = None
@ -1957,12 +2002,12 @@ def main():
}
add_coin = ""
disable_coin = ""
coins_changed = False
htmlhost = "127.0.0.1"
xmr_restore_height = DEFAULT_XMR_RESTORE_HEIGHT
wow_restore_height = DEFAULT_WOW_RESTORE_HEIGHT
print_versions = False
prepare_bin_only = False
upgrade_cores = False
no_cores = False
enable_tor = False
disable_tor = False
@ -2001,6 +2046,9 @@ def main():
if name == "preparebinonly":
prepare_bin_only = True
continue
if name == "upgradecores":
upgrade_cores = True
continue
if name == "nocores":
no_cores = True
continue
@ -2060,13 +2108,13 @@ def main():
for coin in [s.strip().lower() for s in s[1].split(",")]:
ensure_coin_valid(coin)
with_coins.add(coin)
coins_changed = True
with_coins_changed = True
continue
if name in ("withoutcoin", "withoutcoins"):
for coin in [s.strip().lower() for s in s[1].split(",")]:
ensure_coin_valid(coin, test_disabled=False)
with_coins.discard(coin)
coins_changed = True
with_coins_changed = True
continue
if name == "addcoin":
add_coin = s[1].strip().lower()
@ -2212,7 +2260,8 @@ def main():
"blocks_confirmed": 2,
"override_feerate": 0.002,
"conf_target": 2,
"core_version_group": 21,
"core_version_no": getKnownVersion("particl"),
"core_version_group": 23,
},
"bitcoin": {
"connection_type": "rpc",
@ -2225,7 +2274,8 @@ def main():
"use_segwit": True,
"blocks_confirmed": 1,
"conf_target": 2,
"core_version_group": 22,
"core_version_no": getKnownVersion("bitcoin"),
"core_version_group": 28,
},
"bitcoincash": {
"connection_type": "rpc",
@ -2240,6 +2290,7 @@ def main():
"use_segwit": False,
"blocks_confirmed": 1,
"conf_target": 2,
"core_version_no": getKnownVersion("bitcoincash"),
"core_version_group": 22,
},
"litecoin": {
@ -2253,9 +2304,26 @@ def main():
"use_segwit": True,
"blocks_confirmed": 2,
"conf_target": 2,
"core_version_group": 21,
"core_version_no": getKnownVersion("litecoin"),
"core_version_group": 20,
"min_relay_fee": 0.00001,
},
"dogecoin": {
"connection_type": "rpc",
"manage_daemon": shouldManageDaemon("DOGE"),
"rpchost": DOGE_RPC_HOST,
"rpcport": DOGE_RPC_PORT + port_offset,
"onionport": DOGE_ONION_PORT + port_offset,
"datadir": os.getenv("DOGE_DATA_DIR", os.path.join(data_dir, "dogecoin")),
"bindir": os.path.join(bin_dir, "dogecoin"),
"use_segwit": False,
"use_csv": False,
"blocks_confirmed": 2,
"conf_target": 2,
"core_version_no": getKnownVersion("dogecoin"),
"core_version_group": 23,
"min_relay_fee": 0.01, # RECOMMENDED_MIN_TX_FEE
},
"decred": {
"connection_type": "rpc",
"manage_daemon": shouldManageDaemon("DCR"),
@ -2273,6 +2341,7 @@ def main():
"use_segwit": True,
"blocks_confirmed": 2,
"conf_target": 2,
"core_version_no": getKnownVersion("decred"),
"core_type_group": "dcr",
"config_filename": "dcrd.conf",
"min_relay_fee": 0.00001,
@ -2288,6 +2357,7 @@ def main():
"use_csv": False,
"blocks_confirmed": 1,
"conf_target": 2,
"core_version_no": getKnownVersion("namecoin"),
"core_version_group": 18,
"chain_lookups": "local",
},
@ -2312,6 +2382,7 @@ def main():
"walletrpctimeout": 120,
"walletrpctimeoutlong": 600,
"wallet_config_filename": "monero_wallet.conf",
"core_version_no": getKnownVersion("monero"),
"core_type_group": "xmr",
},
"pivx": {
@ -2326,6 +2397,7 @@ def main():
"use_csv": False,
"blocks_confirmed": 1,
"conf_target": 2,
"core_version_no": getKnownVersion("pivx"),
"core_version_group": 17,
},
"dash": {
@ -2340,6 +2412,7 @@ def main():
"use_csv": True,
"blocks_confirmed": 1,
"conf_target": 2,
"core_version_no": getKnownVersion("dash"),
"core_version_group": 18,
},
"firo": {
@ -2354,6 +2427,7 @@ def main():
"use_csv": False,
"blocks_confirmed": 1,
"conf_target": 2,
"core_version_no": getKnownVersion("firo"),
"core_version_group": 14,
"min_relay_fee": 0.00001,
},
@ -2369,6 +2443,7 @@ def main():
"use_csv": True,
"blocks_confirmed": 1,
"conf_target": 2,
"core_version_no": getKnownVersion("navcoin"),
"core_version_group": 18,
"chain_lookups": "local",
"startup_tries": 40,
@ -2393,6 +2468,7 @@ def main():
"rpctimeout": 60,
"walletrpctimeout": 120,
"walletrpctimeoutlong": 300,
"core_version_no": getKnownVersion("wownero"),
"core_type_group": "xmr",
},
}
@ -2403,6 +2479,9 @@ def main():
if LTC_RPC_USER != "":
chainclients["litecoin"]["rpcuser"] = LTC_RPC_USER
chainclients["litecoin"]["rpcpassword"] = LTC_RPC_PWD
if DOGE_RPC_USER != "":
chainclients["dogecoin"]["rpcuser"] = DOGE_RPC_USER
chainclients["dogecoin"]["rpcpassword"] = DOGE_RPC_PWD
if BTC_RPC_USER != "":
chainclients["bitcoin"]["rpcuser"] = BTC_RPC_USER
chainclients["bitcoin"]["rpcpassword"] = BTC_RPC_PWD
@ -2444,7 +2523,7 @@ def main():
init_coins = settings["chainclients"].keys()
logger.info("Active coins: %s", ", ".join(init_coins))
if coins_changed:
if with_coins_changed:
init_coins = with_coins
logger.info("Initialising coins: %s", ", ".join(init_coins))
initialise_wallets(
@ -2501,6 +2580,9 @@ def main():
return 0
if disable_coin != "":
if "particl" in disable_coin:
exitWithError("Cannot disable Particl (required for operation)")
logger.info("Disabling coin: %s", disable_coin)
settings = load_config(config_path)
@ -2563,7 +2645,7 @@ def main():
if not no_cores:
prepareCore(add_coin, known_coins[add_coin], settings, data_dir, extra_opts)
if not prepare_bin_only:
if not (prepare_bin_only or upgrade_cores):
prepareDataDir(
add_coin, settings, chain, particl_wallet_mnemonic, extra_opts
)
@ -2586,19 +2668,95 @@ def main():
logger.info(f"Done. Coin {add_coin} successfully added.")
return 0
logger.info("With coins: %s", ", ".join(with_coins))
logger.info(
"With coins: "
+ (", ".join(with_coins))
+ ("" if with_coins_changed else " (default)")
)
if os.path.exists(config_path):
if not prepare_bin_only:
exitWithError("{} exists".format(config_path))
else:
if prepare_bin_only:
settings = load_config(config_path)
# Add temporary default config for any coins that have not been added
for c in with_coins:
if c not in settings["chainclients"]:
settings["chainclients"][c] = chainclients[c]
elif upgrade_cores:
with open(config_path) as fs:
settings = json.load(fs)
# Add temporary default config for any coins that have not been added
for c in with_coins:
if c not in settings["chainclients"]:
settings["chainclients"][c] = chainclients[c]
with_coins_start = with_coins
if not with_coins_changed:
for coin_name, coin_settings in settings["chainclients"].items():
with_coins_start.add(coin_name)
with_coins = set()
for coin_name in with_coins_start:
if coin_name not in chainclients:
logger.warning(f"Skipping unknown coin: {coin_name}.")
continue
current_coin_settings = chainclients[coin_name]
if coin_name not in settings["chainclients"]:
exitWithError(f"{coin_name} not found in basicswap.json")
coin_settings = settings["chainclients"][coin_name]
current_version = current_coin_settings["core_version_no"]
have_version = coin_settings.get("core_version_no", "")
current_version_group = current_coin_settings.get(
"core_version_group", ""
)
have_version_group = coin_settings.get("core_version_group", "")
logger.info(
f"{coin_name}: have {have_version}, current {current_version}."
)
if not BSX_UPDATE_UNMANAGED and not (
coin_settings.get("manage_daemon", False)
or coin_settings.get("manage_wallet_daemon", False)
):
logger.info(" Unmanaged.")
elif have_version != current_version:
logger.info(f" Trying to update {coin_name}.")
with_coins.add(coin_name)
elif have_version_group != current_version_group:
logger.info(
f" Trying to update {coin_name}, version group differs."
)
with_coins.add(coin_name)
if len(with_coins) < 1:
logger.info("Nothing to do.")
return 0
# Run second loop to update, so all versions are logged together.
# Backup settings
old_config_path = config_path[:-5] + "_" + str(int(time.time())) + ".json"
with open(old_config_path, "w") as fp:
json.dump(settings, fp, indent=4)
for c in with_coins:
prepareCore(c, known_coins[c], settings, data_dir, extra_opts)
current_coin_settings = chainclients[c]
current_version = current_coin_settings["core_version_no"]
current_version_group = current_coin_settings.get(
"core_version_group", ""
)
settings["chainclients"][c]["core_version_no"] = current_version
if current_version_group != "":
settings["chainclients"][c][
"core_version_group"
] = current_version_group
with open(config_path, "w") as fp:
json.dump(settings, fp, indent=4)
logger.info("Done.")
return 0
else:
exitWithError(f"{config_path} exists")
else:
if upgrade_cores:
exitWithError(f"{config_path} not found")
for c in with_coins:
withchainclients[c] = chainclients[c]

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-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.
@ -19,7 +19,7 @@ import basicswap.config as cfg
from basicswap import __version__
from basicswap.ui.util import getCoinName
from basicswap.basicswap import BasicSwap
from basicswap.chainparams import chainparams
from basicswap.chainparams import chainparams, Coins
from basicswap.http_server import HttpThread
from basicswap.contrib.websocket_server import WebsocketServer
@ -58,23 +58,29 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
datadir_path = os.path.expanduser(node_dir)
# Rewrite litecoin.conf for 0.21.3
# Rewrite litecoin.conf
# TODO: Remove
needs_rewrite: bool = False
ltc_conf_path = os.path.join(datadir_path, "litecoin.conf")
if os.path.exists(ltc_conf_path):
config_to_add = ["blockfilterindex=0", "peerblockfilters=0"]
with open(ltc_conf_path) as fp:
for line in fp:
line = line.strip()
if line in config_to_add:
config_to_add.remove(line)
if len(config_to_add) > 0:
if line.endswith("=onion"):
needs_rewrite = True
break
if needs_rewrite:
logger.info("Rewriting litecoin.conf")
shutil.copyfile(ltc_conf_path, ltc_conf_path + ".last")
with open(ltc_conf_path, "a") as fp:
for line in config_to_add:
fp.write(line + "\n")
with (
open(ltc_conf_path + ".last") as fp_from,
open(ltc_conf_path, "w") as fp_to,
):
for line in fp_from:
if line.strip().endswith("=onion"):
fp_to.write(line.strip()[:-6] + "\n")
else:
fp_to.write(line)
args = [
daemon_bin,
@ -241,12 +247,25 @@ def getWalletBinName(coin_id: int, coin_settings, default_name: str) -> str:
) + (".exe" if os.name == "nt" else "")
def getCoreBinArgs(coin_id: int, coin_settings):
def getCoreBinArgs(coin_id: int, coin_settings, prepare=False, use_tor_proxy=False):
extra_args = []
if "config_filename" in coin_settings:
extra_args.append("--conf=" + coin_settings["config_filename"])
if "port" in coin_settings:
extra_args.append("--port=" + str(int(coin_settings["port"])))
if prepare is False and use_tor_proxy:
if coin_id == Coins.BCH:
# Without this BCH (27.1) will bind to the default BTC port, even with proxy set
extra_args.append("--bind=127.0.0.1:" + str(int(coin_settings["port"])))
else:
extra_args.append("--port=" + str(int(coin_settings["port"])))
# BTC versions from v28 fail to start if the onionport is in use.
# As BCH may use port 8334, disable it here.
# When tor is enabled a bind option for the onionport will be added to bitcoin.conf.
# https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-28.0.md?plain=1#L84
if prepare is False and use_tor_proxy is False and coin_id == Coins.BTC:
port: int = coin_settings.get("port", 8333)
extra_args.append(f"--bind=0.0.0.0:{port}")
return extra_args
@ -421,7 +440,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
swap_client.log.info(f"Starting {display_name} daemon")
filename: str = getCoreBinName(coin_id, v, c + "d")
extra_opts = getCoreBinArgs(coin_id, v)
extra_opts = getCoreBinArgs(coin_id, v, use_tor_proxy=swap_client.use_tor_proxy)
daemons.append(
startDaemon(v["datadir"], v["bindir"], filename, opts=extra_opts)
)

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -31,6 +32,7 @@ class Coins(IntEnum):
LTC_MWEB = 15
# ZANO = 16
BCH = 17
DOGE = 18
chainparams = {
@ -153,6 +155,44 @@ chainparams = {
"max_amount": 10000000 * COIN,
},
},
Coins.DOGE: {
"name": "dogecoin",
"ticker": "DOGE",
"message_magic": "Dogecoin Signed Message:\n",
"blocks_target": 60 * 1,
"decimal_places": 8,
"mainnet": {
"rpcport": 22555,
"pubkey_address": 30,
"script_address": 22,
"key_prefix": 158,
"hrp": "doge",
"bip44": 3,
"min_amount": 100000, # TODO increase above fee
"max_amount": 10000000 * COIN,
},
"testnet": {
"rpcport": 44555,
"pubkey_address": 113,
"script_address": 196,
"key_prefix": 241,
"hrp": "tdge",
"bip44": 1,
"min_amount": 100000,
"max_amount": 10000000 * COIN,
"name": "testnet4",
},
"regtest": {
"rpcport": 18332,
"pubkey_address": 111,
"script_address": 196,
"key_prefix": 239,
"hrp": "rdge",
"bip44": 1,
"min_amount": 100000,
"max_amount": 10000000 * COIN,
},
},
Coins.DCR: {
"name": "decred",
"ticker": "DCR",

View file

@ -36,6 +36,10 @@ LITECOIND = os.getenv("LITECOIND", "litecoind" + bin_suffix)
LITECOIN_CLI = os.getenv("LITECOIN_CLI", "litecoin-cli" + bin_suffix)
LITECOIN_TX = os.getenv("LITECOIN_TX", "litecoin-tx" + bin_suffix)
DOGECOIND = os.getenv("DOGECOIND", "dogecoind" + bin_suffix)
DOGECOIN_CLI = os.getenv("DOGECOIN_CLI", "dogecoin-cli" + bin_suffix)
DOGECOIN_TX = os.getenv("DOGECOIN_TX", "dogecoin-tx" + bin_suffix)
NAMECOIN_BINDIR = os.path.expanduser(
os.getenv("NAMECOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "namecoin"))
)

View file

@ -2,6 +2,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.
@ -53,6 +54,10 @@ class CoinInterface:
self._mx_wallet = threading.Lock()
self._altruistic = True
def interface_type(self) -> int:
# coin_type() returns the base coin type, interface_type() returns the coin+balance type.
return self.coin_type()
def setDefaults(self):
self._unknown_wallet_seed = True
self._restore_height = None
@ -188,7 +193,7 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
def curve_type():
return Curves.secp256k1
def getNewSecretKey(self) -> bytes:
def getNewRandomKey(self) -> bytes:
return i2b(getSecretInt())
def getPubkey(self, privkey: bytes) -> bytes:

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-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.
@ -1296,7 +1296,7 @@ class BTCInterface(Secp256k1Interface):
def getWalletTransaction(self, txid: bytes):
try:
return bytes.fromhex(self.rpc_wallet("gettransaction", [txid.hex()]))
return bytes.fromhex(self.rpc_wallet("gettransaction", [txid.hex()])["hex"])
except Exception as e: # noqa: F841
# TODO: filter errors
return None
@ -1397,6 +1397,7 @@ class BTCInterface(Secp256k1Interface):
cb_swap_value: int,
b_fee: int,
restore_height: int,
spend_actual_balance: bool = False,
lock_tx_vout=None,
) -> bytes:
self._log.info(
@ -1466,7 +1467,6 @@ class BTCInterface(Secp256k1Interface):
vout: int = -1,
):
# Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True):
self.importWatchOnlyAddress(dest_address, "bid")
self._log.info("Imported watch-only addr: {}".format(dest_address))

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.
@ -1726,6 +1726,7 @@ class DCRInterface(Secp256k1Interface):
cb_swap_value: int,
b_fee: int,
restore_height: int,
spend_actual_balance: bool = False,
lock_tx_vout=None,
) -> bytes:
self._log.info("spendBLockTx %s:\n", chain_b_lock_txid.hex())

View file

@ -0,0 +1,58 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 The BasicSwap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .btc import BTCInterface
from basicswap.chainparams import Coins
from basicswap.util.crypto import hash160
from basicswap.contrib.test_framework.script import (
CScript,
OP_DUP,
OP_CHECKSIG,
OP_HASH160,
OP_EQUAL,
OP_EQUALVERIFY,
)
class DOGEInterface(BTCInterface):
@staticmethod
def coin_type():
return Coins.DOGE
@staticmethod
def xmr_swap_b_lock_spend_tx_vsize() -> int:
return 192
def __init__(self, coin_settings, network, swap_client=None):
super(DOGEInterface, self).__init__(coin_settings, network, swap_client)
def getScriptDest(self, script: bytearray) -> bytearray:
# P2SH
script_hash = hash160(script)
assert len(script_hash) == 20
return CScript([OP_HASH160, script_hash, OP_EQUAL])
def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray:
# Return P2PKH
return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG])
def encodeScriptDest(self, script_dest: bytes) -> str:
# Extract hash from script
script_hash = script_dest[2:-1]
return self.sh_to_address(script_hash)
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
add_bytes = 107
size = len(tx.serialize_with_witness()) + add_bytes
pay_fee = round(fee_rate * size / 1000)
self._log.info(
f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}."
)
return pay_fee

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2023 tecnovert
# 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.
@ -52,7 +53,6 @@ class LTCInterface(BTCInterface):
def getWalletInfo(self):
rv = super(LTCInterface, self).getWalletInfo()
mweb_info = self.rpc_wallet_mweb("getwalletinfo")
rv["mweb_balance"] = mweb_info["balance"]
rv["mweb_unconfirmed"] = mweb_info["unconfirmed_balance"]
@ -88,8 +88,8 @@ class LTCInterface(BTCInterface):
class LTCInterfaceMWEB(LTCInterface):
@staticmethod
def coin_type():
def interface_type(self) -> int:
return Coins.LTC_MWEB
def __init__(self, coin_settings, network, swap_client=None):

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2023 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.
@ -666,6 +666,7 @@ class NAVInterface(BTCInterface):
cb_swap_value: int,
b_fee: int,
restore_height: int,
spend_actual_balance: bool = False,
lock_tx_vout=None,
) -> bytes:
self._log.info("spendBLockTx %s:\n", chain_b_lock_txid.hex())

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-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.
@ -187,6 +187,10 @@ class PARTInterface(BTCInterface):
class PARTInterfaceBlind(PARTInterface):
def interface_type(self) -> int:
return Coins.PART_BLIND
@staticmethod
def balance_type():
return BalanceTypes.BLIND
@ -240,7 +244,7 @@ class PARTInterfaceBlind(PARTInterface):
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes) -> bytes:
# Nonce is derived from vkbv, ephemeral_key isn't used
ephemeral_key = self.getNewSecretKey()
ephemeral_key = self.getNewRandomKey()
ephemeral_pubkey = self.getPubkey(ephemeral_key)
assert len(ephemeral_pubkey) == 33
nonce = self.getScriptLockTxNonce(vkbv)
@ -257,9 +261,7 @@ class PARTInterfaceBlind(PARTInterface):
]
params = [inputs, outputs]
rv = self.rpc_wallet("createrawparttransaction", params)
tx_bytes = bytes.fromhex(rv["hex"])
return tx_bytes
return bytes.fromhex(rv["hex"])
def fundSCLockTx(self, tx_bytes: bytes, feerate: int, vkbv: bytes) -> bytes:
feerate_str = self.format_amount(feerate)
@ -288,7 +290,7 @@ class PARTInterfaceBlind(PARTInterface):
"lockUnspents": True,
"feeRate": feerate_str,
}
rv = self.rpc(
rv = self.rpc_wallet(
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
)
return bytes.fromhex(rv["hex"])
@ -307,7 +309,7 @@ class PARTInterfaceBlind(PARTInterface):
lock_tx_obj = self.rpc("decoderawtransaction", [tx_lock_bytes.hex()])
assert self.getTxid(tx_lock_bytes).hex() == lock_tx_obj["txid"]
# Nonce is derived from vkbv, ephemeral_key isn't used
ephemeral_key = self.getNewSecretKey()
ephemeral_key = self.getNewRandomKey()
ephemeral_pubkey = self.getPubkey(ephemeral_key)
assert len(ephemeral_pubkey) == 33
nonce = self.getScriptLockTxNonce(vkbv)
@ -348,7 +350,7 @@ class PARTInterfaceBlind(PARTInterface):
dummy_witness_stack = [x.hex() for x in dummy_witness_stack]
# Use a junk change pubkey to avoid adding unused keys to the wallet
zero_change_key = self.getNewSecretKey()
zero_change_key = self.getNewRandomKey()
zero_change_pubkey = self.getPubkey(zero_change_key)
inputs_info = {
"0": {
@ -428,7 +430,7 @@ class PARTInterfaceBlind(PARTInterface):
dummy_witness_stack = [x.hex() for x in dummy_witness_stack]
# Use a junk change pubkey to avoid adding unused keys to the wallet
zero_change_key = self.getNewSecretKey()
zero_change_key = self.getNewRandomKey()
zero_change_pubkey = self.getPubkey(zero_change_key)
inputs_info = {
"0": {
@ -745,7 +747,7 @@ class PARTInterfaceBlind(PARTInterface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
# Use a junk change pubkey to avoid adding unused keys to the wallet
zero_change_key = self.getNewSecretKey()
zero_change_key = self.getNewRandomKey()
zero_change_pubkey = self.getPubkey(zero_change_key)
inputs_info = {
"0": {
@ -949,7 +951,7 @@ class PARTInterfaceBlind(PARTInterface):
dummy_witness_stack = [x.hex() for x in dummy_witness_stack]
# Use a junk change pubkey to avoid adding unused keys to the wallet
zero_change_key = self.getNewSecretKey()
zero_change_key = self.getNewRandomKey()
zero_change_pubkey = self.getPubkey(zero_change_key)
inputs_info = {
"0": {
@ -1158,10 +1160,44 @@ class PARTInterfaceBlind(PARTInterface):
sub_fee: bool = False,
lock_unspents: bool = True,
) -> str:
txn = self.rpc_wallet(
"createrawtransaction", [[], {addr_to: self.format_amount(amount)}]
# Estimate lock tx size / fee
# self.createSCLockTx
vkbv = self.getNewRandomKey()
ephemeral_key = self.getNewRandomKey()
ephemeral_pubkey = self.getPubkey(ephemeral_key)
assert len(ephemeral_pubkey) == 33
nonce = self.getScriptLockTxNonce(vkbv)
inputs = []
outputs = [
{
"type": "blind",
"amount": self.format_amount(amount),
"address": addr_to,
"nonce": nonce.hex(),
"data": ephemeral_pubkey.hex(),
}
]
params = [inputs, outputs]
tx_hex = self.rpc_wallet("createrawparttransaction", params)["hex"]
# self.fundSCLockTx
tx_obj = self.rpc("decoderawtransaction", [tx_hex])
assert len(tx_obj["vout"]) == 1
txo = tx_obj["vout"][0]
blinded_info = self.rpc(
"rewindrangeproof", [txo["rangeproof"], txo["valueCommitment"], nonce.hex()]
)
outputs_info = {
0: {
"value": blinded_info["amount"],
"blind": blinded_info["blind"],
"nonce": nonce.hex(),
}
}
options = {
"lockUnspents": lock_unspents,
"conf_target": self._conf_target,
@ -1170,10 +1206,16 @@ class PARTInterfaceBlind(PARTInterface):
options["subtractFeeFromOutputs"] = [
0,
]
return self.rpc_wallet("fundrawtransactionfrom", ["blind", txn, options])["hex"]
return self.rpc_wallet(
"fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options]
)["hex"]
class PARTInterfaceAnon(PARTInterface):
def interface_type(self) -> int:
return Coins.PART_ANON
@staticmethod
def balance_type():
return BalanceTypes.ANON

View file

@ -326,7 +326,7 @@ class XMRInterface(CoinInterface):
return float(self.format_amount(fee_per_k_bytes)), "get_fee_estimate"
def getNewSecretKey(self) -> bytes:
def getNewRandomKey(self) -> bytes:
# Note: Returned bytes are in big endian order
return i2b(edu.get_secret())

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
# 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.
@ -250,6 +251,8 @@ def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes:
"is_expired": o.expire_at <= swap_client.getTime(),
"is_own_offer": o.was_sent,
"is_revoked": True if o.active_ind == 2 else False,
"is_public": o.addr_to == swap_client.network_addr
or o.addr_to.strip() == "",
}
if with_extra_info:
offer_data["amount_negotiable"] = o.amount_negotiable
@ -704,7 +707,10 @@ def js_identities(self, url_split, post_string: str, is_json: bool) -> bytes:
ensure("address" in filters, "Must provide an address to modify data")
swap_client.setIdentityData(filters, set_data)
return bytes(json.dumps(swap_client.listIdentities(filters)), "UTF-8")
rv = swap_client.listIdentities(filters)
if "address" in filters:
rv = {} if len(rv) < 1 else rv[0]
return bytes(json.dumps(rv), "UTF-8")
def js_automationstrategies(self, url_split, post_string: str, is_json: bool) -> bytes:
@ -829,28 +835,40 @@ def js_getcoinseed(self, url_split, post_string, is_json) -> bytes:
raise ValueError("Particl wallet seed is set from the Basicswap mnemonic.")
ci = swap_client.ci(coin)
rv = {"coin": ci.ticker()}
if coin in (Coins.XMR, Coins.WOW):
key_view = swap_client.getWalletKey(coin, 1, for_ed25519=True)
key_spend = swap_client.getWalletKey(coin, 2, for_ed25519=True)
address = ci.getAddressFromKeys(key_view, key_spend)
return bytes(
json.dumps(
{
"coin": ci.ticker(),
"key_view": ci.encodeKey(key_view),
"key_spend": ci.encodeKey(key_spend),
"address": address,
}
),
"UTF-8",
expect_address = swap_client.getCachedMainWalletAddress(ci)
rv.update(
{
"key_view": ci.encodeKey(key_view),
"key_spend": ci.encodeKey(key_spend),
"address": address,
"expected_address": (
"Unset" if expect_address is None else expect_address
),
}
)
else:
seed_key = swap_client.getWalletKey(coin, 1)
seed_id = ci.getSeedHash(seed_key)
expect_seedid = swap_client.getStringKV(
"main_wallet_seedid_" + ci.coin_name().lower()
)
rv.update(
{
"seed": seed_key.hex(),
"seed_id": seed_id.hex(),
"expected_seed_id": "Unset" if expect_seedid is None else expect_seedid,
}
)
seed_key = swap_client.getWalletKey(coin, 1)
seed_id = ci.getSeedHash(seed_key)
return bytes(
json.dumps(
{"coin": ci.ticker(), "seed": seed_key.hex(), "seed_id": seed_id.hex()}
),
json.dumps(rv),
"UTF-8",
)

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-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.
@ -15,9 +15,10 @@ from basicswap.chainparams import (
Coins,
)
from basicswap.basicswap_util import (
EventLogTypes,
KeyTypes,
SwapTypes,
EventLogTypes,
TxTypes,
)
from . import ProtocolInterface
from basicswap.contrib.test_framework.script import CScript, CScriptOp, OP_CHECKMULTISIG
@ -55,7 +56,7 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()))
# The no-script coin is always the follower
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from)
reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to)
ci_from = self.ci(Coins(offer.coin_from))
ci_to = self.ci(Coins(offer.coin_to))
ci_follower = ci_from if reverse_bid else ci_to
@ -89,16 +90,20 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
summed_pkbs = ci_follower.getPubkey(vkbs)
if summed_pkbs != xmr_swap.pkbs:
err_msg: str = "Summed key does not match expected wallet spend pubkey"
have_pk = summed_pkbs.hex()
expect_pk = xmr_swap.pkbs.hex()
self.log.error(f"{err_msg}. Got: {have_pk}, Expect: {expect_pk}")
self.log.error(
f"{err_msg}. Got: {summed_pkbs.hex()}, Expect: {xmr_swap.pkbs.hex()}"
)
raise ValueError(err_msg)
if ci_follower.coin_type() in (Coins.XMR, Coins.WOW):
coin_to: int = ci_follower.interface_type()
base_coin_to: int = ci_follower.coin_type()
if coin_to in (Coins.XMR, Coins.WOW):
address_to = self.getCachedMainWalletAddress(ci_follower, use_cursor)
elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON):
address_to = self.getCachedStealthAddressForCoin(base_coin_to, use_cursor)
else:
address_to = self.getCachedStealthAddressForCoin(
ci_follower.coin_type(), use_cursor
address_to = self.getReceiveAddressFromPool(
base_coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, use_cursor
)
amount = bid.amount_to
lock_tx_vout = bid.getLockTXBVout()
@ -145,10 +150,11 @@ def getChainBSplitKey(swap_client, bid, xmr_swap, offer):
was_sent: bool = bid.was_received if reverse_bid else bid.was_sent
key_type = KeyTypes.KBSF if was_sent else KeyTypes.KBSL
return ci_follower.encodeKey(
swap_client.getPathKey(
ci_leader.coin_type(),
ci_follower.coin_type(),
ci_leader.interface_type(),
ci_follower.interface_type(),
bid.created_at,
xmr_swap.contract_count,
key_type,

View file

@ -356,4 +356,14 @@ select.disabled-select-enabled {
#toggle-auto-refresh[data-enabled="true"] {
@apply bg-green-500 hover:bg-green-600 focus:ring-green-300;
}
[data-popper-placement] {
will-change: transform;
transform: translateZ(0);
}
.tooltip {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}

View file

Before

(image error) Size: 2.3 KiB

After

(image error) Size: 2.3 KiB

View file

Before

(image error) Size: 16 KiB

After

(image error) Size: 16 KiB

View file

Before

(image error) Size: 1.8 KiB

After

(image error) Size: 1.8 KiB

Binary file not shown.

Before

(image error) Size: 1.7 KiB

Binary file not shown.

Before

(image error) Size: 7.7 KiB

Binary file not shown.

Before

(image error) Size: 1.7 KiB

Binary file not shown.

Before

(image error) Size: 7.7 KiB

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
// Config
// CONFIG
const config = {
apiKeys: getAPIKeys(),
coins: [
@ -38,14 +38,7 @@ const config = {
currentResolution: 'year'
};
function getAPIKeys() {
return {
cryptoCompare: '{{chart_api_key}}',
coinGecko: '{{coingecko_api_key}}'
};
}
// Utils
// UTILS
const utils = {
formatNumber: (number, decimals = 2) =>
number.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ','),
@ -69,7 +62,7 @@ const utils = {
}
};
// Error
// ERROR
class AppError extends Error {
constructor(message, type = 'AppError') {
super(message);
@ -77,7 +70,7 @@ class AppError extends Error {
}
}
// Log
// LOG
const logger = {
log: (message) => console.log(`[AppLog] ${new Date().toISOString()}: ${message}`),
warn: (message) => console.warn(`[AppWarn] ${new Date().toISOString()}: ${message}`),
@ -86,8 +79,9 @@ const logger = {
// API
const api = {
makePostRequest: (url, headers = {}) => {
return new Promise((resolve, reject) => {
makePostRequest: (url, headers = {}) => {
const apiKeys = getAPIKeys();
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/json/readurl');
xhr.setRequestHeader('Content-Type', 'application/json');
@ -147,12 +141,12 @@ const api = {
.map(coin => coin.name)
.join(',');
const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=${coinIds}&vs_currencies=usd,btc&include_24hr_vol=true&include_24hr_change=true&api_key=${config.apiKeys.coinGecko}`;
console.log(`Fetching data for multiple coins from CoinGecko: ${url}`);
//console.log(`Fetching data for multiple coins from CoinGecko: ${url}`);
try {
const data = await api.makePostRequest(url);
console.log(`Raw CoinGecko data:`, data);
//console.log(`Raw CoinGecko data:`, data);
if (typeof data !== 'object' || data === null) {
throw new AppError(`Invalid data structure received from CoinGecko`);
@ -171,11 +165,11 @@ const api = {
};
});
console.log(`Transformed CoinGecko data:`, transformedData);
//console.log(`Transformed CoinGecko data:`, transformedData);
cache.set(cacheKey, transformedData);
return transformedData;
} catch (error) {
console.error(`Error fetching CoinGecko data:`, error);
//console.error(`Error fetching CoinGecko data:`, error);
return { error: error.message };
}
},
@ -185,30 +179,30 @@ const api = {
coinSymbols = [coinSymbols];
}
console.log(`Fetching historical data for coins: ${coinSymbols.join(', ')}`);
//console.log(`Fetching historical data for coins: ${coinSymbols.join(', ')}`);
const results = {};
const fetchPromises = coinSymbols.map(async coin => {
const coinConfig = config.coins.find(c => c.symbol === coin);
if (!coinConfig) {
console.error(`Coin configuration not found for ${coin}`);
//console.error(`Coin configuration not found for ${coin}`);
return;
}
if (coin === 'WOW') {
const url = `${config.apiEndpoints.coinGecko}/coins/wownero/market_chart?vs_currency=usd&days=1&api_key=${config.apiKeys.coinGecko}`;
console.log(`CoinGecko URL for WOW: ${url}`);
//console.log(`CoinGecko URL for WOW: ${url}`);
try {
const response = await api.makePostRequest(url);
if (response && response.prices) {
results[coin] = response.prices;
} else {
console.error(`Unexpected data structure for WOW:`, response);
//console.error(`Unexpected data structure for WOW:`, response);
}
} catch (error) {
console.error(`Error fetching CoinGecko data for WOW:`, error);
//console.error(`Error fetching CoinGecko data for WOW:`, error);
}
} else {
const resolution = config.resolutions[config.currentResolution];
@ -219,31 +213,31 @@ const api = {
url = `${config.apiEndpoints.cryptoCompareHistorical}?fsym=${coin}&tsym=USD&limit=${resolution.days}&api_key=${config.apiKeys.cryptoCompare}`;
}
console.log(`CryptoCompare URL for ${coin}: ${url}`);
//console.log(`CryptoCompare URL for ${coin}: ${url}`);
try {
const response = await api.makePostRequest(url);
if (response.Response === "Error") {
console.error(`API Error for ${coin}:`, response.Message);
//console.error(`API Error for ${coin}:`, response.Message);
} else if (response.Data && response.Data.Data) {
results[coin] = response.Data;
} else {
console.error(`Unexpected data structure for ${coin}:`, response);
//console.error(`Unexpected data structure for ${coin}:`, response);
}
} catch (error) {
console.error(`Error fetching CryptoCompare data for ${coin}:`, error);
//console.error(`Error fetching CryptoCompare data for ${coin}:`, error);
}
}
});
await Promise.all(fetchPromises);
console.log('Final results object:', JSON.stringify(results, null, 2));
//console.log('Final results object:', JSON.stringify(results, null, 2));
return results;
}
};
// Cache
// CACHE
const cache = {
set: (key, value, customTtl = null) => {
const item = {
@ -252,7 +246,7 @@ const cache = {
expiresAt: Date.now() + (customTtl || app.cacheTTL)
};
localStorage.setItem(key, JSON.stringify(item));
console.log(`Cache set for ${key}, expires in ${(customTtl || app.cacheTTL) / 1000} seconds`);
//console.log(`Cache set for ${key}, expires in ${(customTtl || app.cacheTTL) / 1000} seconds`);
},
get: (key) => {
const itemStr = localStorage.getItem(key);
@ -263,17 +257,17 @@ const cache = {
const item = JSON.parse(itemStr);
const now = Date.now();
if (now < item.expiresAt) {
console.log(`Cache hit for ${key}, ${(item.expiresAt - now) / 1000} seconds remaining`);
//console.log(`Cache hit for ${key}, ${(item.expiresAt - now) / 1000} seconds remaining`);
return {
value: item.value,
remainingTime: item.expiresAt - now
};
} else {
console.log(`Cache expired for ${key}`);
//console.log(`Cache expired for ${key}`);
localStorage.removeItem(key);
}
} catch (e) {
console.error('Error parsing cache item:', e);
//console.error('Error parsing cache item:', e);
localStorage.removeItem(key);
}
return null;
@ -287,7 +281,7 @@ const cache = {
localStorage.removeItem(key);
}
});
console.log('Cache cleared');
//console.log('Cache cleared');
}
};
@ -343,7 +337,7 @@ displayCoinData: (coin, data) => {
updateUI(false);
} catch (error) {
console.error(`Error displaying data for ${coin}:`, error.message);
//console.error(`Error displaying data for ${coin}:`, error.message);
updateUI(true);
}
},
@ -488,7 +482,7 @@ displayCoinData: (coin, data) => {
}
};
// Chart
// CHART
const chartModule = {
chart: null,
currentCoin: 'BTC',
@ -675,12 +669,12 @@ const chartModule = {
plugins: [chartModule.verticalLinePlugin]
});
console.log('Chart initialized:', chartModule.chart);
//console.log('Chart initialized:', chartModule.chart);
},
prepareChartData: (coinSymbol, data) => {
if (!data) {
console.error(`No data received for ${coinSymbol}`);
//console.error(`No data received for ${coinSymbol}`);
return [];
}
@ -739,7 +733,7 @@ const chartModule = {
y: price
}));
} else {
console.error(`Unexpected data structure for ${coinSymbol}:`, data);
//console.error(`Unexpected data structure for ${coinSymbol}:`, data);
return [];
}
@ -748,7 +742,7 @@ const chartModule = {
y: point.y
}));
} catch (error) {
console.error(`Error preparing chart data for ${coinSymbol}:`, error);
//console.error(`Error preparing chart data for ${coinSymbol}:`, error);
return [];
}
},
@ -786,21 +780,21 @@ const chartModule = {
if (cachedData && Object.keys(cachedData.value).length > 0) {
data = cachedData.value;
console.log(`Using cached data for ${coinSymbol} (${config.currentResolution})`);
//console.log(`Using cached data for ${coinSymbol} (${config.currentResolution})`);
} else {
console.log(`Fetching fresh data for ${coinSymbol} (${config.currentResolution})`);
//console.log(`Fetching fresh data for ${coinSymbol} (${config.currentResolution})`);
const allData = await api.fetchHistoricalDataXHR([coinSymbol]);
data = allData[coinSymbol];
if (!data || Object.keys(data).length === 0) {
throw new Error(`No data returned for ${coinSymbol}`);
}
console.log(`Caching new data for ${cacheKey}`);
//console.log(`Caching new data for ${cacheKey}`);
cache.set(cacheKey, data, config.cacheTTL);
cachedData = null;
}
const chartData = chartModule.prepareChartData(coinSymbol, data);
console.log(`Prepared chart data for ${coinSymbol}:`, chartData.slice(0, 5));
//console.log(`Prepared chart data for ${coinSymbol}:`, chartData.slice(0, 5));
if (chartData.length === 0) {
throw new Error(`No valid chart data for ${coinSymbol}`);
@ -832,7 +826,7 @@ const chartModule = {
chartModule.chart.update('active');
} else {
console.error('Chart object not initialized');
//console.error('Chart object not initialized');
throw new Error('Chart object not initialized');
}
@ -841,7 +835,7 @@ const chartModule = {
ui.updateLoadTimeAndCache(loadTime, cachedData);
} catch (error) {
console.error(`Error updating chart for ${coinSymbol}:`, error);
//console.error(`Error updating chart for ${coinSymbol}:`, error);
ui.displayErrorMessage(`Failed to update chart for ${coinSymbol}: ${error.message}`);
} finally {
chartModule.hideChartLoader();
@ -849,14 +843,30 @@ const chartModule = {
},
showChartLoader: () => {
document.getElementById('chart-loader').classList.remove('hidden');
document.getElementById('coin-chart').classList.add('hidden');
const loader = document.getElementById('chart-loader');
const chart = document.getElementById('coin-chart');
if (!loader || !chart) {
//console.warn('Chart loader or chart container elements not found');
return;
}
loader.classList.remove('hidden');
chart.classList.add('hidden');
},
hideChartLoader: () => {
document.getElementById('chart-loader').classList.add('hidden');
document.getElementById('coin-chart').classList.remove('hidden');
}
const loader = document.getElementById('chart-loader');
const chart = document.getElementById('coin-chart');
if (!loader || !chart) {
//console.warn('Chart loader or chart container elements not found');
return;
}
loader.classList.add('hidden');
chart.classList.remove('hidden');
},
};
Chart.register(chartModule.verticalLinePlugin);
@ -928,7 +938,7 @@ const app = {
chartModule.initChart();
chartModule.showChartLoader();
} else {
console.warn('Chart container not found, skipping chart initialization');
//console.warn('Chart container not found, skipping chart initialization');
}
console.log('Loading all coin data...');
@ -941,13 +951,13 @@ const app = {
}
ui.setActiveContainer('btc-container');
console.log('Setting up event listeners and initializations...');
//console.log('Setting up event listeners and initializations...');
app.setupEventListeners();
app.initializeSelectImages();
app.initAutoRefresh();
} catch (error) {
console.error('Error during initialization:', error);
//console.error('Error during initialization:', error);
ui.displayErrorMessage('Failed to initialize the dashboard. Please try refreshing the page.');
} finally {
ui.hideLoader();
@ -959,7 +969,7 @@ const app = {
},
loadAllCoinData: async () => {
console.log('Loading data for all coins...');
//console.log('Loading data for all coins...');
try {
const allCoinData = await api.fetchCoinGeckoDataXHR();
if (allCoinData.error) {
@ -974,24 +984,24 @@ const app = {
const cacheKey = `coinData_${coin.symbol}`;
cache.set(cacheKey, coinData);
} else {
console.warn(`No data found for ${coin.symbol}`);
//console.warn(`No data found for ${coin.symbol}`);
}
}
} catch (error) {
console.error('Error loading all coin data:', error);
//console.error('Error loading all coin data:', error);
ui.displayErrorMessage('Failed to load coin data. Please try refreshing the page.');
} finally {
console.log('All coin data loaded');
//console.log('All coin data loaded');
}
},
loadCoinData: async (coin) => {
console.log(`Loading data for ${coin.symbol}...`);
//console.log(`Loading data for ${coin.symbol}...`);
const cacheKey = `coinData_${coin.symbol}`;
let cachedData = cache.get(cacheKey);
let data;
if (cachedData) {
console.log(`Using cached data for ${coin.symbol}`);
//console.log(`Using cached data for ${coin.symbol}`);
data = cachedData.value;
} else {
try {
@ -1004,11 +1014,11 @@ const app = {
if (data.error) {
throw new Error(data.error);
}
console.log(`Caching new data for ${coin.symbol}`);
//console.log(`Caching new data for ${coin.symbol}`);
cache.set(cacheKey, data);
cachedData = null;
} catch (error) {
console.error(`Error fetching ${coin.symbol} data:`, error.message);
//console.error(`Error fetching ${coin.symbol} data:`, error.message);
data = {
error: error.message
};
@ -1018,16 +1028,16 @@ const app = {
}
ui.displayCoinData(coin.symbol, data);
ui.updateLoadTimeAndCache(0, cachedData);
console.log(`Data loaded for ${coin.symbol}`);
//console.log(`Data loaded for ${coin.symbol}`);
},
setupEventListeners: () => {
console.log('Setting up event listeners...');
//console.log('Setting up event listeners...');
config.coins.forEach(coin => {
const container = document.getElementById(`${coin.symbol.toLowerCase()}-container`);
if (container) {
container.addEventListener('click', () => {
console.log(`${coin.symbol} container clicked`);
//console.log(`${coin.symbol} container clicked`);
ui.setActiveContainer(`${coin.symbol.toLowerCase()}-container`);
if (chartModule.chart) {
if (coin.symbol === 'WOW') {
@ -1054,7 +1064,7 @@ const app = {
if (closeErrorButton) {
closeErrorButton.addEventListener('click', ui.hideErrorMessage);
}
console.log('Event listeners set up');
//console.log('Event listeners set up');
},
initAutoRefresh: () => {
@ -1090,7 +1100,7 @@ const app = {
earliestExpiration = Math.min(earliestExpiration, cachedItem.expiresAt);
}
} catch (error) {
console.error(`Error parsing cached item ${key}:`, error);
//console.error(`Error parsing cached item ${key}:`, error);
localStorage.removeItem(key);
}
}
@ -1149,7 +1159,7 @@ const app = {
const cacheKey = `coinData_${coin.symbol}`;
cache.set(cacheKey, coinData);
} else {
console.error(`No data found for ${coin.symbol}`);
//console.error(`No data found for ${coin.symbol}`);
}
}
@ -1164,7 +1174,7 @@ const app = {
console.log('All data refreshed successfully');
} catch (error) {
console.error('Error refreshing all data:', error);
//console.error('Error refreshing all data:', error);
ui.displayErrorMessage('Failed to refresh all data. Please try again.');
} finally {
ui.hideLoader();
@ -1231,7 +1241,7 @@ const app = {
},
startSpinAnimation: () => {
console.log('Starting spin animation on auto-refresh button');
//console.log('Starting spin animation on auto-refresh button');
const svg = document.querySelector('#toggle-auto-refresh svg');
if (svg) {
svg.classList.add('animate-spin');
@ -1242,7 +1252,7 @@ const app = {
},
stopSpinAnimation: () => {
console.log('Stopping spin animation on auto-refresh button');
//console.log('Stopping spin animation on auto-refresh button');
const svg = document.querySelector('#toggle-auto-refresh svg');
if (svg) {
svg.classList.remove('animate-spin');
@ -1250,7 +1260,7 @@ const app = {
},
updateLastRefreshedTime: () => {
console.log('Updating last refreshed time');
//console.log('Updating last refreshed time');
const lastRefreshedElement = document.getElementById('last-refreshed-time');
if (lastRefreshedElement && app.lastRefreshedTime) {
const formattedTime = app.lastRefreshedTime.toLocaleTimeString();
@ -1268,43 +1278,43 @@ const app = {
},
updateBTCPrice: async () => {
console.log('Updating BTC price...');
//console.log('Updating BTC price...');
try {
const priceData = await api.fetchCoinGeckoDataXHR();
if (priceData.error) {
console.error('Error fetching BTC price:', priceData.error);
//console.error('Error fetching BTC price:', priceData.error);
app.btcPriceUSD = 0;
} else if (priceData.btc && priceData.btc.current_price) {
app.btcPriceUSD = priceData.btc.current_price;
} else {
console.error('Unexpected BTC data structure:', priceData);
//console.error('Unexpected BTC data structure:', priceData);
app.btcPriceUSD = 0;
}
} catch (error) {
console.error('Error fetching BTC price:', error);
//console.error('Error fetching BTC price:', error);
app.btcPriceUSD = 0;
}
console.log('Current BTC price:', app.btcPriceUSD);
//console.log('Current BTC price:', app.btcPriceUSD);
},
sortTable: (columnIndex) => {
console.log(`Sorting column: ${columnIndex}`);
//console.log(`Sorting column: ${columnIndex}`);
const sortableColumns = [0, 5, 6, 7]; // 0: Time, 5: Rate, 6: Market +/-, 7: Trade
if (!sortableColumns.includes(columnIndex)) {
console.log(`Column ${columnIndex} is not sortable`);
//console.log(`Column ${columnIndex} is not sortable`);
return;
}
const table = document.querySelector('table');
if (!table) {
console.error("Table not found for sorting.");
//console.error("Table not found for sorting.");
return;
}
const rows = Array.from(table.querySelectorAll('tbody tr'));
console.log(`Found ${rows.length} rows to sort`);
const sortIcon = document.getElementById(`sort-icon-${columnIndex}`);
if (!sortIcon) {
console.error("Sort icon not found.");
//console.error("Sort icon not found.");
return;
}
const sortOrder = sortIcon.textContent === '↓' ? 1 : -1;
@ -1318,7 +1328,7 @@ sortTable: (columnIndex) => {
case 1: // Time column
aValue = getSafeTextContent(a.querySelector('td:first-child .text-xs:first-child'));
bValue = getSafeTextContent(b.querySelector('td:first-child .text-xs:first-child'));
console.log(`Comparing times: "${aValue}" vs "${bValue}"`);
//console.log(`Comparing times: "${aValue}" vs "${bValue}"`);
const parseTime = (timeStr) => {
const [value, unit] = timeStr.split(' ');
@ -1337,7 +1347,7 @@ sortTable: (columnIndex) => {
case 6: // Market +/-
aValue = getSafeTextContent(a.cells[columnIndex]);
bValue = getSafeTextContent(b.cells[columnIndex]);
console.log(`Comparing values: "${aValue}" vs "${bValue}"`);
//console.log(`Comparing values: "${aValue}" vs "${bValue}"`);
aValue = parseFloat(aValue.replace(/[^\d.-]/g, '') || '0');
bValue = parseFloat(bValue.replace(/[^\d.-]/g, '') || '0');
@ -1346,8 +1356,8 @@ sortTable: (columnIndex) => {
case 7: // Trade
const aCell = a.cells[columnIndex];
const bCell = b.cells[columnIndex];
console.log('aCell:', aCell ? aCell.outerHTML : 'null');
console.log('bCell:', bCell ? bCell.outerHTML : 'null');
//console.log('aCell:', aCell ? aCell.outerHTML : 'null');
//console.log('bCell:', bCell ? bCell.outerHTML : 'null');
aValue = getSafeTextContent(aCell.querySelector('a')) ||
getSafeTextContent(aCell.querySelector('button')) ||
@ -1359,7 +1369,7 @@ sortTable: (columnIndex) => {
aValue = aValue.toLowerCase();
bValue = bValue.toLowerCase();
console.log(`Comparing trade actions: "${aValue}" vs "${bValue}"`);
//console.log(`Comparing trade actions: "${aValue}" vs "${bValue}"`);
if (aValue === bValue) return 0;
if (aValue === "swap") return -1 * sortOrder;
@ -1369,7 +1379,7 @@ sortTable: (columnIndex) => {
default:
aValue = getSafeTextContent(a.cells[columnIndex]);
bValue = getSafeTextContent(b.cells[columnIndex]);
console.log(`Comparing default values: "${aValue}" vs "${bValue}"`);
//console.log(`Comparing default values: "${aValue}" vs "${bValue}"`);
return aValue.localeCompare(bValue, undefined, {
numeric: true,
sensitivity: 'base'
@ -1381,9 +1391,9 @@ sortTable: (columnIndex) => {
if (tbody) {
rows.forEach(row => tbody.appendChild(row));
} else {
console.error("Table body not found.");
//console.error("Table body not found.");
}
console.log('Sorting completed');
//console.log('Sorting completed');
},
initializeSelectImages: () => {
@ -1391,7 +1401,7 @@ sortTable: (columnIndex) => {
const select = document.getElementById(selectId);
const button = document.getElementById(`${selectId}_button`);
if (!select || !button) {
console.error(`Elements not found for ${selectId}`);
//console.error(`Elements not found for ${selectId}`);
return;
}
const selectedOption = select.options[select.selectedIndex];
@ -1418,7 +1428,7 @@ sortTable: (columnIndex) => {
select.addEventListener('change', handleSelectChange);
updateSelectedImage(selectId);
} else {
console.error(`Select element not found for ${selectId}`);
//console.error(`Select element not found for ${selectId}`);
}
});
},
@ -1445,7 +1455,7 @@ updateResolutionButtons: (coinSymbol) => {
});
},
toggleAutoRefresh: () => {
toggleAutoRefresh: () => {
console.log('Toggling auto-refresh');
app.isAutoRefreshEnabled = !app.isAutoRefreshEnabled;
localStorage.setItem('autoRefreshEnabled', app.isAutoRefreshEnabled.toString());
@ -1480,4 +1490,12 @@ resolutionButtons.forEach(button => {
});
});
// LOAD
app.init();
document.addEventListener('visibilitychange', () => {
if (!document.hidden && chartModule.chart) {
console.log('Page became visible, reinitializing chart');
chartModule.updateChart(chartModule.currentCoin, true);
}
});

View file

@ -25,7 +25,7 @@
<div class="flex items-center">
<p class="text-sm text-gray-90 dark:text-white font-medium">© 2024~ (BSX) BasicSwap</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
<p class="text-sm text-coolGray-400 font-medium">BSX: v{{ version }}</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
<p class="text-sm text-coolGray-400 font-medium">GUI: v3.1.1</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
<p class="text-sm text-coolGray-400 font-medium">GUI: v3.1.2</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
<p class="mr-2 text-sm font-bold dark:text-white text-gray-90 ">Made with </p>
{{ love_svg | safe }}
</div>

View file

@ -4,7 +4,7 @@
<section class="relative py-24 overflow-hidden">
<div class="container px-4 mx-auto mb-16 md:mb-0">
<div class="md:w-1/2 pl-4">
<span class="inline-block py-1 px-3 mb-4 text-xs leading-5 bg-blue-500 text-white font-medium rounded-full shadow-sm">(BSX) BasicSwap v{{ version }} - (GUI) v.3.1.1</span>
<span class="inline-block py-1 px-3 mb-4 text-xs leading-5 bg-blue-500 text-white font-medium rounded-full shadow-sm">(BSX) BasicSwap v{{ version }} - (GUI) v.3.1.2</span>
<h3 class="mb-6 text-4xl md:text-5xl leading-tight text-coolGray-900 font-bold tracking-tighter dark:text-white">Welcome to BasicSwap DEX</h3>
<p class="mb-12 text-lg md:text-xl text-coolGray-500 dark:text-gray-300 font-medium">The World's Most Secure and Decentralized DEX, Safely swap cryptocurrencies without central points of failure.
Its free, completely trustless, and highly secure.</p>

View file

@ -392,21 +392,11 @@
</th>
<th class="p-0">
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Settings</span>
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Value</span>
</div>
</th>
</tr>
</thead>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600 h-20">
<td class="py-3 px-6">Amount you will get <span class="bold" id="bid_amt_from">{{ data.amt_from }}</span> {{ data.tla_from }}
{% if data.xmr_type == true %}
(excluding estimated {{ data.amt_from_lock_spend_tx_fee }} {{ data.tla_from }} in tx fees).
{% else %}
(excluding a tx fee).
{% endif %}</td>
<td class="py-3 px-6">Amount you will send <span class="bold" id="bid_amt_to">{{ data.amt_to }}</span> {{ data.tla_to }}
</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Send From Address</td>
<td class="py-3 px-6">
@ -475,21 +465,135 @@ if (document.readyState === 'loading') {
handleBidsPageAddress();
}
</script>
{% if data.amount_negotiable == true %}
<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">Amount</td>
<td class="py-3 px-6">
<input type="text" class="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-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="bid_amount" name="bid_amount" value="{{ data.bid_amount }}" onchange="updateBidParams('amount');">
</td>
</tr>
{% endif %}
{% if data.rate_negotiable == true %}
<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">Rate</td>
<td class="py-3 px-6"> <input type="text" class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-100 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="bid_rate" name="bid_rate" value="{{ data.bid_rate }}" onchange="updateBidParams('rate');">
</td>
</tr>
{% endif %}
{% if data.amount_negotiable == true %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="px-6">
<span class="inline-flex bold align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
<img class="h-7" src="/static/images/coins/{{ data.coin_to }}.png" alt="{{ data.coin_to }}">
</span>
<span class="bold">Sending ({{ data.tla_to }})</span>
</td>
<td class="py-3 px-6">
<div class="relative">
<input type="text"
class="bg-gray-50 text-gray-900 appearance-none pr-28 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0"
id="bid_amount_send"
autocomplete="off"
name="bid_amount_send"
value=""
max="{{ data.amt_to }}"
onchange="validateMaxAmount(this, {{ 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">
max {{ data.amt_to }} ({{ data.tla_to }})
</div>
</div>
</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="px-6">
<span class="inline-flex bold align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
<img class="h-7" src="/static/images/coins/{{ data.coin_from }}.png" alt="{{ data.coin_from }}">
</span>
<span class="bold">Receiving ({{ data.tla_from }})</span>
</td>
<td class="py-3 px-6">
<div class="relative">
<input type="text"
class="bg-gray-50 text-gray-900 appearance-none pr-28 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0"
id="bid_amount"
autocomplete="off"
name="bid_amount"
value=""
max="{{ data.amt_from }}"
onchange="validateMaxAmount(this, {{ data.amt_from }}); updateBidParams('receiving');">
<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_from }} ({{ data.tla_from }})
</div>
</div>
{% if data.xmr_type == true %}
<div class="text-sm mt-1 text-gray-400 dark:text-gray-300">(excluding estimated <span class="bold">{{ data.amt_from_lock_spend_tx_fee }} {{ data.tla_from }}</span> in tx fees)</div>
{% else %}
<div class="text-sm mt-1 text-gray-400 dark:text-gray-300">(excluding a tx fee)</div>
{% endif %}
</td>
</tr>
{% else %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="px-6">
<span class="inline-flex bold align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
<img class="h-7" src="/static/images/coins/{{ data.coin_to }}.png" alt="{{ data.coin_to }}">
</span>
<span class="bold">Sending ({{ data.tla_to }})</span>
</td>
<td class="py-3 px-6">
<div class="relative">
<input type="text"
class="bg-gray-50 text-gray-900 appearance-none pr-28 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0 cursor-not-allowed"
id="bid_amount_send"
autocomplete="off"
name="bid_amount_send"
value="{{ data.amt_to }}"
max="{{ data.amt_to }}"
disabled>
<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 }})
</div>
</div>
<div class="text-sm mt-1 text-gray-400 dark:text-gray-300">(Amount not variable)</div>
</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="px-6">
<span class="inline-flex bold align-middle items-center justify-center w-9 h-10 bg-white-50 rounded">
<img class="h-7" src="/static/images/coins/{{ data.coin_from }}.png" alt="{{ data.coin_from }}">
</span>
<span class="bold">Receiving ({{ data.tla_from }})</span>
</td>
<td class="py-3 px-6">
<div class="relative">
<input type="text"
class="bg-gray-50 text-gray-900 appearance-none pr-28 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0 cursor-not-allowed"
id="bid_amount"
autocomplete="off"
name="bid_amount"
value="{{ data.bid_amount }}"
max="{{ data.amt_from }}"
disabled>
<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_from }} ({{ data.tla_from }})
</div>
</div>
{% if data.xmr_type == true %}
<div class="text-sm mt-1 text-gray-400 dark:text-gray-300">(excluding estimated <span class="bold">{{ data.amt_from_lock_spend_tx_fee }} {{ data.tla_from }}</span> in tx fees)</div>
{% else %}
<div class="text-sm mt-1 text-gray-400 dark:text-gray-300">(excluding a tx fee)</div>
{% endif %}
</td>
</tr>
{% endif %}
<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">Rate </td>
<td class="py-3 px-6">
{% if data.rate_negotiable == true %}
<input type="text"
class="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-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0"
id="bid_rate"
name="bid_rate"
value="{{ data.bid_rate }}"
placeholder="Current rate: {{ data.rate }}"
onchange="updateBidParams('rate')">
{% else %}
<input type="text"
class="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-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0 cursor-not-allowed"
id="bid_rate"
name="bid_rate"
value="{{ data.rate }}"
title="Rate is not negotiable"
disabled>
<div class="text-sm mt-1 text-gray-400 dark:text-gray-300">(Rate is not negotiable)</div>
{% endif %}
</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Minutes valid</td>
<td class="py-3 px-6">
@ -519,15 +623,24 @@ if (document.readyState === 'loading') {
</div>
<div class="rounded-b-md">
<div class="w-full md:w-0/12">
<div class="flex flex-wrap justify-end pt-6 pr-6 border-t border-gray-100 dark:border-gray-400">
<div class="w-full md:w-auto p-1.5 ml-2">
<div class="flex flex-wrap justify-end pt-6 pr-6 border-t border-gray-100 dark:border-gray-400">
<div class="w-full md:w-auto p-1.5">
<button type="button" onclick="resetForm()" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md shadow-button focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">
Clear Form
</button>
</div>
<div class="w-full md:w-auto p-1.5 ml-2">
<input type="hidden" name="confirm" value="true">
<button name="sendbid" value="Send Bid" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Send Bid</button>
</div>
<div class="w-full md:w-auto p-1.5">
<button name="cancel" value="Cancel" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Cancel</button>
</div>
</div>
<button name="sendbid" value="Send Bid" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
Send Bid
</button>
</div>
<div class="w-full md:w-auto p-1.5">
<button name="cancel" value="Cancel" type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
Cancel
</button>
</div>
</div>
</div>
<!-- TODO:
<div class="w-full md:w-auto p-1.5 ml-2"><button name="check_rates" value="Lookup Rates" type="button" onclick='lookup_rates();' class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md shadow-button focus:ring-0 focus:outline-none"><svg class="mr-2"
@ -538,15 +651,12 @@ if (document.readyState === 'loading') {
</div>
</div>
</section>
<div id="confirmModal" class="fixed inset-0 z-50 hidden overflow-y-auto">
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity duration-300 ease-out"></div>
<div class="relative z-50 min-h-screen px-4 flex items-center justify-center">
<div class="bg-white dark:bg-gray-500 rounded-lg max-w-2xl w-full p-6 shadow-lg transition-opacity duration-300 ease-out">
<div class="text-center">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-6">Confirm Bid</h2>
<div class="space-y-4 text-left mb-8">
<div class="bg-gray-50 dark:bg-gray-600 rounded-lg p-4">
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Amount you will get:</div>
@ -556,7 +666,6 @@ if (document.readyState === 'loading') {
<div class="text-sm text-gray-500 dark:text-gray-400 mt-1" id="modal-fee-info"></div>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-600 rounded-lg p-4">
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Amount you will send:</div>
<div class="font-medium text-gray-900 dark:text-white text-lg">
@ -564,20 +673,17 @@ if (document.readyState === 'loading') {
<span id="modal-send-currency"></span>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-600 rounded-lg p-4">
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Send From Address:</div>
<div class="font-mono text-sm p-2 bg-white dark:bg-gray-500 rounded border border-gray-300 dark:border-gray-400 overflow-x-auto text-gray-900 dark:text-white">
<span id="modal-addr-from"></span>
</div>
</div>
<div class="bg-gray-50 dark:bg-gray-600 rounded-lg p-4">
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Minutes valid:</div>
<div class="font-medium text-gray-900 dark:text-white text-lg" id="modal-valid-mins"></div>
</div>
</div>
<div class="flex justify-center gap-4">
<button type="submit" name="sendbid" value="confirm"
class="px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
@ -592,115 +698,333 @@ if (document.readyState === 'loading') {
</div>
</div>
</div>
<script>
function updateBidParams(value_changed) {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
const amt_var = document.getElementById('amt_var').value;
const rate_var = document.getElementById('rate_var').value;
let amt_from = '';
let rate = '';
if (amt_var == 'True') {
amt_from = document.getElementById('bid_amount').value;
} else {
amt_from = document.getElementById('amount_from').value;
}
if (rate_var == 'True') {
rate = document.getElementById('bid_rate').value;
} else {
rate = document.getElementById('offer_rate').value;
}
if (value_changed == 'amount') {
document.getElementById('bid_amt_from').innerHTML = amt_from;
}
xhr_bid_params.open('POST', '/json/rate');
xhr_bid_params.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_bid_params.send('coin_from=' + coin_from + '&coin_to=' + coin_to + '&rate=' + rate + '&amt_from=' + amt_from);
const xhr_rates = new XMLHttpRequest();
xhr_rates.onload = () => {
if (xhr_rates.status == 200) {
const obj = JSON.parse(xhr_rates.response);
const inner_html = '<h4 class="bold>Rates</h4><pre><code>' + JSON.stringify(obj, null, ' ') + '</code></pre>';
const ratesDisplay = document.getElementById('rates_display');
if (ratesDisplay) {
ratesDisplay.innerHTML = inner_html;
}
}
};
const xhr_bid_params = new XMLHttpRequest();
xhr_bid_params.onload = () => {
if (xhr_bid_params.status == 200) {
const obj = JSON.parse(xhr_bid_params.response);
const bidAmountSendInput = document.getElementById('bid_amount_send');
if (bidAmountSendInput) {
bidAmountSendInput.value = obj['amount_to'];
}
updateModalValues();
}
};
function lookup_rates() {
const coin_from = document.getElementById('coin_from')?.value;
const coin_to = document.getElementById('coin_to')?.value;
if (!coin_from || !coin_to || coin_from === '-1' || coin_to === '-1') {
alert('Coins from and to must be set first.');
return;
}
const ratesDisplay = document.getElementById('rates_display');
if (ratesDisplay) {
ratesDisplay.innerHTML = '<h4>Rates</h4><p>Updating...</p>';
}
xhr_rates.open('POST', '/json/rates');
xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_rates.send(`coin_from=${coin_from}&coin_to=${coin_to}`);
}
function updateModalValues() {
document.getElementById('modal-amt-receive').textContent = document.getElementById('bid_amt_from').textContent;
document.getElementById('modal-amt-send').textContent = document.getElementById('bid_amt_to').textContent;
document.getElementById('modal-receive-currency').textContent = '{{ data.tla_from }}';
document.getElementById('modal-send-currency').textContent = '{{ data.tla_to }}';
{% if data.xmr_type == true %}
document.getElementById('modal-fee-info').textContent = `(excluding estimated {{ data.amt_from_lock_spend_tx_fee }} {{ data.tla_from }} in tx fees)`;
{% else %}
document.getElementById('modal-fee-info').textContent = '(excluding a tx fee)';
{% endif %}
const addrSelect = document.querySelector('select[name="addr_from"]');
const selectedText = addrSelect.options[addrSelect.selectedIndex].text;
const addrText = selectedText === 'New Address' ? selectedText : selectedText.split(' ')[0];
document.getElementById('modal-addr-from').textContent = addrText;
document.getElementById('modal-valid-mins').textContent = document.querySelector('input[name="validmins"]').value;
function resetForm() {
const bidAmountSendInput = document.getElementById('bid_amount_send');
const bidAmountInput = document.getElementById('bid_amount');
const bidRateInput = document.getElementById('bid_rate');
const validMinsInput = document.querySelector('input[name="validmins"]');
const addrFromSelect = document.querySelector('select[name="addr_from"]');
if (bidAmountSendInput) {
const defaultSendAmount = bidAmountSendInput.getAttribute('max');
bidAmountSendInput.value = defaultSendAmount;
}
if (bidAmountInput) {
const defaultReceiveAmount = bidAmountInput.getAttribute('max');
bidAmountInput.value = defaultReceiveAmount;
}
if (bidRateInput && !bidRateInput.disabled) {
const defaultRate = document.getElementById('offer_rate')?.value || '';
bidRateInput.value = defaultRate;
}
if (validMinsInput) {
validMinsInput.value = "60";
}
if (addrFromSelect) {
if (addrFromSelect.options.length > 1) {
addrFromSelect.selectedIndex = 1;
} else {
addrFromSelect.selectedIndex = 0;
}
const selectedOption = addrFromSelect.options[addrFromSelect.selectedIndex];
saveAddress(selectedOption.value, selectedOption.text);
}
updateBidParams('rate');
updateModalValues();
const errorMessages = document.querySelectorAll('.error-message');
errorMessages.forEach(msg => msg.remove());
const inputs = document.querySelectorAll('input');
inputs.forEach(input => {
input.classList.remove('border-red-500', 'focus:border-red-500');
});
}
function updateBidParams(value_changed) {
const coin_from = document.getElementById('coin_from')?.value;
const coin_to = document.getElementById('coin_to')?.value;
const amt_var = document.getElementById('amt_var')?.value;
const rate_var = document.getElementById('rate_var')?.value;
const bidAmountInput = document.getElementById('bid_amount');
const bidAmountSendInput = document.getElementById('bid_amount_send');
const amountFromInput = document.getElementById('amount_from');
const bidRateInput = document.getElementById('bid_rate');
const offerRateInput = document.getElementById('offer_rate');
if (!coin_from || !coin_to || !amt_var || !rate_var) return;
const rate = rate_var === 'True' && bidRateInput ?
parseFloat(bidRateInput.value) || 0 :
parseFloat(offerRateInput?.value || '0');
if (!rate) return;
if (value_changed === 'rate') {
if (bidAmountSendInput && bidAmountInput) {
const sendAmount = parseFloat(bidAmountSendInput.value) || 0;
const receiveAmount = (sendAmount / rate).toFixed(8);
bidAmountInput.value = receiveAmount;
}
} else if (value_changed === 'sending') {
if (bidAmountSendInput && bidAmountInput) {
const sendAmount = parseFloat(bidAmountSendInput.value) || 0;
const receiveAmount = (sendAmount / rate).toFixed(8);
bidAmountInput.value = receiveAmount;
}
} else if (value_changed === 'receiving') {
if (bidAmountInput && bidAmountSendInput) {
const receiveAmount = parseFloat(bidAmountInput.value) || 0;
const sendAmount = (receiveAmount * rate).toFixed(8);
bidAmountSendInput.value = sendAmount;
}
}
validateAmountsAfterChange();
xhr_bid_params.open('POST', '/json/rate');
xhr_bid_params.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_bid_params.send(`coin_from=${coin_from}&coin_to=${coin_to}&rate=${rate}&amt_from=${bidAmountInput?.value || '0'}`);
updateModalValues();
}
function validateAmountsAfterChange() {
const bidAmountSendInput = document.getElementById('bid_amount_send');
const bidAmountInput = document.getElementById('bid_amount');
if (bidAmountSendInput) {
const maxSend = parseFloat(bidAmountSendInput.getAttribute('max'));
validateMaxAmount(bidAmountSendInput, maxSend);
}
if (bidAmountInput) {
const maxReceive = parseFloat(bidAmountInput.getAttribute('max'));
validateMaxAmount(bidAmountInput, maxReceive);
}
}
function validateMaxAmount(input, maxAmount) {
if (!input) return;
const value = parseFloat(input.value) || 0;
if (value > maxAmount) {
input.value = maxAmount;
}
}
function showConfirmModal() {
updateModalValues();
document.getElementById('confirmModal').classList.remove('hidden');
return false;
updateModalValues();
const modal = document.getElementById('confirmModal');
if (modal) {
modal.classList.remove('hidden');
}
return false;
}
function hideConfirmModal() {
document.getElementById('confirmModal').classList.add('hidden');
return false;
const modal = document.getElementById('confirmModal');
if (modal) {
modal.classList.add('hidden');
}
return false;
}
function updateModalValues() {
const bidAmountInput = document.getElementById('bid_amount');
const bidAmountSendInput = document.getElementById('bid_amount_send');
if (bidAmountInput) {
const modalAmtReceive = document.getElementById('modal-amt-receive');
if (modalAmtReceive) {
modalAmtReceive.textContent = bidAmountInput.value;
}
const modalReceiveCurrency = document.getElementById('modal-receive-currency');
if (modalReceiveCurrency) {
modalReceiveCurrency.textContent = ' {{ data.tla_from }}';
}
}
if (bidAmountSendInput) {
const modalAmtSend = document.getElementById('modal-amt-send');
if (modalAmtSend) {
modalAmtSend.textContent = bidAmountSendInput.value;
}
const modalSendCurrency = document.getElementById('modal-send-currency');
if (modalSendCurrency) {
modalSendCurrency.textContent = ' {{ data.tla_to }}';
}
}
const modalFeeInfo = document.getElementById('modal-fee-info');
if (modalFeeInfo) {
{% if data.xmr_type == true %}
modalFeeInfo.textContent = `(excluding estimated {{ data.amt_from_lock_spend_tx_fee }} {{ data.tla_from }} in tx fees)`;
{% else %}
modalFeeInfo.textContent = '(excluding a tx fee)';
{% endif %}
}
const addrSelect = document.querySelector('select[name="addr_from"]');
if (addrSelect) {
const modalAddrFrom = document.getElementById('modal-addr-from');
if (modalAddrFrom) {
const selectedOption = addrSelect.options[addrSelect.selectedIndex];
const addrText = selectedOption.value === '-1' ? 'New Address' : selectedOption.text.split(' ')[0];
modalAddrFrom.textContent = addrText;
}
}
const validMinsInput = document.querySelector('input[name="validmins"]');
if (validMinsInput) {
const modalValidMins = document.getElementById('modal-valid-mins');
if (modalValidMins) {
modalValidMins.textContent = validMinsInput.value;
}
}
}
function handleBidsPageAddress() {
const selectElement = document.querySelector('select[name="addr_from"]');
const STORAGE_KEY = 'lastUsedAddressBids';
if (!selectElement) return;
function loadInitialAddress() {
try {
const savedAddressJSON = localStorage.getItem(STORAGE_KEY);
if (savedAddressJSON) {
const savedAddress = JSON.parse(savedAddressJSON);
if (savedAddress && savedAddress.value) {
selectElement.value = savedAddress.value;
} else {
selectFirstAddress();
}
} else {
selectFirstAddress();
}
} catch (e) {
console.error('Error loading saved address:', e);
selectFirstAddress();
}
}
function selectFirstAddress() {
if (selectElement.options.length > 1) {
const firstOption = selectElement.options[1];
if (firstOption) {
selectElement.value = firstOption.value;
saveAddress(firstOption.value, firstOption.text);
}
}
}
selectElement.addEventListener('change', (event) => {
if (event.target.selectedOptions[0]) {
saveAddress(event.target.value, event.target.selectedOptions[0].text);
}
});
loadInitialAddress();
}
function saveAddress(value, text) {
try {
const addressData = { value, text };
localStorage.setItem('lastUsedAddressBids', JSON.stringify(addressData));
} catch (e) {
console.error('Error saving address:', e);
}
}
function confirmPopup() {
return confirm("Are you sure?");
}
function handleCancelClick(event) {
event.preventDefault();
window.location.href = `/offer/${window.location.pathname.split('/')[2]}`;
if (event) event.preventDefault();
const pathParts = window.location.pathname.split('/');
const offerId = pathParts[pathParts.indexOf('offer') + 1];
window.location.href = `/offer/${offerId}`;
}
document.addEventListener('DOMContentLoaded', function() {
const sendBidBtn = document.querySelector('button[name="sendbid"][value="Send Bid"]');
if (sendBidBtn) {
sendBidBtn.onclick = showConfirmModal;
}
const modalCancelBtn = document.querySelector('#confirmModal .flex button:last-child');
if (modalCancelBtn) {
modalCancelBtn.onclick = hideConfirmModal;
}
const sendBidBtn = document.querySelector('button[name="sendbid"][value="Send Bid"]');
if (sendBidBtn) {
sendBidBtn.onclick = showConfirmModal;
}
const mainCancelBtn = document.querySelector('button[name="cancel"]');
if (mainCancelBtn) {
mainCancelBtn.onclick = handleCancelClick;
}
// Input change listeners
const validMinsInput = document.querySelector('input[name="validmins"]');
if (validMinsInput) {
validMinsInput.addEventListener('input', updateModalValues);
}
const addrFromSelect = document.querySelector('select[name="addr_from"]');
if (addrFromSelect) {
addrFromSelect.addEventListener('change', updateModalValues);
}
const modalCancelBtn = document.querySelector('#confirmModal .flex button:last-child');
if (modalCancelBtn) {
modalCancelBtn.onclick = hideConfirmModal;
}
const bidAmountInput = document.getElementById('bid_amount');
if (bidAmountInput) {
bidAmountInput.addEventListener('change', () => {
updateBidParams('amount');
updateModalValues();
});
}
const mainCancelBtn = document.querySelector('button[name="cancel"]');
if (mainCancelBtn) {
mainCancelBtn.onclick = handleCancelClick;
}
const bidRateInput = document.getElementById('bid_rate');
if (bidRateInput) {
bidRateInput.addEventListener('change', () => {
updateBidParams('rate');
updateModalValues();
});
}
const validMinsInput = document.querySelector('input[name="validmins"]');
if (validMinsInput) {
validMinsInput.addEventListener('input', updateModalValues);
}
const addrFromSelect = document.querySelector('select[name="addr_from"]');
if (addrFromSelect) {
addrFromSelect.addEventListener('change', updateModalValues);
}
handleBidsPageAddress();
});
</script>
@ -768,67 +1092,5 @@ document.addEventListener('DOMContentLoaded', function() {
</section>
</div>
{% include 'footer.html' %}
<script>
const xhr_rates = new XMLHttpRequest();
xhr_rates.onload = () => {
if (xhr_rates.status == 200) {
const obj = JSON.parse(xhr_rates.response);
inner_html = '<h4 class="bold>Rates</h4><pre><code>' + JSON.stringify(obj, null, ' ') + '</code></pre>';
document.getElementById('rates_display').innerHTML = inner_html;
}
}
function lookup_rates() {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
if (coin_from == '-1' || coin_to == '-1') {
alert('Coins from and to must be set first.');
return;
}
inner_html = '<h4>Rates</h4><p>Updating...</p>';
document.getElementById('rates_display').innerHTML = inner_html;
xhr_rates.open('POST', '/json/rates');
xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_rates.send('coin_from=' + coin_from + '&coin_to=' + coin_to);
}
const xhr_bid_params = new XMLHttpRequest();
xhr_bid_params.onload = () => {
if (xhr_bid_params.status == 200) {
const obj = JSON.parse(xhr_bid_params.response);
document.getElementById('bid_amt_to').innerHTML = obj['amount_to'];
}
}
function updateBidParams(value_changed) {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
const amt_var = document.getElementById('amt_var').value;
const rate_var = document.getElementById('rate_var').value;
let amt_from = '';
let rate = '';
if (amt_var == 'True') {
amt_from = document.getElementById('bid_amount').value;
}
else {
amt_from = document.getElementById('amount_from').value;
}
if (rate_var == 'True') {
rate = document.getElementById('bid_rate').value;
}
else {
rate = document.getElementById('offer_rate').value;
}
if (value_changed == 'amount') {
document.getElementById('bid_amt_from').innerHTML = amt_from;
}
xhr_bid_params.open('POST', '/json/rate');
xhr_bid_params.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_bid_params.send('coin_from=' + coin_from + '&coin_to=' + coin_to + '&rate=' + rate + '&amt_from=' + amt_from);
}
function confirmPopup() {
return confirm("Are you sure?");
}
</script>
</body>
</html>

View file

@ -168,6 +168,8 @@
</div>
</div>
{% if data.swap_style == 'xmr' %}
{% if data.coin_from | int in (6, 9) %} {# Not XMR or WOW #}
{% else %}
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Fee From Confirm Target</p>
<div class="relative">
@ -202,6 +204,7 @@
</div> <span class="text-sm mt-2 block dark:text-white"> <b>Lock Tx Spend Fee:</b> {{ data.amt_from_lock_spend_tx_fee }} {{ data.tla_from }} </span>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
@ -238,7 +241,9 @@
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 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-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" readonly>
</div>
</div>
{% if data.swap_style == 'xmr' and coin_to != '6' %}
{% if data.swap_style == 'xmr' %}
{% if data.coin_to | int in (6, 9) %} {# Not XMR or WOW #}
{% else %}
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Fee To Confirm Target</p>
<div class="relative">
@ -273,6 +278,7 @@
</div>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
@ -467,4 +473,4 @@
</div>
{% include 'footer.html' %}
</body>
</html>
</html>

View file

@ -86,7 +86,7 @@
<select class="pl-10 hover:border-blue-500 pl-10 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-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="addr_to">
<option{% if data.addr_to=="-1" %} selected{% endif %} value="-1">Public Network</option>
{% for a in addrs_to %}
<option{% if data.addr_to==a[0] %} selected{% endif %} value="{{ a[0] }}">{{ a[0] }} {{ a[1] }}</option>
<option{% if data.addr_to==a[0] %} selected{% endif %} value="{{ a[0] }}">{{ a[0] }} ({{ a[1] }})</option>
{% endfor %}
</select>
</div>
@ -283,15 +283,15 @@ if (document.readyState === 'loading') {
</div>
</div>
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base dark:text-white text-coolGray-800">Rate</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_rate_svg | safe }}
</div>
<input class="pl-10 hover:border-blue-500 pl-10 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-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" onchange="set_rate('rate');">
</div>
<div class="text-sm mt-2">
<a href="" id="get_rate_inferred_button" class="mt-2 dark:text-white bold text-coolGray-800">Get Rate Inferred:</a><span class="dark:text-white" id="rate_inferred_display"></span>
<p class="mb-1.5 font-medium text-base dark:text-white text-coolGray-800">Rate</p>
<div class="flex items-center gap-2">
<div class="relative flex-1">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_rate_svg | safe }}
</div>
<input class="pl-10 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-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" onchange="set_rate('rate');">
</div>
<button type="button" id="get_rate_inferred_button" class="px-4 py-2.5 text-sm font-medium text-white bg-blue-500 hover:bg-blue-600 rounded-md shadow-sm focus:outline-none">Get Rate Inferred</button>
</div>
<div class="flex form-check form-check-inline mt-5">
<div class="flex items-center h-5"> <input class="form-check-input hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="rate_lock" name="rate_lock" value="rl" checked=checked> </div>
@ -306,7 +306,6 @@ if (document.readyState === 'loading') {
</div>
</div>
</div>
{% if debug_mode == true %}
<div class="py-0 border-b items-center justify-between -mx-4 mb-6 pb-3 border-gray-400 border-opacity-20">
<div class="w-full md:w-10/12">
@ -414,181 +413,225 @@ if (document.readyState === 'loading') {
</div>
</div>
</section>
<script>
const xhr_rates = new XMLHttpRequest();
xhr_rates.onload = () => {
if (xhr_rates.status == 200) {
const obj = JSON.parse(xhr_rates.response);
inner_html = '<pre><code>' + JSON.stringify(obj, null, ' ') + '</code></pre>';
document.getElementById('rates_display').innerHTML = inner_html;
}
}
const xhr_rate = new XMLHttpRequest();
xhr_rate.onload = () => {
if (xhr_rate.status == 200) {
const obj = JSON.parse(xhr_rate.response);
if (obj.hasOwnProperty('rate')) {
document.getElementById('rate').value = obj['rate'];
} else
if (obj.hasOwnProperty('amount_to')) {
document.getElementById('amt_to').value = obj['amount_to'];
} else
if (obj.hasOwnProperty('amount_from')) {
document.getElementById('amt_from').value = obj['amount_from'];
}
}
}
function lookup_rates() {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
if (coin_from === '-1' || coin_to === '-1') {
alert('Coins from and to must be set first.');
return;
}
const selectedCoin = (coin_from === '15') ? '3' : coin_from;
inner_html = '<p>Updating...</p>';
<script>
const xhr_rates = new XMLHttpRequest();
xhr_rates.onload = () => {
if (xhr_rates.status == 200) {
const obj = JSON.parse(xhr_rates.response);
inner_html = '<pre><code>' + JSON.stringify(obj, null, ' ') + '</code></pre>';
document.getElementById('rates_display').innerHTML = inner_html;
document.querySelector(".pricejsonhidden").classList.remove("hidden");
}
};
const xhr_rates = new XMLHttpRequest();
xhr_rates.onreadystatechange = function() {
if (xhr_rates.readyState === XMLHttpRequest.DONE) {
if (xhr_rates.status === 200) {
document.getElementById('rates_display').innerHTML = xhr_rates.responseText;
} else {
console.error('Error fetching data:', xhr_rates.statusText);
}
}
};
const xhr_rate = new XMLHttpRequest();
xhr_rate.onload = () => {
if (xhr_rate.status == 200) {
const obj = JSON.parse(xhr_rate.response);
if (obj.hasOwnProperty('rate')) {
document.getElementById('rate').value = obj['rate'];
} else if (obj.hasOwnProperty('amount_to')) {
document.getElementById('amt_to').value = obj['amount_to'];
} else if (obj.hasOwnProperty('amount_from')) {
document.getElementById('amt_from').value = obj['amount_from'];
}
}
};
xhr_rates.open('POST', '/json/rates');
xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_rates.send('coin_from=' + selectedCoin + '&coin_to=' + coin_to);
function lookup_rates() {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
if (coin_from === '-1' || coin_to === '-1') {
alert('Coins from and to must be set first.');
return;
}
function getRateInferred(event) {
event.preventDefault(); // Prevent default form submission behavior
const selectedCoin = (coin_from === '15') ? '3' : coin_from;
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
const params = 'coin_from=' + encodeURIComponent(coin_from) + '&coin_to=' + encodeURIComponent(coin_to);
inner_html = '<p>Updating...</p>';
document.getElementById('rates_display').innerHTML = inner_html;
document.querySelector(".pricejsonhidden").classList.remove("hidden");
const xhr_rates = new XMLHttpRequest();
xhr_rates.onreadystatechange = function() {
if (xhr_rates.readyState === XMLHttpRequest.DONE) {
//console.log(xhr_rates.responseText);
if (xhr_rates.status === 200) {
try {
const responseData = JSON.parse(xhr_rates.responseText);
const xhr_rates = new XMLHttpRequest();
xhr_rates.onreadystatechange = function() {
if (xhr_rates.readyState === XMLHttpRequest.DONE) {
if (xhr_rates.status === 200) {
document.getElementById('rates_display').innerHTML = xhr_rates.responseText;
} else {
console.error('Error fetching data:', xhr_rates.statusText);
}
}
};
xhr_rates.open('POST', '/json/rates');
xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_rates.send('coin_from=' + selectedCoin + '&coin_to=' + coin_to);
}
function getRateInferred(event) {
event.preventDefault();
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
const params = 'coin_from=' + encodeURIComponent(coin_from) + '&coin_to=' + encodeURIComponent(coin_to);
const xhr_rates = new XMLHttpRequest();
xhr_rates.onreadystatechange = function() {
if (xhr_rates.readyState === XMLHttpRequest.DONE) {
if (xhr_rates.status === 200) {
try {
const responseData = JSON.parse(xhr_rates.responseText);
if (responseData.coingecko && responseData.coingecko.rate_inferred) {
const rateInferred = responseData.coingecko.rate_inferred;
//document.getElementById('rate_inferred_display').innerText = " (" + coin_from + " to " + coin_to + "): " + rateInferred;
document.getElementById('rate_inferred_display').innerText = " " + rateInferred;
} catch (error) {
console.error('Error parsing response:', error);
document.getElementById('rate').value = rateInferred;
set_rate('rate');
} else {
document.getElementById('rate').value = 'Error: Rate limit';
console.error('Rate limit reached or invalid response format');
}
} else {
console.error('Error fetching data:', xhr_rates.statusText);
} catch (error) {
document.getElementById('rate').value = 'Error: Rate limit';
console.error('Error parsing response:', error);
}
} else {
document.getElementById('rate').value = 'Error: Rate limit';
console.error('Error fetching data:', xhr_rates.statusText);
}
};
}
};
xhr_rates.open('POST', '/json/rates');
xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_rates.send(params);
xhr_rates.open('POST', '/json/rates');
xhr_rates.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_rates.send(params);
}
document.getElementById('get_rate_inferred_button').addEventListener('click', getRateInferred);
function set_swap_type_enabled(coin_from, coin_to, swap_type) {
const adaptor_sig_only_coins = [
'6', /* XMR */
'9', /* WOW */
'8', /* PART_ANON */
'7', /* PART_BLIND */
'13', /* FIRO */
'18', /* DOGE */
'17' /* BCH */
];
const secret_hash_only_coins = [
'11', /* PIVX */
'12' /* DASH */
];
let make_hidden = false;
coin_from = String(coin_from);
coin_to = String(coin_to);
if (adaptor_sig_only_coins.indexOf(coin_from) !== -1 || adaptor_sig_only_coins.indexOf(coin_to) !== -1) {
swap_type.disabled = true;
swap_type.value = 'xmr_swap';
make_hidden = true;
swap_type.classList.add('select-disabled');
} else if (secret_hash_only_coins.indexOf(coin_from) !== -1 || secret_hash_only_coins.indexOf(coin_to) !== -1) {
swap_type.disabled = true;
swap_type.value = 'seller_first';
make_hidden = true;
swap_type.classList.add('select-disabled');
} else {
swap_type.disabled = false;
swap_type.classList.remove('select-disabled');
swap_type.value = 'xmr_swap';
}
document.getElementById('get_rate_inferred_button').addEventListener('click', getRateInferred);
let swap_type_hidden = document.getElementById('swap_type_hidden');
if (make_hidden) {
if (!swap_type_hidden) {
swap_type_hidden = document.createElement('input');
swap_type_hidden.setAttribute('id', 'swap_type_hidden');
swap_type_hidden.setAttribute('type', 'hidden');
swap_type_hidden.setAttribute('name', 'swap_type');
document.getElementById('form').appendChild(swap_type_hidden);
}
swap_type_hidden.setAttribute('value', swap_type.value);
} else if (swap_type_hidden) {
swap_type_hidden.parentNode.removeChild(swap_type_hidden);
}
}
function set_swap_type_enabled(coin_from, coin_to, swap_type) {
const adaptor_sig_only_coins = ['6' /* XMR */,'9' /* WOW */, '8' /* PART_ANON */, '7' /* PART_BLIND */, '13' /* FIRO */, '17' /* BCH */];
const secret_hash_only_coins = ['11' /* PIVX */, '12' /* DASH */];
let make_hidden = false;
if (adaptor_sig_only_coins.includes(coin_from) || adaptor_sig_only_coins.includes(coin_to)) {
swap_type.disabled = true;
swap_type.value = 'xmr_swap';
make_hidden = true;
swap_type.classList.add('select-disabled');
} else
if (secret_hash_only_coins.includes(coin_from) && secret_hash_only_coins.includes(coin_to)) {
swap_type.disabled = true;
swap_type.value = 'seller_first';
make_hidden = true;
swap_type.classList.add('select-disabled');
document.addEventListener('DOMContentLoaded', function() {
const coin_from = document.getElementById('coin_from');
const coin_to = document.getElementById('coin_to');
if (coin_from && coin_to) {
coin_from.addEventListener('change', function() {
const swap_type = document.getElementById('swap_type');
set_swap_type_enabled(this.value, coin_to.value, swap_type);
});
coin_to.addEventListener('change', function() {
const swap_type = document.getElementById('swap_type');
set_swap_type_enabled(coin_from.value, this.value, swap_type);
});
}
});
function set_rate(value_changed) {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
const amt_from = document.getElementById('amt_from').value;
const amt_to = document.getElementById('amt_to').value;
const rate = document.getElementById('rate').value;
const lock_rate = rate == '' ? false : document.getElementById('rate_lock').checked;
if (value_changed === 'coin_from' || value_changed === 'coin_to') {
document.getElementById('rate').value = '';
return;
}
const swap_type = document.getElementById('swap_type');
set_swap_type_enabled(coin_from, coin_to, swap_type);
if (coin_from == '-1' || coin_to == '-1') {
return;
}
let params = 'coin_from=' + coin_from + '&coin_to=' + coin_to;
if (value_changed == 'rate' || (lock_rate && value_changed == 'amt_from') || (amt_to == '' && value_changed == 'amt_from')) {
if (rate == '' || (amt_from == '' && amt_to == '')) {
return;
} else if (amt_from == '' && amt_to != '') {
if (value_changed == 'amt_from') {
return;
}
params += '&rate=' + rate + '&amt_to=' + amt_to;
} else {
swap_type.disabled = false;
swap_type.classList.remove('select-disabled');
params += '&rate=' + rate + '&amt_from=' + amt_from;
}
let swap_type_hidden = document.getElementById('swap_type_hidden');
if (make_hidden) {
if (!swap_type_hidden) {
swap_type_hidden = document.createElement('input');
swap_type_hidden.setAttribute('id', 'swap_type_hidden');
swap_type_hidden.setAttribute('type', 'hidden');
swap_type_hidden.setAttribute('name', 'swap_type');
document.getElementById('form').appendChild(swap_type_hidden)
}
swap_type_hidden.setAttribute('value', swap_type.value);
} else
if (swap_type_hidden) {
swap_type_hidden.parentNode.removeChild(swap_type_hidden);
}
}
function set_rate(value_changed) {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
const amt_from = document.getElementById('amt_from').value;
const amt_to = document.getElementById('amt_to').value;
const rate = document.getElementById('rate').value;
const lock_rate = rate == '' ? false : document.getElementById('rate_lock').checked;
const swap_type = document.getElementById('swap_type');
set_swap_type_enabled(coin_from, coin_to, swap_type);
if (coin_from == '-1' || coin_to == '-1') {
} else if (lock_rate && value_changed == 'amt_to') {
if (amt_to == '' || rate == '') {
return;
}
params = 'coin_from=' + coin_from + '&coin_to=' + coin_to;
if (value_changed == 'rate' || (lock_rate && value_changed == 'amt_from') || (amt_to == '' && value_changed == 'amt_from')) {
if (rate == '' || (amt_from == '' && amt_to == '')) {
return;
} else
if (amt_from == '' && amt_to != '') {
if (value_changed == 'amt_from') { // Don't try and set a value just cleared
return;
}
params += '&rate=' + rate + '&amt_to=' + amt_to;
} else {
params += '&rate=' + rate + '&amt_from=' + amt_from;
}
} else
if (lock_rate && value_changed == 'amt_to') {
if (amt_to == '' || rate == '') {
return;
}
params += '&amt_to=' + amt_to + '&rate=' + rate;
} else {
if (amt_from == '' || amt_to == '') {
return;
}
params += '&amt_from=' + amt_from + '&amt_to=' + amt_to;
params += '&amt_to=' + amt_to + '&rate=' + rate;
} else {
if (amt_from == '' || amt_to == '') {
return;
}
xhr_rate.open('POST', '/json/rate');
xhr_rate.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_rate.send(params);
params += '&amt_from=' + amt_from + '&amt_to=' + amt_to;
}
document.addEventListener("DOMContentLoaded", function() {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
const swap_type = document.getElementById('swap_type');
set_swap_type_enabled(coin_from, coin_to, swap_type);
});
</script>
xhr_rate.open('POST', '/json/rate');
xhr_rate.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_rate.send(params);
}
document.addEventListener("DOMContentLoaded", function() {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
const swap_type = document.getElementById('swap_type');
set_swap_type_enabled(coin_from, coin_to, swap_type);
});
</script>
</div>
<script src="static/js/new_offer.js"></script>
{% include 'footer.html' %}

View file

@ -175,6 +175,8 @@
</div>
</div>
{% if data.swap_style == 'xmr' %}
{% if data.coin_from | int in (6, 9) %} {# Not XMR or WOW #}
{% else %}
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Fee From Confirm Target</p>
<div class="relative">
@ -199,6 +201,7 @@
</div>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
@ -233,7 +236,9 @@
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 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-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" readonly>
</div>
</div>
{% if data.swap_style == 'xmr' and coin_to != '6' %}
{% if data.swap_style == 'xmr' %}
{% if data.coin_to | int in (6, 9) %} {# Not XMR or WOW #}
{% else %}
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Fee To Confirm Target</p>
<div class="relative">
@ -259,6 +264,7 @@
</div>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
@ -449,4 +455,4 @@
</div>
{% include 'footer.html' %}
</body>
</html>
</html>

View file

@ -11,10 +11,17 @@
<script>
function getAPIKeys() {
return {
cryptoCompare: '{{chart_api_key}}',
coinGecko: '{{coingecko_api_key}}'
cryptoCompare: "{{ chart_api_key|safe }}",
coinGecko: "{{ coingecko_api_key|safe }}"
};
}
function getWebSocketConfig() {
return {
port: "{{ ws_port|safe }}",
fallbackPort: "11700"
};
}
</script>
{% if sent_offers %}
@ -163,7 +170,7 @@ function getAPIKeys() {
'PIVX': {'name': 'PIVX', 'symbol': 'PIVX', 'image': 'PIVX.png', 'show': true},
'DASH': {'name': 'Dash', 'symbol': 'DASH', 'image': 'Dash.png', 'show': true},
'ETH': {'name': 'Ethereum', 'symbol': 'ETH', 'image': 'Ethereum.png', 'show': false},
'DOGE': {'name': 'Dogecoin', 'symbol': 'DOGE', 'image': 'Doge.png', 'show': false},
'DOGE': {'name': 'Dogecoin', 'symbol': 'DOGE', 'image': 'Dogecoin.png', 'show': true},
'DCR': {'name': 'Decred', 'symbol': 'DCR', 'image': 'Decred.png', 'show': true},
'ZANO': {'name': 'Zano', 'symbol': 'ZANO', 'image': 'Zano.png', 'show': false},
'WOW': {'name': 'Wownero', 'symbol': 'WOW', 'image': 'Wownero.png', 'show': true}
@ -185,7 +192,7 @@ function getAPIKeys() {
{% for coin_symbol in custom_order %}
{% if coin_symbol in display_coins and coin_data[coin_symbol]['show'] %}
<div class="w-full sm:w-1/2 lg:w-1/5 p-3" id="{{ coin_symbol.lower() }}-container">
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="{{ coin_symbol.lower() }}-container">
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white {% if coin_symbol == 'BTC' %}active-container{% endif %}" style="min-height: 180px;">
<div class="flex items-center">
<img src="/static/images/coins/{{ coin_data[coin_symbol]['image'] }}" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="{{ coin_data[coin_symbol]['name'] }}">
@ -283,6 +290,18 @@ function getAPIKeys() {
{% endif %}
</div>
</div>
<div class="pt-3 px-3 md:w-auto hover-container">
<div class="flex">
<div class="relative">
{{ input_arrow_down_svg | safe }}
<select name="sent_from" id="sent_from" class="bg-gray-50 text-gray-900 appearance-none pr-10 pl-5 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none block w-full p-2.5 focus:ring-0">
<option value="any" {% if not filters.sent_from %} selected{% endif %}>All Offers</option>
<option value="public" {% if filters.sent_from == 'public' %} selected{% endif %}>Public</option>
<option value="private" {% if filters.sent_from == 'private' %} selected{% endif %}>Private</option>
</select>
</div>
</div>
</div>
<div class="w-full md:w-auto pt-3 px-3">
<div class="relative">
<button type="button" id="clearFilters" class="transition-opacity duration-200 flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm hover:text-white dark:text-white dark:bg-gray-500 bg-coolGray-200 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-coolGray-200 dark:border-gray-400 rounded-md shadow-button focus:ring-0 focus:outline-none" disabled>
@ -300,13 +319,6 @@ function getAPIKeys() {
</button>
</div>
</div>
<div class="w-full md:w-auto pt-3 px-3 hidden">
<div class="relative">
<button id="toggleView" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white bg-blue-600 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
<span>Toggle JSON View</span>
</button>
</div>
</div>
</div>
</div>
</div>
@ -322,15 +334,20 @@ function getAPIKeys() {
<div id="jsonView" class="hidden mb-4">
<pre id="jsonContent" class="bg-gray-100 p-4 rounded overflow-auto" style="max-height: 300px;"></pre>
</div>
<div class="container mt-5 mx-auto">
<div class="container mt-5 mx-auto px-4">
<div class="pt-0 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-0">
<div class="w-auto mt-6 pb-6 overflow-x-auto">
<div class="w-auto mt-6 overflow-x-auto">
<table class="w-full min-w-max">
<thead class="uppercase">
<tr>
<th class="p-0" data-sortable="true" data-column-index="0">
<div class="py-3 pl-4 justify-center rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm mr-1 text-gray-600 dark:text-gray-300 font-semibold"></span>
</div>
</th>
<th class="p-0" data-sortable="true" data-column-index="0">
<div class="py-3 pl-4 justify-center bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm mr-1 text-gray-600 dark:text-gray-300 font-semibold">Time</span>
<span class="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-0"></span>
</div>
@ -400,11 +417,18 @@ function getAPIKeys() {
<div class="w-full">
<div class="flex flex-wrap justify-between items-center pl-6 pt-6 pr-6 border-t border-gray-100 dark:border-gray-400">
<div class="flex items-center">
<p class="text-sm font-heading dark:text-gray-400 mr-4">Last refreshed: <span id="lastRefreshTime">Never</span></p>
<p class="text-sm font-heading dark:text-gray-400 mr-4"><span class="ml-4" data-listing-label>Network Listings: </span><span id="newEntriesCount"></span></p>
<p class="text-sm font-heading dark:text-gray-400 mr-4"><span id="nextRefreshContainer" class="ml-4">Next refresh: <span id="nextRefreshTime"></span>
</span></p>
</div>
<div class="flex items-center mr-4">
<span id="status-dot" class="w-2.5 h-2.5 rounded-full bg-gray-500 mr-2"></span>
<span id="status-text" class="text-sm text-gray-500">Connecting...</span>
</div>
<p class="text-sm font-heading dark:text-gray-400 mr-4">
Last refreshed: <span id="lastRefreshTime">Never</span>
</p>
<p class="text-sm font-heading dark:text-gray-400 mr-4">
<span data-listing-label>Network Listings: </span>
<span id="newEntriesCount"></span>
</p>
</div>
<div class="flex items-center space-x-2">
<button type="button" id="prevPage" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
{{ page_back_svg | safe }}

View file

@ -281,6 +281,13 @@
</th>
</tr>
</thead>
<tr>
<td colspan="2" class="py-3 px-6">
<div class="flex items-center">
<span class="text-red-500 dark:text-red-500 text-sm font-medium">WARNING: Advanced features - Only enable if you know what you're doing!</span>
</div>
</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold w-96 bold">Debug Mode</td>
<td class="py-3 px-6">

View file

@ -82,6 +82,25 @@
</div>
</section>
{% else %}
{% if w.cid == '18' %} {# DOGE #}
<section class="py-4" id="messages_notice">
<div class="container px-4 mx-auto">
<div class="p-6 rounded-lg bg-coolGray-100 dark:bg-gray-500 shadow-sm">
<div class="flex items-start">
<svg class="w-6 h-6 text-blue-500 mt-1 mr-3 flex-shrink-0" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"></path></svg>
<div class="flex flex-wrap -m-1">
<ul class="ml-4">
<li class="font-semibold text-lg dark:text-white mb-2">NOTICE:</li>
<li class="font-medium text-gray-600 dark:text-white leading-relaxed">
This version of DOGE Core is experimental and has been custom-built for compatibility with BasicSwap. As a result, it may not always be fully aligned with upstream changes, features unrelated to BasicSwap might not work as expected, and its code may differ from the official release.
</li>
</ul>
</div>
</div>
</div>
</div>
</section>
{% endif %}
<form method="post" autocomplete="off">
<section>
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
@ -189,7 +208,7 @@
{# / encrypted #}
<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">Expected Seed:</td>
<td class="py-3 px-6">{{ w.expected_seed }}</td> {% if block_unknown_seeds and w.expected_seed != true %} {# Only show addresses if wallet seed is correct #}
<td class="py-3 px-6">{{ w.expected_seed }}</td>
</tr>
</table>
</div>
@ -201,6 +220,7 @@
</div>
</div>
</section>
{% if block_unknown_seeds and w.expected_seed != true %} {# Only show addresses if wallet seed is correct #}
<section class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
<div class="pb-6 border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
@ -221,16 +241,6 @@
</div>
</section>
{% else %}
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="p-6">
<div class="flex items-center">
<h4 class="font-semibold text-2xl text-black dark:text-white">Deposit</h4>
@ -246,32 +256,7 @@
<div class="pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6">
<div class="container mx-auto">
<div class="flex flex-wrap max-w-7xl mx-auto -m-3">
{% if w.cid == '1' %} {# PART #}
<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">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
<div id="qrcode-stealth" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Stealth Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-particl-stealth" 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-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-10 focus:ring-0" id="stealth_address"> {{ w.stealth_address }}
</div>
</div>
<div id="tooltip-copy-particl-stealth" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{# / PART #}
<div class="flex flex-wrap max-w-7xl mx-auto justify-center -m-3">
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
@ -349,7 +334,31 @@
</div>
</div>
{% endif %}
{% if w.cid == '3' %}
{% if w.cid == '1' %} {# PART #}
<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">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
<div id="qrcode-stealth" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Stealth Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-particl-stealth" 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-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-10 focus:ring-0" id="stealth_address"> {{ w.stealth_address }}
</div>
</div>
<div id="tooltip-copy-particl-stealth" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</div>
</div>
{# / PART #}
{% elif w.cid == '3' %}
{# LTC #}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
@ -879,6 +888,7 @@ const coinNameToSymbol = {
'Monero': 'XMR',
'Wownero': 'WOW',
'Litecoin': 'LTC',
'Dogecoin': 'DOGE',
'Firo': 'FIRO',
'Dash': 'DASH',
'PIVX': 'PIVX',

View file

@ -230,6 +230,7 @@ const COIN_SYMBOLS = {
'Monero': 'monero',
'Wownero': 'wownero',
'Litecoin': 'litecoin',
'Dogecoin': 'dogecoin',
'Firo': 'zcoin',
'Dash': 'dash',
'PIVX': 'pivx',

View file

@ -528,6 +528,15 @@ def page_newoffer(self, url_split, post_string):
coins_from, coins_to = listAvailableCoins(swap_client, split_from=True)
addrs_from_raw = swap_client.listSMSGAddresses("offer_send_from")
addrs_to_raw = swap_client.listSMSGAddresses("offer_send_to")
all_addresses = swap_client.listAllSMSGAddresses({})
addr_notes = {addr["addr"]: addr["note"] for addr in all_addresses}
addrs_from = [(addr[0], addr_notes.get(addr[0], "")) for addr in addrs_from_raw]
addrs_to = [(addr[0], addr_notes.get(addr[0], "")) for addr in addrs_to_raw]
automation_filters = {"type_ind": Concepts.OFFER, "sort_by": "label"}
automation_strategies = swap_client.listAutomationStrategies(automation_filters)
@ -556,8 +565,8 @@ def page_newoffer(self, url_split, post_string):
"err_messages": err_messages,
"coins_from": coins_from,
"coins": coins_to,
"addrs": swap_client.listSMSGAddresses("offer_send_from"),
"addrs_to": swap_client.listSMSGAddresses("offer_send_to"),
"addrs": addrs_from,
"addrs_to": addrs_to,
"data": page_data,
"automation_strategies": automation_strategies,
"summary": summary,
@ -581,7 +590,7 @@ def page_offer(self, url_split, post_string):
offer, xmr_offer = swap_client.getXmrOffer(offer_id)
ensure(offer, "Unknown offer ID")
extend_data = { # Defaults
extend_data = {
"nb_validmins": 10,
}
messages = []
@ -598,7 +607,6 @@ def page_offer(self, url_split, post_string):
reverse_bid: bool = True if offer.bid_reversed else False
# Set defaults
debugind = -1
bid_amount = ci_from.format_amount(offer.amount_from)
bid_rate = ci_to.format_amount(offer.rate)
@ -617,7 +625,6 @@ def page_offer(self, url_split, post_string):
except Exception as ex:
err_messages.append("Revoke offer failed: " + str(ex))
elif b"repeat_offer" in form_data:
# Can't set the post data here as browsers will always resend the original post data when responding to redirects
self.send_response(302)
self.send_header("Location", "/newoffer?offer_from=" + offer_id.hex())
self.end_headers()

View file

@ -204,12 +204,12 @@ It may take a few minutes to start as the coin daemons are started before the ht
Add a coin (Stop basicswap first):
export SWAP_DATADIR=/Users/$USER/coinswaps
export SWAP_DATADIR=$HOME/coinswaps
basicswap-prepare --usebtcfastsync --datadir=/$SWAP_DATADIR --addcoin=bitcoin
Start after installed:
export SWAP_DATADIR=/Users/$USER/coinswaps
export SWAP_DATADIR=$HOME/coinswaps
. $SWAP_DATADIR/venv/bin/activate && python -V
basicswap-run --datadir=$SWAP_DATADIR

View file

@ -1,4 +1,3 @@
version: '3.4'
services:
swapclient:
image: i_swapclient

View file

@ -1,4 +1,3 @@
version: '3.4'
services:
swapclient:

View file

@ -1,4 +1,3 @@
version: '3.3'
networks:
default:

View file

@ -0,0 +1,16 @@
dogecoin_core:
image: i_dogecoin
build:
context: dogecoin
dockerfile: Dockerfile
container_name: dogecoin_core
volumes:
- ${DATA_PATH}/dogecoin:/data
expose:
- ${DOGE_RPC_PORT}
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
restart: unless-stopped

View file

@ -0,0 +1,25 @@
FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dogecoin --withoutcoin=particl && \
find /coin_bin -name *.tar.gz -delete
FROM debian:bullseye-slim
COPY --from=install_stage /coin_bin .
ENV DOGECOIN_DATA /data
RUN groupadd -r dogecoin && useradd -r -m -g dogecoin dogecoin \
&& apt-get update \
&& apt-get install -qq --no-install-recommends gosu \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir "$DOGECOIN_DATA" \
&& chown -R dogecoin:dogecoin "$DOGECOIN_DATA" \
&& ln -sfn "$DOGECOIN_DATA" /home/dogecoin/.dogecoin \
&& chown -h dogecoin:dogecoin /home/dogecoin/.dogecoin
VOLUME /data
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 8332 8333 18332 18333 18443 18444
CMD ["/dogecoin/dogecoind", "--datadir=/data"]

View file

@ -0,0 +1,11 @@
#!/bin/bash
set -e
if [[ "$1" == "dogecoin-cli" || "$1" == "dogecoin-tx" || "$1" == "dogecoind" || "$1" == "test_dogecoin" ]]; then
mkdir -p "$DOGECOIN_DATA"
chown -h dogecoin:dogecoin /home/dogecoin/.dogecoin
exec gosu dogecoin "$@"
else
exec "$@"
fi

View file

@ -6,7 +6,7 @@ ENV LANG=C.UTF-8 \
RUN apt-get update; \
apt-get install -y --no-install-recommends \
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata wget unzip;
python3-pip libpython3-dev gnupg pkg-config gcc libc-dev gosu tzdata wget unzip cmake ninja-build;
ARG BASICSWAP_URL=https://github.com/basicswap/basicswap/archive/master.zip
ARG BASICSWAP_DIR=basicswap-master

View file

@ -114,15 +114,15 @@
(define-public basicswap
(package
(name "basicswap")
(version "0.14.1")
(version "0.14.3")
(source (origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/basicswap/basicswap")
(commit "062cc6dbdc3c1f489d2bf78ce7cd99fbc885f14e")))
(commit "3b60472c04a58f26e33665f0eb0e88a558050c74")))
(sha256
(base32
"16m61d45rn4lzvximsnkvrdg4hfsdk4460lhyarixjcdzknh1z1z"))
"0xrli8mzigm0ryn28y28xvy4gc0358ck2036ncx5f1sj5s8dwfkh"))
(file-name (git-file-name name version))))
(build-system pyproject-build-system)

File diff suppressed because it is too large Load diff

View file

@ -24,29 +24,143 @@ lcEDLINaz1xuHAtAxqTQKMYCP1xtd5rhGOe1FkGfVYEJX97+JgMGa8+2nD5+A6wG
0+JaJllqzfXY1VhNoVmfS/hFPQ+t/84jNSGR5Kn956C5MvTK65VumH+NRE59kpt1
nsIQNKu/v6fZUnbRtCFC05BSwIjoTzFvKXycJkCVjdSYARWkagki4bbFC1WZQuA9
BOF5TOUAYt6zaEBfAJgjeRT71Mr03eNExXaLm9k/hmvapGpmtJQhLY6NKPm/ctyf
IaEz/bkCDQRdVC8lARAAu64IaLWAvMStxVsC/+NnwBBxYPef4Iq5gB5P1NgkmkD+
tyohWVnzdN/hwVDX3BAXevF8M+y6MouUA9IxJRt2W9PK06ArTdwhFpiam2NAO5OO
UhuJ1F8eAhRQ5VvI8MbVttZKSk3LiCmXGSj5UUXEFKS1B7WztZVwqG6YswoAPwbN
erZuwYbH2gfa9LK+av1cdZ8tnDaVmZWL8z1xSCyfRa/UAtZht/CEoTvAwXJ6CxVU
BngIlqVnK0KvOrNzol2m5x4NgPcdtdDlrTQE+SpqTKjyroRe27D+atiO6pFG/TOT
kx4TWXR07YTeZQJT/fntV409daIxEgShD0md7nJ7rVYy8u+9Z4JLlt2mtnsUKHez
o1Axrlri05cewPVYQLuJND/5e2X9UzSTpY3NubQAtkD1PpM5JeCbslT9PcMnRuUy
dZbhn7ieW0b57uWpOpE11s2eIJ5ixSci4mSJE9kW+IcCic/PPoD1Rh2CvFTBPl/b
sw6Bzw64LMflPjgWkR7NVQb1DETfXo5C2A/QU6Z/o7O4JaAeAoGki/sCmeAi5W+F
1kcjPk/L/TXM6ZccMytVQOECYBOYVUxZ2VbhknKOcSFQcpk8bj2xsD1xX2EYhkXc
CQkvutIgHGz/dt6dtvcaaL85krWD/y8h68TTFjQXK0+g8gcpexfqTMcLnF7pqEEA
EQEAAYkCPAQYAQgAJhYhBClZA2Lsh4qB/TwgK1JSe+2r6HmEBQJdVC8lAhsMBQkD
w8drAAoJEFJSe+2r6HmEDzEP/A8H3JkeSa/03kWvudFloVbGbfvP+XkKvGnAZPGH
z3ne/SV2tcXljNgU15xHvLktI4GluEfJxRPUqvUal1zOR9hqpas0vX8gsf0r0d3o
m2DHCyMY8GscfDF05Y8fqf0nU5/oLDlwwp11IyW8BDLSwwANsTLZ1ysukfYc4hoo
pU71/wdAl85fae7I2QRduImWlMADfUtc9Orfb1tAhPtaCJVZj5vgfUNSZOTUJ73R
GbdL3Z2dc42lO3mRMyDkPdykkq0EgOo6zZLuHZQFhxTzWIWeUT8vWNjpkdTeRHLv
v3cwPRx1k1atrM+pE9YkhCg0EOMTcmN+FMekgnU+ee0cibn5wWOvE05zwRKYROx3
4va2U6TUU6KkV3fFuq3qqkXaiMFauhI1lSFGgccg7BCNMhbBpOBkfGI3croFGSm2
pTydJ87/+P9C9ecOZSqCE7Zt5IfDs/xV7DjxBK99Z5+RGxtsIpNlxpsUvlMSsxUN
hOWyiCKr6NIOfOzdLYDkhHcKMqWGmc1zC3HHHuZvX5u6orTyYXWqc8X5p3Kh7Qjf
/ChtN2P6SCOUQquEvpiY5J1TdmQSuoqHzg3ZrN+7EOKdnUH7y1KB7iTvgQ07lcHn
AMbkFDcpQA+tAMd99LVNSXh8urXhJ/AtxaJbNbCSvpkOGB4WHLy/V+JdomFC9Pb3
oPei
=42dS
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
t8FIucvlOdt4EqKaSmoBQZk0Aj/N5q4=
=vjZr
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,31 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBF8V/EkBCAC8YTo6YJLNY0To+25b+dcSRcMCo/g9TJlraoJagO9Hr0Njbryg
jG5iptxi6UjDD+8xPK7YYRhaKyzJq1yTjGe5u5WEEtMfNaiVgA6dSEOXTdH4xT6q
v3VundebzZ7TFue7kj7fzEh7t9x2k5+RI2RvOs26ANEBKgJliQIZDXKOLcQuW7k9
9pWvqMWqRyn8WVGNf/UGBoFDcXQ1wo3h6m/LMJIO5L2IGlQWPmc8WT3uHJ/X/5Ln
slQ1ml7h+JjNwN0rAY/ZaJHSEi2y0RtLRzISP0EsA6EbqvJNGI8jqs5rpImgUn9U
8Q8Xz6hLPAiVTmteF63LlKo03wRcH8d/FVSvABEBAAG0N1BhdHJpY2sgTG9kZGVy
IDxwYXRyaWNrbG9kZGVyQHVzZXJzLm5vcmVwbHkuZ2l0aHViLmNvbT6JAVQEEwEI
AD4CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTcbvSov58bHk3h7lItOjRb
mNDcHwUCYtNqvwUJB3/VdgAKCRAtOjRbmNDcH+sVB/9jGPwrd1Om6L3ALzkZniR7
ODYFN4m8MRC4LPH2Ngt1Ea3/5DA68hEzQVGAFF+m7i7ZH9bmTvGB9R+qqF9WLTRc
aoO0XvYI8YrRLuhZFazafsLFRD5/c6QfpkBAjiDuxNIjEg2i+nY3avraxicKQKBY
PWWY0TFbz8K+CgIBh8Dnv7lqcxCFWHit/KHHjGAOvIPD5sLtv42dYk4TBEff4MVK
CzuCQtU8viy5doQPYHwfNADpOguskiNtFZmG2iPwgIE2tzHpLG2kidzZvJbHDcXY
XP13FnLvONf2bkS11gZSRm8pa6uay8/KfBNlCeMOYQDVoCuBbD5/2MwuV6o6OfSI
uQENBF8V/EkBCADN8eWUf0OtQdthNoWhRgotz/EzLI9r3sVv2SqbA++rHW9TC7mB
Wl/3e5emXWgKI1EK1Poz5HeKnL3SRx3xizgBTK6+RNQK6svvaLwcx06y8pZP9RqX
jLaRR67fXZCL+ulPtTcbt/JwlaTaokwWsgfy3UZRcK33llLbvWFjht2OGfx8B6Z9
UFRxW4sP0HuE3RrnMATGymWvOZlwYDr73HltksnOEFkz4lVP5VK9kdbndQjIB3Cf
zw/waTqjX+xXjJsFMYZhEDARhP5BQIoQvEv8KRtptNoLJGFZ9RGf+fIHiar2GAZL
4WZbZ0IuGLj419TkgvsUkI83Bx97DkS5Xa+jABEBAAGJATwEGAEIACYCGwwWIQTc
bvSov58bHk3h7lItOjRbmNDcHwUCYtNq0AUJB3/VhwAKCRAtOjRbmNDcH8cfB/4q
Puoir46sAGHBJt4TVe+R5ErVmGfGVUc3n6svguJnRMTAi1gpb6EapjdR9gUx+3Ja
wUE1keJuw5xeFi2JGp/XHt+8LAhsRAaLA4YViho8KL3yjzARvqrkYfl+FuO6kZIj
FEPJjRI1hOx5pWtPa3L3GZOexYDhRVdIJDci3gbFmU8HjgFx0G50zAysGR4DLVXj
FQBPvt4asUTdx30HU/pxWqFEzAeJPOVyjoxotdsMcIYXVBDhte5eADJ4OSMmc7k3
k46yHnbD4wyqqGtWqxHitTrl2U+M5MO5rlOZpGtIMtHz186OyMySZ5Gc886vPlOG
XgtNHT7E4rDrhySwy6Yk
=DQYN
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
tS35OCTSJzNMoXtTZqCkDLc9
=Z8rt
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -1,52 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
PSxdIAmV43ZrNIOxKzYOsA==
=w412
-----END PGP PUBLIC KEY BLOCK-----
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
PSxdIAmV43ZrNIOxKzYOsA==
=w412
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -1,6 +1,6 @@
pyzmq==26.2.0
python-gnupg==0.5.3
Jinja2==3.1.4
Jinja2==3.1.5
pycryptodome==3.21.0
PySocks==1.7.1
coincurve@https://github.com/basicswap/coincurve/archive/refs/tags/basicswap_v0.2.zip

View file

@ -80,9 +80,9 @@ cffi==1.17.1 \
coincurve @ https://github.com/basicswap/coincurve/archive/refs/tags/basicswap_v0.2.zip \
--hash=sha256:c309deef22c929c9ab5b3adf7adbda940bffcea6c6ec7c66202d6c3d4e3ceb79
# via -r requirements.in
jinja2==3.1.4 \
--hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \
--hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d
jinja2==3.1.5 \
--hash=sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb \
--hash=sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb
# via -r requirements.in
markupsafe==3.0.2 \
--hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \

View file

@ -148,7 +148,7 @@ def stopDaemons(daemons):
def wait_for_bid(
delay_event, swap_client, bid_id, state=None, sent: bool = False, wait_for: int = 20
) -> None:
logging.info("wait_for_bid %s", bid_id.hex())
swap_client.log.debug(f"TEST: wait_for_bid {bid_id.hex()}")
for i in range(wait_for):
if delay_event.is_set():
raise ValueError("Test stopped.")
@ -161,6 +161,10 @@ def wait_for_bid(
assert len(bids) < 2
for bid in bids:
if bid[2] == bid_id:
if i > 0 and i % 10 == 0:
swap_client.log.debug(
f"TEST: wait_for_bid {bid_id.hex()}: Bid state {bid[5]}, target {state}."
)
if isinstance(state, (list, tuple)):
if bid[5] in state:
return
@ -169,6 +173,11 @@ def wait_for_bid(
elif state is not None and state != bid[5]:
continue
return
else:
if i > 0 and i % 10 == 0:
swap_client.log.debug(
f"TEST: wait_for_bid {bid_id.hex()}: Bid not found."
)
raise ValueError("wait_for_bid timed out.")

View file

@ -44,12 +44,24 @@ from tests.basicswap.test_bch_xmr import (
BCH_BASE_PORT,
BCH_BASE_RPC_PORT,
)
from tests.basicswap.extended.test_doge import (
DOGE_BASE_PORT,
DOGE_BASE_RPC_PORT,
)
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
import basicswap.config as cfg
import basicswap.bin.run as runSystem
XMR_BASE_P2P_PORT = 17792
XMR_BASE_RPC_PORT = 29798
XMR_BASE_WALLET_RPC_PORT = 29998
FIRO_BASE_PORT = 34832
FIRO_BASE_RPC_PORT = 35832
FIRO_RPC_PORT_BASE = int(os.getenv("FIRO_RPC_PORT_BASE", FIRO_BASE_RPC_PORT))
TEST_PATH = os.path.expanduser(os.getenv("TEST_PATH", "~/test_basicswap1"))
PARTICL_PORT_BASE = int(os.getenv("PARTICL_PORT_BASE", BASE_PORT))
@ -64,16 +76,7 @@ DECRED_RPC_PORT_BASE = int(os.getenv("DECRED_RPC_PORT_BASE", DCR_BASE_RPC_PORT))
BITCOINCASH_RPC_PORT_BASE = int(
os.getenv("BITCOINCASH_RPC_PORT_BASE", BCH_BASE_RPC_PORT)
)
FIRO_BASE_PORT = 34832
FIRO_BASE_RPC_PORT = 35832
FIRO_RPC_PORT_BASE = int(os.getenv("FIRO_RPC_PORT_BASE", FIRO_BASE_RPC_PORT))
XMR_BASE_P2P_PORT = 17792
XMR_BASE_RPC_PORT = 29798
XMR_BASE_WALLET_RPC_PORT = 29998
DOGECOIN_RPC_PORT_BASE = int(os.getenv("DOGECOIN_RPC_PORT_BASE", DOGE_BASE_RPC_PORT))
EXTRA_CONFIG_JSON = json.loads(os.getenv("EXTRA_CONFIG_JSON", "{}"))
@ -131,9 +134,11 @@ def run_prepare(
os.environ["BTC_RPC_PORT"] = str(BITCOIN_RPC_PORT_BASE)
os.environ["LTC_RPC_PORT"] = str(LITECOIN_RPC_PORT_BASE)
os.environ["DCR_RPC_PORT"] = str(DECRED_RPC_PORT_BASE)
os.environ["FIRO_RPC_PORT"] = str(FIRO_RPC_PORT_BASE)
os.environ["BCH_PORT"] = str(BCH_BASE_PORT)
os.environ["BCH_RPC_PORT"] = str(BITCOINCASH_RPC_PORT_BASE)
os.environ["FIRO_RPC_PORT"] = str(FIRO_RPC_PORT_BASE)
os.environ["DOGE_PORT"] = str(DOGE_BASE_PORT)
os.environ["DOGE_RPC_PORT"] = str(DOGECOIN_RPC_PORT_BASE)
os.environ["XMR_RPC_USER"] = "xmr_user"
os.environ["XMR_RPC_PWD"] = "xmr_pwd"
@ -433,6 +438,40 @@ def run_prepare(
for opt in EXTRA_CONFIG_JSON.get("bch{}".format(node_id), []):
fp.write(opt + "\n")
if "dogecoin" in coins_array:
config_filename = os.path.join(datadir_path, "dogecoin", "dogecoin.conf")
with open(config_filename, "r") as fp:
lines = fp.readlines()
with open(config_filename, "w") as fp:
for line in lines:
if not line.startswith("prune"):
fp.write(line)
fp.write("port={}\n".format(DOGE_BASE_PORT + node_id + port_ofs))
fp.write("bind=127.0.0.1\n")
fp.write("dnsseed=0\n")
fp.write("discover=0\n")
fp.write("listenonion=0\n")
fp.write("upnp=0\n")
fp.write("debug=1\n")
if use_rpcauth:
salt = generate_salt(16)
rpc_user = "test_doge_" + str(node_id)
rpc_pass = "test_doge_pwd_" + str(node_id)
fp.write(
"rpcauth={}:{}${}\n".format(
rpc_user, salt, password_to_hmac(salt, rpc_pass)
)
)
settings["chainclients"]["dogecoin"]["rpcuser"] = rpc_user
settings["chainclients"]["dogecoin"]["rpcpassword"] = rpc_pass
for ip in range(num_nodes):
if ip != node_id:
fp.write(
"connect=127.0.0.1:{}\n".format(DOGE_BASE_PORT + ip + port_ofs)
)
for opt in EXTRA_CONFIG_JSON.get("doge{}".format(node_id), []):
fp.write(opt + "\n")
with open(config_path) as fs:
settings = json.load(fs)

View file

@ -879,7 +879,7 @@ class Test(BaseTest):
ci = DCRInterface(coin_settings, "mainnet")
k = ci.getNewSecretKey()
k = ci.getNewRandomKey()
K = ci.getPubkey(k)
pkh = ci.pkh(K)
@ -1417,8 +1417,8 @@ class Test(BaseTest):
# fee_rate is in sats/kvB
fee_rate: int = 10000
a = ci.getNewSecretKey()
b = ci.getNewSecretKey()
a = ci.getNewRandomKey()
b = ci.getNewRandomKey()
A = ci.getPubkey(a)
B = ci.getPubkey(b)
@ -1477,8 +1477,8 @@ class Test(BaseTest):
assert expect_size - size_actual < 10
# Test chain b (no-script) lock tx size
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
test_delay_event.wait(1)

View file

@ -0,0 +1,500 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import random
import logging
import unittest
import basicswap.config as cfg
from basicswap.basicswap import (
Coins,
)
from basicswap.util.address import (
toWIF,
)
from tests.basicswap.common import (
stopDaemons,
make_rpc_func,
waitForRPC,
)
from basicswap.bin.run import startDaemon
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from tests.basicswap.test_xmr import test_delay_event, callnoderpc
from basicswap.contrib.test_framework.messages import (
CTransaction,
CTxIn,
COutPoint,
ToHex,
)
from basicswap.contrib.test_framework.script import (
CScript,
OP_CHECKLOCKTIMEVERIFY,
)
from tests.basicswap.test_btc_xmr import TestFunctions
logger = logging.getLogger()
DOGE_BINDIR = os.path.expanduser(
os.getenv("DOGE_BINDIR", os.path.join(cfg.DEFAULT_TEST_BINDIR, "dogecoin"))
)
DOGED = os.getenv("DOGED", "dogecoind" + cfg.bin_suffix)
DOGE_CLI = os.getenv("DOGE_CLI", "dogecoin-cli" + cfg.bin_suffix)
DOGE_TX = os.getenv("DOGE_TX", "dogecoin-tx" + cfg.bin_suffix)
DOGE_BASE_PORT = 22556
DOGE_BASE_RPC_PORT = 18442
def prepareDataDir(
datadir, node_id, conf_file, dir_prefix, base_p2p_port, base_rpc_port, num_nodes=3
):
node_dir = os.path.join(datadir, dir_prefix + str(node_id))
if not os.path.exists(node_dir):
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)
fp.write(
"rpcauth={}:{}${}\n".format(
"test" + str(node_id),
salt,
password_to_hmac(salt, "test_pass" + str(node_id)),
)
)
fp.write("daemon=0\n")
fp.write("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("acceptnonstdtxn=0\n")
for i in range(0, num_nodes):
if node_id == i:
continue
fp.write("addnode=127.0.0.1:{}\n".format(base_p2p_port + i))
return node_dir
class Test(TestFunctions):
__test__ = True
test_coin = Coins.DOGE
test_coin_from = Coins.BTC
test_coin_to = Coins.DOGE
doge_daemons = []
doge_addr = None
start_ltc_nodes = False
start_xmr_nodes = False
test_atomic = False
test_xmr = True
pause_chain = False
doge_seeds = [
"516b471da2a67bcfd42a1da7f7ae8f9a1b02c34f6a2d6a943ceec5dca68e7fa1",
"a8c0911fba070d5cc2784703afeb0f7c3b9b524b8a53466c04e01933d9fede78",
"7b3b533ac3a27114ae17c8cca0d2cd9f736e7519ae52b8ec8f1f452e8223d082",
]
@classmethod
def prepareExtraDataDir(cls, i):
if not cls.restore_instance:
prepareDataDir(
cfg.TEST_DATADIRS,
i,
"dogecoin.conf",
"doge_",
base_p2p_port=DOGE_BASE_PORT,
base_rpc_port=DOGE_BASE_RPC_PORT,
)
cls.doge_daemons.append(
startDaemon(
os.path.join(cfg.TEST_DATADIRS, "doge_" + str(i)),
DOGE_BINDIR,
DOGED,
)
)
logging.info("Started %s %d", DOGED, cls.doge_daemons[-1].handle.pid)
dogeRpc = make_rpc_func(i, base_rpc_port=DOGE_BASE_RPC_PORT)
waitForRPC(dogeRpc, test_delay_event, rpc_command="getblockchaininfo")
if len(dogeRpc("listwallets")) < 1:
dogeRpc("createwallet", ["wallet.dat", False, True, "", False, False])
wif_prefix: int = 239
wif = toWIF(wif_prefix, bytes.fromhex(cls.doge_seeds[i]), False)
dogeRpc("sethdseed", [True, wif])
waitForRPC(
dogeRpc,
test_delay_event,
max_tries=12,
)
@classmethod
def addPIDInfo(cls, sc, i):
sc.setDaemonPID(Coins.DOGE, cls.doge_daemons[i].handle.pid)
@classmethod
def sync_blocks(cls, wait_for: int = 20, num_nodes: int = 3) -> None:
logging.info("Syncing blocks")
for i in range(wait_for):
if test_delay_event.is_set():
raise ValueError("Test stopped.")
block_hash0 = callnoderpc(
0, "getbestblockhash", base_rpc_port=DOGE_BASE_RPC_PORT
)
matches: int = 0
for i in range(1, num_nodes):
block_hash = callnoderpc(
i, "getbestblockhash", base_rpc_port=DOGE_BASE_RPC_PORT
)
if block_hash == block_hash0:
matches += 1
if matches == num_nodes - 1:
return
test_delay_event.wait(1)
raise ValueError("sync_blocks timed out.")
@classmethod
def prepareExtraCoins(cls):
if cls.restore_instance:
void_block_rewards_pubkey = cls.getRandomPubkey()
cls.doge_addr = (
cls.swap_clients[0]
.ci(Coins.DOGE)
.pubkey_to_address(void_block_rewards_pubkey)
)
else:
num_blocks = 400
cls.doge_addr = callnoderpc(
0, "getnewaddress", ["mining_addr"], base_rpc_port=DOGE_BASE_RPC_PORT
)
logging.info("Mining %d DOGE blocks to %s", num_blocks, cls.doge_addr)
callnoderpc(
0,
"generatetoaddress",
[num_blocks, cls.doge_addr],
base_rpc_port=DOGE_BASE_RPC_PORT,
)
doge_addr1 = callnoderpc(
1, "getnewaddress", ["initial addr"], base_rpc_port=DOGE_BASE_RPC_PORT
)
for i in range(5):
callnoderpc(
0,
"sendtoaddress",
[doge_addr1, 1000],
base_rpc_port=DOGE_BASE_RPC_PORT,
)
# Set future block rewards to nowhere (a random address), so wallet amounts stay constant
void_block_rewards_pubkey = cls.getRandomPubkey()
cls.doge_addr = (
cls.swap_clients[0]
.ci(Coins.DOGE)
.pubkey_to_address(void_block_rewards_pubkey)
)
num_blocks = 100
logging.info("Mining %d DOGE blocks to %s", num_blocks, cls.doge_addr)
callnoderpc(
0,
"generatetoaddress",
[num_blocks, cls.doge_addr],
base_rpc_port=DOGE_BASE_RPC_PORT,
)
cls.sync_blocks()
@classmethod
def tearDownClass(cls):
logging.info("Finalising DOGE Test")
super(Test, cls).tearDownClass()
stopDaemons(cls.doge_daemons)
cls.doge_daemons.clear()
@classmethod
def addCoinSettings(cls, settings, datadir, node_id):
settings["chainclients"]["dogecoin"] = {
"connection_type": "rpc",
"manage_daemon": False,
"rpcport": DOGE_BASE_RPC_PORT + node_id,
"rpcuser": "test" + str(node_id),
"rpcpassword": "test_pass" + str(node_id),
"datadir": os.path.join(datadir, "doge_" + str(node_id)),
"bindir": DOGE_BINDIR,
"use_csv": False,
"use_segwit": False,
"blocks_confirmed": 1,
"min_relay_fee": 0.01, # RECOMMENDED_MIN_TX_FEE
}
@classmethod
def coins_loop(cls):
super(Test, cls).coins_loop()
if cls.pause_chain:
return
ci0 = cls.swap_clients[0].ci(cls.test_coin)
try:
if cls.doge_addr is not None:
ci0.rpc_wallet("generatetoaddress", [1, cls.doge_addr])
except Exception as e:
logging.warning("coins_loop generate {}".format(e))
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
return callnoderpc(
node_id, method, params, wallet, base_rpc_port=DOGE_BASE_RPC_PORT
)
def mineBlock(self, num_blocks: int = 1):
self.callnoderpc("generatetoaddress", [num_blocks, self.doge_addr])
def test_003_cltv(self):
logging.info("---------- Test {} cltv".format(self.test_coin.name))
ci = self.swap_clients[0].ci(self.test_coin)
self.pause_chain = True
try:
start_height: int = self.callnoderpc("getblockcount")
num_blocks: int = 1351 # consensus.BIP65Height = 1351;
if start_height < num_blocks:
to_mine = num_blocks - start_height
logging.info("Mining %d DOGE blocks to %s", to_mine, self.doge_addr)
ci.rpc("generatetoaddress", [to_mine, self.doge_addr])
# self.check_softfork_active("bip65") # TODO: Re-enable next version
chain_height: int = self.callnoderpc("getblockcount")
script = CScript(
[
chain_height + 3,
OP_CHECKLOCKTIMEVERIFY,
]
)
script_dest = ci.getScriptDest(script)
script_info = ci.rpc_wallet(
"decodescript",
[
script_dest.hex(),
],
)
script_addr = ci.encodeScriptDest(script_dest)
assert script_info["address"] == script_addr
prevout_amount: int = ci.make_int(1.1)
tx = CTransaction()
tx.nVersion = ci.txVersion()
tx.vout.append(ci.txoType()(prevout_amount, script_dest))
tx_hex = tx.serialize().hex()
tx = CTransaction()
tx.nVersion = ci.txVersion()
tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
tx_hex = ToHex(tx)
tx_funded = ci.rpc_wallet("fundrawtransaction", [tx_hex])
utxo_pos = 0 if tx_funded["changepos"] == 1 else 1
tx_signed = ci.rpc_wallet(
"signrawtransactionwithwallet",
[
tx_funded["hex"],
],
)["hex"]
txid = ci.rpc(
"sendrawtransaction",
[
tx_signed,
],
)
addr_out = ci.rpc_wallet(
"getnewaddress",
[
"cltv test",
],
)
pkh = ci.decodeAddress(addr_out)
script_out = ci.getScriptForPubkeyHash(pkh)
tx_spend = CTransaction()
tx_spend.nVersion = ci.txVersion()
tx_spend.nLockTime = chain_height + 3
tx_spend.vin.append(
CTxIn(
COutPoint(int(txid, 16), utxo_pos),
scriptSig=CScript(
[
script,
]
),
)
)
tx_spend.vout.append(ci.txoType()(ci.make_int(1.099), script_out))
tx_spend_hex = ToHex(tx_spend)
tx_spend.nLockTime = chain_height + 2
tx_spend_invalid_hex = ToHex(tx_spend)
for tx_hex in [tx_spend_invalid_hex, tx_spend_hex]:
try:
txid = self.callnoderpc(
"sendrawtransaction",
[
tx_hex,
],
)
except Exception as e:
assert "non-final" in str(
e
) or "Locktime requirement not satisfied" in str(e)
else:
assert False, "Should fail"
self.mineBlock(5)
txid = ci.rpc(
"sendrawtransaction",
[
tx_spend_hex,
],
)
self.mineBlock()
ci.rpc("syncwithvalidationinterfacequeue")
# Ensure tx was mined
tx_wallet = ci.rpc_wallet(
"gettransaction",
[
txid,
],
)
assert len(tx_wallet["blockhash"]) == 64
finally:
self.pause_chain = False
def test_010_txn_size(self):
logging.info("---------- Test {} txn size".format(self.test_coin.name))
swap_clients = self.swap_clients
ci = swap_clients[0].ci(self.test_coin)
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
# fee_rate is in sats/kvB
fee_rate: int = 1000000
# Test chain b (no-script) lock tx size
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
test_delay_event.wait(1)
addr_out = ci.getNewAddress(False)
lock_tx_b_spend_txid = ci.spendBLockTx(
lock_tx_b_txid, addr_out, v, s, amount, fee_rate, 0
)
test_delay_event.wait(1)
lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid)
if lock_tx_b_spend is None:
lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid)
assert lock_tx_b_spend is not None
tx_obj = ci.loadTx(lock_tx_b_spend)
tx_out_value: int = tx_obj.vout[0].nValue
fee_paid = amount - tx_out_value
actual_size = len(lock_tx_b_spend)
expect_size: int = ci.xmr_swap_b_lock_spend_tx_vsize()
fee_expect = round(fee_rate * expect_size / 1000)
assert fee_expect == fee_paid
assert expect_size >= actual_size
assert expect_size - actual_size < 10
def test_01_a_full_swap(self):
self.do_test_01_full_swap(self.test_coin_from, self.test_coin_to)
def test_01_b_full_swap_reverse(self):
self.prepare_balance(self.test_coin_to, 100.0, 1800, 1801)
self.do_test_01_full_swap(self.test_coin_to, self.test_coin_from)
def test_01_c_full_swap_to_part(self):
self.do_test_01_full_swap(self.test_coin, Coins.PART)
def test_01_d_full_swap_from_part(self):
self.do_test_01_full_swap(Coins.PART, self.test_coin)
def test_02_a_leader_recover_a_lock_tx(self):
self.do_test_02_leader_recover_a_lock_tx(self.test_coin_from, self.test_coin_to)
def test_02_b_leader_recover_a_lock_tx_reverse(self):
self.prepare_balance(self.test_coin_to, 100.0, 1800, 1801)
self.do_test_02_leader_recover_a_lock_tx(self.test_coin_to, self.test_coin_from)
def test_03_a_follower_recover_a_lock_tx(self):
self.do_test_03_follower_recover_a_lock_tx(
self.test_coin_from, self.test_coin_to
)
def test_03_b_follower_recover_a_lock_tx_reverse(self):
self.prepare_balance(self.test_coin_to, 100.0, 1800, 1801)
self.do_test_03_follower_recover_a_lock_tx(
self.test_coin_to, self.test_coin_from
)
def test_03_e_follower_recover_a_lock_tx_mercy_release(self):
self.do_test_03_follower_recover_a_lock_tx(
self.test_coin_from, self.test_coin_to, with_mercy=True
)
def test_03_f_follower_recover_a_lock_tx_mercy_release_reverse(self):
self.prepare_balance(self.test_coin_to, 100.0, 1800, 1801)
self.prepare_balance(self.test_coin_from, 100.0, 1801, 1800)
self.do_test_03_follower_recover_a_lock_tx(
self.test_coin_to, self.test_coin_from, with_mercy=True
)
def test_04_a_follower_recover_b_lock_tx(self):
self.do_test_04_follower_recover_b_lock_tx(
self.test_coin_from, self.test_coin_to
)
def test_04_b_follower_recover_b_lock_tx_reverse(self):
self.prepare_balance(self.test_coin_to, 100.0, 1800, 1801)
self.do_test_04_follower_recover_b_lock_tx(
self.test_coin_to, self.test_coin_from
)
def test_05_self_bid(self):
self.do_test_05_self_bid(self.test_coin_from, self.test_coin_to)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,132 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
"""
export RESET_TEST=true
export TEST_PATH=/tmp/test_doge
mkdir -p ${TEST_PATH}/bin
cp -r ~/tmp/basicswap_bin/* ${TEST_PATH}/bin
export PYTHONPATH=$(pwd)
export TEST_COINS_LIST='bitcoin,dogecoin'
python tests/basicswap/extended/test_doge.py
"""
import sys
import logging
import unittest
from tests.basicswap.common import (
wait_for_balance,
)
from tests.basicswap.extended.test_xmr_persistent import (
BaseTestWithPrepare,
UI_PORT,
)
from tests.basicswap.extended.test_scripts import (
wait_for_offers,
)
from tests.basicswap.util import (
read_json_api,
)
logger = logging.getLogger()
logger.level = logging.DEBUG
if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout))
def wait_for_bid(
delay_event, node_id, bid_id, state=None, sent: bool = False, num_tries: int = 40
) -> None:
for i in range(num_tries):
delay_event.wait(3)
if delay_event.is_set():
raise ValueError("Test stopped.")
bid = read_json_api(UI_PORT + node_id, f"bids/{bid_id}")
if "state" not in bid:
continue
if state is None:
return
if bid["state"].lower() == state.lower():
return
raise ValueError("wait_for_bid failed")
def prepare_balance(
delay_event,
node_id,
node_id_take_from,
coin_ticker,
amount,
wait_for: int = 20,
test_balance: bool = True,
) -> None:
print(f"prepare_balance on node {node_id}, {coin_ticker}: {amount}")
balance_type: str = "balance"
address_type: str = "deposit_address"
js_w = read_json_api(UI_PORT + node_id, "wallets")
current_balance: float = float(js_w[coin_ticker][balance_type])
if test_balance and current_balance >= amount:
return
post_json = {
"value": amount,
"address": js_w[coin_ticker][address_type],
"subfee": False,
}
json_rv = read_json_api(
UI_PORT + node_id_take_from,
"wallets/{}/withdraw".format(coin_ticker.lower()),
post_json,
)
assert len(json_rv["txid"]) == 64
wait_for_amount: float = amount
if not test_balance:
wait_for_amount += current_balance
wait_for_balance(
delay_event,
f"http://127.0.0.1:{UI_PORT + node_id}/json/wallets/{coin_ticker.lower()}",
balance_type,
wait_for_amount,
iterations=wait_for,
)
class DOGETest(BaseTestWithPrepare):
def test_a(self):
amount_from = 10.0
offer_json = {
"coin_from": "btc",
"coin_to": "doge",
"amt_from": amount_from,
"amt_to": 100.0,
"amt_var": True,
"lockseconds": 3600,
"automation_strat_id": 1,
}
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)
wait_for_offers(self.delay_event, 1, 1, offer_id)
post_json = {"offer_id": offer_id, "amount_from": amount_from}
bid_id = read_json_api(UI_PORT + 1, "bids/new", post_json)["bid_id"]
wait_for_bid(self.delay_event, 0, bid_id, "completed", num_tries=240)
wait_for_bid(self.delay_event, 1, bid_id, "completed")
if __name__ == "__main__":
unittest.main()

View file

@ -147,6 +147,8 @@ 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}")
for i in range(20):
delay_event.wait(1)
if delay_event.is_set():
raise ValueError("Test stopped.")
if offer_id is not None:
bids = read_json_api(UI_PORT + node_id, "bids", {"offer_id": offer_id})
else:

View file

@ -103,7 +103,6 @@ class Test(BaseTest):
@classmethod
def prepareExtraCoins(cls):
pass
num_blocks = 300
cls.wow_addr = cls.callwownodewallet(cls, 1, "get_address")["address"]
if callrpc_xmr(WOW_BASE_RPC_PORT + 1, "get_block_count")["count"] < num_blocks:

View file

@ -56,11 +56,11 @@ from tests.basicswap.util import (
from tests.basicswap.common_xmr import (
prepare_nodes,
XMR_BASE_RPC_PORT,
DOGE_BASE_RPC_PORT,
)
from basicswap.interface.dcr.rpc import callrpc as callrpc_dcr
import basicswap.bin.run as runSystem
test_path = os.path.expanduser(os.getenv("TEST_PATH", "/tmp/test_persistent"))
RESET_TEST = make_boolean(os.getenv("RESET_TEST", "false"))
@ -70,6 +70,7 @@ UI_PORT = 12700 + PORT_OFS
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))
LITECOIN_RPC_PORT_BASE = int(os.getenv("LITECOIN_RPC_PORT_BASE", LTC_BASE_RPC_PORT))
DOGECOIN_RPC_PORT_BASE = int(os.getenv("DOGECOIN_RPC_PORT_BASE", DOGE_BASE_RPC_PORT))
BITCOINCASH_RPC_PORT_BASE = int(
os.getenv("BITCOINCASH_RPC_PORT_BASE", BCH_BASE_RPC_PORT)
)
@ -137,6 +138,17 @@ def callbchrpc(
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
def calldogerpc(
node_id,
method,
params=[],
wallet=None,
base_rpc_port=DOGECOIN_RPC_PORT_BASE + PORT_OFS,
):
auth = "test_doge_{0}:test_doge_pwd_{0}".format(node_id)
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
def updateThread(cls):
while not cls.delay_event.is_set():
try:
@ -146,6 +158,8 @@ def updateThread(cls):
callltcrpc(0, "generatetoaddress", [1, cls.ltc_addr])
if cls.bch_addr is not None:
callbchrpc(0, "generatetoaddress", [1, cls.bch_addr])
if cls.doge_addr is not None:
calldogerpc(0, "generatetoaddress", [1, cls.doge_addr])
except Exception as e:
print("updateThread error", str(e))
cls.delay_event.wait(random.randrange(cls.update_min, cls.update_max))
@ -204,74 +218,39 @@ def updateThreadDCR(cls):
cls.delay_event.wait(random.randrange(cls.dcr_update_min, cls.dcr_update_max))
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(Test, cls).setUpClass()
def signal_handler(self, sig, frame):
logging.info("signal {} detected.".format(sig))
self.delay_event.set()
cls.update_min = int(os.getenv("UPDATE_THREAD_MIN_WAIT", "1"))
cls.update_max = cls.update_min * 4
cls.xmr_update_min = int(os.getenv("XMR_UPDATE_THREAD_MIN_WAIT", "1"))
cls.xmr_update_max = cls.xmr_update_min * 4
def run_thread(self, client_id):
client_path = os.path.join(test_path, "client{}".format(client_id))
testargs = ["basicswap-run", "-datadir=" + client_path, "-regtest"]
with patch.object(sys, "argv", testargs):
runSystem.main()
cls.dcr_update_min = int(os.getenv("DCR_UPDATE_THREAD_MIN_WAIT", "1"))
cls.dcr_update_max = cls.dcr_update_min * 4
cls.delay_event = threading.Event()
cls.update_thread = None
cls.update_thread_xmr = None
cls.update_thread_dcr = None
cls.processes = []
cls.btc_addr = None
cls.ltc_addr = None
cls.bch_addr = None
cls.xmr_addr = None
cls.dcr_addr = "SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH"
cls.dcr_acc = None
def start_processes(self):
self.delay_event.clear()
random.seed(time.time())
if os.path.exists(test_path) and not RESET_TEST:
logging.info(f"Continuing with existing directory: {test_path}")
else:
logging.info("Preparing %d nodes.", NUM_NODES)
prepare_nodes(
NUM_NODES,
TEST_COINS_LIST,
True,
{"min_sequence_lock_seconds": 60},
PORT_OFS,
for i in range(NUM_NODES):
self.processes.append(
multiprocessing.Process(
target=run_thread,
args=(
self,
i,
),
)
signal.signal(
signal.SIGINT, lambda signal, frame: cls.signal_handler(cls, signal, frame)
)
self.processes[-1].start()
def signal_handler(self, sig, frame):
logging.info("signal {} detected.".format(sig))
self.delay_event.set()
for i in range(NUM_NODES):
waitForServer(self.delay_event, UI_PORT + i)
def run_thread(self, client_id):
client_path = os.path.join(test_path, "client{}".format(client_id))
testargs = ["basicswap-run", "-datadir=" + client_path, "-regtest"]
with patch.object(sys, "argv", testargs):
runSystem.main()
def start_processes(self):
self.delay_event.clear()
for i in range(NUM_NODES):
self.processes.append(
multiprocessing.Process(target=self.run_thread, args=(i,))
)
self.processes[-1].start()
for i in range(NUM_NODES):
waitForServer(self.delay_event, UI_PORT + i)
wallets = read_json_api(UI_PORT + 1, "wallets")
wallets = read_json_api(UI_PORT + 1, "wallets")
if "monero" in TEST_COINS_LIST:
xmr_auth = None
if os.getenv("XMR_RPC_USER", "") != "":
xmr_auth = (os.getenv("XMR_RPC_USER", ""), os.getenv("XMR_RPC_PWD", ""))
@ -300,127 +279,187 @@ class Test(unittest.TestCase):
],
)
self.btc_addr = callbtcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
num_blocks: int = 500 # Mine enough to activate segwit
if callbtcrpc(0, "getblockcount") < num_blocks:
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, self.btc_addr)
callbtcrpc(0, "generatetoaddress", [num_blocks, self.btc_addr])
logging.info("BTC blocks: %d", callbtcrpc(0, "getblockcount"))
self.btc_addr = callbtcrpc(0, "getnewaddress", ["mining_addr", "bech32"])
num_blocks: int = 500 # Mine enough to activate segwit
if callbtcrpc(0, "getblockcount") < num_blocks:
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, self.btc_addr)
callbtcrpc(0, "generatetoaddress", [num_blocks, self.btc_addr])
logging.info("BTC blocks: %d", callbtcrpc(0, "getblockcount"))
if "litecoin" in TEST_COINS_LIST:
self.ltc_addr = callltcrpc(
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
if "litecoin" in TEST_COINS_LIST:
self.ltc_addr = callltcrpc(
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
)
num_blocks: int = 431
have_blocks: int = callltcrpc(0, "getblockcount")
if have_blocks < 500:
logging.info("Mining %d Litecoin blocks to %s", num_blocks, self.ltc_addr)
callltcrpc(
0,
"generatetoaddress",
[num_blocks - have_blocks, self.ltc_addr],
wallet="wallet.dat",
)
num_blocks: int = 431
# https://github.com/litecoin-project/litecoin/issues/807
# Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block.
mweb_addr = callltcrpc(
0, "getnewaddress", ["mweb_addr", "mweb"], wallet="mweb"
)
callltcrpc(0, "sendtoaddress", [mweb_addr, 1.0], wallet="wallet.dat")
num_blocks = 69
have_blocks: int = callltcrpc(0, "getblockcount")
if have_blocks < 500:
logging.info(
"Mining %d Litecoin blocks to %s", num_blocks, self.ltc_addr
)
callltcrpc(
0,
"generatetoaddress",
[num_blocks - have_blocks, self.ltc_addr],
wallet="wallet.dat",
)
# https://github.com/litecoin-project/litecoin/issues/807
# Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block.
mweb_addr = callltcrpc(
0, "getnewaddress", ["mweb_addr", "mweb"], wallet="mweb"
)
callltcrpc(0, "sendtoaddress", [mweb_addr, 1.0], wallet="wallet.dat")
num_blocks = 69
have_blocks: int = callltcrpc(0, "getblockcount")
callltcrpc(
0,
"generatetoaddress",
[500 - have_blocks, self.ltc_addr],
wallet="wallet.dat",
)
if "decred" in TEST_COINS_LIST:
if RESET_TEST:
_ = calldcrrpc(0, "getnewaddress")
# assert (addr == self.dcr_addr)
self.dcr_acc = calldcrrpc(
0,
"getaccount",
[
self.dcr_addr,
],
)
calldcrrpc(
0,
"generate",
[
110,
],
)
else:
self.dcr_acc = calldcrrpc(
0,
"getaccount",
[
self.dcr_addr,
],
)
self.update_thread_dcr = threading.Thread(
target=updateThreadDCR, args=(self,)
callltcrpc(
0,
"generatetoaddress",
[500 - have_blocks, self.ltc_addr],
wallet="wallet.dat",
)
self.update_thread_dcr.start()
if "bitcoincash" in TEST_COINS_LIST:
self.bch_addr = callbchrpc(
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
)
num_blocks: int = 200
have_blocks: int = callbchrpc(0, "getblockcount")
if have_blocks < num_blocks:
logging.info(
"Mining %d Bitcoincash blocks to %s",
num_blocks - have_blocks,
self.bch_addr,
)
callbchrpc(
0,
"generatetoaddress",
[num_blocks - have_blocks, self.bch_addr],
wallet="wallet.dat",
)
if "decred" in TEST_COINS_LIST:
if RESET_TEST:
# Lower output split threshold for more stakeable outputs
for i in range(NUM_NODES):
callpartrpc(
i,
"walletsettings",
[
"stakingoptions",
{"stakecombinethreshold": 100, "stakesplitthreshold": 200},
],
)
self.update_thread = threading.Thread(target=updateThread, args=(self,))
self.update_thread.start()
_ = calldcrrpc(0, "getnewaddress")
# assert (addr == self.dcr_addr)
self.dcr_acc = calldcrrpc(
0,
"getaccount",
[
self.dcr_addr,
],
)
calldcrrpc(
0,
"generate",
[
110,
],
)
else:
self.dcr_acc = calldcrrpc(
0,
"getaccount",
[
self.dcr_addr,
],
)
self.update_thread_xmr = threading.Thread(target=updateThreadXMR, args=(self,))
self.update_thread_xmr.start()
self.update_thread_dcr = threading.Thread(target=updateThreadDCR, args=(self,))
self.update_thread_dcr.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):
if self.delay_event.is_set():
raise ValueError("Test stopped.")
particl_blocks = callpartrpc(0, "getblockcount")
print("particl_blocks", particl_blocks)
if particl_blocks >= num_blocks:
break
self.delay_event.wait(1)
logging.info("PART blocks: %d", callpartrpc(0, "getblockcount"))
assert particl_blocks >= num_blocks
if "bitcoincash" in TEST_COINS_LIST:
self.bch_addr = callbchrpc(
0, "getnewaddress", ["mining_addr"], wallet="wallet.dat"
)
num_blocks: int = 200
have_blocks: int = callbchrpc(0, "getblockcount")
if have_blocks < num_blocks:
logging.info(
"Mining %d Bitcoincash blocks to %s",
num_blocks - have_blocks,
self.bch_addr,
)
callbchrpc(
0,
"generatetoaddress",
[num_blocks - have_blocks, self.bch_addr],
wallet="wallet.dat",
)
if "dogecoin" in TEST_COINS_LIST:
self.doge_addr = calldogerpc(0, "getnewaddress", ["mining_addr"])
num_blocks: int = 200
have_blocks: int = calldogerpc(0, "getblockcount")
if have_blocks < num_blocks:
logging.info(
"Mining %d Dogecoin blocks to %s",
num_blocks - have_blocks,
self.doge_addr,
)
calldogerpc(
0, "generatetoaddress", [num_blocks - have_blocks, self.doge_addr]
)
if RESET_TEST:
# Lower output split threshold for more stakeable outputs
for i in range(NUM_NODES):
callpartrpc(
i,
"walletsettings",
[
"stakingoptions",
{"stakecombinethreshold": 100, "stakesplitthreshold": 200},
],
)
self.update_thread = threading.Thread(target=updateThread, args=(self,))
self.update_thread.start()
self.update_thread_xmr = threading.Thread(target=updateThreadXMR, args=(self,))
self.update_thread_xmr.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):
if self.delay_event.is_set():
raise ValueError("Test stopped.")
particl_blocks = callpartrpc(0, "getblockcount")
print("particl_blocks", particl_blocks)
if particl_blocks >= num_blocks:
break
self.delay_event.wait(1)
logging.info("PART blocks: %d", callpartrpc(0, "getblockcount"))
assert particl_blocks >= num_blocks
class BaseTestWithPrepare(unittest.TestCase):
__test__ = False
update_min = int(os.getenv("UPDATE_THREAD_MIN_WAIT", "1"))
update_max = update_min * 4
xmr_update_min = int(os.getenv("XMR_UPDATE_THREAD_MIN_WAIT", "1"))
xmr_update_max = xmr_update_min * 4
dcr_update_min = int(os.getenv("DCR_UPDATE_THREAD_MIN_WAIT", "1"))
dcr_update_max = dcr_update_min * 4
delay_event = threading.Event()
update_thread = None
update_thread_xmr = None
update_thread_dcr = None
processes = []
btc_addr = None
ltc_addr = None
bch_addr = None
xmr_addr = None
dcr_addr = "SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH"
dcr_acc = None
doge_addr = None
initialised = False
@classmethod
def setUpClass(cls):
super(BaseTestWithPrepare, cls).setUpClass()
random.seed(time.time())
if os.path.exists(test_path) and not RESET_TEST:
logging.info(f"Continuing with existing directory: {test_path}")
else:
logging.info("Preparing %d nodes.", NUM_NODES)
prepare_nodes(
NUM_NODES,
TEST_COINS_LIST,
True,
{"min_sequence_lock_seconds": 60},
PORT_OFS,
)
signal.signal(
signal.SIGINT, lambda signal, frame: signal_handler(cls, signal, frame)
)
@classmethod
def tearDownClass(cls):
@ -441,12 +480,17 @@ class Test(unittest.TestCase):
cls.update_thread_dcr = None
cls.processes = []
def test_persistent(self):
self.start_processes()
def setUp(self):
if self.initialised:
return
start_processes(self)
waitForServer(self.delay_event, UI_PORT + 0)
waitForServer(self.delay_event, UI_PORT + 1)
self.initialised = True
class Test(BaseTestWithPrepare):
def test_persistent(self):
while not self.delay_event.is_set():
logging.info("Looping indefinitely, ctrl+c to exit.")

View file

@ -513,8 +513,8 @@ class TestBCH(BasicSwapTest):
# fee_rate is in sats/B
fee_rate: int = 1
a = ci.getNewSecretKey()
b = ci.getNewSecretKey()
a = ci.getNewRandomKey()
b = ci.getNewRandomKey()
A = ci.getPubkey(a)
B = ci.getPubkey(b)

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021-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.
@ -61,7 +61,6 @@ logger = logging.getLogger()
class TestFunctions(BaseTest):
base_rpc_port = None
extra_wait_time = 0
node_a_id = 0
node_b_id = 1
@ -170,7 +169,11 @@ class TestFunctions(BaseTest):
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(
test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED
test_delay_event,
swap_clients[id_offerer],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
bid0 = read_json_api(1800 + id_offerer, f"bids/{bid_id.hex()}")
@ -331,7 +334,11 @@ class TestFunctions(BaseTest):
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(
test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED
test_delay_event,
swap_clients[id_offerer],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[id_follower].setBidDebugInd(
@ -377,6 +384,9 @@ class TestFunctions(BaseTest):
id_offerer: int = self.node_a_id
id_bidder: int = self.node_b_id
abandon_all_swaps(test_delay_event, self.swap_clients[id_offerer])
abandon_all_swaps(test_delay_event, self.swap_clients[id_bidder])
swap_clients = self.swap_clients
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
ci_from = swap_clients[id_offerer].ci(coin_from)
@ -389,7 +399,7 @@ class TestFunctions(BaseTest):
)
swap_clients[id_follower].ci(
coin_from if reverse_bid else coin_to
coin_to if reverse_bid else coin_from
)._altruistic = with_mercy
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
@ -409,20 +419,17 @@ class TestFunctions(BaseTest):
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(
test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED
test_delay_event,
swap_clients[id_offerer],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
debug_type = (
DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2
if with_mercy
else DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND
)
swap_clients[id_leader].setBidDebugInd(bid_id, debug_type)
debug_type = (
DebugTypes.BID_DONT_SPEND_COIN_B_LOCK
if with_mercy
else DebugTypes.BID_STOP_AFTER_COIN_A_LOCK
swap_clients[id_leader].setBidDebugInd(
bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2
)
debug_type = DebugTypes.BID_DONT_SPEND_COIN_B_LOCK
swap_clients[id_follower].setBidDebugInd(bid_id, debug_type)
swap_clients[id_leader].setBidDebugInd(
@ -439,7 +446,7 @@ class TestFunctions(BaseTest):
expect_state = (
(BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED, BidStates.SWAP_COMPLETED)
if with_mercy
else BidStates.BID_STALLED_FOR_TEST
else (BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED_SWIPED)
)
wait_for_bid(
test_delay_event,
@ -470,6 +477,19 @@ class TestFunctions(BaseTest):
wait_for_none_active(test_delay_event, 1800 + id_offerer)
wait_for_none_active(test_delay_event, 1800 + id_bidder)
if with_mercy is False:
# Test manually redeeming the no-script lock tx
offerer_key = read_json_api(
1800 + id_offerer,
"bids/{}".format(bid_id.hex()),
{"chainbkeysplit": True},
)["splitkey"]
data = {"spendchainblocktx": True, "remote_key": offerer_key}
redeemed_txid = read_json_api(
1800 + id_bidder, "bids/{}".format(bid_id.hex()), data
)["txid"]
assert len(redeemed_txid) == 64
def do_test_04_follower_recover_b_lock_tx(
self, coin_from, coin_to, lock_value: int = 32
):
@ -518,7 +538,11 @@ class TestFunctions(BaseTest):
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(
test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED
test_delay_event,
swap_clients[id_offerer],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[id_follower].setBidDebugInd(
@ -1144,8 +1168,8 @@ class BasicSwapTest(TestFunctions):
# fee_rate is in sats/kvB
fee_rate: int = 1000
a = ci.getNewSecretKey()
b = ci.getNewSecretKey()
a = ci.getNewRandomKey()
b = ci.getNewRandomKey()
A = ci.getPubkey(a)
B = ci.getPubkey(b)
@ -1214,8 +1238,8 @@ class BasicSwapTest(TestFunctions):
assert expect_vsize - vsize_actual < 10
# Test chain b (no-script) lock tx size
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
@ -1598,7 +1622,13 @@ class BasicSwapTest(TestFunctions):
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[2],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[2].acceptBid(bid_id)
wait_for_bid(
@ -1659,7 +1689,13 @@ class BasicSwapTest(TestFunctions):
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, amt_swap)
swap_clients[1].abandonBid(bid_id)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_ACCEPTED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_ACCEPTED,
wait_for=(self.extra_wait_time + 40),
)
try:
swap_clients[0].setMockTimeOffset(7200)
@ -1698,6 +1734,22 @@ class BasicSwapTest(TestFunctions):
amt_swap: int = ci_from.make_int(balance_from_before, r=1)
rate_swap: int = ci_to.make_int(2.0, r=1)
try:
offer_id = swap_clients[id_offerer].postOffer(
coin_from,
coin_to,
amt_swap,
rate_swap,
amt_swap,
SwapTypes.XMR_SWAP,
auto_accept_bids=True,
)
except Exception as e:
assert "Insufficient funds" in str(e)
else:
assert False, "Should fail"
amt_swap -= ci_from.make_int(1)
offer_id = swap_clients[id_offerer].postOffer(
coin_from,
coin_to,
@ -1709,26 +1761,32 @@ class BasicSwapTest(TestFunctions):
)
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
# First bid should work
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap)
event = wait_for_event(
test_delay_event,
swap_clients[id_offerer],
Concepts.BID,
bid_id,
event_type=EventLogTypes.ERROR,
wait_for=60,
)
assert "Insufficient funds" in event.event_msg
wait_for_bid(
test_delay_event,
swap_clients[id_offerer],
bid_id,
BidStates.BID_RECEIVED,
wait_for=20,
(BidStates.BID_ACCEPTED, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED),
wait_for=40,
)
# Should be out of funds for second bid (over remaining offer value causes a hard auto accept fail)
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap)
wait_for_bid(
test_delay_event,
swap_clients[id_offerer],
bid_id,
BidStates.BID_AACCEPT_FAIL,
wait_for=40,
)
try:
swap_clients[id_offerer].acceptBid(bid_id)
except Exception as e:
assert "Insufficient funds" in str(e)
else:
assert False, "Should fail"
def test_08_insufficient_funds_rev(self):
tla_from = self.test_coin_from.name
logging.info("---------- Test {} Insufficient Funds (reverse)".format(tla_from))
@ -1791,6 +1849,117 @@ class TestBTC(BasicSwapTest):
start_ltc_nodes = False
base_rpc_port = BTC_BASE_RPC_PORT
def test_003_api(self):
logging.info("---------- Test API")
help_output = read_json_api(1800, "help")
assert "getcoinseed" in help_output["commands"]
rv = read_json_api(1800, "getcoinseed")
assert rv["error"] == "No post data"
rv = read_json_api(1800, "getcoinseed", {"coin": "PART"})
assert "seed is set from the Basicswap mnemonic" in rv["error"]
rv = read_json_api(1800, "getcoinseed", {"coin": "BTC"})
assert (
rv["seed"]
== "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
)
assert rv["seed_id"] == "3da5c0af91879e8ce97d9a843874601c08688078"
assert rv["seed_id"] == rv["expected_seed_id"]
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_label": "test 1"},
)
assert isinstance(rv, dict)
assert rv["address"] == "ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F"
assert rv["label"] == "test 1"
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_label": "test 2"},
)
assert isinstance(rv, dict)
assert rv["address"] == "ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F"
assert rv["label"] == "test 2"
rv = read_json_api(
1800,
"identities/pPCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_label": "test 3"},
)
assert rv["error"] == "Invalid identity address"
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_note": "note 1"},
)
assert isinstance(rv, dict)
assert rv["address"] == "ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F"
assert rv["label"] == "test 2"
assert rv["note"] == "note 1"
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_automation_override": 1},
)
assert isinstance(rv, dict)
assert rv["automation_override"] == 1
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_visibility_override": "hide"},
)
assert isinstance(rv, dict)
assert rv["visibility_override"] == 1
rv = read_json_api(1800, "automationstrategies")
assert len(rv) == 2
rv = read_json_api(1800, "automationstrategies/1")
assert rv["label"] == "Accept All"
sx_addr = read_json_api(1800, "wallets/part/newstealthaddress")
assert (
callnoderpc(
0,
"getaddressinfo",
[
sx_addr,
],
)["isstealthaddress"]
is True
)
rv = read_json_api(1800, "wallets/part")
assert "locked_utxos" in rv
rv = read_json_api(
1800, "validateamount", {"coin": "part", "amount": 0.000000015}
)
assert "Mantissa too long" in rv["error"]
rv = read_json_api(
1800,
"validateamount",
{"coin": "part", "amount": 0.000000015, "method": "roundoff"},
)
assert rv == "0.00000002"
rv = read_json_api(
1800,
"validateamount",
{"coin": "part", "amount": 0.000000015, "method": "rounddown"},
)
assert rv == "0.00000001"
def test_009_wallet_encryption(self):
for coin in ("btc", "part", "xmr"):

View file

@ -185,8 +185,8 @@ class Test(unittest.TestCase):
coin_settings = {"rpcport": 0, "rpcauth": "none"}
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
vk_sign = ci.getNewSecretKey()
vk_encrypt = ci.getNewSecretKey()
vk_sign = ci.getNewRandomKey()
vk_encrypt = ci.getNewRandomKey()
pk_sign = ci.getPubkey(vk_sign)
pk_encrypt = ci.getPubkey(vk_encrypt)
@ -209,7 +209,7 @@ class Test(unittest.TestCase):
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
vk = ci.getNewSecretKey()
vk = ci.getNewRandomKey()
pk = ci.getPubkey(vk)
message = "test signing message"
@ -224,7 +224,7 @@ class Test(unittest.TestCase):
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
vk = ci.getNewSecretKey()
vk = ci.getNewRandomKey()
pk = ci.getPubkey(vk)
sig = ci.signCompact(vk, "test signing message")
assert len(sig) == 64
@ -239,7 +239,7 @@ class Test(unittest.TestCase):
coin_settings.update(REQUIRED_SETTINGS)
ci = BTCInterface(coin_settings, "regtest")
vk = ci.getNewSecretKey()
vk = ci.getNewRandomKey()
pk = ci.getPubkey(vk)
sig = ci.signRecoverable(vk, "test signing message")
assert len(sig) == 65
@ -264,7 +264,7 @@ class Test(unittest.TestCase):
ci = XMRInterface(coin_settings, "regtest")
key = ci.getNewSecretKey()
key = ci.getNewRandomKey()
proof = ci.proveDLEAG(key)
assert ci.verifyDLEAG(proof)

View file

@ -2,16 +2,13 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021-2023 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.
import json
import random
import logging
import unittest
from urllib import parse
from urllib.request import urlopen
from basicswap.basicswap import (
Coins,
@ -27,7 +24,6 @@ from basicswap.util import (
format_amount,
)
from tests.basicswap.util import (
post_json_req,
read_json_api,
)
from tests.basicswap.common import (
@ -61,9 +57,7 @@ class Test(BaseTest):
"subfee": False,
"type_to": "blind",
}
json_rv = json.loads(
post_json_req("http://127.0.0.1:1800/json/wallets/part/withdraw", post_json)
)
json_rv = read_json_api(1800, "wallets/part/withdraw", post_json)
assert len(json_rv["txid"]) == 64
logging.info("Waiting for blind balance")
@ -141,9 +135,9 @@ class Test(BaseTest):
# fee_rate is in sats/kvB
fee_rate: int = 1000
vkbv = ci.getNewSecretKey()
a = ci.getNewSecretKey()
b = ci.getNewSecretKey()
vkbv = ci.getNewRandomKey()
a = ci.getNewRandomKey()
b = ci.getNewRandomKey()
A = ci.getPubkey(a)
B = ci.getPubkey(b)
@ -210,8 +204,8 @@ class Test(BaseTest):
assert ci.rpc("sendrawtransaction", [lock_spend_tx.hex()]) == txid
# Test chain b (no-script) lock tx size
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
@ -388,7 +382,7 @@ class Test(BaseTest):
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
swap_clients[0].setBidDebugInd(
bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND
bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2
)
swap_clients[0].acceptXmrBid(bid_id)
@ -397,7 +391,7 @@ class Test(BaseTest):
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_STALLED_FOR_TEST,
(BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED_SWIPED),
wait_for=180,
)
wait_for_bid(
@ -422,21 +416,14 @@ class Test(BaseTest):
wait_for_none_active(test_delay_event, 1800)
wait_for_none_active(test_delay_event, 1801)
data = parse.urlencode({"chainbkeysplit": True}).encode()
offerer_key = json.loads(
urlopen(
"http://127.0.0.1:1800/json/bids/{}".format(bid_id.hex()), data=data
).read()
offerer_key = read_json_api(
1800, "bids/{}".format(bid_id.hex()), {"chainbkeysplit": True}
)["splitkey"]
data = parse.urlencode(
{"spendchainblocktx": True, "remote_key": offerer_key}
).encode()
redeemed_txid = json.loads(
urlopen(
"http://127.0.0.1:1801/json/bids/{}".format(bid_id.hex()), data=data
).read()
)["txid"]
data = {"spendchainblocktx": True, "remote_key": offerer_key}
redeemed_txid = read_json_api(1801, "bids/{}".format(bid_id.hex()), data)[
"txid"
]
assert len(redeemed_txid) == 64
def do_test_04_follower_recover_b_lock_tx(self, coin_from, coin_to):

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-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.
@ -161,115 +161,6 @@ class Test(BaseTest):
rv = read_json_api(1800, "rateslist?from=PART&to=BTC")
assert len(rv) == 1
def test_003_api(self):
logging.info("---------- Test API")
help_output = read_json_api(1800, "help")
assert "getcoinseed" in help_output["commands"]
rv = read_json_api(1800, "getcoinseed")
assert rv["error"] == "No post data"
rv = read_json_api(1800, "getcoinseed", {"coin": "PART"})
assert "seed is set from the Basicswap mnemonic" in rv["error"]
rv = read_json_api(1800, "getcoinseed", {"coin": "BTC"})
assert (
rv["seed"]
== "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
)
assert rv["seed_id"] == "3da5c0af91879e8ce97d9a843874601c08688078"
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_label": "test 1"},
)
assert len(rv) == 1
assert rv[0]["address"] == "ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F"
assert rv[0]["label"] == "test 1"
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_label": "test 2"},
)
assert len(rv) == 1
assert rv[0]["address"] == "ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F"
assert rv[0]["label"] == "test 2"
rv = read_json_api(
1800,
"identities/pPCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_label": "test 3"},
)
assert rv["error"] == "Invalid identity address"
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_note": "note 1"},
)
assert len(rv) == 1
assert rv[0]["address"] == "ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F"
assert rv[0]["label"] == "test 2"
assert rv[0]["note"] == "note 1"
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_automation_override": 1},
)
assert len(rv) == 1
assert rv[0]["automation_override"] == 1
rv = read_json_api(
1800,
"identities/ppCsRro5po7Yu6kyu5XjSyr3A1PPdk9j1F",
{"set_visibility_override": "hide"},
)
assert len(rv) == 1
assert rv[0]["visibility_override"] == 1
rv = read_json_api(1800, "automationstrategies")
assert len(rv) == 2
rv = read_json_api(1800, "automationstrategies/1")
assert rv["label"] == "Accept All"
sx_addr = read_json_api(1800, "wallets/part/newstealthaddress")
assert (
callnoderpc(
0,
"getaddressinfo",
[
sx_addr,
],
)["isstealthaddress"]
is True
)
rv = read_json_api(1800, "wallets/part")
assert "locked_utxos" in rv
rv = read_json_api(
1800, "validateamount", {"coin": "part", "amount": 0.000000015}
)
assert "Mantissa too long" in rv["error"]
rv = read_json_api(
1800,
"validateamount",
{"coin": "part", "amount": 0.000000015, "method": "roundoff"},
)
assert rv == "0.00000002"
rv = read_json_api(
1800,
"validateamount",
{"coin": "part", "amount": 0.000000015, "method": "rounddown"},
)
assert rv == "0.00000001"
def test_004_validateSwapType(self):
logging.info("---------- Test validateSwapType")

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-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.
@ -318,6 +318,7 @@ class BaseTest(unittest.TestCase):
xmr_daemons = []
xmr_wallet_auth = []
restore_instance = False
extra_wait_time = 0
start_ltc_nodes = False
start_xmr_nodes = True
@ -1077,8 +1078,8 @@ class Test(BaseTest):
# fee_rate is in sats/kvB
fee_rate: int = 1000
a = ci.getNewSecretKey()
b = ci.getNewSecretKey()
a = ci.getNewRandomKey()
b = ci.getNewRandomKey()
A = ci.getPubkey(a)
B = ci.getPubkey(b)
@ -1147,8 +1148,8 @@ class Test(BaseTest):
assert expect_vsize - vsize_actual < 10
# Test chain b (no-script) lock tx size
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
@ -1176,8 +1177,8 @@ class Test(BaseTest):
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
fee_rate: int = 1000 # TODO: How to set feerate for rpc functions?
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
@ -1332,7 +1333,13 @@ class Test(BaseTest):
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert xmr_swap
@ -1386,19 +1393,28 @@ class Test(BaseTest):
101 * COIN,
SwapTypes.XMR_SWAP,
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS,
lock_value=12,
lock_value=16,
)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert xmr_swap
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
swap_clients[1].setBidDebugInd(
bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2, False
)
swap_clients[0].acceptXmrBid(bid_id)
@ -1444,7 +1460,13 @@ class Test(BaseTest):
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert xmr_swap
@ -1505,7 +1527,13 @@ class Test(BaseTest):
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert xmr_swap
@ -1562,7 +1590,13 @@ class Test(BaseTest):
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert xmr_swap
@ -1597,6 +1631,7 @@ class Test(BaseTest):
def test_05_btc_xmr(self):
logging.info("---------- Test BTC to XMR")
swap_clients = self.swap_clients
offer_id = swap_clients[0].postOffer(
Coins.BTC,
Coins.XMR,
@ -1613,7 +1648,13 @@ class Test(BaseTest):
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert xmr_swap
@ -1637,6 +1678,75 @@ class Test(BaseTest):
swap_clients[1].ci(Coins.XMR).setFeePriority(0)
def test_05b_btc_xmr_withfee(self):
logging.info("---------- Test BTC to XMR")
swap_clients = self.swap_clients
self.prepare_balance(Coins.BTC, 100.0, 1801, 1800)
self.prepare_balance(Coins.XMR, 20.0, 1800, 1801)
js_w1_before = read_json_api(1801, "wallets")
ci1_btc = swap_clients[1].ci(Coins.BTC)
btc_total = ci1_btc.make_int(js_w1_before["BTC"]["balance"]) + ci1_btc.make_int(
js_w1_before["BTC"]["unconfirmed"]
)
try:
offer_id = swap_clients[1].postOffer(
Coins.BTC,
Coins.XMR,
btc_total,
0,
10 * COIN,
SwapTypes.XMR_SWAP,
extra_options={"amount_to": 10 * XMR_COIN},
)
except Exception as e:
assert "Insufficient funds" in str(e)
else:
assert False, "Should fail"
offer_id = swap_clients[1].postOffer(
Coins.BTC,
Coins.XMR,
btc_total - 1 * COIN,
0,
10 * COIN,
SwapTypes.XMR_SWAP,
extra_options={"amount_to": 10 * XMR_COIN},
)
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
offers = swap_clients[0].listOffers(filters={"offer_id": offer_id})
offer = offers[0]
swap_clients[0].ci(Coins.XMR).setFeePriority(3)
bid_id = swap_clients[0].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(
test_delay_event,
swap_clients[1],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[1].acceptXmrBid(bid_id)
wait_for_bid(
test_delay_event,
swap_clients[1],
bid_id,
BidStates.SWAP_COMPLETED,
wait_for=180,
)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.SWAP_COMPLETED,
sent=True,
)
def test_06_multiple_swaps(self):
logging.info("---------- Test Multiple concurrent swaps")
swap_clients = self.swap_clients
@ -1676,17 +1786,35 @@ class Test(BaseTest):
SwapTypes.XMR_SWAP,
)
wait_for_bid(test_delay_event, swap_clients[0], bid1_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid1_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[0].acceptXmrBid(bid1_id)
wait_for_offer(test_delay_event, swap_clients[1], offer3_id)
offer3 = swap_clients[1].getOffer(offer3_id)
bid3_id = swap_clients[1].postXmrBid(offer3_id, offer3.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid2_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid2_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[0].acceptXmrBid(bid2_id)
wait_for_bid(test_delay_event, swap_clients[0], bid3_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid3_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[0].acceptXmrBid(bid3_id)
wait_for_bid(
@ -1941,7 +2069,13 @@ class Test(BaseTest):
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert xmr_swap
@ -2048,7 +2182,13 @@ class Test(BaseTest):
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
assert xmr_swap
@ -2083,8 +2223,8 @@ class Test(BaseTest):
ci = swap_clients[1].ci(Coins.PART_ANON)
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
fee_rate: int = 1000
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
@ -2165,7 +2305,13 @@ class Test(BaseTest):
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[0].acceptXmrBid(bid_id)
@ -2207,7 +2353,13 @@ class Test(BaseTest):
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.SEND_LOCKED_XMR)
swap_clients[0].acceptXmrBid(bid_id)
@ -2326,7 +2478,13 @@ class Test(BaseTest):
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[2],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[2].acceptBid(bid_id)
wait_for_bid(
@ -2381,7 +2539,13 @@ class Test(BaseTest):
offer = swap_clients[1].getOffer(offer_id)
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.B_LOCK_TX_MISSED_SEND)
swap_clients[0].acceptXmrBid(bid_id)
@ -2426,7 +2590,13 @@ class Test(BaseTest):
bid_id = swap_clients[1].postXmrBid(offer_id, amt_swap)
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED)
wait_for_bid(
test_delay_event,
swap_clients[0],
bid_id,
BidStates.BID_RECEIVED,
wait_for=(self.extra_wait_time + 40),
)
swap_clients[0].acceptXmrBid(bid_id)
wait_for_bid(