mirror of
https://github.com/basicswap/basicswap.git
synced 2025-04-02 12:29:05 +00:00
Merge branch 'dev'
This commit is contained in:
commit
40f334ed0e
70 changed files with 6424 additions and 3568 deletions
.github
Dockerfilebasicswap
__init__.pybasicswap.py
bin
chainparams.pyconfig.pyinterface
js_server.pyprotocols
static
css
images/coins
Dogecoin-20.pngDogecoin.pngLitecoin%20MWEB.pngParticlAnon-20.pngParticlAnon.pngParticlBlind-20.pngParticlBlind.png
js
templates
footer.htmlindex.htmloffer.htmloffer_confirm.htmloffer_new_1.htmloffer_new_2.htmloffers.htmlsettings.htmlwallet.htmlwallets.html
ui
doc
docker
guix.scmpgp/keys
bitcoin_laanwj.pgpdash_pasta.pgpdogecoin_patricklodder.pgpdogecoin_xanimo.pgplitecoin_davidburkett38.pgp
requirements.inrequirements.txttests/basicswap
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal 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"
|
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
name = "basicswap"
|
||||
|
||||
__version__ = "0.14.2"
|
||||
__version__ = "0.14.3"
|
||||
|
|
|
@ -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
252
basicswap/bin/prepare.py
Normal file → Executable 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]
|
||||
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"))
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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())
|
||||
|
|
58
basicswap/interface/doge.py
Normal file
58
basicswap/interface/doge.py
Normal 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
|
|
@ -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):
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Before ![]() (image error) Size: 2.3 KiB After ![]() (image error) Size: 2.3 KiB ![]() ![]() |
Before ![]() (image error) Size: 16 KiB After ![]() (image error) Size: 16 KiB ![]() ![]() |
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
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
It’s free, completely trustless, and highly secure.</p>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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' %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -230,6 +230,7 @@ const COIN_SYMBOLS = {
|
|||
'Monero': 'monero',
|
||||
'Wownero': 'wownero',
|
||||
'Litecoin': 'litecoin',
|
||||
'Dogecoin': 'dogecoin',
|
||||
'Firo': 'zcoin',
|
||||
'Dash': 'dash',
|
||||
'PIVX': 'pivx',
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
version: '3.4'
|
||||
services:
|
||||
swapclient:
|
||||
image: i_swapclient
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
version: '3.4'
|
||||
services:
|
||||
|
||||
swapclient:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
version: '3.3'
|
||||
|
||||
networks:
|
||||
default:
|
||||
|
|
16
docker/production/compose-fragments/1_dogecoin.yml
Normal file
16
docker/production/compose-fragments/1_dogecoin.yml
Normal 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
|
25
docker/production/dogecoin/Dockerfile
Normal file
25
docker/production/dogecoin/Dockerfile
Normal 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"]
|
11
docker/production/dogecoin/entrypoint.sh
Executable file
11
docker/production/dogecoin/entrypoint.sh
Executable 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
|
|
@ -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
|
||||
|
|
6
guix.scm
6
guix.scm
|
@ -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
|
@ -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-----
|
||||
|
|
31
pgp/keys/dogecoin_patricklodder.pgp
Normal file
31
pgp/keys/dogecoin_patricklodder.pgp
Normal 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-----
|
41
pgp/keys/dogecoin_xanimo.pgp
Normal file
41
pgp/keys/dogecoin_xanimo.pgp
Normal file
|
@ -0,0 +1,41 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
tS35OCTSJzNMoXtTZqCkDLc9
|
||||
=Z8rt
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -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-----
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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.")
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
500
tests/basicswap/extended/test_doge.py
Normal file
500
tests/basicswap/extended/test_doge.py
Normal 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()
|
132
tests/basicswap/extended/test_doge_with_prepare.py
Normal file
132
tests/basicswap/extended/test_doge_with_prepare.py
Normal 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()
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue