mirror of
https://github.com/basicswap/basicswap.git
synced 2026-04-19 14:38:43 +02:00
Merge branch 'dev'
This commit is contained in:
@@ -30,6 +30,9 @@ jobs:
|
|||||||
- name: Install
|
- name: Install
|
||||||
run: |
|
run: |
|
||||||
pip install .
|
pip install .
|
||||||
|
# Print the core versions to a file for caching
|
||||||
|
basicswap-prepare --version --withcoins=bitcoin | tail -n +2 > core_versions.txt
|
||||||
|
cat core_versions.txt
|
||||||
- name: Running flake8
|
- name: Running flake8
|
||||||
run: |
|
run: |
|
||||||
flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
|
flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
|
||||||
@@ -44,21 +47,20 @@ jobs:
|
|||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
env:
|
env:
|
||||||
cache-name: cache-cores
|
cache-name: cache-cores
|
||||||
CACHE_KEY: $(printf $(python bin/basicswap-prepare.py --version --withcoins=bitcoin) | sha256sum | head -c 64)
|
|
||||||
with:
|
with:
|
||||||
path: $BIN_DIR
|
path: /tmp/cached_bin
|
||||||
key: $CACHE_KEY
|
key: cores-${{ runner.os }}-${{ hashFiles('**/core_versions.txt') }}
|
||||||
|
|
||||||
- if: ${{ steps.cache-yarn.outputs.cache-hit != 'true' }}
|
- if: ${{ steps.cache-cores.outputs.cache-hit != 'true' }}
|
||||||
name: Running basicswap-prepare
|
name: Running basicswap-prepare
|
||||||
run: |
|
run: |
|
||||||
basicswap-prepare --bindir="$BIN_DIR" --preparebinonly --withcoins=particl,bitcoin,monero
|
basicswap-prepare --bindir="$BIN_DIR" --preparebinonly --withcoins=particl,bitcoin,monero
|
||||||
- name: Running test_xmr
|
- name: Running test_xmr
|
||||||
run: |
|
run: |
|
||||||
export PYTHONPATH=$(pwd)
|
export PYTHONPATH=$(pwd)
|
||||||
export PARTICL_BINDIR="$BIN_DIR/particl";
|
export PARTICL_BINDIR="$BIN_DIR/particl"
|
||||||
export BITCOIN_BINDIR="$BIN_DIR/bitcoin";
|
export BITCOIN_BINDIR="$BIN_DIR/bitcoin"
|
||||||
export XMR_BINDIR="$BIN_DIR/monero";
|
export XMR_BINDIR="$BIN_DIR/monero"
|
||||||
pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx"
|
pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx"
|
||||||
- name: Running test_encrypted_xmr_reload
|
- name: Running test_encrypted_xmr_reload
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
+80
-51
@@ -598,6 +598,13 @@ class BasicSwap(BaseApp):
|
|||||||
"chain_median_time": None,
|
"chain_median_time": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Passthrough settings
|
||||||
|
for setting_name in ("wallet_name", "mweb_wallet_name"):
|
||||||
|
if setting_name in chain_client_settings:
|
||||||
|
self.coin_clients[coin][setting_name] = chain_client_settings[
|
||||||
|
setting_name
|
||||||
|
]
|
||||||
|
|
||||||
if coin in (Coins.FIRO, Coins.LTC):
|
if coin in (Coins.FIRO, Coins.LTC):
|
||||||
if not chain_client_settings.get("min_relay_fee"):
|
if not chain_client_settings.get("min_relay_fee"):
|
||||||
chain_client_settings["min_relay_fee"] = 0.00001
|
chain_client_settings["min_relay_fee"] = 0.00001
|
||||||
@@ -842,17 +849,11 @@ class BasicSwap(BaseApp):
|
|||||||
elif coin == Coins.XMR:
|
elif coin == Coins.XMR:
|
||||||
from .interface.xmr import XMRInterface
|
from .interface.xmr import XMRInterface
|
||||||
|
|
||||||
xmr_i = XMRInterface(self.coin_clients[coin], self.chain, self)
|
return XMRInterface(self.coin_clients[coin], self.chain, self)
|
||||||
chain_client_settings = self.getChainClientSettings(coin)
|
|
||||||
xmr_i.setWalletFilename(chain_client_settings["walletfile"])
|
|
||||||
return xmr_i
|
|
||||||
elif coin == Coins.WOW:
|
elif coin == Coins.WOW:
|
||||||
from .interface.wow import WOWInterface
|
from .interface.wow import WOWInterface
|
||||||
|
|
||||||
wow_i = WOWInterface(self.coin_clients[coin], self.chain, self)
|
return WOWInterface(self.coin_clients[coin], self.chain, self)
|
||||||
chain_client_settings = self.getChainClientSettings(coin)
|
|
||||||
wow_i.setWalletFilename(chain_client_settings["walletfile"])
|
|
||||||
return wow_i
|
|
||||||
elif coin == Coins.PIVX:
|
elif coin == Coins.PIVX:
|
||||||
from .interface.pivx import PIVXInterface
|
from .interface.pivx import PIVXInterface
|
||||||
|
|
||||||
@@ -1974,6 +1975,32 @@ class BasicSwap(BaseApp):
|
|||||||
if not offer.rate_negotiable:
|
if not offer.rate_negotiable:
|
||||||
ensure(offer.rate == bid_rate, "Bid rate must match offer rate.")
|
ensure(offer.rate == bid_rate, "Bid rate must match offer rate.")
|
||||||
|
|
||||||
|
def ensureWalletCanSend(
|
||||||
|
self, ci, swap_type, ensure_balance: int, estimated_fee: int, for_offer=True
|
||||||
|
) -> None:
|
||||||
|
balance_msg: str = (
|
||||||
|
f"{ci.format_amount(ensure_balance)} {ci.coin_name()} with estimated fee {ci.format_amount(estimated_fee)}"
|
||||||
|
)
|
||||||
|
self.log.debug(f"Ensuring wallet can send {balance_msg}.")
|
||||||
|
try:
|
||||||
|
if ci.interface_type() in self.scriptless_coins:
|
||||||
|
ci.ensureFunds(ensure_balance + estimated_fee)
|
||||||
|
else:
|
||||||
|
pi = self.pi(swap_type)
|
||||||
|
_ = pi.getFundedInitiateTxTemplate(ci, ensure_balance, False)
|
||||||
|
# TODO: Save the prefunded tx so the fee can't change, complicates multiple offers at the same time.
|
||||||
|
except Exception as e:
|
||||||
|
type_str = "offer" if for_offer else "bid"
|
||||||
|
err_msg = f"Insufficient funds for {type_str} of {balance_msg}."
|
||||||
|
if self.debug:
|
||||||
|
self.log.error(f"ensureWalletCanSend failed {e}")
|
||||||
|
current_balance: int = ci.getSpendableBalance()
|
||||||
|
err_msg += (
|
||||||
|
f" Debug: Spendable balance: {ci.format_amount(current_balance)}."
|
||||||
|
)
|
||||||
|
self.log.error(err_msg)
|
||||||
|
raise ValueError(err_msg)
|
||||||
|
|
||||||
def getOfferAddressTo(self, extra_options) -> str:
|
def getOfferAddressTo(self, extra_options) -> str:
|
||||||
if "addr_send_to" in extra_options:
|
if "addr_send_to" in extra_options:
|
||||||
return extra_options["addr_send_to"]
|
return extra_options["addr_send_to"]
|
||||||
@@ -2007,25 +2034,25 @@ class BasicSwap(BaseApp):
|
|||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError("Unknown coin to type")
|
raise ValueError("Unknown coin to type")
|
||||||
|
|
||||||
valid_for_seconds: int = extra_options.get("valid_for_seconds", 60 * 60)
|
|
||||||
amount_to: int = extra_options.get(
|
|
||||||
"amount_to", int((amount * rate) // ci_from.COIN())
|
|
||||||
)
|
|
||||||
|
|
||||||
# Recalculate the rate so it will match the bid rate
|
|
||||||
rate = ci_from.make_int(amount_to / amount, r=1)
|
|
||||||
|
|
||||||
self.validateSwapType(coin_from_t, coin_to_t, swap_type)
|
self.validateSwapType(coin_from_t, coin_to_t, swap_type)
|
||||||
self.validateOfferAmounts(
|
|
||||||
coin_from_t, coin_to_t, amount, amount_to, min_bid_amount
|
|
||||||
)
|
|
||||||
self.validateOfferLockValue(
|
self.validateOfferLockValue(
|
||||||
swap_type, coin_from_t, coin_to_t, lock_type, lock_value
|
swap_type, coin_from_t, coin_to_t, lock_type, lock_value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
valid_for_seconds: int = extra_options.get("valid_for_seconds", 60 * 60)
|
||||||
self.validateOfferValidTime(
|
self.validateOfferValidTime(
|
||||||
swap_type, coin_from_t, coin_to_t, valid_for_seconds
|
swap_type, coin_from_t, coin_to_t, valid_for_seconds
|
||||||
)
|
)
|
||||||
|
|
||||||
|
amount_to: int = extra_options.get(
|
||||||
|
"amount_to", int((amount * rate) // ci_from.COIN())
|
||||||
|
)
|
||||||
|
self.validateOfferAmounts(
|
||||||
|
coin_from_t, coin_to_t, amount, amount_to, min_bid_amount
|
||||||
|
)
|
||||||
|
# Recalculate the rate so it will match the bid rate
|
||||||
|
rate: int = ci_from.make_int(amount_to / amount, r=1)
|
||||||
|
|
||||||
offer_addr_to = self.getOfferAddressTo(extra_options)
|
offer_addr_to = self.getOfferAddressTo(extra_options)
|
||||||
|
|
||||||
reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to)
|
reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to)
|
||||||
@@ -2066,8 +2093,8 @@ class BasicSwap(BaseApp):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if "from_fee_override" in extra_options:
|
if "from_fee_override" in extra_options:
|
||||||
msg_buf.fee_rate_from = make_int(
|
msg_buf.fee_rate_from = ci_from.make_int(
|
||||||
extra_options["from_fee_override"], self.ci(coin_from).exp()
|
extra_options["from_fee_override"]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# TODO: conf_target = ci_from.settings.get('conf_target', 2)
|
# TODO: conf_target = ci_from.settings.get('conf_target', 2)
|
||||||
@@ -2077,12 +2104,10 @@ class BasicSwap(BaseApp):
|
|||||||
fee_rate, fee_src = self.getFeeRateForCoin(coin_from, conf_target)
|
fee_rate, fee_src = self.getFeeRateForCoin(coin_from, conf_target)
|
||||||
if "from_fee_multiplier_percent" in extra_options:
|
if "from_fee_multiplier_percent" in extra_options:
|
||||||
fee_rate *= extra_options["fee_multiplier"] / 100.0
|
fee_rate *= extra_options["fee_multiplier"] / 100.0
|
||||||
msg_buf.fee_rate_from = make_int(fee_rate, self.ci(coin_from).exp())
|
msg_buf.fee_rate_from = ci_from.make_int(fee_rate)
|
||||||
|
|
||||||
if "to_fee_override" in extra_options:
|
if "to_fee_override" in extra_options:
|
||||||
msg_buf.fee_rate_to = make_int(
|
msg_buf.fee_rate_to = ci_to.make_int(extra_options["to_fee_override"])
|
||||||
extra_options["to_fee_override"], self.ci(coin_to).exp()
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# TODO: conf_target = ci_to.settings.get('conf_target', 2)
|
# TODO: conf_target = ci_to.settings.get('conf_target', 2)
|
||||||
conf_target = 2
|
conf_target = 2
|
||||||
@@ -2091,7 +2116,7 @@ class BasicSwap(BaseApp):
|
|||||||
fee_rate, fee_src = self.getFeeRateForCoin(coin_to, conf_target)
|
fee_rate, fee_src = self.getFeeRateForCoin(coin_to, conf_target)
|
||||||
if "to_fee_multiplier_percent" in extra_options:
|
if "to_fee_multiplier_percent" in extra_options:
|
||||||
fee_rate *= extra_options["fee_multiplier"] / 100.0
|
fee_rate *= extra_options["fee_multiplier"] / 100.0
|
||||||
msg_buf.fee_rate_to = make_int(fee_rate, self.ci(coin_to).exp())
|
msg_buf.fee_rate_to = ci_to.make_int(fee_rate)
|
||||||
|
|
||||||
if swap_type == SwapTypes.XMR_SWAP:
|
if swap_type == SwapTypes.XMR_SWAP:
|
||||||
xmr_offer = XmrOffer()
|
xmr_offer = XmrOffer()
|
||||||
@@ -2116,27 +2141,19 @@ class BasicSwap(BaseApp):
|
|||||||
msg_buf.fee_rate_to
|
msg_buf.fee_rate_to
|
||||||
) # Unused: TODO - Set priority?
|
) # Unused: TODO - Set priority?
|
||||||
|
|
||||||
ensure_balance: int = int(amount)
|
# If a prefunded txn is not used, check that the wallet balance can cover the tx fee.
|
||||||
if coin_from in self.scriptless_coins:
|
if "prefunded_itx" not in extra_options:
|
||||||
# TODO: Better tx size estimate, xmr_swap_b_lock_tx_vsize could be larger than xmr_swap_b_lock_spend_tx_vsize
|
# 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 = (
|
estimated_fee: int = (
|
||||||
msg_buf.fee_rate_from
|
msg_buf.fee_rate_from * ci_from.est_lock_tx_vsize() // 1000
|
||||||
* ci_from.xmr_swap_b_lock_spend_tx_vsize()
|
|
||||||
/ 1000
|
|
||||||
)
|
)
|
||||||
ci_from.ensureFunds(msg_buf.amount_from + estimated_fee)
|
self.ensureWalletCanSend(ci_from, swap_type, int(amount), 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
|
# TODO: Send proof of funds with offer
|
||||||
# proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr)
|
# proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr)
|
||||||
# proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(
|
# proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(
|
||||||
# coin_from_t, ensure_balance, proof_of_funds_hash
|
# coin_from_t, ensure_balance, proof_of_funds_hash
|
||||||
# )
|
# )
|
||||||
|
|
||||||
offer_bytes = msg_buf.to_bytes()
|
offer_bytes = msg_buf.to_bytes()
|
||||||
payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex()
|
payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex()
|
||||||
@@ -2174,6 +2191,8 @@ class BasicSwap(BaseApp):
|
|||||||
was_sent=True,
|
was_sent=True,
|
||||||
bid_reversed=bid_reversed,
|
bid_reversed=bid_reversed,
|
||||||
security_token=security_token,
|
security_token=security_token,
|
||||||
|
from_feerate=msg_buf.fee_rate_from,
|
||||||
|
to_feerate=msg_buf.fee_rate_to,
|
||||||
)
|
)
|
||||||
offer.setState(OfferStates.OFFER_SENT)
|
offer.setState(OfferStates.OFFER_SENT)
|
||||||
|
|
||||||
@@ -3525,14 +3544,12 @@ class BasicSwap(BaseApp):
|
|||||||
|
|
||||||
self.checkCoinsReady(coin_from, coin_to)
|
self.checkCoinsReady(coin_from, coin_to)
|
||||||
|
|
||||||
balance_to: int = ci_to.getSpendableBalance()
|
# TODO: Better tx size estimate
|
||||||
ensure(
|
fee_rate, fee_src = self.getFeeRateForCoin(coin_to, conf_target=2)
|
||||||
balance_to > amount_to,
|
fee_rate_to = ci_to.make_int(fee_rate)
|
||||||
"{} spendable balance is too low: {} < {}".format(
|
estimated_fee: int = fee_rate_to * ci_to.est_lock_tx_vsize() // 1000
|
||||||
ci_to.coin_name(),
|
self.ensureWalletCanSend(
|
||||||
ci_to.format_amount(balance_to),
|
ci_to, offer.swap_type, int(amount_to), estimated_fee, for_offer=False
|
||||||
ci_to.format_amount(amount_to),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to)
|
reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to)
|
||||||
@@ -4069,6 +4086,18 @@ class BasicSwap(BaseApp):
|
|||||||
ci_from = self.ci(coin_from)
|
ci_from = self.ci(coin_from)
|
||||||
ci_to = self.ci(coin_to)
|
ci_to = self.ci(coin_to)
|
||||||
|
|
||||||
|
# TODO: Better tx size estimate
|
||||||
|
fee_rate, fee_src = self.getFeeRateForCoin(coin_to, conf_target=2)
|
||||||
|
fee_rate_from = ci_to.make_int(fee_rate)
|
||||||
|
estimated_fee: int = fee_rate_from * ci_to.est_lock_tx_vsize() // 1000
|
||||||
|
self.ensureWalletCanSend(
|
||||||
|
ci_to,
|
||||||
|
offer.swap_type,
|
||||||
|
offer.amount_from,
|
||||||
|
estimated_fee,
|
||||||
|
for_offer=False,
|
||||||
|
)
|
||||||
|
|
||||||
if xmr_swap.contract_count is None:
|
if xmr_swap.contract_count is None:
|
||||||
xmr_swap.contract_count = self.getNewContractId(use_cursor)
|
xmr_swap.contract_count = self.getNewContractId(use_cursor)
|
||||||
|
|
||||||
|
|||||||
+72
-18
@@ -33,7 +33,7 @@ import basicswap.config as cfg
|
|||||||
from basicswap import __version__
|
from basicswap import __version__
|
||||||
from basicswap.base import getaddrinfo_tor
|
from basicswap.base import getaddrinfo_tor
|
||||||
from basicswap.basicswap import BasicSwap
|
from basicswap.basicswap import BasicSwap
|
||||||
from basicswap.chainparams import Coins
|
from basicswap.chainparams import Coins, chainparams, getCoinIdFromName
|
||||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||||
from basicswap.ui.util import getCoinName
|
from basicswap.ui.util import getCoinName
|
||||||
from basicswap.util import toBool
|
from basicswap.util import toBool
|
||||||
@@ -162,6 +162,7 @@ LOG_LEVEL = logging.DEBUG
|
|||||||
logger.level = LOG_LEVEL
|
logger.level = LOG_LEVEL
|
||||||
if not len(logger.handlers):
|
if not len(logger.handlers):
|
||||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||||
|
logging.getLogger("gnupg").setLevel(logging.INFO)
|
||||||
|
|
||||||
BSX_DOCKER_MODE = toBool(os.getenv("BSX_DOCKER_MODE", "false"))
|
BSX_DOCKER_MODE = toBool(os.getenv("BSX_DOCKER_MODE", "false"))
|
||||||
BSX_LOCAL_TOR = toBool(os.getenv("BSX_LOCAL_TOR", "false"))
|
BSX_LOCAL_TOR = toBool(os.getenv("BSX_LOCAL_TOR", "false"))
|
||||||
@@ -210,6 +211,7 @@ LTC_RPC_PWD = os.getenv("LTC_RPC_PWD", "")
|
|||||||
|
|
||||||
BTC_RPC_HOST = os.getenv("BTC_RPC_HOST", "127.0.0.1")
|
BTC_RPC_HOST = os.getenv("BTC_RPC_HOST", "127.0.0.1")
|
||||||
BTC_RPC_PORT = int(os.getenv("BTC_RPC_PORT", 19996))
|
BTC_RPC_PORT = int(os.getenv("BTC_RPC_PORT", 19996))
|
||||||
|
BTC_PORT = int(os.getenv("BTC_PORT", 8333))
|
||||||
BTC_ONION_PORT = int(os.getenv("BTC_ONION_PORT", 8334))
|
BTC_ONION_PORT = int(os.getenv("BTC_ONION_PORT", 8334))
|
||||||
BTC_RPC_USER = os.getenv("BTC_RPC_USER", "")
|
BTC_RPC_USER = os.getenv("BTC_RPC_USER", "")
|
||||||
BTC_RPC_PWD = os.getenv("BTC_RPC_PWD", "")
|
BTC_RPC_PWD = os.getenv("BTC_RPC_PWD", "")
|
||||||
@@ -253,8 +255,8 @@ NAV_RPC_PWD = os.getenv("NAV_RPC_PWD", "")
|
|||||||
|
|
||||||
BCH_RPC_HOST = os.getenv("BCH_RPC_HOST", "127.0.0.1")
|
BCH_RPC_HOST = os.getenv("BCH_RPC_HOST", "127.0.0.1")
|
||||||
BCH_RPC_PORT = int(os.getenv("BCH_RPC_PORT", 19997))
|
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_PORT = int(os.getenv("BCH_PORT", 19798))
|
||||||
|
BCH_ONION_PORT = int(os.getenv("BCH_ONION_PORT", 8335))
|
||||||
BCH_RPC_USER = os.getenv("BCH_RPC_USER", "")
|
BCH_RPC_USER = os.getenv("BCH_RPC_USER", "")
|
||||||
BCH_RPC_PWD = os.getenv("BCH_RPC_PWD", "")
|
BCH_RPC_PWD = os.getenv("BCH_RPC_PWD", "")
|
||||||
|
|
||||||
@@ -288,10 +290,14 @@ TEST_ONION_LINK = toBool(os.getenv("TEST_ONION_LINK", "false"))
|
|||||||
|
|
||||||
BITCOIN_FASTSYNC_URL = os.getenv(
|
BITCOIN_FASTSYNC_URL = os.getenv(
|
||||||
"BITCOIN_FASTSYNC_URL",
|
"BITCOIN_FASTSYNC_URL",
|
||||||
"https://eu2.contabostorage.com/1f50a74c9dc14888a8664415dad3d020:utxosets/",
|
"https://snapshots.btcpay.tech/",
|
||||||
)
|
)
|
||||||
BITCOIN_FASTSYNC_FILE = os.getenv(
|
BITCOIN_FASTSYNC_FILE = os.getenv(
|
||||||
"BITCOIN_FASTSYNC_FILE", "utxo-snapshot-bitcoin-mainnet-820852.tar"
|
"BITCOIN_FASTSYNC_FILE", "utxo-snapshot-bitcoin-mainnet-867690.tar"
|
||||||
|
)
|
||||||
|
BITCOIN_FASTSYNC_SIG_URL = os.getenv(
|
||||||
|
"BITCOIN_FASTSYNC_SIG_URL",
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Encrypt new wallets with this password, must match the Particl wallet password when adding coins
|
# Encrypt new wallets with this password, must match the Particl wallet password when adding coins
|
||||||
@@ -357,6 +363,18 @@ def shouldManageDaemon(prefix: str) -> bool:
|
|||||||
return toBool(manage_daemon)
|
return toBool(manage_daemon)
|
||||||
|
|
||||||
|
|
||||||
|
def getWalletName(coin_params: str, default_name: str, prefix_override=None) -> str:
|
||||||
|
prefix: str = coin_params["ticker"] if prefix_override is None else prefix_override
|
||||||
|
env_var_name: str = prefix + "_WALLET_NAME"
|
||||||
|
|
||||||
|
if env_var_name in os.environ and coin_params.get("has_multiwallet", True) is False:
|
||||||
|
raise ValueError("Can't set wallet name for {}.".format(coin_params["ticker"]))
|
||||||
|
|
||||||
|
wallet_name: str = os.getenv(env_var_name, default_name)
|
||||||
|
assert len(wallet_name) > 0
|
||||||
|
return wallet_name
|
||||||
|
|
||||||
|
|
||||||
def getKnownVersion(coin_name: str) -> str:
|
def getKnownVersion(coin_name: str) -> str:
|
||||||
version, version_tag, _ = known_coins[coin_name]
|
version, version_tag, _ = known_coins[coin_name]
|
||||||
return version + version_tag
|
return version + version_tag
|
||||||
@@ -539,6 +557,8 @@ def ensureValidSignatureBy(result, signing_key_name):
|
|||||||
if result.key_id not in expected_key_ids[signing_key_name]:
|
if result.key_id not in expected_key_ids[signing_key_name]:
|
||||||
raise ValueError("Signature made by unexpected keyid: " + result.key_id)
|
raise ValueError("Signature made by unexpected keyid: " + result.key_id)
|
||||||
|
|
||||||
|
logger.debug(f"Found valid signature by {signing_key_name} ({result.key_id}).")
|
||||||
|
|
||||||
|
|
||||||
def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts={}):
|
def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts={}):
|
||||||
version, version_tag, signers = version_data
|
version, version_tag, signers = version_data
|
||||||
@@ -1113,6 +1133,8 @@ def writeTorSettings(fp, coin, coin_settings, tor_control_password):
|
|||||||
|
|
||||||
def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
||||||
core_settings = settings["chainclients"][coin]
|
core_settings = settings["chainclients"][coin]
|
||||||
|
wallet_name = core_settings.get("wallet_name", "wallet.dat")
|
||||||
|
assert len(wallet_name) > 0
|
||||||
data_dir = core_settings["datadir"]
|
data_dir = core_settings["datadir"]
|
||||||
tor_control_password = extra_opts.get("tor_control_password", None)
|
tor_control_password = extra_opts.get("tor_control_password", None)
|
||||||
|
|
||||||
@@ -1293,7 +1315,7 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
fp.write("rpcport={}\n".format(core_settings["rpcport"]))
|
fp.write("rpcport={}\n".format(core_settings["rpcport"]))
|
||||||
fp.write("printtoconsole=0\n")
|
fp.write("printtoconsole=0\n")
|
||||||
fp.write("daemon=0\n")
|
fp.write("daemon=0\n")
|
||||||
fp.write("wallet=wallet.dat\n")
|
fp.write(f"wallet={wallet_name}\n")
|
||||||
|
|
||||||
if tor_control_password is not None:
|
if tor_control_password is not None:
|
||||||
writeTorSettings(fp, coin, core_settings, tor_control_password)
|
writeTorSettings(fp, coin, core_settings, tor_control_password)
|
||||||
@@ -1416,7 +1438,7 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|||||||
|
|
||||||
# Double check
|
# Double check
|
||||||
if extra_opts.get("check_btc_fastsync", True):
|
if extra_opts.get("check_btc_fastsync", True):
|
||||||
check_btc_fastsync_data(base_dir, sync_file_path)
|
check_btc_fastsync_data(base_dir, BITCOIN_FASTSYNC_FILE)
|
||||||
|
|
||||||
with tarfile.open(sync_file_path) as ft:
|
with tarfile.open(sync_file_path) as ft:
|
||||||
ft.extractall(path=data_dir)
|
ft.extractall(path=data_dir)
|
||||||
@@ -1763,6 +1785,7 @@ def initialise_wallets(
|
|||||||
] + [c for c in with_coins if c != "particl"]
|
] + [c for c in with_coins if c != "particl"]
|
||||||
for coin_name in start_daemons:
|
for coin_name in start_daemons:
|
||||||
coin_settings = settings["chainclients"][coin_name]
|
coin_settings = settings["chainclients"][coin_name]
|
||||||
|
wallet_name = coin_settings.get("wallet_name", "wallet.dat")
|
||||||
c = swap_client.getCoinIdFromName(coin_name)
|
c = swap_client.getCoinIdFromName(coin_name)
|
||||||
|
|
||||||
if c == Coins.XMR:
|
if c == Coins.XMR:
|
||||||
@@ -1854,9 +1877,9 @@ def initialise_wallets(
|
|||||||
swap_client.waitForDaemonRPC(c, with_wallet=False)
|
swap_client.waitForDaemonRPC(c, with_wallet=False)
|
||||||
# Create wallet if it doesn't exist yet
|
# Create wallet if it doesn't exist yet
|
||||||
wallets = swap_client.callcoinrpc(c, "listwallets")
|
wallets = swap_client.callcoinrpc(c, "listwallets")
|
||||||
if len(wallets) < 1:
|
if wallet_name not in wallets:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Creating wallet.dat for {}.".format(getCoinName(c))
|
f'Creating wallet "{wallet_name}" for {getCoinName(c)}.'
|
||||||
)
|
)
|
||||||
|
|
||||||
if c in (Coins.BTC, Coins.LTC, Coins.DOGE, Coins.DASH):
|
if c in (Coins.BTC, Coins.LTC, Coins.DOGE, Coins.DASH):
|
||||||
@@ -1865,7 +1888,7 @@ def initialise_wallets(
|
|||||||
c,
|
c,
|
||||||
"createwallet",
|
"createwallet",
|
||||||
[
|
[
|
||||||
"wallet.dat",
|
wallet_name,
|
||||||
False,
|
False,
|
||||||
True,
|
True,
|
||||||
WALLET_ENCRYPTION_PWD,
|
WALLET_ENCRYPTION_PWD,
|
||||||
@@ -1875,7 +1898,13 @@ def initialise_wallets(
|
|||||||
)
|
)
|
||||||
swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD)
|
swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD)
|
||||||
else:
|
else:
|
||||||
swap_client.callcoinrpc(c, "createwallet", ["wallet.dat"])
|
swap_client.callcoinrpc(
|
||||||
|
c,
|
||||||
|
"createwallet",
|
||||||
|
[
|
||||||
|
wallet_name,
|
||||||
|
],
|
||||||
|
)
|
||||||
if WALLET_ENCRYPTION_PWD != "":
|
if WALLET_ENCRYPTION_PWD != "":
|
||||||
encrypt_wallet(swap_client, c)
|
encrypt_wallet(swap_client, c)
|
||||||
|
|
||||||
@@ -1961,25 +1990,32 @@ def load_config(config_path):
|
|||||||
|
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
logger.info("Signal %d detected" % (sig))
|
logger.info(f"Signal {sig} detected")
|
||||||
|
|
||||||
|
|
||||||
def check_btc_fastsync_data(base_dir, sync_file_path):
|
def check_btc_fastsync_data(base_dir, sync_filename):
|
||||||
|
logger.info("Validating signature for: " + sync_filename)
|
||||||
|
|
||||||
github_pgp_url = "https://raw.githubusercontent.com/basicswap/basicswap/master/pgp"
|
github_pgp_url = "https://raw.githubusercontent.com/basicswap/basicswap/master/pgp"
|
||||||
gitlab_pgp_url = "https://gitlab.com/particl/basicswap/-/raw/master/pgp"
|
gitlab_pgp_url = "https://gitlab.com/particl/basicswap/-/raw/master/pgp"
|
||||||
asc_filename = BITCOIN_FASTSYNC_FILE + ".asc"
|
asc_filename = sync_filename + ".asc"
|
||||||
asc_file_path = os.path.join(base_dir, asc_filename)
|
asc_file_path = os.path.join(base_dir, asc_filename)
|
||||||
|
sync_file_path = os.path.join(base_dir, sync_filename)
|
||||||
if not os.path.exists(asc_file_path):
|
if not os.path.exists(asc_file_path):
|
||||||
asc_file_urls = (
|
asc_file_urls = [
|
||||||
github_pgp_url + "/sigs/" + asc_filename,
|
github_pgp_url + "/sigs/" + asc_filename,
|
||||||
gitlab_pgp_url + "/sigs/" + asc_filename,
|
gitlab_pgp_url + "/sigs/" + asc_filename,
|
||||||
)
|
]
|
||||||
|
if BITCOIN_FASTSYNC_SIG_URL:
|
||||||
|
asc_file_urls.append("/".join([BITCOIN_FASTSYNC_SIG_URL, asc_filename]))
|
||||||
for url in asc_file_urls:
|
for url in asc_file_urls:
|
||||||
try:
|
try:
|
||||||
downloadFile(url, asc_file_path)
|
downloadFile(url, asc_file_path)
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning("Download failed: %s", str(e))
|
logging.warning("Download failed: %s", str(e))
|
||||||
|
if not os.path.exists(asc_file_path):
|
||||||
|
raise ValueError("Unable to find snapshot signature file.")
|
||||||
gpg = gnupg.GPG()
|
gpg = gnupg.GPG()
|
||||||
pubkey_filename = "{}_{}.pgp".format("particl", "tecnovert")
|
pubkey_filename = "{}_{}.pgp".format("particl", "tecnovert")
|
||||||
pubkeyurls = [
|
pubkeyurls = [
|
||||||
@@ -2251,7 +2287,7 @@ def main():
|
|||||||
check_sig = True
|
check_sig = True
|
||||||
|
|
||||||
if check_sig:
|
if check_sig:
|
||||||
check_btc_fastsync_data(data_dir, sync_file_path)
|
check_btc_fastsync_data(data_dir, BITCOIN_FASTSYNC_FILE)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Failed to download BTC fastsync file: {e}\nRe-running the command should resume the download or try manually downloading from {sync_file_url}"
|
f"Failed to download BTC fastsync file: {e}\nRe-running the command should resume the download or try manually downloading from {sync_file_url}"
|
||||||
@@ -2282,6 +2318,7 @@ def main():
|
|||||||
"onionport": BTC_ONION_PORT + port_offset,
|
"onionport": BTC_ONION_PORT + port_offset,
|
||||||
"datadir": os.getenv("BTC_DATA_DIR", os.path.join(data_dir, "bitcoin")),
|
"datadir": os.getenv("BTC_DATA_DIR", os.path.join(data_dir, "bitcoin")),
|
||||||
"bindir": os.path.join(bin_dir, "bitcoin"),
|
"bindir": os.path.join(bin_dir, "bitcoin"),
|
||||||
|
"port": BTC_PORT + port_offset,
|
||||||
"use_segwit": True,
|
"use_segwit": True,
|
||||||
"blocks_confirmed": 1,
|
"blocks_confirmed": 1,
|
||||||
"conf_target": 2,
|
"conf_target": 2,
|
||||||
@@ -2384,7 +2421,6 @@ def main():
|
|||||||
"walletrpchost": XMR_WALLET_RPC_HOST,
|
"walletrpchost": XMR_WALLET_RPC_HOST,
|
||||||
"walletrpcuser": XMR_WALLET_RPC_USER,
|
"walletrpcuser": XMR_WALLET_RPC_USER,
|
||||||
"walletrpcpassword": XMR_WALLET_RPC_PWD,
|
"walletrpcpassword": XMR_WALLET_RPC_PWD,
|
||||||
"walletfile": "swap_wallet",
|
|
||||||
"datadir": os.getenv("XMR_DATA_DIR", os.path.join(data_dir, "monero")),
|
"datadir": os.getenv("XMR_DATA_DIR", os.path.join(data_dir, "monero")),
|
||||||
"bindir": os.path.join(bin_dir, "monero"),
|
"bindir": os.path.join(bin_dir, "monero"),
|
||||||
"restore_height": xmr_restore_height,
|
"restore_height": xmr_restore_height,
|
||||||
@@ -2471,7 +2507,6 @@ def main():
|
|||||||
"walletrpchost": WOW_WALLET_RPC_HOST,
|
"walletrpchost": WOW_WALLET_RPC_HOST,
|
||||||
"walletrpcuser": WOW_WALLET_RPC_USER,
|
"walletrpcuser": WOW_WALLET_RPC_USER,
|
||||||
"walletrpcpassword": WOW_WALLET_RPC_PWD,
|
"walletrpcpassword": WOW_WALLET_RPC_PWD,
|
||||||
"walletfile": "swap_wallet",
|
|
||||||
"datadir": os.getenv("WOW_DATA_DIR", os.path.join(data_dir, "wownero")),
|
"datadir": os.getenv("WOW_DATA_DIR", os.path.join(data_dir, "wownero")),
|
||||||
"bindir": os.path.join(bin_dir, "wownero"),
|
"bindir": os.path.join(bin_dir, "wownero"),
|
||||||
"restore_height": wow_restore_height,
|
"restore_height": wow_restore_height,
|
||||||
@@ -2484,6 +2519,25 @@ def main():
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for coin_name, coin_settings in chainclients.items():
|
||||||
|
coin_id = getCoinIdFromName(coin_name)
|
||||||
|
coin_params = chainparams[coin_id]
|
||||||
|
if coin_settings.get("core_type_group", "") == "xmr":
|
||||||
|
default_name = "swap_wallet"
|
||||||
|
else:
|
||||||
|
default_name = "wallet.dat"
|
||||||
|
|
||||||
|
if coin_name == "litecoin":
|
||||||
|
set_name: str = getWalletName(
|
||||||
|
coin_params, "mweb", prefix_override="LTC_MWEB"
|
||||||
|
)
|
||||||
|
if set_name != "mweb":
|
||||||
|
coin_settings["mweb_wallet_name"] = set_name
|
||||||
|
|
||||||
|
set_name: str = getWalletName(coin_params, default_name)
|
||||||
|
if set_name != default_name:
|
||||||
|
coin_settings["wallet_name"] = set_name
|
||||||
|
|
||||||
if PART_RPC_USER != "":
|
if PART_RPC_USER != "":
|
||||||
chainclients["particl"]["rpcuser"] = PART_RPC_USER
|
chainclients["particl"]["rpcuser"] = PART_RPC_USER
|
||||||
chainclients["particl"]["rpcpassword"] = PART_RPC_PWD
|
chainclients["particl"]["rpcpassword"] = PART_RPC_PWD
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ def getCoreBinArgs(coin_id: int, coin_settings, prepare=False, use_tor_proxy=Fal
|
|||||||
extra_args = []
|
extra_args = []
|
||||||
if "config_filename" in coin_settings:
|
if "config_filename" in coin_settings:
|
||||||
extra_args.append("--conf=" + coin_settings["config_filename"])
|
extra_args.append("--conf=" + coin_settings["config_filename"])
|
||||||
if "port" in coin_settings:
|
if "port" in coin_settings and coin_id != Coins.BTC:
|
||||||
if prepare is False and use_tor_proxy:
|
if prepare is False and use_tor_proxy:
|
||||||
if coin_id == Coins.BCH:
|
if coin_id == Coins.BCH:
|
||||||
# Without this BCH (27.1) will bind to the default BTC port, even with proxy set
|
# Without this BCH (27.1) will bind to the default BTC port, even with proxy set
|
||||||
@@ -440,7 +440,9 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
|||||||
swap_client.log.info(f"Starting {display_name} daemon")
|
swap_client.log.info(f"Starting {display_name} daemon")
|
||||||
|
|
||||||
filename: str = getCoreBinName(coin_id, v, c + "d")
|
filename: str = getCoreBinName(coin_id, v, c + "d")
|
||||||
extra_opts = getCoreBinArgs(coin_id, v, use_tor_proxy=swap_client.use_tor_proxy)
|
extra_opts = getCoreBinArgs(
|
||||||
|
coin_id, v, use_tor_proxy=swap_client.use_tor_proxy
|
||||||
|
)
|
||||||
daemons.append(
|
daemons.append(
|
||||||
startDaemon(v["datadir"], v["bindir"], filename, opts=extra_opts)
|
startDaemon(v["datadir"], v["bindir"], filename, opts=extra_opts)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2019-2024 tecnovert
|
# 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
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -199,6 +199,7 @@ chainparams = {
|
|||||||
"message_magic": "Decred Signed Message:\n",
|
"message_magic": "Decred Signed Message:\n",
|
||||||
"blocks_target": 60 * 5,
|
"blocks_target": 60 * 5,
|
||||||
"decimal_places": 8,
|
"decimal_places": 8,
|
||||||
|
"has_multiwallet": False,
|
||||||
"mainnet": {
|
"mainnet": {
|
||||||
"rpcport": 9109,
|
"rpcport": 9109,
|
||||||
"pubkey_address": 0x073F,
|
"pubkey_address": 0x073F,
|
||||||
@@ -404,6 +405,7 @@ chainparams = {
|
|||||||
"has_cltv": False,
|
"has_cltv": False,
|
||||||
"has_csv": False,
|
"has_csv": False,
|
||||||
"has_segwit": False,
|
"has_segwit": False,
|
||||||
|
"has_multiwallet": False,
|
||||||
"mainnet": {
|
"mainnet": {
|
||||||
"rpcport": 8888,
|
"rpcport": 8888,
|
||||||
"pubkey_address": 82,
|
"pubkey_address": 82,
|
||||||
@@ -443,6 +445,7 @@ chainparams = {
|
|||||||
"decimal_places": 8,
|
"decimal_places": 8,
|
||||||
"has_csv": True,
|
"has_csv": True,
|
||||||
"has_segwit": True,
|
"has_segwit": True,
|
||||||
|
"has_multiwallet": False,
|
||||||
"mainnet": {
|
"mainnet": {
|
||||||
"rpcport": 44444,
|
"rpcport": 44444,
|
||||||
"pubkey_address": 53,
|
"pubkey_address": 53,
|
||||||
@@ -519,10 +522,13 @@ chainparams = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
name_map = {}
|
||||||
ticker_map = {}
|
ticker_map = {}
|
||||||
|
|
||||||
|
|
||||||
for c, params in chainparams.items():
|
for c, params in chainparams.items():
|
||||||
|
name_map[params["name"].lower()] = c
|
||||||
ticker_map[params["ticker"].lower()] = c
|
ticker_map[params["ticker"].lower()] = c
|
||||||
|
|
||||||
|
|
||||||
@@ -530,4 +536,11 @@ def getCoinIdFromTicker(ticker: str) -> str:
|
|||||||
try:
|
try:
|
||||||
return ticker_map[ticker.lower()]
|
return ticker_map[ticker.lower()]
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError("Unknown coin")
|
raise ValueError(f"Unknown coin {ticker}")
|
||||||
|
|
||||||
|
|
||||||
|
def getCoinIdFromName(name: str) -> str:
|
||||||
|
try:
|
||||||
|
return name_map[name.lower()]
|
||||||
|
except Exception:
|
||||||
|
raise ValueError(f"Unknown coin {name}")
|
||||||
|
|||||||
@@ -578,10 +578,8 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||||||
return page_offers(self, url_split, post_string, sent=True)
|
return page_offers(self, url_split, post_string, sent=True)
|
||||||
if page == "bid":
|
if page == "bid":
|
||||||
return page_bid(self, url_split, post_string)
|
return page_bid(self, url_split, post_string)
|
||||||
if page == "receivedbids":
|
if page == "bids":
|
||||||
return page_bids(self, url_split, post_string, received=True)
|
return page_bids(self, url_split, post_string)
|
||||||
if page == "sentbids":
|
|
||||||
return page_bids(self, url_split, post_string, sent=True)
|
|
||||||
if page == "availablebids":
|
if page == "availablebids":
|
||||||
return page_bids(self, url_split, post_string, available=True)
|
return page_bids(self, url_split, post_string, available=True)
|
||||||
if page == "watched":
|
if page == "watched":
|
||||||
|
|||||||
+18
-12
@@ -217,6 +217,10 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
rv += output.nValue
|
rv += output.nValue
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def est_lock_tx_vsize() -> int:
|
||||||
|
return 110
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
return 147
|
return 147
|
||||||
@@ -262,7 +266,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
self._rpcport = coin_settings["rpcport"]
|
self._rpcport = coin_settings["rpcport"]
|
||||||
self._rpcauth = coin_settings["rpcauth"]
|
self._rpcauth = coin_settings["rpcauth"]
|
||||||
self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||||
self._rpc_wallet = "wallet.dat"
|
self._rpc_wallet = coin_settings.get("wallet_name", "wallet.dat")
|
||||||
self.rpc_wallet = make_rpc_func(
|
self.rpc_wallet = make_rpc_func(
|
||||||
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
|
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
|
||||||
)
|
)
|
||||||
@@ -297,16 +301,14 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
|
|
||||||
# Wallet name is "" for some LTC and PART installs on older cores
|
# Wallet name is "" for some LTC and PART installs on older cores
|
||||||
if self._rpc_wallet not in wallets and len(wallets) > 0:
|
if self._rpc_wallet not in wallets and len(wallets) > 0:
|
||||||
self._log.debug("Changing {} wallet name.".format(self.ticker()))
|
self._log.debug(f"Changing {self.ticker()} wallet name.")
|
||||||
for wallet_name in wallets:
|
for wallet_name in wallets:
|
||||||
# Skip over other expected wallets
|
# Skip over other expected wallets
|
||||||
if wallet_name in ("mweb",):
|
if wallet_name in ("mweb",):
|
||||||
continue
|
continue
|
||||||
self._rpc_wallet = wallet_name
|
self._rpc_wallet = wallet_name
|
||||||
self._log.info(
|
self._log.info(
|
||||||
"Switched {} wallet name to {}.".format(
|
f"Switched {self.ticker()} wallet name to {self._rpc_wallet}."
|
||||||
self.ticker(), self._rpc_wallet
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
self.rpc_wallet = make_rpc_func(
|
self.rpc_wallet = make_rpc_func(
|
||||||
self._rpcport,
|
self._rpcport,
|
||||||
@@ -377,9 +379,9 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
|
|
||||||
chain_synced = round(blockchaininfo["verificationprogress"], 3)
|
chain_synced = round(blockchaininfo["verificationprogress"], 3)
|
||||||
if chain_synced < 1.0:
|
if chain_synced < 1.0:
|
||||||
raise ValueError("{} chain isn't synced.".format(self.coin_name()))
|
raise ValueError(f"{self.coin_name()} chain isn't synced.")
|
||||||
|
|
||||||
self._log.debug("Finding block at time: {}".format(start_time))
|
self._log.debug(f"Finding block at time: {start_time}")
|
||||||
|
|
||||||
rpc_conn = self.open_rpc()
|
rpc_conn = self.open_rpc()
|
||||||
try:
|
try:
|
||||||
@@ -393,7 +395,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
block_hash = block_header["previousblockhash"]
|
block_hash = block_header["previousblockhash"]
|
||||||
finally:
|
finally:
|
||||||
self.close_rpc(rpc_conn)
|
self.close_rpc(rpc_conn)
|
||||||
raise ValueError("{} wallet restore height not found.".format(self.coin_name()))
|
raise ValueError(f"{self.coin_name()} wallet restore height not found.")
|
||||||
|
|
||||||
def getWalletSeedID(self) -> str:
|
def getWalletSeedID(self) -> str:
|
||||||
wi = self.rpc_wallet("getwalletinfo")
|
wi = self.rpc_wallet("getwalletinfo")
|
||||||
@@ -1802,16 +1804,20 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
def unlockWallet(self, password: str):
|
def unlockWallet(self, password: str):
|
||||||
if password == "":
|
if password == "":
|
||||||
return
|
return
|
||||||
self._log.info("unlockWallet - {}".format(self.ticker()))
|
self._log.info(f"unlockWallet - {self.ticker()}")
|
||||||
|
|
||||||
if self.coin_type() == Coins.BTC:
|
if self.coin_type() == Coins.BTC:
|
||||||
# Recreate wallet if none found
|
# Recreate wallet if none found
|
||||||
# Required when encrypting an existing btc wallet, workaround is to delete the btc wallet and recreate
|
# Required when encrypting an existing btc wallet, workaround is to delete the btc wallet and recreate
|
||||||
wallets = self.rpc("listwallets")
|
wallets = self.rpc("listwallets")
|
||||||
if len(wallets) < 1:
|
if len(wallets) < 1:
|
||||||
self._log.info("Creating wallet.dat for {}.".format(self.coin_name()))
|
self._log.info(
|
||||||
|
f'Creating wallet "{self._rpc_wallet}" for {self.coin_name()}.'
|
||||||
|
)
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
||||||
self.rpc("createwallet", ["wallet.dat", False, True, "", False, False])
|
self.rpc(
|
||||||
|
"createwallet", [self._rpc_wallet, False, True, "", False, False]
|
||||||
|
)
|
||||||
self.rpc_wallet("encryptwallet", [password])
|
self.rpc_wallet("encryptwallet", [password])
|
||||||
|
|
||||||
# Max timeout value, ~3 years
|
# Max timeout value, ~3 years
|
||||||
@@ -1819,7 +1825,7 @@ class BTCInterface(Secp256k1Interface):
|
|||||||
self._sc.checkWalletSeed(self.coin_type())
|
self._sc.checkWalletSeed(self.coin_type())
|
||||||
|
|
||||||
def lockWallet(self):
|
def lockWallet(self):
|
||||||
self._log.info("lockWallet - {}".format(self.ticker()))
|
self._log.info(f"lockWallet - {self.ticker()}")
|
||||||
self.rpc_wallet("walletlock")
|
self.rpc_wallet("walletlock")
|
||||||
|
|
||||||
def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray:
|
def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray:
|
||||||
|
|||||||
@@ -211,6 +211,10 @@ class DCRInterface(Secp256k1Interface):
|
|||||||
def txoType():
|
def txoType():
|
||||||
return CTxOut
|
return CTxOut
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def est_lock_tx_vsize() -> int:
|
||||||
|
return 224
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
return 327
|
return 327
|
||||||
@@ -273,6 +277,9 @@ class DCRInterface(Secp256k1Interface):
|
|||||||
self._connection_type = coin_settings["connection_type"]
|
self._connection_type = coin_settings["connection_type"]
|
||||||
self._altruistic = coin_settings.get("altruistic", True)
|
self._altruistic = coin_settings.get("altruistic", True)
|
||||||
|
|
||||||
|
if "wallet_name" in coin_settings:
|
||||||
|
raise ValueError(f"Invalid setting for {self.coin_name()}: wallet_name")
|
||||||
|
|
||||||
def open_rpc(self):
|
def open_rpc(self):
|
||||||
return openrpc(self._rpcport, self._rpcauth, host=self._rpc_host)
|
return openrpc(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ class DOGEInterface(BTCInterface):
|
|||||||
def coin_type():
|
def coin_type():
|
||||||
return Coins.DOGE
|
return Coins.DOGE
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def est_lock_tx_vsize() -> int:
|
||||||
|
return 192
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
return 192
|
return 192
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ class FIROInterface(BTCInterface):
|
|||||||
self._rpcport, self._rpcauth, host=self._rpc_host
|
self._rpcport, self._rpcauth, host=self._rpc_host
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if "wallet_name" in coin_settings:
|
||||||
|
raise ValueError(f"Invalid setting for {self.coin_name()}: wallet_name")
|
||||||
|
|
||||||
def getExchangeName(self, exchange_name: str) -> str:
|
def getExchangeName(self, exchange_name: str) -> str:
|
||||||
return "zcoin"
|
return "zcoin"
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class LTCInterface(BTCInterface):
|
|||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super(LTCInterface, self).__init__(coin_settings, network, swap_client)
|
super(LTCInterface, self).__init__(coin_settings, network, swap_client)
|
||||||
self._rpc_wallet_mweb = "mweb"
|
self._rpc_wallet_mweb = coin_settings.get("mweb_wallet_name", "mweb")
|
||||||
self.rpc_wallet_mweb = make_rpc_func(
|
self.rpc_wallet_mweb = make_rpc_func(
|
||||||
self._rpcport,
|
self._rpcport,
|
||||||
self._rpcauth,
|
self._rpcauth,
|
||||||
@@ -94,7 +94,7 @@ class LTCInterfaceMWEB(LTCInterface):
|
|||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client)
|
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client)
|
||||||
self._rpc_wallet = "mweb"
|
self._rpc_wallet = coin_settings.get("mweb_wallet_name", "mweb")
|
||||||
self.rpc_wallet = make_rpc_func(
|
self.rpc_wallet = make_rpc_func(
|
||||||
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
|
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
|
||||||
)
|
)
|
||||||
@@ -128,7 +128,7 @@ class LTCInterfaceMWEB(LTCInterface):
|
|||||||
|
|
||||||
self._log.info("init_wallet - {}".format(self.ticker()))
|
self._log.info("init_wallet - {}".format(self.ticker()))
|
||||||
|
|
||||||
self._log.info("Creating mweb wallet for {}.".format(self.coin_name()))
|
self._log.info(f"Creating wallet {self._rpc_wallet} for {self.coin_name()}.")
|
||||||
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
|
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup
|
||||||
self.rpc("createwallet", ["mweb", False, True, password, False, False, True])
|
self.rpc("createwallet", ["mweb", False, True, password, False, False, True])
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ class NAVInterface(BTCInterface):
|
|||||||
self._rpcport, self._rpcauth, host=self._rpc_host
|
self._rpcport, self._rpcauth, host=self._rpc_host
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if "wallet_name" in coin_settings:
|
||||||
|
raise ValueError(f"Invalid setting for {self.coin_name()}: wallet_name")
|
||||||
|
|
||||||
def use_p2shp2wsh(self) -> bool:
|
def use_p2shp2wsh(self) -> bool:
|
||||||
# p2sh-p2wsh
|
# p2sh-p2wsh
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ class PARTInterface(BTCInterface):
|
|||||||
def txVersion() -> int:
|
def txVersion() -> int:
|
||||||
return 0xA0
|
return 0xA0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def est_lock_tx_vsize() -> int:
|
||||||
|
return 138
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
return 200
|
return 200
|
||||||
@@ -195,6 +199,10 @@ class PARTInterfaceBlind(PARTInterface):
|
|||||||
def balance_type():
|
def balance_type():
|
||||||
return BalanceTypes.BLIND
|
return BalanceTypes.BLIND
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def est_lock_tx_vsize() -> int:
|
||||||
|
return 980
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
return 1032
|
return 1032
|
||||||
@@ -1220,6 +1228,10 @@ class PARTInterfaceAnon(PARTInterface):
|
|||||||
def balance_type():
|
def balance_type():
|
||||||
return BalanceTypes.ANON
|
return BalanceTypes.ANON
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def est_lock_tx_vsize() -> int:
|
||||||
|
return 1153
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
raise ValueError("Not possible")
|
raise ValueError("Not possible")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2024 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# 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
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -71,15 +71,20 @@ class XMRInterface(CoinInterface):
|
|||||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||||
raise ValueError("Not possible")
|
raise ValueError("Not possible")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def est_lock_tx_vsize() -> int:
|
||||||
|
# TODO: Estimate with ringsize
|
||||||
|
return 1604
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
def xmr_swap_b_lock_spend_tx_vsize() -> int:
|
||||||
# TODO: Estimate with ringsize
|
# TODO: Estimate with ringsize
|
||||||
return 1604
|
return 1604
|
||||||
|
|
||||||
def is_transient_error(self, ex) -> bool:
|
def is_transient_error(self, ex) -> bool:
|
||||||
# str_error: str = str(ex).lower()
|
str_error: str = str(ex).lower()
|
||||||
# if "failed to get output distribution" in str_error:
|
if "failed to get earliest fork height" in str_error:
|
||||||
# return True
|
return True
|
||||||
return super().is_transient_error(ex)
|
return super().is_transient_error(ex)
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
@@ -94,6 +99,7 @@ class XMRInterface(CoinInterface):
|
|||||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||||
self._wallet_password = None
|
self._wallet_password = None
|
||||||
self._have_checked_seed = False
|
self._have_checked_seed = False
|
||||||
|
self._wallet_filename = coin_settings.get("wallet_name", "swap_wallet")
|
||||||
|
|
||||||
daemon_login = None
|
daemon_login = None
|
||||||
if coin_settings.get("rpcuser", "") != "":
|
if coin_settings.get("rpcuser", "") != "":
|
||||||
@@ -170,9 +176,6 @@ class XMRInterface(CoinInterface):
|
|||||||
ensure(new_priority >= 0 and new_priority < 4, "Invalid fee_priority value")
|
ensure(new_priority >= 0 and new_priority < 4, "Invalid fee_priority value")
|
||||||
self._fee_priority = new_priority
|
self._fee_priority = new_priority
|
||||||
|
|
||||||
def setWalletFilename(self, wallet_filename):
|
|
||||||
self._wallet_filename = wallet_filename
|
|
||||||
|
|
||||||
def createWallet(self, params):
|
def createWallet(self, params):
|
||||||
if self._wallet_password is not None:
|
if self._wallet_password is not None:
|
||||||
params["password"] = self._wallet_password
|
params["password"] = self._wallet_password
|
||||||
|
|||||||
+187
-189
@@ -1,153 +1,157 @@
|
|||||||
/* General Styles */
|
/* General Styles */
|
||||||
.bold {
|
.bold {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monospace {
|
.monospace {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floatright {
|
.floatright {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 1.25rem;
|
top: 1.25rem;
|
||||||
right: 1.25rem;
|
right: 1.25rem;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table Styles */
|
/* Table Styles */
|
||||||
.padded_row td {
|
.padded_row td {
|
||||||
padding-top: 1.5em;
|
padding-top: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal Styles */
|
/* Modal Styles */
|
||||||
.modal-highest {
|
.modal-highest {
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Animation */
|
/* Animation */
|
||||||
#hide {
|
#hide {
|
||||||
-moz-animation: cssAnimation 0s ease-in 15s forwards;
|
-moz-animation: cssAnimation 0s ease-in 15s forwards;
|
||||||
-webkit-animation: cssAnimation 0s ease-in 15s forwards;
|
-webkit-animation: cssAnimation 0s ease-in 15s forwards;
|
||||||
-o-animation: cssAnimation 0s ease-in 15s forwards;
|
-o-animation: cssAnimation 0s ease-in 15s forwards;
|
||||||
animation: cssAnimation 0s ease-in 15s forwards;
|
animation: cssAnimation 0s ease-in 15s forwards;
|
||||||
-webkit-animation-fill-mode: forwards;
|
-webkit-animation-fill-mode: forwards;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes cssAnimation {
|
@keyframes cssAnimation {
|
||||||
to {
|
to {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes cssAnimation {
|
@-webkit-keyframes cssAnimation {
|
||||||
to {
|
to {
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom Select Styles */
|
/* Custom Select Styles */
|
||||||
.custom-select .select {
|
.custom-select .select {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background-image: url('/static/images/other/coin.png');
|
background-image: url('/static/images/other/coin.png');
|
||||||
background-position: 10px center;
|
background-position: 10px center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select select::-webkit-scrollbar {
|
.custom-select select::-webkit-scrollbar {
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select .select option {
|
.custom-select .select option {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
text-indent: 0;
|
text-indent: 0;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 0 50%;
|
background-position: 0 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select .select option.no-space {
|
.custom-select .select option.no-space {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select .select option[data-image] {
|
.custom-select .select option[data-image] {
|
||||||
background-image: url('');
|
background-image: url('');
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select .select-icon {
|
.custom-select .select-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select .select-image {
|
.custom-select .select-image {
|
||||||
display: none;
|
display: none;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select .select:focus + .select-dropdown .select-image {
|
.custom-select .select:focus + .select-dropdown .select-image {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Blur and Overlay Styles */
|
/* Blur and Overlay Styles */
|
||||||
.blurred {
|
.blurred {
|
||||||
filter: blur(3px);
|
filter: blur(3px);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-overlay.non-blurred {
|
.error-overlay.non-blurred {
|
||||||
filter: none;
|
filter: none;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
user-select: auto;
|
user-select: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Form Element Styles */
|
/* Form Element Styles */
|
||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
@media screen and (-webkit-min-device-pixel-ratio: 0) {
|
||||||
select:disabled,
|
select:disabled,
|
||||||
input:disabled,
|
input:disabled,
|
||||||
textarea:disabled {
|
textarea:disabled {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
border: 1px solid red !important;
|
border: 1px solid red !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Active Container Styles */
|
/* Active Container Styles */
|
||||||
.active-container {
|
.active-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.active-container::before {
|
.active-container::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
border: 1px solid rgb(77, 132, 240);
|
border: 1px solid rgb(77, 132, 240);
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Center Spin Animation */
|
/* Center Spin Animation */
|
||||||
.center-spin {
|
.center-spin {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
0% { transform: rotate(0deg); }
|
0% {
|
||||||
100% { transform: rotate(360deg); }
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hover Container Styles */
|
/* Hover Container Styles */
|
||||||
@@ -155,215 +159,209 @@
|
|||||||
.hover-container:hover #coin_to,
|
.hover-container:hover #coin_to,
|
||||||
.hover-container:hover #coin_from_button,
|
.hover-container:hover #coin_from_button,
|
||||||
.hover-container:hover #coin_from {
|
.hover-container:hover #coin_from {
|
||||||
border-color: #3b82f6;
|
border-color: #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#coin_to_button, #coin_from_button {
|
#coin_to_button,
|
||||||
background-repeat: no-repeat;
|
#coin_from_button {
|
||||||
background-position: center;
|
background-repeat: no-repeat;
|
||||||
background-size: 20px 20px;
|
background-position: center;
|
||||||
|
background-size: 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Input-like Container Styles */
|
/* Input-like Container Styles */
|
||||||
.input-like-container {
|
.input-like-container {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
width: 360px;
|
width: 360px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: #374151;
|
color: #374151;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: 90px;
|
min-height: 90px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-like-container.dark {
|
.input-like-container.dark {
|
||||||
background-color: #374151;
|
background-color: #374151;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-like-container.copying {
|
.input-like-container.copying {
|
||||||
width: inherit;
|
width: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* QR Code Styles */
|
/* QR Code Styles */
|
||||||
.qrcode {
|
.qrcode {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qrcode-border {
|
.qrcode-border {
|
||||||
border: 2px solid;
|
border: 2px solid;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qrcode img {
|
.qrcode img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#showQR {
|
#showQR {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
height: 25px;
|
height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qrcode-container {
|
.qrcode-container {
|
||||||
margin-top: 25px;
|
margin-top: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disabled Element Styles */
|
/* Disabled Element Styles */
|
||||||
select.select-disabled,
|
select.select-disabled,
|
||||||
.disabled-input-enabled,
|
.disabled-input-enabled,
|
||||||
select.disabled-select-enabled {
|
select.disabled-select-enabled {
|
||||||
opacity: 0.40 !important;
|
opacity: 0.40 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Shutdown Modal Styles */
|
/* Shutdown Modal Styles */
|
||||||
#shutdownModal {
|
#shutdownModal {
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
#shutdownModal > div:first-child {
|
#shutdownModal > div:first-child {
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
#shutdownModal > div:last-child {
|
#shutdownModal > div:last-child {
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
#shutdownModal > div {
|
#shutdownModal > div {
|
||||||
transition: opacity 0.3s ease-out;
|
transition: opacity 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
#shutdownModal.hidden > div {
|
#shutdownModal.hidden > div {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#shutdownModal:not(.hidden) > div {
|
#shutdownModal:not(.hidden) > div {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shutdown-button {
|
.shutdown-button {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shutdown-button.shutdown-disabled {
|
.shutdown-button.shutdown-disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
color: #a0aec0;
|
color: #a0aec0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shutdown-button.shutdown-disabled:hover {
|
.shutdown-button.shutdown-disabled:hover {
|
||||||
background-color: #4a5568;
|
background-color: #4a5568;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shutdown-button.shutdown-disabled svg {
|
.shutdown-button.shutdown-disabled svg {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Loading Line Animation */
|
||||||
/* Loading line animation */
|
|
||||||
.loading-line {
|
.loading-line {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-line::before {
|
.loading-line::before {
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(to right, transparent, #007bff, transparent);
|
background: linear-gradient(to right, transparent, #007bff, transparent);
|
||||||
animation: loading 1.5s infinite;
|
animation: loading 1.5s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes loading {
|
@keyframes loading {
|
||||||
0% {
|
0% {
|
||||||
transform: translateX(-100%);
|
transform: translateX(-100%);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Hide the loading line once data is loaded */
|
|
||||||
.usd-value:not(.loading) .loading-line,
|
.usd-value:not(.loading) .loading-line,
|
||||||
.profit-loss:not(.loading) .loading-line {
|
.profit-loss:not(.loading) .loading-line {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resolution-button {
|
/* Resolution Button Styles */
|
||||||
|
.resolution-button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: #4B5563; /* gray-600 */
|
color: #4B5563;
|
||||||
font-size: 0.875rem; /* text-sm */
|
font-size: 0.875rem;
|
||||||
font-weight: 500; /* font-medium */
|
font-weight: 500;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
outline: 2px solid transparent;
|
outline: 2px solid transparent;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resolution-button:hover {
|
.resolution-button:hover {
|
||||||
color: #1F2937; /* gray-800 */
|
color: #1F2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resolution-button:focus {
|
.resolution-button:focus {
|
||||||
outline: 2px solid #3B82F6; /* blue-500 */
|
outline: 2px solid #3B82F6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resolution-button.active {
|
.resolution-button.active {
|
||||||
color: #3B82F6; /* blue-500 */
|
color: #3B82F6;
|
||||||
outline: 2px solid #3B82F6; /* blue-500 */
|
outline: 2px solid #3B82F6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .resolution-button {
|
.dark .resolution-button {
|
||||||
color: #9CA3AF; /* gray-400 */
|
color: #9CA3AF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .resolution-button:hover {
|
.dark .resolution-button:hover {
|
||||||
color: #F3F4F6; /* gray-100 */
|
color: #F3F4F6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .resolution-button.active {
|
.dark .resolution-button.active {
|
||||||
color: #60A5FA; /* blue-400 */
|
color: #60A5FA;
|
||||||
outline-color: #60A5FA; /* blue-400 */
|
outline-color: #60A5FA;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
|
||||||
|
|
||||||
#toggle-volume.active {
|
|
||||||
@apply bg-green-500 hover:bg-green-600 focus:ring-green-300;
|
|
||||||
}
|
|
||||||
#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 {
|
/* Toggle Button Styles */
|
||||||
backface-visibility: hidden;
|
#toggle-volume.active {
|
||||||
-webkit-backface-visibility: hidden;
|
@apply bg-green-500 hover:bg-green-600 focus:ring-green-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#toggle-auto-refresh[data-enabled="true"] {
|
||||||
|
@apply bg-green-500 hover:bg-green-600 focus:ring-green-300;
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+319
-327
@@ -1,3 +1,4 @@
|
|||||||
|
// EVENT MANAGER
|
||||||
const EventManager = {
|
const EventManager = {
|
||||||
listeners: new Map(),
|
listeners: new Map(),
|
||||||
|
|
||||||
@@ -72,12 +73,11 @@ let currentSortDirection = 'desc';
|
|||||||
let filterTimeout = null;
|
let filterTimeout = null;
|
||||||
|
|
||||||
// CONFIGURATION CONSTANTS
|
// CONFIGURATION CONSTANTS
|
||||||
|
// TIME CONSTANTS
|
||||||
// Time Constants
|
|
||||||
const CACHE_DURATION = 10 * 60 * 1000;
|
const CACHE_DURATION = 10 * 60 * 1000;
|
||||||
|
|
||||||
// Application Constants
|
// APP CONSTANTS
|
||||||
const itemsPerPage = 100;
|
const itemsPerPage = 50;
|
||||||
const isSentOffers = window.offersTableConfig.isSentOffers;
|
const isSentOffers = window.offersTableConfig.isSentOffers;
|
||||||
|
|
||||||
const offersConfig = {
|
const offersConfig = {
|
||||||
@@ -142,6 +142,7 @@ const totalPagesSpan = document.getElementById('totalPages');
|
|||||||
const lastRefreshTimeSpan = document.getElementById('lastRefreshTime');
|
const lastRefreshTimeSpan = document.getElementById('lastRefreshTime');
|
||||||
const newEntriesCountSpan = document.getElementById('newEntriesCount');
|
const newEntriesCountSpan = document.getElementById('newEntriesCount');
|
||||||
|
|
||||||
|
|
||||||
// MANAGER OBJECTS
|
// MANAGER OBJECTS
|
||||||
const WebSocketManager = {
|
const WebSocketManager = {
|
||||||
ws: null,
|
ws: null,
|
||||||
@@ -248,7 +249,6 @@ const WebSocketManager = {
|
|||||||
this.connectionState.lastConnectAttempt = Date.now();
|
this.connectionState.lastConnectAttempt = Date.now();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const config = getWebSocketConfig();
|
|
||||||
const wsPort = config.port || window.ws_port || '11700';
|
const wsPort = config.port || window.ws_port || '11700';
|
||||||
|
|
||||||
if (!wsPort) {
|
if (!wsPort) {
|
||||||
@@ -349,7 +349,6 @@ const WebSocketManager = {
|
|||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
updateOffersTable();
|
updateOffersTable();
|
||||||
updateJsonView();
|
|
||||||
updatePaginationInfo();
|
updatePaginationInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -641,14 +640,13 @@ const CacheManager = {
|
|||||||
|
|
||||||
window.CacheManager = CacheManager;
|
window.CacheManager = CacheManager;
|
||||||
|
|
||||||
// Identity cache management
|
// IDENTITY CACHE MANAGEMENT
|
||||||
const IdentityManager = {
|
const IdentityManager = {
|
||||||
cache: new Map(),
|
cache: new Map(),
|
||||||
pendingRequests: new Map(),
|
pendingRequests: new Map(),
|
||||||
retryDelay: 2000,
|
retryDelay: 2000,
|
||||||
maxRetries: 3,
|
maxRetries: 3,
|
||||||
cacheTimeout: 5 * 60 * 1000, // 5 minutes
|
cacheTimeout: 5 * 60 * 1000,
|
||||||
|
|
||||||
async getIdentityData(address) {
|
async getIdentityData(address) {
|
||||||
|
|
||||||
const cachedData = this.getCachedIdentity(address);
|
const cachedData = this.getCachedIdentity(address);
|
||||||
@@ -849,22 +847,13 @@ function continueInitialization() {
|
|||||||
listingLabel.textContent = isSentOffers ? 'Total Listings: ' : 'Network Listings: ';
|
listingLabel.textContent = isSentOffers ? 'Total Listings: ' : 'Network Listings: ';
|
||||||
}
|
}
|
||||||
//console.log('Initialization completed');
|
//console.log('Initialization completed');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeTooltips() {
|
function initializeTooltips() {
|
||||||
if (typeof Tooltip === 'undefined') {
|
if (window.TooltipManager) {
|
||||||
console.warn('Tooltip is not defined. Make sure the required library is loaded.');
|
window.TooltipManager.initializeTooltips();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltipElements = document.querySelectorAll('[data-tooltip-target]');
|
|
||||||
tooltipElements.forEach((el) => {
|
|
||||||
const tooltipId = el.getAttribute('data-tooltip-target');
|
|
||||||
const tooltipElement = document.getElementById(tooltipId);
|
|
||||||
if (tooltipElement) {
|
|
||||||
new Tooltip(tooltipElement, el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DATA PROCESSING FUNCTIONS
|
// DATA PROCESSING FUNCTIONS
|
||||||
@@ -880,114 +869,105 @@ function getValidOffers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function filterAndSortData() {
|
function filterAndSortData() {
|
||||||
//console.log('[Debug] Starting filter with data length:', originalJsonData.length);
|
const formData = new FormData(filterForm);
|
||||||
|
const filters = Object.fromEntries(formData);
|
||||||
|
|
||||||
const formData = new FormData(filterForm);
|
localStorage.setItem('offersTableSettings', JSON.stringify({
|
||||||
const filters = Object.fromEntries(formData);
|
coin_to: filters.coin_to,
|
||||||
//console.log('[Debug] Active filters:', filters);
|
coin_from: filters.coin_from,
|
||||||
|
status: filters.status,
|
||||||
|
sent_from: filters.sent_from,
|
||||||
|
sortColumn: currentSortColumn,
|
||||||
|
sortDirection: currentSortDirection
|
||||||
|
}));
|
||||||
|
|
||||||
if (filters.coin_to !== 'any') {
|
if (filters.coin_to !== 'any') {
|
||||||
filters.coin_to = coinIdToName[filters.coin_to] || filters.coin_to;
|
filters.coin_to = coinIdToName[filters.coin_to] || filters.coin_to;
|
||||||
}
|
}
|
||||||
if (filters.coin_from !== 'any') {
|
if (filters.coin_from !== 'any') {
|
||||||
filters.coin_from = coinIdToName[filters.coin_from] || filters.coin_from;
|
filters.coin_from = coinIdToName[filters.coin_from] || filters.coin_from;
|
||||||
}
|
}
|
||||||
|
|
||||||
let filteredData = [...originalJsonData];
|
let filteredData = [...originalJsonData];
|
||||||
|
|
||||||
const sentFromFilter = filters.sent_from || 'any';
|
const sentFromFilter = filters.sent_from || 'any';
|
||||||
|
filteredData = filteredData.filter(offer => {
|
||||||
|
if (sentFromFilter === 'public') return offer.is_public;
|
||||||
|
if (sentFromFilter === 'private') return !offer.is_public;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
filteredData = filteredData.filter(offer => {
|
filteredData = filteredData.filter(offer => {
|
||||||
if (sentFromFilter === 'public') {
|
if (!isSentOffers && isOfferExpired(offer)) return false;
|
||||||
return offer.is_public;
|
|
||||||
} else if (sentFromFilter === 'private') {
|
|
||||||
return !offer.is_public;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
filteredData = filteredData.filter(offer => {
|
const coinFrom = (offer.coin_from || '').toLowerCase();
|
||||||
if (!isSentOffers && isOfferExpired(offer)) {
|
const coinTo = (offer.coin_to || '').toLowerCase();
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const coinFrom = (offer.coin_from || '').toLowerCase();
|
if (filters.coin_to !== 'any' && !coinMatches(coinTo, filters.coin_to)) return false;
|
||||||
const coinTo = (offer.coin_to || '').toLowerCase();
|
if (filters.coin_from !== 'any' && !coinMatches(coinFrom, filters.coin_from)) return false;
|
||||||
|
|
||||||
if (filters.coin_to !== 'any') {
|
if (isSentOffers && filters.status && filters.status !== 'any') {
|
||||||
if (!coinMatches(coinTo, filters.coin_to)) {
|
const isExpired = offer.expire_at <= Math.floor(Date.now() / 1000);
|
||||||
return false;
|
const isRevoked = Boolean(offer.is_revoked);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.coin_from !== 'any') {
|
switch (filters.status) {
|
||||||
if (!coinMatches(coinFrom, filters.coin_from)) {
|
case 'active': return !isExpired && !isRevoked;
|
||||||
return false;
|
case 'expired': return isExpired && !isRevoked;
|
||||||
}
|
case 'revoked': return isRevoked;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
if (isSentOffers && filters.status && filters.status !== 'any') {
|
if (currentSortColumn !== null) {
|
||||||
const currentTime = Math.floor(Date.now() / 1000);
|
const priceCache = new Map();
|
||||||
const isExpired = offer.expire_at <= currentTime;
|
const getPrice = coin => {
|
||||||
const isRevoked = Boolean(offer.is_revoked);
|
if (priceCache.has(coin)) return priceCache.get(coin);
|
||||||
|
const symbol = coin === 'Firo' || coin === 'Zcoin' ? 'zcoin' :
|
||||||
|
coin === 'Bitcoin Cash' ? 'bitcoin-cash' :
|
||||||
|
coin.includes('Particl') ? 'particl' :
|
||||||
|
coin.toLowerCase();
|
||||||
|
const price = latestPrices[symbol]?.usd || 0;
|
||||||
|
priceCache.set(coin, price);
|
||||||
|
return price;
|
||||||
|
};
|
||||||
|
|
||||||
switch (filters.status) {
|
const calculateValue = offer => {
|
||||||
case 'active':
|
const fromUSD = parseFloat(offer.amount_from) * getPrice(offer.coin_from);
|
||||||
return !isExpired && !isRevoked;
|
const toUSD = parseFloat(offer.amount_to) * getPrice(offer.coin_to);
|
||||||
case 'expired':
|
return (isSentOffers || offer.is_own_offer) ?
|
||||||
return isExpired && !isRevoked;
|
((toUSD / fromUSD) - 1) * 100 :
|
||||||
case 'revoked':
|
((fromUSD / toUSD) - 1) * 100;
|
||||||
return isRevoked;
|
};
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
const sortValues = new Map();
|
||||||
});
|
if (currentSortColumn === 5 || currentSortColumn === 6) {
|
||||||
|
filteredData.forEach(offer => {
|
||||||
|
sortValues.set(offer.offer_id, calculateValue(offer));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (currentSortColumn !== null) {
|
filteredData.sort((a, b) => {
|
||||||
filteredData.sort((a, b) => {
|
let comparison;
|
||||||
let comparison = 0;
|
switch(currentSortColumn) {
|
||||||
|
case 0: // Time
|
||||||
|
comparison = a.created_at - b.created_at;
|
||||||
|
break;
|
||||||
|
case 5: // Rate
|
||||||
|
case 6: // Market +/-
|
||||||
|
comparison = sortValues.get(a.offer_id) - sortValues.get(b.offer_id);
|
||||||
|
break;
|
||||||
|
case 7: // Trade
|
||||||
|
comparison = a.offer_id.localeCompare(b.offer_id);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
comparison = 0;
|
||||||
|
}
|
||||||
|
return currentSortDirection === 'desc' ? -comparison : comparison;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
switch(currentSortColumn) {
|
return filteredData;
|
||||||
case 0: // Time
|
|
||||||
comparison = a.created_at - b.created_at;
|
|
||||||
break;
|
|
||||||
case 5: // Rate
|
|
||||||
comparison = parseFloat(a.rate) - parseFloat(b.rate);
|
|
||||||
break;
|
|
||||||
case 6: // Market +/-
|
|
||||||
const aFromSymbol = getCoinSymbolLowercase(a.coin_from);
|
|
||||||
const aToSymbol = getCoinSymbolLowercase(a.coin_to);
|
|
||||||
const bFromSymbol = getCoinSymbolLowercase(b.coin_from);
|
|
||||||
const bToSymbol = getCoinSymbolLowercase(b.coin_to);
|
|
||||||
|
|
||||||
const aFromPrice = latestPrices[aFromSymbol]?.usd || 0;
|
|
||||||
const aToPrice = latestPrices[aToSymbol]?.usd || 0;
|
|
||||||
const bFromPrice = latestPrices[bFromSymbol]?.usd || 0;
|
|
||||||
const bToPrice = latestPrices[bToSymbol]?.usd || 0;
|
|
||||||
|
|
||||||
const aMarketRate = aToPrice / aFromPrice;
|
|
||||||
const bMarketRate = bToPrice / bFromPrice;
|
|
||||||
|
|
||||||
const aOfferedRate = parseFloat(a.rate);
|
|
||||||
const bOfferedRate = parseFloat(b.rate);
|
|
||||||
|
|
||||||
const aPercentDiff = ((aOfferedRate - aMarketRate) / aMarketRate) * 100;
|
|
||||||
const bPercentDiff = ((bOfferedRate - bMarketRate) / bMarketRate) * 100;
|
|
||||||
|
|
||||||
comparison = aPercentDiff - bPercentDiff;
|
|
||||||
break;
|
|
||||||
case 7: // Trade
|
|
||||||
comparison = a.offer_id.localeCompare(b.offer_id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return currentSortDirection === 'desc' ? -comparison : comparison;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//console.log(`[Debug] Filtered data length: ${filteredData.length}`);
|
|
||||||
return filteredData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) {
|
async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) {
|
||||||
@@ -1191,7 +1171,6 @@ async function fetchOffers() {
|
|||||||
originalJsonData = [...jsonData];
|
originalJsonData = [...jsonData];
|
||||||
|
|
||||||
await updateOffersTable();
|
await updateOffersTable();
|
||||||
updateJsonView();
|
|
||||||
updatePaginationInfo();
|
updatePaginationInfo();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1280,10 +1259,6 @@ function updateRowTimes() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateJsonView() {
|
|
||||||
jsonContent.textContent = JSON.stringify(jsonData, null, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLastRefreshTime() {
|
function updateLastRefreshTime() {
|
||||||
if (lastRefreshTimeSpan) {
|
if (lastRefreshTimeSpan) {
|
||||||
lastRefreshTimeSpan.textContent = lastRefreshTime ? new Date(lastRefreshTime).toLocaleTimeString() : 'Never';
|
lastRefreshTimeSpan.textContent = lastRefreshTime ? new Date(lastRefreshTime).toLocaleTimeString() : 'Never';
|
||||||
@@ -1428,28 +1403,26 @@ function updateClearFiltersButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanupRow(row) {
|
function cleanupRow(row) {
|
||||||
EventManager.removeAll(row);
|
const tooltipTriggers = row.querySelectorAll('[data-tooltip-trigger-id]');
|
||||||
|
tooltipTriggers.forEach(trigger => {
|
||||||
const tooltips = row.querySelectorAll('[data-tooltip-target]');
|
if (window.TooltipManager) {
|
||||||
tooltips.forEach(tooltip => {
|
window.TooltipManager.destroy(trigger);
|
||||||
const tooltipId = tooltip.getAttribute('data-tooltip-target');
|
|
||||||
const tooltipElement = document.getElementById(tooltipId);
|
|
||||||
if (tooltipElement) {
|
|
||||||
tooltipElement.remove();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
EventManager.removeAll(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function cleanupTable() {
|
function cleanupTable() {
|
||||||
EventManager.clearAll();
|
EventManager.clearAll();
|
||||||
|
|
||||||
if (offersBody) {
|
if (offersBody) {
|
||||||
const existingRows = offersBody.querySelectorAll('tr');
|
const existingRows = offersBody.querySelectorAll('tr');
|
||||||
existingRows.forEach(row => {
|
existingRows.forEach(row => cleanupRow(row));
|
||||||
cleanupRow(row);
|
|
||||||
});
|
|
||||||
offersBody.innerHTML = '';
|
offersBody.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
if (window.TooltipManager) {
|
||||||
|
window.TooltipManager.cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNoOffersScenario() {
|
function handleNoOffersScenario() {
|
||||||
@@ -1490,6 +1463,10 @@ function handleNoOffersScenario() {
|
|||||||
|
|
||||||
async function updateOffersTable() {
|
async function updateOffersTable() {
|
||||||
try {
|
try {
|
||||||
|
if (window.TooltipManager) {
|
||||||
|
window.TooltipManager.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
const PRICES_CACHE_KEY = 'prices_coingecko';
|
const PRICES_CACHE_KEY = 'prices_coingecko';
|
||||||
const cachedPrices = CacheManager.get(PRICES_CACHE_KEY);
|
const cachedPrices = CacheManager.get(PRICES_CACHE_KEY);
|
||||||
latestPrices = cachedPrices?.value || getEmptyPriceData();
|
latestPrices = cachedPrices?.value || getEmptyPriceData();
|
||||||
@@ -1510,16 +1487,13 @@ async function updateOffersTable() {
|
|||||||
|
|
||||||
const BATCH_SIZE = 5;
|
const BATCH_SIZE = 5;
|
||||||
const identities = [];
|
const identities = [];
|
||||||
|
|
||||||
for (let i = 0; i < itemsToDisplay.length; i += BATCH_SIZE) {
|
for (let i = 0; i < itemsToDisplay.length; i += BATCH_SIZE) {
|
||||||
const batch = itemsToDisplay.slice(i, i + BATCH_SIZE);
|
const batch = itemsToDisplay.slice(i, i + BATCH_SIZE);
|
||||||
const batchPromises = batch.map(offer =>
|
const batchPromises = batch.map(offer =>
|
||||||
offer.addr_from ? IdentityManager.getIdentityData(offer.addr_from) : Promise.resolve(null)
|
offer.addr_from ? IdentityManager.getIdentityData(offer.addr_from) : Promise.resolve(null)
|
||||||
);
|
);
|
||||||
|
|
||||||
const batchResults = await Promise.all(batchPromises);
|
const batchResults = await Promise.all(batchPromises);
|
||||||
identities.push(...batchResults);
|
identities.push(...batchResults);
|
||||||
|
|
||||||
if (i + BATCH_SIZE < itemsToDisplay.length) {
|
if (i + BATCH_SIZE < itemsToDisplay.length) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
}
|
}
|
||||||
@@ -1533,13 +1507,12 @@ async function updateOffersTable() {
|
|||||||
const totalPages = Math.max(1, Math.ceil(validOffers.length / itemsPerPage));
|
const totalPages = Math.max(1, Math.ceil(validOffers.length / itemsPerPage));
|
||||||
currentPage = Math.min(currentPage, totalPages);
|
currentPage = Math.min(currentPage, totalPages);
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
|
|
||||||
const existingRows = offersBody.querySelectorAll('tr');
|
const existingRows = offersBody.querySelectorAll('tr');
|
||||||
existingRows.forEach(row => {
|
existingRows.forEach(row => {
|
||||||
cleanupRow(row);
|
cleanupRow(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
itemsToDisplay.forEach((offer, index) => {
|
itemsToDisplay.forEach((offer, index) => {
|
||||||
const identity = identities[index];
|
const identity = identities[index];
|
||||||
const row = createTableRow(offer, identity);
|
const row = createTableRow(offer, identity);
|
||||||
@@ -1551,11 +1524,21 @@ async function updateOffersTable() {
|
|||||||
offersBody.textContent = '';
|
offersBody.textContent = '';
|
||||||
offersBody.appendChild(fragment);
|
offersBody.appendChild(fragment);
|
||||||
|
|
||||||
|
const tooltipTriggers = offersBody.querySelectorAll('[data-tooltip-target]');
|
||||||
|
tooltipTriggers.forEach(trigger => {
|
||||||
|
const targetId = trigger.getAttribute('data-tooltip-target');
|
||||||
|
const tooltipContent = document.getElementById(targetId);
|
||||||
|
|
||||||
|
if (tooltipContent) {
|
||||||
|
window.TooltipManager.create(trigger, tooltipContent.innerHTML, {
|
||||||
|
placement: trigger.getAttribute('data-tooltip-placement') || 'top'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
initializeTooltips();
|
|
||||||
updateRowTimes();
|
updateRowTimes();
|
||||||
updatePaginationControls(totalPages);
|
updatePaginationControls(totalPages);
|
||||||
|
|
||||||
if (tableRateModule?.initializeTable) {
|
if (tableRateModule?.initializeTable) {
|
||||||
tableRateModule.initializeTable();
|
tableRateModule.initializeTable();
|
||||||
}
|
}
|
||||||
@@ -1571,7 +1554,6 @@ async function updateOffersTable() {
|
|||||||
try {
|
try {
|
||||||
const cachedOffers = CacheManager.get('offers_cached');
|
const cachedOffers = CacheManager.get('offers_cached');
|
||||||
if (cachedOffers?.value) {
|
if (cachedOffers?.value) {
|
||||||
console.log('Recovering with cached offers data');
|
|
||||||
jsonData = cachedOffers.value;
|
jsonData = cachedOffers.value;
|
||||||
updateOffersTable();
|
updateOffersTable();
|
||||||
}
|
}
|
||||||
@@ -1650,65 +1632,65 @@ function getIdentityInfo(address, identity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createTableRow(offer, identity = null) {
|
function createTableRow(offer, identity = null) {
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
const uniqueId = `${offer.offer_id}_${offer.created_at}`;
|
const uniqueId = `${offer.offer_id}_${offer.created_at}`;
|
||||||
|
|
||||||
row.className = 'relative opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600';
|
row.className = 'relative opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600';
|
||||||
row.setAttribute('data-offer-id', uniqueId);
|
row.setAttribute('data-offer-id', uniqueId);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
coin_from: coinFrom,
|
coin_from: coinFrom,
|
||||||
coin_to: coinTo,
|
coin_to: coinTo,
|
||||||
created_at: createdAt,
|
created_at: createdAt,
|
||||||
expire_at: expireAt,
|
expire_at: expireAt,
|
||||||
amount_from: amountFrom,
|
amount_from: amountFrom,
|
||||||
amount_to: amountTo,
|
amount_to: amountTo,
|
||||||
is_own_offer: isOwnOffer,
|
is_own_offer: isOwnOffer,
|
||||||
is_revoked: isRevoked,
|
is_revoked: isRevoked,
|
||||||
is_public: isPublic
|
is_public: isPublic
|
||||||
} = offer;
|
} = offer;
|
||||||
|
|
||||||
const coinFromSymbol = coinNameToSymbol[coinFrom] || coinFrom.toLowerCase();
|
const coinFromSymbol = coinNameToSymbol[coinFrom] || coinFrom.toLowerCase();
|
||||||
const coinToSymbol = coinNameToSymbol[coinTo] || coinTo.toLowerCase();
|
const coinToSymbol = coinNameToSymbol[coinTo] || coinTo.toLowerCase();
|
||||||
const coinFromDisplay = getDisplayName(coinFrom);
|
const coinFromDisplay = getDisplayName(coinFrom);
|
||||||
const coinToDisplay = getDisplayName(coinTo);
|
const coinToDisplay = getDisplayName(coinTo);
|
||||||
const postedTime = formatTime(createdAt, true);
|
const postedTime = formatTime(createdAt, true);
|
||||||
const expiresIn = formatTime(expireAt);
|
const expiresIn = formatTime(expireAt);
|
||||||
|
|
||||||
const currentTime = Math.floor(Date.now() / 1000);
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
const isActuallyExpired = currentTime > expireAt;
|
const isActuallyExpired = currentTime > expireAt;
|
||||||
const fromAmount = parseFloat(amountFrom) || 0;
|
const fromAmount = parseFloat(amountFrom) || 0;
|
||||||
const toAmount = parseFloat(amountTo) || 0;
|
const toAmount = parseFloat(amountTo) || 0;
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
${!isPublic ? createPrivateIndicator() : '<td class="w-0 p-0 m-0"></td>'}
|
${!isPublic ? createPrivateIndicator() : '<td class="w-0 p-0 m-0"></td>'}
|
||||||
${createTimeColumn(offer, postedTime, expiresIn)}
|
${createTimeColumn(offer, postedTime, expiresIn)}
|
||||||
${createDetailsColumn(offer, identity)}
|
${createDetailsColumn(offer, identity)}
|
||||||
${createTakerAmountColumn(offer, coinTo, coinFrom)}
|
${createTakerAmountColumn(offer, coinTo, coinFrom)}
|
||||||
${createSwapColumn(offer, coinFromDisplay, coinToDisplay, coinFromSymbol, coinToSymbol)}
|
${createSwapColumn(offer, coinFromDisplay, coinToDisplay, coinFromSymbol, coinToSymbol)}
|
||||||
${createOrderbookColumn(offer, coinFrom, coinTo)}
|
${createOrderbookColumn(offer, coinFrom, coinTo)}
|
||||||
${createRateColumn(offer, coinFrom, coinTo)}
|
${createRateColumn(offer, coinFrom, coinTo)}
|
||||||
${createPercentageColumn(offer)}
|
${createPercentageColumn(offer)}
|
||||||
${createActionColumn(offer, isActuallyExpired)}
|
${createActionColumn(offer, isActuallyExpired)}
|
||||||
${createTooltips(
|
${createTooltips(
|
||||||
offer,
|
offer,
|
||||||
isOwnOffer,
|
isOwnOffer,
|
||||||
coinFrom,
|
coinFrom,
|
||||||
coinTo,
|
coinTo,
|
||||||
fromAmount,
|
fromAmount,
|
||||||
toAmount,
|
toAmount,
|
||||||
postedTime,
|
postedTime,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
isActuallyExpired,
|
isActuallyExpired,
|
||||||
Boolean(isRevoked),
|
Boolean(isRevoked),
|
||||||
identity
|
identity
|
||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
updateTooltipTargets(row, uniqueId);
|
updateTooltipTargets(row, uniqueId);
|
||||||
updateProfitLoss(row, coinFrom, coinTo, fromAmount, toAmount, isOwnOffer);
|
updateProfitLoss(row, coinFrom, coinTo, fromAmount, toAmount, isOwnOffer);
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPrivateIndicator() {
|
function createPrivateIndicator() {
|
||||||
@@ -2090,7 +2072,7 @@ function createRecipientTooltip(uniqueId, identityInfo, identity, successRate, t
|
|||||||
` : ''}
|
` : ''}
|
||||||
|
|
||||||
${identity ? `
|
${identity ? `
|
||||||
<div class="border-t border-gray-400 pt-2 mt-2">
|
<div class= pt-2 mt-2">
|
||||||
<div class="text-white text-xs tracking-wide font-semibold mb-2">Swap History:</div>
|
<div class="text-white text-xs tracking-wide font-semibold mb-2">Swap History:</div>
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<div class="text-center p-2 bg-gray-500 rounded-md">
|
<div class="text-center p-2 bg-gray-500 rounded-md">
|
||||||
@@ -2299,7 +2281,6 @@ function applyFilters() {
|
|||||||
filterTimeout = setTimeout(() => {
|
filterTimeout = setTimeout(() => {
|
||||||
jsonData = filterAndSortData();
|
jsonData = filterAndSortData();
|
||||||
updateOffersTable();
|
updateOffersTable();
|
||||||
updateJsonView();
|
|
||||||
updatePaginationInfo();
|
updatePaginationInfo();
|
||||||
updateClearFiltersButton();
|
updateClearFiltersButton();
|
||||||
filterTimeout = null;
|
filterTimeout = null;
|
||||||
@@ -2317,7 +2298,6 @@ function clearFilters() {
|
|||||||
const selectElements = filterForm.querySelectorAll('select');
|
const selectElements = filterForm.querySelectorAll('select');
|
||||||
selectElements.forEach(select => {
|
selectElements.forEach(select => {
|
||||||
select.value = 'any';
|
select.value = 'any';
|
||||||
// Trigger change event
|
|
||||||
const event = new Event('change', { bubbles: true });
|
const event = new Event('change', { bubbles: true });
|
||||||
select.dispatchEvent(event);
|
select.dispatchEvent(event);
|
||||||
});
|
});
|
||||||
@@ -2331,7 +2311,6 @@ function clearFilters() {
|
|||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
|
|
||||||
updateOffersTable();
|
updateOffersTable();
|
||||||
updateJsonView();
|
|
||||||
updateCoinFilterImages();
|
updateCoinFilterImages();
|
||||||
updateClearFiltersButton();
|
updateClearFiltersButton();
|
||||||
}
|
}
|
||||||
@@ -2421,7 +2400,7 @@ function isOfferExpired(offer) {
|
|||||||
const currentTime = Math.floor(Date.now() / 1000);
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
const isExpired = offer.expire_at <= currentTime;
|
const isExpired = offer.expire_at <= currentTime;
|
||||||
if (isExpired) {
|
if (isExpired) {
|
||||||
console.log(`Offer ${offer.offer_id} is expired. Expire time: ${offer.expire_at}, Current time: ${currentTime}`);
|
// console.log(`Offer ${offer.offer_id} is expired. Expire time: ${offer.expire_at}, Current time: ${currentTime}`);
|
||||||
}
|
}
|
||||||
return isExpired;
|
return isExpired;
|
||||||
}
|
}
|
||||||
@@ -2582,7 +2561,6 @@ if (refreshButton) {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error('Unable to fetch price data');
|
throw new Error('Unable to fetch price data');
|
||||||
}
|
}
|
||||||
updateJsonView();
|
|
||||||
updatePaginationInfo();
|
updatePaginationInfo();
|
||||||
lastRefreshTime = now;
|
lastRefreshTime = now;
|
||||||
updateLastRefreshTime();
|
updateLastRefreshTime();
|
||||||
@@ -2653,29 +2631,50 @@ function handleTableSort(columnIndex, header) {
|
|||||||
currentSortDirection = 'desc';
|
currentSortDirection = 'desc';
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.sort-icon').forEach(icon => {
|
localStorage.setItem('offersTableSettings', JSON.stringify({
|
||||||
icon.classList.remove('text-blue-500');
|
coin_to: document.getElementById('coin_to').value,
|
||||||
icon.textContent = '↓';
|
coin_from: document.getElementById('coin_from').value,
|
||||||
});
|
status: document.getElementById('status')?.value || 'any',
|
||||||
|
sent_from: document.getElementById('sent_from').value,
|
||||||
const sortIcon = document.getElementById(`sort-icon-${columnIndex}`);
|
sortColumn: currentSortColumn,
|
||||||
if (sortIcon) {
|
sortDirection: currentSortDirection
|
||||||
sortIcon.textContent = currentSortDirection === 'asc' ? '↑' : '↓';
|
}));
|
||||||
sortIcon.classList.add('text-blue-500');
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll('th[data-sortable="true"]').forEach(th => {
|
document.querySelectorAll('th[data-sortable="true"]').forEach(th => {
|
||||||
if (th === header) {
|
const columnSpan = th.querySelector('span:not(.sort-icon)');
|
||||||
th.classList.add('text-blue-500');
|
const icon = th.querySelector('.sort-icon');
|
||||||
|
const thisColumnIndex = parseInt(th.getAttribute('data-column-index'));
|
||||||
|
|
||||||
|
if (thisColumnIndex === columnIndex) {
|
||||||
|
if (columnSpan) {
|
||||||
|
columnSpan.classList.remove('text-gray-600', 'dark:text-gray-300');
|
||||||
|
columnSpan.classList.add('text-blue-500', 'dark:text-blue-500');
|
||||||
|
}
|
||||||
|
if (icon) {
|
||||||
|
icon.classList.remove('text-gray-600', 'dark:text-gray-400');
|
||||||
|
icon.classList.add('text-blue-500', 'dark:text-blue-500');
|
||||||
|
icon.textContent = currentSortDirection === 'asc' ? '↑' : '↓';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
th.classList.remove('text-blue-500');
|
if (columnSpan) {
|
||||||
|
columnSpan.classList.remove('text-blue-500', 'dark:text-blue-500');
|
||||||
|
columnSpan.classList.add('text-gray-600', 'dark:text-gray-300');
|
||||||
|
}
|
||||||
|
if (icon) {
|
||||||
|
icon.classList.remove('text-blue-500', 'dark:text-blue-500');
|
||||||
|
icon.classList.add('text-gray-600', 'dark:text-gray-400');
|
||||||
|
icon.textContent = '↓';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
localStorage.setItem('tableSortColumn', currentSortColumn);
|
if (window.sortTimeout) {
|
||||||
localStorage.setItem('tableSortDirection', currentSortDirection);
|
clearTimeout(window.sortTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
applyFilters();
|
window.sortTimeout = setTimeout(() => {
|
||||||
|
applyFilters();
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TIMER MANAGEMENT
|
// TIMER MANAGEMENT
|
||||||
@@ -2713,72 +2712,93 @@ const timerManager = {
|
|||||||
|
|
||||||
// INITIALIZATION AND EVENT BINDING
|
// INITIALIZATION AND EVENT BINDING
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
console.log('DOM content loaded, initializing...');
|
const saved = localStorage.getItem('offersTableSettings');
|
||||||
console.log('View type:', isSentOffers ? 'sent offers' : 'received offers');
|
if (saved) {
|
||||||
|
const settings = JSON.parse(saved);
|
||||||
|
|
||||||
updateClearFiltersButton();
|
['coin_to', 'coin_from', 'status', 'sent_from'].forEach(id => {
|
||||||
initializeTableEvents();
|
const element = document.getElementById(id);
|
||||||
|
if (element && settings[id]) element.value = settings[id];
|
||||||
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log('Starting WebSocket initialization...');
|
|
||||||
WebSocketManager.initialize();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
if (initializeTableRateModule()) {
|
if (settings.sortColumn !== undefined) {
|
||||||
continueInitialization();
|
currentSortColumn = settings.sortColumn;
|
||||||
} else {
|
currentSortDirection = settings.sortDirection;
|
||||||
let retryCount = 0;
|
|
||||||
const maxRetries = 5;
|
|
||||||
const retryInterval = setInterval(() => {
|
|
||||||
retryCount++;
|
|
||||||
if (initializeTableRateModule()) {
|
|
||||||
clearInterval(retryInterval);
|
|
||||||
continueInitialization();
|
|
||||||
} else if (retryCount >= maxRetries) {
|
|
||||||
clearInterval(retryInterval);
|
|
||||||
continueInitialization();
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
timerManager.addInterval(() => {
|
|
||||||
if (WebSocketManager.isConnected()) {
|
|
||||||
console.log('WebSocket Status: Connected');
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
timerManager.addInterval(() => {
|
document.querySelectorAll('.sort-icon').forEach(icon => {
|
||||||
CacheManager.cleanup();
|
icon.classList.remove('text-blue-500');
|
||||||
}, 300000);
|
icon.textContent = '↓';
|
||||||
|
});
|
||||||
|
|
||||||
updateCoinFilterImages();
|
const sortIcon = document.getElementById(`sort-icon-${currentSortColumn}`);
|
||||||
fetchOffers().then(() => {
|
if (sortIcon) {
|
||||||
applyFilters();
|
sortIcon.textContent = currentSortDirection === 'asc' ? '↑' : '↓';
|
||||||
}).catch(error => {
|
sortIcon.classList.add('text-blue-500');
|
||||||
console.error('Error fetching initial offers:', error);
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const listingLabel = document.querySelector('span[data-listing-label]');
|
updateClearFiltersButton();
|
||||||
if (listingLabel) {
|
initializeTableEvents();
|
||||||
listingLabel.textContent = isSentOffers ? 'Total Listings: ' : 'Network Listings: ';
|
initializeTooltips();
|
||||||
}
|
updateCoinFilterImages();
|
||||||
|
|
||||||
timerManager.addInterval(updateRowTimes, 900000);
|
setTimeout(() => {
|
||||||
|
WebSocketManager.initialize();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
EventManager.add(document, 'visibilitychange', () => {
|
if (initializeTableRateModule()) {
|
||||||
if (!document.hidden) {
|
continueInitialization();
|
||||||
console.log('Page became visible, checking WebSocket connection');
|
} else {
|
||||||
if (!WebSocketManager.isConnected()) {
|
let retryCount = 0;
|
||||||
WebSocketManager.connect();
|
const maxRetries = 5;
|
||||||
}
|
const retryInterval = setInterval(() => {
|
||||||
}
|
retryCount++;
|
||||||
});
|
if (initializeTableRateModule()) {
|
||||||
|
clearInterval(retryInterval);
|
||||||
|
continueInitialization();
|
||||||
|
} else if (retryCount >= maxRetries) {
|
||||||
|
clearInterval(retryInterval);
|
||||||
|
continueInitialization();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
EventManager.add(window, 'beforeunload', () => {
|
timerManager.addInterval(() => {
|
||||||
cleanup();
|
if (WebSocketManager.isConnected()) {
|
||||||
});
|
console.log('🟢 WebSocket connection one established');
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
console.log('Initialization completed');
|
timerManager.addInterval(() => {
|
||||||
|
CacheManager.cleanup();
|
||||||
|
}, 300000);
|
||||||
|
|
||||||
|
timerManager.addInterval(updateRowTimes, 900000);
|
||||||
|
|
||||||
|
EventManager.add(document, 'visibilitychange', () => {
|
||||||
|
if (!document.hidden) {
|
||||||
|
if (!WebSocketManager.isConnected()) {
|
||||||
|
WebSocketManager.connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
EventManager.add(window, 'beforeunload', () => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
updateCoinFilterImages();
|
||||||
|
fetchOffers().then(() => {
|
||||||
|
applyFilters();
|
||||||
|
if (!isSentOffers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error fetching initial offers:', error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function cleanup() {
|
async function cleanup() {
|
||||||
@@ -2796,35 +2816,23 @@ async function cleanup() {
|
|||||||
console.error(`[Cleanup Error ${timeFromStart}ms] ${step}:`, error);
|
console.error(`[Cleanup Error ${timeFromStart}ms] ${step}:`, error);
|
||||||
this.errors.push({ step, error, time: timeFromStart });
|
this.errors.push({ step, error, time: timeFromStart });
|
||||||
},
|
},
|
||||||
summarize: function() {
|
summarizeLogs: function() {
|
||||||
const totalTime = Date.now() - this.startTime;
|
console.log('Cleanup Summary:');
|
||||||
console.group('Cleanup Summary');
|
console.log(`Total cleanup time: ${Date.now() - this.startTime}ms`);
|
||||||
console.log(`Total cleanup time: ${totalTime}ms`);
|
console.log(`Steps completed: ${this.steps.length}`);
|
||||||
console.log('Steps completed:', this.steps.length);
|
console.log(`Errors encountered: ${this.errors.length}`);
|
||||||
console.log('Errors encountered:', this.errors.length);
|
|
||||||
|
|
||||||
if (this.steps.length > 0) {
|
|
||||||
console.group('Steps Timeline');
|
|
||||||
this.steps.forEach(({step, time}) => {
|
|
||||||
console.log(`${time}ms - ${step}`);
|
|
||||||
});
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.errors.length > 0) {
|
|
||||||
console.group('Errors');
|
|
||||||
this.errors.forEach(({step, error, time}) => {
|
|
||||||
console.log(`${time}ms - ${step}:`, error);
|
|
||||||
});
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
|
||||||
console.groupEnd();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
debug.addStep('Starting cleanup process');
|
debug.addStep('Starting cleanup process');
|
||||||
|
|
||||||
|
debug.addStep('Starting tooltip cleanup');
|
||||||
|
if (window.TooltipManager) {
|
||||||
|
window.TooltipManager.cleanup();
|
||||||
|
}
|
||||||
|
debug.addStep('Tooltip cleanup completed');
|
||||||
|
|
||||||
debug.addStep('Clearing timers');
|
debug.addStep('Clearing timers');
|
||||||
const timerCount = timerManager.intervals.length + timerManager.timeouts.length;
|
const timerCount = timerManager.intervals.length + timerManager.timeouts.length;
|
||||||
timerManager.clearAll();
|
timerManager.clearAll();
|
||||||
@@ -2863,16 +2871,9 @@ async function cleanup() {
|
|||||||
debug.addStep('Global state reset', globals);
|
debug.addStep('Global state reset', globals);
|
||||||
|
|
||||||
debug.addStep('Clearing global references');
|
debug.addStep('Clearing global references');
|
||||||
if (window.WebSocketManager) {
|
[
|
||||||
window.WebSocketManager = null;
|
'WebSocketManager',
|
||||||
}
|
'tableRateModule',
|
||||||
if (window.tableRateModule) {
|
|
||||||
window.tableRateModule = null;
|
|
||||||
}
|
|
||||||
debug.addStep('Global references cleared');
|
|
||||||
|
|
||||||
debug.addStep('Clearing DOM references');
|
|
||||||
const elementRefs = [
|
|
||||||
'offersBody',
|
'offersBody',
|
||||||
'filterForm',
|
'filterForm',
|
||||||
'prevPageButton',
|
'prevPageButton',
|
||||||
@@ -2881,25 +2882,24 @@ async function cleanup() {
|
|||||||
'totalPagesSpan',
|
'totalPagesSpan',
|
||||||
'lastRefreshTimeSpan',
|
'lastRefreshTimeSpan',
|
||||||
'newEntriesCountSpan'
|
'newEntriesCountSpan'
|
||||||
];
|
].forEach(ref => {
|
||||||
let clearedRefs = 0;
|
|
||||||
elementRefs.forEach(ref => {
|
|
||||||
if (window[ref]) {
|
if (window[ref]) {
|
||||||
window[ref] = null;
|
window[ref] = null;
|
||||||
clearedRefs++;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
debug.addStep('DOM references cleared', `Cleared ${clearedRefs} references`);
|
debug.addStep('Global references cleared');
|
||||||
|
|
||||||
debug.addStep('Clearing tooltips');
|
debug.addStep('Cleaning up tooltip containers');
|
||||||
const tooltips = document.querySelectorAll('[role="tooltip"]');
|
const tooltipContainers = document.querySelectorAll('.tooltip-container');
|
||||||
const tooltipCount = tooltips.length;
|
tooltipContainers.forEach(container => {
|
||||||
tooltips.forEach(tooltip => tooltip.remove());
|
if (container && container.parentNode) {
|
||||||
debug.addStep('Tooltips cleared', `Removed ${tooltipCount} tooltips`);
|
container.parentNode.removeChild(container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
debug.addStep('Tooltip containers cleaned up');
|
||||||
|
|
||||||
debug.addStep('Clearing document/window events');
|
debug.addStep('Clearing document/window events');
|
||||||
const events = ['visibilitychange', 'beforeunload', 'scroll'];
|
['visibilitychange', 'beforeunload', 'scroll'].forEach(event => {
|
||||||
events.forEach(event => {
|
|
||||||
document.removeEventListener(event, null);
|
document.removeEventListener(event, null);
|
||||||
window.removeEventListener(event, null);
|
window.removeEventListener(event, null);
|
||||||
});
|
});
|
||||||
@@ -2919,6 +2919,9 @@ async function cleanup() {
|
|||||||
|
|
||||||
debug.addStep('Starting failsafe cleanup');
|
debug.addStep('Starting failsafe cleanup');
|
||||||
try {
|
try {
|
||||||
|
if (window.TooltipManager) {
|
||||||
|
window.TooltipManager.cleanup();
|
||||||
|
}
|
||||||
WebSocketManager.cleanup();
|
WebSocketManager.cleanup();
|
||||||
EventManager.clearAll();
|
EventManager.clearAll();
|
||||||
timerManager.clearAll();
|
timerManager.clearAll();
|
||||||
@@ -2931,21 +2934,10 @@ async function cleanup() {
|
|||||||
debug.addError('Critical failsafe cleanup', criticalError);
|
debug.addError('Critical failsafe cleanup', criticalError);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
debug.addStep('Running final cleanups');
|
debug.summarizeLogs();
|
||||||
if (document.body.classList.contains('optimize-scroll')) {
|
|
||||||
document.body.classList.remove('optimize-scroll');
|
|
||||||
}
|
|
||||||
if (document.body.classList.contains('is-scrolling')) {
|
|
||||||
document.body.classList.remove('is-scrolling');
|
|
||||||
}
|
|
||||||
const inlineStyles = document.querySelectorAll('style[data-dynamic]');
|
|
||||||
inlineStyles.forEach(style => style.remove());
|
|
||||||
debug.addStep('Final cleanups completed');
|
|
||||||
|
|
||||||
debug.summarize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.cleanup = cleanup;
|
window.cleanup = cleanup;
|
||||||
|
|
||||||
console.log('Offers Table Module fully initialized');
|
//console.log('Offers Table Module fully initialized');
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ const api = {
|
|||||||
const cachedData = cache.get(cacheKey);
|
const cachedData = cache.get(cacheKey);
|
||||||
|
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
console.log('Using cached CoinGecko data');
|
//console.log('Using cached CoinGecko data');
|
||||||
return cachedData.value;
|
return cachedData.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ const api = {
|
|||||||
|
|
||||||
if (typeof response !== 'object' || response === null) {
|
if (typeof response !== 'object' || response === null) {
|
||||||
if (fallbackData) {
|
if (fallbackData) {
|
||||||
console.log('Using fallback data due to invalid response');
|
//console.log('Using fallback data due to invalid response');
|
||||||
return fallbackData;
|
return fallbackData;
|
||||||
}
|
}
|
||||||
throw new AppError('Invalid data structure received from CoinGecko');
|
throw new AppError('Invalid data structure received from CoinGecko');
|
||||||
@@ -202,7 +202,7 @@ const api = {
|
|||||||
|
|
||||||
if (response.error || response.Error) {
|
if (response.error || response.Error) {
|
||||||
if (fallbackData) {
|
if (fallbackData) {
|
||||||
console.log('Using fallback data due to API error');
|
//console.log('Using fallback data due to API error');
|
||||||
return fallbackData;
|
return fallbackData;
|
||||||
}
|
}
|
||||||
throw new AppError(response.error || response.Error);
|
throw new AppError(response.error || response.Error);
|
||||||
@@ -229,7 +229,7 @@ const api = {
|
|||||||
|
|
||||||
const cachedData = cache.get(cacheKey);
|
const cachedData = cache.get(cacheKey);
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
console.log('Using expired cache data due to error');
|
//console.log('Using expired cache data due to error');
|
||||||
return cachedData.value;
|
return cachedData.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ const rateLimiter = {
|
|||||||
error.name === 'NetworkError') {
|
error.name === 'NetworkError') {
|
||||||
const cachedData = cache.get(`coinData_${apiName}`);
|
const cachedData = cache.get(`coinData_${apiName}`);
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
console.log('Using cached data due to request failure');
|
//console.log('Using cached data due to request failure');
|
||||||
return cachedData.value;
|
return cachedData.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,7 +421,7 @@ const cache = {
|
|||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing cache item:', error.message);
|
//console.error('Error parsing cache item:', error.message);
|
||||||
localStorage.removeItem(key);
|
localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -912,7 +912,7 @@ const chartModule = {
|
|||||||
let data;
|
let data;
|
||||||
if (cachedData && Object.keys(cachedData.value).length > 0) {
|
if (cachedData && Object.keys(cachedData.value).length > 0) {
|
||||||
data = cachedData.value;
|
data = cachedData.value;
|
||||||
console.log(`Using cached data for ${coinSymbol}`);
|
//console.log(`Using cached data for ${coinSymbol}`);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const allData = await api.fetchHistoricalDataXHR([coinSymbol]);
|
const allData = await api.fetchHistoricalDataXHR([coinSymbol]);
|
||||||
@@ -923,7 +923,7 @@ const chartModule = {
|
|||||||
cache.set(cacheKey, data, config.cacheTTL);
|
cache.set(cacheKey, data, config.cacheTTL);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message.includes('429') && currentChartData.length > 0) {
|
if (error.message.includes('429') && currentChartData.length > 0) {
|
||||||
console.warn(`Rate limit hit for ${coinSymbol}, maintaining current chart`);
|
//console.warn(`Rate limit hit for ${coinSymbol}, maintaining current chart`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const expiredCache = localStorage.getItem(cacheKey);
|
const expiredCache = localStorage.getItem(cacheKey);
|
||||||
@@ -931,7 +931,7 @@ const chartModule = {
|
|||||||
try {
|
try {
|
||||||
const parsedCache = JSON.parse(expiredCache);
|
const parsedCache = JSON.parse(expiredCache);
|
||||||
data = parsedCache.value;
|
data = parsedCache.value;
|
||||||
console.log(`Using expired cache data for ${coinSymbol}`);
|
//console.log(`Using expired cache data for ${coinSymbol}`);
|
||||||
} catch (cacheError) {
|
} catch (cacheError) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@@ -1042,15 +1042,15 @@ const app = {
|
|||||||
minimumRefreshInterval: 60 * 1000, // 1 min
|
minimumRefreshInterval: 60 * 1000, // 1 min
|
||||||
|
|
||||||
init: () => {
|
init: () => {
|
||||||
console.log('Initializing app...');
|
//console.log('Init');
|
||||||
window.addEventListener('load', app.onLoad);
|
window.addEventListener('load', app.onLoad);
|
||||||
app.loadLastRefreshedTime();
|
app.loadLastRefreshedTime();
|
||||||
app.updateAutoRefreshButton();
|
app.updateAutoRefreshButton();
|
||||||
console.log('App initialized');
|
//console.log('App initialized');
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoad: async () => {
|
onLoad: async () => {
|
||||||
console.log('App onLoad event triggered');
|
//console.log('App onLoad event triggered');
|
||||||
ui.showLoader();
|
ui.showLoader();
|
||||||
try {
|
try {
|
||||||
volumeToggle.init();
|
volumeToggle.init();
|
||||||
@@ -1061,7 +1061,7 @@ const app = {
|
|||||||
chartModule.showChartLoader();
|
chartModule.showChartLoader();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Loading all coin data...');
|
//console.log('Loading all coin data...');
|
||||||
await app.loadAllCoinData();
|
await app.loadAllCoinData();
|
||||||
|
|
||||||
if (chartModule.chart) {
|
if (chartModule.chart) {
|
||||||
@@ -1087,7 +1087,7 @@ const app = {
|
|||||||
if (chartModule.chart) {
|
if (chartModule.chart) {
|
||||||
chartModule.hideChartLoader();
|
chartModule.hideChartLoader();
|
||||||
}
|
}
|
||||||
console.log('App onLoad completed');
|
//console.log('App onLoad completed');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loadAllCoinData: async () => {
|
loadAllCoinData: async () => {
|
||||||
@@ -1192,7 +1192,7 @@ setupEventListeners: () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
initAutoRefresh: () => {
|
initAutoRefresh: () => {
|
||||||
console.log('Initializing auto-refresh...');
|
//console.log('Initializing auto-refresh...');
|
||||||
const toggleAutoRefreshButton = document.getElementById('toggle-auto-refresh');
|
const toggleAutoRefreshButton = document.getElementById('toggle-auto-refresh');
|
||||||
if (toggleAutoRefreshButton) {
|
if (toggleAutoRefreshButton) {
|
||||||
toggleAutoRefreshButton.addEventListener('click', app.toggleAutoRefresh);
|
toggleAutoRefreshButton.addEventListener('click', app.toggleAutoRefresh);
|
||||||
@@ -1208,7 +1208,7 @@ setupEventListeners: () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
scheduleNextRefresh: () => {
|
scheduleNextRefresh: () => {
|
||||||
console.log('Scheduling next refresh...');
|
//console.log('Scheduling next refresh...');
|
||||||
if (app.autoRefreshInterval) {
|
if (app.autoRefreshInterval) {
|
||||||
clearTimeout(app.autoRefreshInterval);
|
clearTimeout(app.autoRefreshInterval);
|
||||||
}
|
}
|
||||||
@@ -1274,7 +1274,7 @@ setupEventListeners: () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Starting refresh of all data...');
|
//console.log('Starting refresh of all data...');
|
||||||
app.isRefreshing = true;
|
app.isRefreshing = true;
|
||||||
ui.showLoader();
|
ui.showLoader();
|
||||||
chartModule.showChartLoader();
|
chartModule.showChartLoader();
|
||||||
@@ -1381,7 +1381,7 @@ setupEventListeners: () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateNextRefreshTime: () => {
|
updateNextRefreshTime: () => {
|
||||||
console.log('Updating next refresh time display');
|
//console.log('Updating next refresh time display');
|
||||||
const nextRefreshSpan = document.getElementById('next-refresh-time');
|
const nextRefreshSpan = document.getElementById('next-refresh-time');
|
||||||
const labelElement = document.getElementById('next-refresh-label');
|
const labelElement = document.getElementById('next-refresh-label');
|
||||||
const valueElement = document.getElementById('next-refresh-value');
|
const valueElement = document.getElementById('next-refresh-value');
|
||||||
@@ -1417,7 +1417,7 @@ setupEventListeners: () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateAutoRefreshButton: () => {
|
updateAutoRefreshButton: () => {
|
||||||
console.log('Updating auto-refresh button state');
|
//console.log('Updating auto-refresh button state');
|
||||||
const button = document.getElementById('toggle-auto-refresh');
|
const button = document.getElementById('toggle-auto-refresh');
|
||||||
if (button) {
|
if (button) {
|
||||||
if (app.isAutoRefreshEnabled) {
|
if (app.isAutoRefreshEnabled) {
|
||||||
@@ -1462,7 +1462,7 @@ setupEventListeners: () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadLastRefreshedTime: () => {
|
loadLastRefreshedTime: () => {
|
||||||
console.log('Loading last refreshed time from storage');
|
//console.log('Loading last refreshed time from storage');
|
||||||
const storedTime = localStorage.getItem('lastRefreshedTime');
|
const storedTime = localStorage.getItem('lastRefreshedTime');
|
||||||
if (storedTime) {
|
if (storedTime) {
|
||||||
app.lastRefreshedTime = new Date(parseInt(storedTime));
|
app.lastRefreshedTime = new Date(parseInt(storedTime));
|
||||||
@@ -1471,7 +1471,7 @@ setupEventListeners: () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateBTCPrice: async () => {
|
updateBTCPrice: async () => {
|
||||||
console.log('Updating BTC price...');
|
//console.log('Updating BTC price...');
|
||||||
try {
|
try {
|
||||||
const priceData = await api.fetchCoinGeckoDataXHR();
|
const priceData = await api.fetchCoinGeckoDataXHR();
|
||||||
|
|
||||||
|
|||||||
+278
-281
@@ -1,309 +1,306 @@
|
|||||||
(function(window) {
|
class TooltipManager {
|
||||||
'use strict';
|
constructor() {
|
||||||
|
this.activeTooltips = new Map();
|
||||||
const tooltipContainer = document.createElement('div');
|
this.sizeCheckIntervals = new Map();
|
||||||
tooltipContainer.className = 'tooltip-container';
|
this.setupStyles();
|
||||||
|
this.setupCleanupEvents();
|
||||||
const style = document.createElement('style');
|
|
||||||
style.textContent = `
|
|
||||||
[role="tooltip"] {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 9999;
|
|
||||||
transition: opacity 0.2s ease-in-out;
|
|
||||||
pointer-events: auto;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 0;
|
|
||||||
overflow: visible;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function ensureContainerExists() {
|
|
||||||
if (!document.body.contains(tooltipContainer)) {
|
|
||||||
document.body.appendChild(tooltipContainer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static initialize() {
|
||||||
|
if (!window.TooltipManager) {
|
||||||
|
window.TooltipManager = new TooltipManager();
|
||||||
|
}
|
||||||
|
return window.TooltipManager;
|
||||||
|
}
|
||||||
|
|
||||||
function rafThrottle(callback) {
|
create(element, content, options = {}) {
|
||||||
let requestId = null;
|
if (!element) return null;
|
||||||
let lastArgs = null;
|
|
||||||
|
|
||||||
const later = (context) => {
|
this.destroy(element);
|
||||||
requestId = null;
|
|
||||||
callback.apply(context, lastArgs);
|
|
||||||
};
|
|
||||||
|
|
||||||
return function(...args) {
|
const checkSize = () => {
|
||||||
lastArgs = args;
|
const rect = element.getBoundingClientRect();
|
||||||
if (requestId === null) {
|
if (rect.width && rect.height) {
|
||||||
requestId = requestAnimationFrame(() => later(this));
|
clearInterval(this.sizeCheckIntervals.get(element));
|
||||||
|
this.sizeCheckIntervals.delete(element);
|
||||||
|
this.createTooltip(element, content, options, rect);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.sizeCheckIntervals.set(element, setInterval(checkSize, 50));
|
||||||
|
checkSize();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function positionElement(targetEl, triggerEl, placement = 'top', offsetDistance = 8) {
|
createTooltip(element, content, options, rect) {
|
||||||
const triggerRect = triggerEl.getBoundingClientRect();
|
const targetId = element.getAttribute('data-tooltip-target');
|
||||||
const targetRect = targetEl.getBoundingClientRect();
|
let bgClass = 'bg-gray-400';
|
||||||
let top, left;
|
let arrowColor = 'rgb(156 163 175)';
|
||||||
|
|
||||||
switch (placement) {
|
if (targetId?.includes('tooltip-offer-')) {
|
||||||
case 'top':
|
const offerId = targetId.split('tooltip-offer-')[1];
|
||||||
top = triggerRect.top - targetRect.height - offsetDistance;
|
const [actualOfferId] = offerId.split('_');
|
||||||
left = triggerRect.left + (triggerRect.width - targetRect.width) / 2;
|
|
||||||
break;
|
|
||||||
case 'bottom':
|
|
||||||
top = triggerRect.bottom + offsetDistance;
|
|
||||||
left = triggerRect.left + (triggerRect.width - targetRect.width) / 2;
|
|
||||||
break;
|
|
||||||
case 'left':
|
|
||||||
top = triggerRect.top + (triggerRect.height - targetRect.height) / 2;
|
|
||||||
left = triggerRect.left - targetRect.width - offsetDistance;
|
|
||||||
break;
|
|
||||||
case 'right':
|
|
||||||
top = triggerRect.top + (triggerRect.height - targetRect.height) / 2;
|
|
||||||
left = triggerRect.right + offsetDistance;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewport = {
|
if (window.jsonData) {
|
||||||
width: window.innerWidth,
|
const offer = window.jsonData.find(o =>
|
||||||
height: window.innerHeight
|
o.unique_id === offerId ||
|
||||||
};
|
o.offer_id === actualOfferId
|
||||||
|
|
||||||
if (left < 0) left = 0;
|
|
||||||
if (top < 0) top = 0;
|
|
||||||
if (left + targetRect.width > viewport.width)
|
|
||||||
left = viewport.width - targetRect.width;
|
|
||||||
if (top + targetRect.height > viewport.height)
|
|
||||||
top = viewport.height - targetRect.height;
|
|
||||||
|
|
||||||
targetEl.style.transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tooltips = new WeakMap();
|
|
||||||
|
|
||||||
class Tooltip {
|
|
||||||
constructor(targetEl, triggerEl, options = {}) {
|
|
||||||
ensureContainerExists();
|
|
||||||
|
|
||||||
this._targetEl = targetEl;
|
|
||||||
this._triggerEl = triggerEl;
|
|
||||||
this._options = {
|
|
||||||
placement: options.placement || 'top',
|
|
||||||
triggerType: options.triggerType || 'hover',
|
|
||||||
offset: options.offset || 8,
|
|
||||||
onShow: options.onShow || function() {},
|
|
||||||
onHide: options.onHide || function() {}
|
|
||||||
};
|
|
||||||
this._visible = false;
|
|
||||||
this._initialized = false;
|
|
||||||
this._hideTimeout = null;
|
|
||||||
this._showTimeout = null;
|
|
||||||
|
|
||||||
if (this._targetEl.parentNode !== tooltipContainer) {
|
|
||||||
tooltipContainer.appendChild(this._targetEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._targetEl.style.visibility = 'hidden';
|
|
||||||
this._targetEl.style.opacity = '0';
|
|
||||||
|
|
||||||
this._showHandler = this.show.bind(this);
|
|
||||||
this._hideHandler = this._handleHide.bind(this);
|
|
||||||
this._updatePosition = rafThrottle(() => {
|
|
||||||
if (this._visible) {
|
|
||||||
positionElement(
|
|
||||||
this._targetEl,
|
|
||||||
this._triggerEl,
|
|
||||||
this._options.placement,
|
|
||||||
this._options.offset
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
if (!this._initialized) {
|
|
||||||
this._setupEventListeners();
|
|
||||||
this._initialized = true;
|
|
||||||
positionElement(
|
|
||||||
this._targetEl,
|
|
||||||
this._triggerEl,
|
|
||||||
this._options.placement,
|
|
||||||
this._options.offset
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_setupEventListeners() {
|
if (offer) {
|
||||||
this._triggerEl.addEventListener('mouseenter', this._showHandler);
|
if (offer.is_revoked) {
|
||||||
this._triggerEl.addEventListener('mouseleave', this._hideHandler);
|
bgClass = 'bg-red-500';
|
||||||
this._triggerEl.addEventListener('focus', this._showHandler);
|
arrowColor = 'rgb(239 68 68)';
|
||||||
this._triggerEl.addEventListener('blur', this._hideHandler);
|
} else if (offer.is_own_offer) {
|
||||||
|
bgClass = 'bg-gray-300';
|
||||||
this._targetEl.addEventListener('mouseenter', () => {
|
arrowColor = 'rgb(209 213 219)';
|
||||||
clearTimeout(this._hideTimeout);
|
} else {
|
||||||
clearTimeout(this._showTimeout);
|
bgClass = 'bg-green-700';
|
||||||
this._visible = true;
|
arrowColor = 'rgb(21 128 61)';
|
||||||
this._targetEl.style.visibility = 'visible';
|
}
|
||||||
this._targetEl.style.opacity = '1';
|
|
||||||
});
|
|
||||||
|
|
||||||
this._targetEl.addEventListener('mouseleave', this._hideHandler);
|
|
||||||
|
|
||||||
if (this._options.triggerType === 'click') {
|
|
||||||
this._triggerEl.addEventListener('click', this._showHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('scroll', this._updatePosition, { passive: true });
|
|
||||||
document.addEventListener('scroll', this._updatePosition, { passive: true, capture: true });
|
|
||||||
window.addEventListener('resize', this._updatePosition, { passive: true });
|
|
||||||
|
|
||||||
let rafId;
|
|
||||||
const smoothUpdate = () => {
|
|
||||||
if (this._visible) {
|
|
||||||
this._updatePosition();
|
|
||||||
rafId = requestAnimationFrame(smoothUpdate);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
this._startSmoothUpdate = () => {
|
|
||||||
if (!rafId) rafId = requestAnimationFrame(smoothUpdate);
|
|
||||||
};
|
|
||||||
|
|
||||||
this._stopSmoothUpdate = () => {
|
|
||||||
if (rafId) {
|
|
||||||
cancelAnimationFrame(rafId);
|
|
||||||
rafId = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_handleHide() {
|
|
||||||
clearTimeout(this._hideTimeout);
|
|
||||||
clearTimeout(this._showTimeout);
|
|
||||||
|
|
||||||
this._hideTimeout = setTimeout(() => {
|
|
||||||
if (this._visible) {
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
show() {
|
|
||||||
clearTimeout(this._hideTimeout);
|
|
||||||
clearTimeout(this._showTimeout);
|
|
||||||
|
|
||||||
this._showTimeout = setTimeout(() => {
|
|
||||||
if (!this._visible) {
|
|
||||||
positionElement(
|
|
||||||
this._targetEl,
|
|
||||||
this._triggerEl,
|
|
||||||
this._options.placement,
|
|
||||||
this._options.offset
|
|
||||||
);
|
|
||||||
|
|
||||||
this._targetEl.style.visibility = 'visible';
|
|
||||||
this._targetEl.style.opacity = '1';
|
|
||||||
this._visible = true;
|
|
||||||
this._startSmoothUpdate();
|
|
||||||
this._options.onShow();
|
|
||||||
}
|
|
||||||
}, 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
hide() {
|
|
||||||
this._targetEl.style.opacity = '0';
|
|
||||||
this._targetEl.style.visibility = 'hidden';
|
|
||||||
this._visible = false;
|
|
||||||
this._stopSmoothUpdate();
|
|
||||||
this._options.onHide();
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
clearTimeout(this._hideTimeout);
|
|
||||||
clearTimeout(this._showTimeout);
|
|
||||||
this._stopSmoothUpdate();
|
|
||||||
|
|
||||||
this._triggerEl.removeEventListener('mouseenter', this._showHandler);
|
|
||||||
this._triggerEl.removeEventListener('mouseleave', this._hideHandler);
|
|
||||||
this._triggerEl.removeEventListener('focus', this._showHandler);
|
|
||||||
this._triggerEl.removeEventListener('blur', this._hideHandler);
|
|
||||||
this._targetEl.removeEventListener('mouseenter', this._showHandler);
|
|
||||||
this._targetEl.removeEventListener('mouseleave', this._hideHandler);
|
|
||||||
|
|
||||||
if (this._options.triggerType === 'click') {
|
|
||||||
this._triggerEl.removeEventListener('click', this._showHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.removeEventListener('scroll', this._updatePosition);
|
|
||||||
document.removeEventListener('scroll', this._updatePosition, true);
|
|
||||||
window.removeEventListener('resize', this._updatePosition);
|
|
||||||
|
|
||||||
this._targetEl.style.visibility = '';
|
|
||||||
this._targetEl.style.opacity = '';
|
|
||||||
this._targetEl.style.transform = '';
|
|
||||||
|
|
||||||
if (this._targetEl.parentNode === tooltipContainer) {
|
|
||||||
document.body.appendChild(this._targetEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._initialized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggle() {
|
|
||||||
if (this._visible) {
|
|
||||||
this.hide();
|
|
||||||
} else {
|
|
||||||
this.show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
document.head.appendChild(style);
|
const instance = tippy(element, {
|
||||||
|
content,
|
||||||
|
allowHTML: true,
|
||||||
|
placement: options.placement || 'top',
|
||||||
|
appendTo: document.body,
|
||||||
|
animation: false,
|
||||||
|
duration: 0,
|
||||||
|
delay: 0,
|
||||||
|
interactive: true,
|
||||||
|
arrow: false,
|
||||||
|
theme: '',
|
||||||
|
moveTransition: 'none',
|
||||||
|
offset: [0, 10],
|
||||||
|
popperOptions: {
|
||||||
|
strategy: 'fixed',
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: 'preventOverflow',
|
||||||
|
options: {
|
||||||
|
boundary: 'viewport',
|
||||||
|
padding: 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'flip',
|
||||||
|
options: {
|
||||||
|
padding: 10,
|
||||||
|
fallbackPlacements: ['top', 'bottom', 'right', 'left']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
onCreate(instance) {
|
||||||
|
instance._originalPlacement = instance.props.placement;
|
||||||
|
},
|
||||||
|
onShow(instance) {
|
||||||
|
if (!document.body.contains(element)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function initTooltips() {
|
const rect = element.getBoundingClientRect();
|
||||||
ensureContainerExists();
|
if (!rect.width || !rect.height) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
document.querySelectorAll('[data-tooltip-target]').forEach(triggerEl => {
|
instance.setProps({
|
||||||
if (tooltips.has(triggerEl)) return;
|
placement: instance._originalPlacement
|
||||||
|
|
||||||
const targetId = triggerEl.getAttribute('data-tooltip-target');
|
|
||||||
const targetEl = document.getElementById(targetId);
|
|
||||||
|
|
||||||
if (targetEl) {
|
|
||||||
const placement = triggerEl.getAttribute('data-tooltip-placement');
|
|
||||||
const triggerType = triggerEl.getAttribute('data-tooltip-trigger');
|
|
||||||
|
|
||||||
const tooltip = new Tooltip(targetEl, triggerEl, {
|
|
||||||
placement: placement || 'top',
|
|
||||||
triggerType: triggerType || 'hover',
|
|
||||||
offset: 8
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tooltips.set(triggerEl, tooltip);
|
if (instance.popper.firstElementChild) {
|
||||||
|
instance.popper.firstElementChild.classList.add(bgClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onMount(instance) {
|
||||||
|
if (instance.popper.firstElementChild) {
|
||||||
|
instance.popper.firstElementChild.classList.add(bgClass);
|
||||||
|
}
|
||||||
|
const arrow = instance.popper.querySelector('.tippy-arrow');
|
||||||
|
if (arrow) {
|
||||||
|
arrow.style.setProperty('color', arrowColor, 'important');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = element.getAttribute('data-tooltip-trigger-id') ||
|
||||||
|
`tooltip-${Math.random().toString(36).substring(7)}`;
|
||||||
|
element.setAttribute('data-tooltip-trigger-id', id);
|
||||||
|
this.activeTooltips.set(id, instance);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(element) {
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
if (this.sizeCheckIntervals.has(element)) {
|
||||||
|
clearInterval(this.sizeCheckIntervals.get(element));
|
||||||
|
this.sizeCheckIntervals.delete(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = element.getAttribute('data-tooltip-trigger-id');
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
const instance = this.activeTooltips.get(id);
|
||||||
|
if (instance?.[0]) {
|
||||||
|
try {
|
||||||
|
instance[0].destroy();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Error destroying tooltip:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.activeTooltips.delete(id);
|
||||||
|
element.removeAttribute('data-tooltip-trigger-id');
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
this.sizeCheckIntervals.forEach((interval) => clearInterval(interval));
|
||||||
|
this.sizeCheckIntervals.clear();
|
||||||
|
|
||||||
|
this.activeTooltips.forEach((instance, id) => {
|
||||||
|
if (instance?.[0]) {
|
||||||
|
try {
|
||||||
|
instance[0].destroy();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Error cleaning up tooltip:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.activeTooltips.clear();
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-tippy-root]').forEach(element => {
|
||||||
|
if (element.parentNode) {
|
||||||
|
element.parentNode.removeChild(element);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
setupStyles() {
|
||||||
document.addEventListener('DOMContentLoaded', initTooltips);
|
if (document.getElementById('tooltip-styles')) return;
|
||||||
} else {
|
|
||||||
initTooltips();
|
document.head.insertAdjacentHTML('beforeend', `
|
||||||
|
<style id="tooltip-styles">
|
||||||
|
[data-tippy-root] {
|
||||||
|
position: fixed !important;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
color: white;
|
||||||
|
position: relative !important;
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-content {
|
||||||
|
padding: 0.5rem 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box .bg-gray-400 {
|
||||||
|
background-color: rgb(156 163 175);
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
.tippy-box:has(.bg-gray-400) .tippy-arrow {
|
||||||
|
color: rgb(156 163 175);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box .bg-red-500 {
|
||||||
|
background-color: rgb(239 68 68);
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
.tippy-box:has(.bg-red-500) .tippy-arrow {
|
||||||
|
color: rgb(239 68 68);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box .bg-gray-300 {
|
||||||
|
background-color: rgb(209 213 219);
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
.tippy-box:has(.bg-gray-300) .tippy-arrow {
|
||||||
|
color: rgb(209 213 219);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box .bg-green-700 {
|
||||||
|
background-color: rgb(21 128 61);
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
}
|
||||||
|
.tippy-box:has(.bg-green-700) .tippy-arrow {
|
||||||
|
color: rgb(21 128 61);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-placement^='top'] > .tippy-arrow::before {
|
||||||
|
border-top-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-placement^='bottom'] > .tippy-arrow::before {
|
||||||
|
border-bottom-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-placement^='left'] > .tippy-arrow::before {
|
||||||
|
border-left-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-placement^='right'] > .tippy-arrow::before {
|
||||||
|
border-right-color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-placement^='top'] > .tippy-arrow {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-placement^='bottom'] > .tippy-arrow {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-placement^='left'] > .tippy-arrow {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-placement^='right'] > .tippy-arrow {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Tooltip = Tooltip;
|
setupCleanupEvents() {
|
||||||
window.initTooltips = initTooltips;
|
window.addEventListener('beforeunload', () => this.cleanup());
|
||||||
|
window.addEventListener('unload', () => this.cleanup());
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
this.cleanup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
})(window);
|
initializeTooltips(selector = '[data-tooltip-target]') {
|
||||||
|
document.querySelectorAll(selector).forEach(element => {
|
||||||
|
const targetId = element.getAttribute('data-tooltip-target');
|
||||||
|
const tooltipContent = document.getElementById(targetId);
|
||||||
|
|
||||||
|
if (tooltipContent) {
|
||||||
|
this.create(element, tooltipContent.innerHTML, {
|
||||||
|
placement: element.getAttribute('data-tooltip-placement') || 'top'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = TooltipManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
TooltipManager.initialize();
|
||||||
|
});
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
<li>
|
<li>
|
||||||
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/active">Swaps In Progress</a>
|
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/active">Swaps In Progress</a>
|
||||||
</li>
|
</li>
|
||||||
<li>{{ breadcrumb_line_svg | safe }}</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+310
-118
@@ -1,43 +1,60 @@
|
|||||||
{% include 'header.html' %}
|
{% include 'header.html' %}
|
||||||
{% from 'style.html' import breadcrumb_line_svg, page_back_svg, page_forwards_svg, filter_clear_svg, filter_apply_svg, circular_arrows_svg, input_arrow_down_svg %}
|
{% from 'style.html' import breadcrumb_line_svg, page_back_svg, page_forwards_svg, filter_clear_svg, filter_apply_svg, circular_arrows_svg, input_arrow_down_svg %}
|
||||||
|
|
||||||
<div class="container mx-auto">
|
<div class="container mx-auto">
|
||||||
<section class="p-5 mt-5">
|
<section class="p-5 mt-5">
|
||||||
<div class="flex flex-wrap items-center -m-2">
|
<div class="flex flex-wrap items-center -m-2">
|
||||||
<div class="w-full md:w-1/2 p-2">
|
<div class="w-full md:w-1/2 p-2">
|
||||||
<ul class="flex flex-wrap items-center gap-x-3 mb-2">
|
<ul class="flex flex-wrap items-center gap-x-3 mb-2">
|
||||||
<li>
|
<li><a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/"><p>Home</p></a></li>
|
||||||
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/"><p>Home</p></a>
|
<li>{{ breadcrumb_line_svg | safe }}</li>
|
||||||
</li>
|
<li><a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="#">Sent Bids / Received Bids</a></li>
|
||||||
<li> {{ breadcrumb_line_svg | safe }} </li>
|
|
||||||
<li> <a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="#">{{ page_type_available }} {{ page_type_received }} {{ page_type_sent }}</a> </li>
|
|
||||||
<li> {{ breadcrumb_line_svg | safe }} </li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="py-4">
|
<section class="py-4">
|
||||||
<div class="container px-4 mx-auto">
|
<div class="container px-4 mx-auto">
|
||||||
<div class="relative py-11 px-16 bg-coolGray-900 dark:bg-blue-500 rounded-md overflow-hidden">
|
<div class="relative py-11 px-16 bg-coolGray-900 dark:bg-blue-500 rounded-md overflow-hidden">
|
||||||
<img class="absolute z-10 left-4 top-4" src="/static/images/elements/dots-red.svg" alt=""> <img class="absolute z-10 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="">
|
<img class="absolute z-10 left-4 top-4" src="/static/images/elements/dots-red.svg" alt="">
|
||||||
|
<img class="absolute z-10 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="">
|
||||||
<img class="absolute h-64 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="">
|
<img class="absolute h-64 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="">
|
||||||
<div class="relative z-20 flex flex-wrap items-center -m-3">
|
<div class="relative z-20 flex flex-wrap items-center -m-3">
|
||||||
<div class="w-full md:w-1/2 p-3">
|
<div class="w-full md:w-1/2 p-3">
|
||||||
<h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">{{ page_type_available }} {{ page_type_received }} {{ page_type_sent }}</h2>
|
<h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">Sent Bids / Received Bids</h2>
|
||||||
<p class="font-normal text-coolGray-200 dark:text-white">{{ page_type_available_description }} {{ page_type_received_description }} {{ page_type_sent_description }}</p>
|
<p class="font-normal text-coolGray-200 dark:text-white">View, and manage bids</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
|
|
||||||
{% if refresh %}
|
|
||||||
<a id="refresh" href="/bid/{{ bid_id }}" class="rounded-full mr-5 flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
|
|
||||||
{{ circular_arrows_svg | safe }}
|
|
||||||
<span>Refresh {{ refresh }} seconds</span>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% include 'inc_messages.html' %}
|
{% include 'inc_messages.html' %}
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="pl-6 pr-6 pt-0 mt-5 h-full overflow-hidden">
|
||||||
|
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||||
|
<div class="w-full pt-2">
|
||||||
|
<div class="mb-4 border-b pb-5 border-gray-200 dark:border-gray-500">
|
||||||
|
<ul class="flex flex-wrap text-sm font-medium text-center text-gray-500 dark:text-gray-400" id="myTab" data-tabs-toggle="#bidstab" role="tablist">
|
||||||
|
<li class="mr-2">
|
||||||
|
<button class="inline-block px-4 py-3 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white focus:outline-none focus:ring-0" id="sent-tab" data-tabs-target="#sent" type="button" role="tab" aria-controls="sent" aria-selected="true">
|
||||||
|
Sent Bids ({{ sent_bids_count }})
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="mr-2">
|
||||||
|
<button class="inline-block px-4 py-3 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white focus:outline-none focus:ring-0" id="received-tab" data-tabs-target="#received" type="button" role="tab" aria-controls="received" aria-selected="false">
|
||||||
|
Received Bids ({{ received_bids_count }})
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
|
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
|
||||||
<div class="pb-6 border-coolGray-100">
|
<div class="pb-6 border-coolGray-100">
|
||||||
<div class="flex flex-wrap items-center justify-between -m-2">
|
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||||
@@ -91,7 +108,8 @@
|
|||||||
<select name="with_expired" class="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-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0">
|
<select name="with_expired" class="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-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0">
|
||||||
<option value="true" {% if filters.with_expired==true %} selected{% endif %}>Include</option>
|
<option value="true" {% if filters.with_expired==true %} selected{% endif %}>Include</option>
|
||||||
<option value="false" {% if filters.with_expired==false %} selected{% endif %}>Exclude</option>
|
<option value="false" {% if filters.with_expired==false %} selected{% endif %}>Exclude</option>
|
||||||
</select> </div>
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -101,120 +119,294 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<div class="relative"> <button type="submit" name='applyfilters' value="Apply Filters" 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">
|
<div class="relative">
|
||||||
{{ filter_apply_svg | safe }}
|
<button type="submit" name='applyfilters' value="Apply Filters" 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>Apply Filters</span>
|
{{ filter_apply_svg | safe }}
|
||||||
</button>
|
<span>Apply Filters</span>
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container mt-5 mx-auto">
|
|
||||||
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
<div id="bidstab">
|
||||||
<div class="px-6">
|
<div class="rounded-lg" id="sent" role="tabpanel" aria-labelledby="sent-tab">
|
||||||
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
<div id="sent-content">
|
||||||
<table class="w-full min-w-max">
|
<div class="container mt-5 mx-auto">
|
||||||
<thead class="uppercase">
|
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||||
<tr class="text-left">
|
<div class="px-6">
|
||||||
<th class="p-0">
|
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||||
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Date/Time at</span>
|
<table class="w-full min-w-max">
|
||||||
|
<thead class="uppercase">
|
||||||
|
<tr class="text-left">
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Date/Time at</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid ID</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Offer ID</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid From</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid Status</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">ITX Status</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">PTX Status</span>
|
||||||
|
</div>
|
||||||
|
</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">Details</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for b in sent_bids %}
|
||||||
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||||
|
<th scope="row" class="flex items-center py-7 px-46 text-gray-900 whitespace-nowrap">
|
||||||
|
<svg class="w-5 h-5 rounded-full ml-5" xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
||||||
|
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#6b7280" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="11"></circle>
|
||||||
|
<polyline points=" 12,6 12,12 18,12 " stroke="#6b7280"></polyline>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div class="pl-3">
|
||||||
|
<div class="font-semibold text-xs dark:text-white">{{ b[0] }}</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td class="py-3 px-6 text-xs monospace"><a href=/bid/{{ b[1] }}>{{ b[1]|truncate(20, True) }}</a></td>
|
||||||
|
<td class="py-3 px-6 text-xs monospace"><a href=/offer/{{ b[2] }}>{{ b[2]|truncate(20, True) }}</a></td>
|
||||||
|
<td class="py-3 px-6 text-xs monospace"><a href=/identity/{{ b[6] }}>{{ b[6] }}</a></td>
|
||||||
|
<td class="py-3 px-6 text-xs">{{ b[3] }}</td>
|
||||||
|
<td class="py-3 px-6 text-xs">{{ b[4] }}</td>
|
||||||
|
<td class="py-3 px-6 text-xs">{{ b[5] }}</td>
|
||||||
|
<td class="py-3 px-6 text-xs">
|
||||||
|
<a class="inline-block w-20 py-1 px-2 font-medium text-center text-sm rounded-md bg-blue-500 text-white border border-blue-500 hover:bg-blue-600 transition duration-200" href="/bid/{{ b[1] }}">Details</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</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">
|
||||||
|
{% if filters.page_no > 1 %}
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<input type="hidden" name="filter_key" value="{{ filter_key }}">
|
||||||
|
<input type="hidden" name="page_no" value="{{ filters.page_no - 1 }}">
|
||||||
|
<button type="submit" name='pageback' value="Previous" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-blue-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
|
||||||
|
{{ page_back_svg | safe }}
|
||||||
|
<span>Previous</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<p class="text-sm font-heading dark:text-white">Page: {{ filters.page_no }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if sent_bids_count >= filters.limit %}
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<input type="hidden" name="filter_key" value="{{ filter_key }}">
|
||||||
|
<input type="hidden" name="page_no" value="{{ filters.page_no + 1 }}">
|
||||||
|
<button type="submit" name='pageforwards' value="Next" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-blue-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
|
||||||
|
<span>Next</span>
|
||||||
|
{{ page_forwards_svg | safe }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</div>
|
||||||
<th class="p-0">
|
|
||||||
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid ID</span>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th class="p-0">
|
|
||||||
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Offer ID</span>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th class="p-0">
|
|
||||||
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid From</span>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th class="p-0">
|
|
||||||
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid Status</span>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th class="p-0">
|
|
||||||
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">ITX Status</span>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th class="p-0">
|
|
||||||
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">PTX Status</span>
|
|
||||||
</div>
|
|
||||||
</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">Details</span>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for b in bids %}
|
|
||||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
|
||||||
<th scope="row" class="flex items-center py-7 px-46 text-gray-900 whitespace-nowrap"> <svg class="w-5 h-5 rounded-full ml-5" xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
|
||||||
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#6b7280" stroke-linejoin="round">
|
|
||||||
<circle cx="12" cy="12" r="11"></circle>
|
|
||||||
<polyline points=" 12,6 12,12 18,12 " stroke="#6b7280"></polyline>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<div class="pl-3">
|
|
||||||
<div class="font-semibold text-xs dark:text-white">{{ b[0] }}</div>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<td class="py-3 px-6 text-xs monospace"> <a href=/bid/{{ b[1] }}>{{ b[1]|truncate(20, True) }}</a> </td>
|
|
||||||
<td class="py-3 px-6 text-xs monospace"> <a href=/offer/{{ b[2] }}>{{ b[2]|truncate(20, True) }}</a> </td>
|
|
||||||
<td class="py-3 px-6 text-xs monospace"> <a href=/identity/{{ b[6] }}>{{ b[6] }}</a> </td>
|
|
||||||
<td class="py-3 px-6 text-xs">{{ b[3] }}</td>
|
|
||||||
<td class="py-3 px-6 text-xs">{{ b[4] }}</td>
|
|
||||||
<td class="py-3 px-6 text-xs">{{ b[5] }}</td>
|
|
||||||
{% if page_type_received or page_type_sent %}
|
|
||||||
<td class="py-3 px-6 text-xs"> <a class="inline-block w-20 py-1 px-2 font-medium text-center text-sm rounded-md bg-blue-500 text-white border border-blue-500 hover:bg-blue-600 transition duration-200 bg-blue-500 text-white hover:bg-blue-600 transition duration-200" href="/bid/{{ b[1] }}">Details</a>
|
|
||||||
</td> {% elif page_type_available %}
|
|
||||||
<td class="py-3 px-6 text-xs"> <a class="inline-block w-20 py-1 px-2 font-medium text-center text-sm rounded-md bg-blue-500 text-white border border-blue-500 hover:bg-blue-600 transition duration-200 bg-blue-500 text-white hover:bg-blue-600 transition duration-200" href="/bid/{{ b[1] }}">Accept</a>
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
{% endfor %}
|
|
||||||
</table> <input type="hidden" name="formid" value="{{ form_id }}"> <input type="hidden" name="pageno" value="{{ filters.page_no }}">
|
|
||||||
</div>
|
|
||||||
</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">
|
|
||||||
{% if filters.page_no > 1 %} <div class="w-full md:w-auto p-1.5">
|
|
||||||
<button type="submit" name='pageback' value="Previous" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-blue-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
|
|
||||||
{{ page_back_svg | safe }}
|
|
||||||
<span>Previous</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="w-full md:w-auto p-1.5">
|
|
||||||
<p class="text-sm font-heading dark:text-white">Page: {{ filters.page_no }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if bids_count > 20 %}
|
</div>
|
||||||
<div class="w-full md:w-auto p-1.5"> <button type="submit" name='pageforwards' value="Next" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-blue-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
|
|
||||||
<span>Next</span>
|
|
||||||
{{ page_forwards_svg | safe }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hidden rounded-lg" id="received" role="tabpanel" aria-labelledby="received-tab">
|
||||||
|
<div id="received-content">
|
||||||
|
<div class="container mt-5 mx-auto">
|
||||||
|
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||||
|
<div class="px-6">
|
||||||
|
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||||
|
<table class="w-full min-w-max">
|
||||||
|
<thead class="uppercase">
|
||||||
|
<tr class="text-left">
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Date/Time at</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid ID</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Offer ID</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid From</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid Status</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">ITX Status</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">PTX Status</span>
|
||||||
|
</div>
|
||||||
|
</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">Details</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for b in received_bids %}
|
||||||
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||||
|
<th scope="row" class="flex items-center py-7 px-46 text-gray-900 whitespace-nowrap">
|
||||||
|
<svg class="w-5 h-5 rounded-full ml-5" xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
||||||
|
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#6b7280" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="11"></circle>
|
||||||
|
<polyline points=" 12,6 12,12 18,12 " stroke="#6b7280"></polyline>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div class="pl-3">
|
||||||
|
<div class="font-semibold text-xs dark:text-white">{{ b[0] }}</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td class="py-3 px-6 text-xs monospace"><a href=/bid/{{ b[1] }}>{{ b[1]|truncate(20, True) }}</a></td>
|
||||||
|
<td class="py-3 px-6 text-xs monospace"><a href=/offer/{{ b[2] }}>{{ b[2]|truncate(20, True) }}</a></td>
|
||||||
|
<td class="py-3 px-6 text-xs monospace"><a href=/identity/{{ b[6] }}>{{ b[6] }}</a></td>
|
||||||
|
<td class="py-3 px-6 text-xs">{{ b[3] }}</td>
|
||||||
|
<td class="py-3 px-6 text-xs">{{ b[4] }}</td>
|
||||||
|
<td class="py-3 px-6 text-xs">{{ b[5] }}</td>
|
||||||
|
<td class="py-3 px-6 text-xs">
|
||||||
|
<a class="inline-block w-20 py-1 px-2 font-medium text-center text-sm rounded-md bg-blue-500 text-white border border-blue-500 hover:bg-blue-600 transition duration-200" href="/bid/{{ b[1] }}">Details</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</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">
|
||||||
|
{% if filters.page_no > 1 %}
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<input type="hidden" name="filter_key" value="{{ filter_key }}">
|
||||||
|
<input type="hidden" name="page_no" value="{{ filters.page_no - 1 }}">
|
||||||
|
<button type="submit" name='pageback' value="Previous" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-blue-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
|
||||||
|
{{ page_back_svg | safe }}
|
||||||
|
<span>Previous</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<p class="text-sm font-heading dark:text-white">Page: {{ filters.page_no }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if received_bids_count >= filters.limit %}
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<input type="hidden" name="filter_key" value="{{ filter_key }}">
|
||||||
|
<input type="hidden" name="page_no" value="{{ filters.page_no + 1 }}">
|
||||||
|
<button type="submit" name='pageforwards' value="Next" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-blue-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
|
||||||
|
<span>Next</span>
|
||||||
|
{{ page_forwards_svg | safe }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||||
|
<input type="hidden" name="pageno" value="{{ filters.page_no }}">
|
||||||
|
<input type="hidden" name="filter_key" value="page_bids">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const tabs = document.querySelectorAll('#myTab button');
|
||||||
|
const contents = document.querySelectorAll('[role="tabpanel"]');
|
||||||
|
|
||||||
|
function switchTab(targetId) {
|
||||||
|
contents.forEach(content => {
|
||||||
|
content.classList.add('hidden');
|
||||||
|
});
|
||||||
|
const targetContent = document.querySelector(targetId);
|
||||||
|
if (targetContent) {
|
||||||
|
targetContent.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
const selected = tab.dataset.tabsTarget === targetId;
|
||||||
|
tab.setAttribute('aria-selected', selected);
|
||||||
|
if (selected) {
|
||||||
|
tab.classList.add('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
|
||||||
|
} else {
|
||||||
|
tab.classList.remove('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
switchTab(tab.dataset.tabsTarget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
switchTab('#sent');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
{% include 'header.html' %}
|
||||||
|
{% from 'style.html' import breadcrumb_line_svg, page_back_svg, page_forwards_svg, filter_clear_svg, filter_apply_svg, input_arrow_down_svg %}
|
||||||
|
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<section class="p-5 mt-5">
|
||||||
|
<div class="flex flex-wrap items-center -m-2">
|
||||||
|
<div class="w-full md:w-1/2 p-2">
|
||||||
|
<ul class="flex flex-wrap items-center gap-x-3 mb-2">
|
||||||
|
<li><a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/"><p>Home</p></a></li>
|
||||||
|
<li>{{ breadcrumb_line_svg | safe }}</li>
|
||||||
|
<li><a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="#">Bids Requests</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="py-4">
|
||||||
|
<div class="container px-4 mx-auto">
|
||||||
|
<div class="relative py-11 px-16 bg-coolGray-900 dark:bg-blue-500 rounded-md overflow-hidden">
|
||||||
|
<img class="absolute z-10 left-4 top-4" src="/static/images/elements/dots-red.svg" alt="">
|
||||||
|
<img class="absolute z-10 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="">
|
||||||
|
<img class="absolute h-64 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="">
|
||||||
|
<div class="relative z-20 flex flex-wrap items-center -m-3">
|
||||||
|
<div class="w-full md:w-1/2 p-3">
|
||||||
|
<h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">Bids Requests</h2>
|
||||||
|
<p class="font-normal text-coolGray-200 dark:text-white">Review and accept bids from other users</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% include 'inc_messages.html' %}
|
||||||
|
|
||||||
|
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
|
||||||
|
<div class="pb-6 border-coolGray-100">
|
||||||
|
<div class="flex flex-wrap items-center justify-between -m-2">
|
||||||
|
<div class="w-full mx-auto pt-2">
|
||||||
|
<form method="post">
|
||||||
|
<div class="flex items-center justify-center pb-4 dark:text-white">
|
||||||
|
<div class="rounded-b-md">
|
||||||
|
<div class="w-full md:w-0/12">
|
||||||
|
<div class="flex flex-wrap justify-center -m-1.5">
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<div class="relative">
|
||||||
|
{{ input_arrow_down_svg | safe }}
|
||||||
|
<select name="sort_by" class="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-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0">
|
||||||
|
<option value="created_at" {% if filters.sort_by=='created_at' %} selected{% endif %}>Time At</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<div class="relative">
|
||||||
|
{{ input_arrow_down_svg | safe }}
|
||||||
|
<select name="sort_dir" class="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-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0">
|
||||||
|
<option value="asc" {% if filters.sort_dir=='asc' %} selected{% endif %}>Ascending</option>
|
||||||
|
<option value="desc" {% if filters.sort_dir=='desc' %} selected{% endif %}>Descending</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<div class="relative">
|
||||||
|
<button type="submit" name='clearfilters' value="Clear Filters" class="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">
|
||||||
|
<span>Clear Filters</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<div class="relative">
|
||||||
|
<button type="submit" name='applyfilters' value="Apply Filters" 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">
|
||||||
|
{{ filter_apply_svg | safe }}
|
||||||
|
<span>Apply Filters</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container mt-5 mx-auto">
|
||||||
|
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||||
|
<div class="px-6">
|
||||||
|
<div class="w-full mt-6 pb-6 overflow-x-auto">
|
||||||
|
<table class="w-full min-w-max">
|
||||||
|
<thead class="uppercase">
|
||||||
|
<tr class="text-left">
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Date/Time at</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Bid ID</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Offer ID</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">From</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th class="p-0">
|
||||||
|
<div class="py-3 px-6 bg-coolGray-200 dark:bg-gray-600">
|
||||||
|
<span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Status</span>
|
||||||
|
</div>
|
||||||
|
</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">Actions</span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for b in bids %}
|
||||||
|
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
|
||||||
|
<th scope="row" class="flex items-center py-7 px-46 text-gray-900 whitespace-nowrap">
|
||||||
|
<svg class="w-5 h-5 rounded-full ml-5" xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 24 24">
|
||||||
|
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#6b7280" stroke-linejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="11"></circle>
|
||||||
|
<polyline points=" 12,6 12,12 18,12 " stroke="#6b7280"></polyline>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div class="pl-3">
|
||||||
|
<div class="font-semibold text-xs dark:text-white">{{ b[0] }}</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td class="py-3 px-6 text-xs monospace"><a href=/bid/{{ b[1] }}>{{ b[1]|truncate(20, True) }}</a></td>
|
||||||
|
<td class="py-3 px-6 text-xs monospace"><a href=/offer/{{ b[2] }}>{{ b[2]|truncate(20, True) }}</a></td>
|
||||||
|
<td class="py-3 px-6 text-xs monospace"><a href=/identity/{{ b[6] }}>{{ b[6] }}</a></td>
|
||||||
|
<td class="py-3 px-6 text-xs">{{ b[3] }}</td>
|
||||||
|
<td class="py-3 px-6">
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<a class="inline-block w-20 py-1 px-2 font-medium text-center text-sm rounded-md bg-blue-500 text-white border border-blue-500 hover:bg-blue-600 transition duration-200" href="/bid/{{ b[1] }}">View</a>
|
||||||
|
{% if b[3] == "Received" %}
|
||||||
|
<form method="post" action="/bid/{{ b[1] }}" class="inline">
|
||||||
|
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||||
|
<button type="submit" name="accept_bid" class="inline-block w-20 py-1 px-2 font-medium text-center text-sm rounded-md bg-green-500 text-white border border-green-500 hover:bg-green-600 transition duration-200">Accept</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<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">
|
||||||
|
{% if filters.page_no > 1 %}
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<input type="hidden" name="filter_key" value="{{ filter_key }}">
|
||||||
|
<input type="hidden" name="page_no" value="{{ filters.page_no - 1 }}">
|
||||||
|
<button type="submit" name='pageback' value="Previous" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-blue-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
|
||||||
|
{{ page_back_svg | safe }}
|
||||||
|
<span>Previous</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<p class="text-sm font-heading dark:text-white">Page: {{ filters.page_no }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if bids_count >= filters.limit %}
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<input type="hidden" name="filter_key" value="{{ filter_key }}">
|
||||||
|
<input type="hidden" name="page_no" value="{{ filters.page_no + 1 }}">
|
||||||
|
<button type="submit" name='pageforwards' value="Next" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-blue-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
|
||||||
|
<span>Next</span>
|
||||||
|
{{ page_forwards_svg | safe }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||||
|
<input type="hidden" name="pageno" value="{{ filters.page_no }}">
|
||||||
|
<input type="hidden" name="filter_key" value="page_available_bids">
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include 'footer.html' %}
|
||||||
+799
-553
File diff suppressed because it is too large
Load Diff
@@ -331,9 +331,6 @@ function getWebSocketConfig() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<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="mt-5 lg:container mx-auto lg:px-0 px-6">
|
<div class="mt-5 lg:container mx-auto lg:px-0 px-6">
|
||||||
<div class="pt-0 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
<div class="pt-0 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||||
<div class="px-0">
|
<div class="px-0">
|
||||||
@@ -399,11 +396,9 @@ function getWebSocketConfig() {
|
|||||||
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Market +/-</span>
|
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Market +/-</span>
|
||||||
<span class="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-6">↓</span>
|
<span class="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-6">↓</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
<th class="p-0">
|
||||||
<th class="p-0" data-sortable="true" data-column-index="7">
|
|
||||||
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 rounded-tr-xl">
|
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 rounded-tr-xl">
|
||||||
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Trade</span>
|
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Trade</span>
|
||||||
<span class="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-7">↓</span>
|
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -447,6 +442,5 @@ function getWebSocketConfig() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<input type="hidden" name="formid" value="{{ form_id }}">
|
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||||
|
|
||||||
<script src="/static/js/offerstable.js"></script>
|
<script src="/static/js/offerstable.js"></script>
|
||||||
{% include 'footer.html' %}
|
{% include 'footer.html' %}
|
||||||
|
|||||||
+52
-36
@@ -149,13 +149,12 @@ def page_bid(self, url_split, post_string):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def page_bids(
|
def page_bids(self, url_split, post_string, sent=False, available=False, received=False):
|
||||||
self, url_split, post_string, sent=False, available=False, received=False
|
|
||||||
):
|
|
||||||
server = self.server
|
server = self.server
|
||||||
swap_client = server.swap_client
|
swap_client = server.swap_client
|
||||||
swap_client.checkSystemStatus()
|
swap_client.checkSystemStatus()
|
||||||
summary = swap_client.getSummary()
|
summary = swap_client.getSummary()
|
||||||
|
filter_key = "page_available_bids" if available else "page_bids"
|
||||||
|
|
||||||
filters = {
|
filters = {
|
||||||
"page_no": 1,
|
"page_no": 1,
|
||||||
@@ -169,26 +168,15 @@ def page_bids(
|
|||||||
filters["bid_state_ind"] = BidStates.BID_RECEIVED
|
filters["bid_state_ind"] = BidStates.BID_RECEIVED
|
||||||
filters["with_expired"] = False
|
filters["with_expired"] = False
|
||||||
|
|
||||||
filter_prefix = (
|
|
||||||
"page_bids_sent"
|
|
||||||
if sent
|
|
||||||
else "page_bids_available" if available else "page_bids_received"
|
|
||||||
)
|
|
||||||
messages = []
|
messages = []
|
||||||
form_data = self.checkForm(post_string, "bids", messages)
|
form_data = self.checkForm(post_string, "bids", messages)
|
||||||
if form_data:
|
if form_data:
|
||||||
if have_data_entry(form_data, "clearfilters"):
|
if have_data_entry(form_data, "clearfilters"):
|
||||||
swap_client.clearFilters(filter_prefix)
|
swap_client.clearFilters(filter_key)
|
||||||
else:
|
else:
|
||||||
if have_data_entry(form_data, "sort_by"):
|
if have_data_entry(form_data, "sort_by"):
|
||||||
sort_by = get_data_entry(form_data, "sort_by")
|
sort_by = get_data_entry(form_data, "sort_by")
|
||||||
ensure(
|
ensure(sort_by in ["created_at"], "Invalid sort by")
|
||||||
sort_by
|
|
||||||
in [
|
|
||||||
"created_at",
|
|
||||||
],
|
|
||||||
"Invalid sort by",
|
|
||||||
)
|
|
||||||
filters["sort_by"] = sort_by
|
filters["sort_by"] = sort_by
|
||||||
if have_data_entry(form_data, "sort_dir"):
|
if have_data_entry(form_data, "sort_dir"):
|
||||||
sort_dir = get_data_entry(form_data, "sort_dir")
|
sort_dir = get_data_entry(form_data, "sort_dir")
|
||||||
@@ -199,7 +187,7 @@ def page_bids(
|
|||||||
if state_ind != -1:
|
if state_ind != -1:
|
||||||
try:
|
try:
|
||||||
_ = BidStates(state_ind)
|
_ = BidStates(state_ind)
|
||||||
except Exception as e: # noqa: F841
|
except Exception:
|
||||||
raise ValueError("Invalid state")
|
raise ValueError("Invalid state")
|
||||||
filters["bid_state_ind"] = state_ind
|
filters["bid_state_ind"] = state_ind
|
||||||
if have_data_entry(form_data, "with_expired"):
|
if have_data_entry(form_data, "with_expired"):
|
||||||
@@ -208,38 +196,52 @@ def page_bids(
|
|||||||
|
|
||||||
set_pagination_filters(form_data, filters)
|
set_pagination_filters(form_data, filters)
|
||||||
if have_data_entry(form_data, "applyfilters"):
|
if have_data_entry(form_data, "applyfilters"):
|
||||||
swap_client.setFilters(filter_prefix, filters)
|
swap_client.setFilters(filter_key, filters)
|
||||||
else:
|
else:
|
||||||
saved_filters = swap_client.getFilters(filter_prefix)
|
saved_filters = swap_client.getFilters(filter_key)
|
||||||
if saved_filters:
|
if saved_filters:
|
||||||
filters.update(saved_filters)
|
filters.update(saved_filters)
|
||||||
|
|
||||||
bids = swap_client.listBids(sent=sent, filters=filters)
|
|
||||||
|
|
||||||
page_data = {
|
page_data = {
|
||||||
"bid_states": listBidStates(),
|
"bid_states": listBidStates(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if available:
|
||||||
|
bids = swap_client.listBids(sent=False, filters=filters)
|
||||||
|
template = server.env.get_template("bids_available.html")
|
||||||
|
return self.render_template(
|
||||||
|
template,
|
||||||
|
{
|
||||||
|
"page_type_available": "Bids Available",
|
||||||
|
"page_type_available_description": "Bids available for you to accept.",
|
||||||
|
"messages": messages,
|
||||||
|
"filters": filters,
|
||||||
|
"data": page_data,
|
||||||
|
"summary": summary,
|
||||||
|
"filter_key": filter_key,
|
||||||
|
"bids": [
|
||||||
|
(format_timestamp(b[0]), b[2].hex(), b[3].hex(),
|
||||||
|
strBidState(b[5]), strTxState(b[7]),
|
||||||
|
strTxState(b[8]), b[11])
|
||||||
|
for b in bids
|
||||||
|
],
|
||||||
|
"bids_count": len(bids),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
sent_bids = swap_client.listBids(sent=True, filters=filters)
|
||||||
|
received_bids = swap_client.listBids(sent=False, filters=filters)
|
||||||
|
|
||||||
template = server.env.get_template("bids.html")
|
template = server.env.get_template("bids.html")
|
||||||
return self.render_template(
|
return self.render_template(
|
||||||
template,
|
template,
|
||||||
{
|
{
|
||||||
"page_type_sent": "Bids Sent" if sent else "",
|
|
||||||
"page_type_available": "Bids Available" if available else "",
|
|
||||||
"page_type_received": "Received Bids" if received else "",
|
|
||||||
"page_type_sent_description": (
|
|
||||||
"All the bids you have placed on offers." if sent else ""
|
|
||||||
),
|
|
||||||
"page_type_available_description": (
|
|
||||||
"Bids available for you to accept." if available else ""
|
|
||||||
),
|
|
||||||
"page_type_received_description": (
|
|
||||||
"All the bids placed on your offers." if received else ""
|
|
||||||
),
|
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
"filters": filters,
|
"filters": filters,
|
||||||
"data": page_data,
|
"data": page_data,
|
||||||
"summary": summary,
|
"summary": summary,
|
||||||
"bids": [
|
"filter_key": filter_key,
|
||||||
|
"sent_bids": [
|
||||||
(
|
(
|
||||||
format_timestamp(b[0]),
|
format_timestamp(b[0]),
|
||||||
b[2].hex(),
|
b[2].hex(),
|
||||||
@@ -249,8 +251,22 @@ def page_bids(
|
|||||||
strTxState(b[8]),
|
strTxState(b[8]),
|
||||||
b[11],
|
b[11],
|
||||||
)
|
)
|
||||||
for b in bids
|
for b in sent_bids
|
||||||
],
|
],
|
||||||
"bids_count": len(bids),
|
"received_bids": [
|
||||||
|
(
|
||||||
|
format_timestamp(b[0]),
|
||||||
|
b[2].hex(),
|
||||||
|
b[3].hex(),
|
||||||
|
strBidState(b[5]),
|
||||||
|
strTxState(b[7]),
|
||||||
|
strTxState(b[8]),
|
||||||
|
b[11],
|
||||||
|
)
|
||||||
|
for b in received_bids
|
||||||
|
],
|
||||||
|
"sent_bids_count": len(sent_bids),
|
||||||
|
"received_bids_count": len(received_bids),
|
||||||
|
"bids_count": len(sent_bids) + len(received_bids),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
iQIzBAABCAAdFiEEjlF9wS7BzDf2QjqKE/E2UcnPDWsFAmeP/40ACgkQE/E2UcnP
|
||||||
|
DWuraw/9HCuAZG+D6xSLWmjA9Dtj9OZMEOIxqvxw+1e2KQ5ek4d1waL63NWFQfMi
|
||||||
|
fDlKKeFbZoL6Dfjbx0GoUJKTfrIVKog6DlVzIi5PuUwPOCBFuLl0g5kHlC20jbPw
|
||||||
|
nu7T6fj6/oD/lqo0rzFDkbsX7Fk4GGC7rYLKfdtYhDgMq9ro7QhSxAOJanRyqzXL
|
||||||
|
dvPNxlyksOyttJLSAZI9BOkrpTWoyb3asOli5oHgdcheHd/2fjby69huS3UWEjdO
|
||||||
|
9Bm73UFlxF2hxCTc2Fqvvb3SBDmNCLlFM0f+DDJNMJGUQViVCar0YRw3R+/NBo83
|
||||||
|
ptutp3bpabHijQFEEpIx/19nh9RQMJjaHHHqdPcTeg8bU/Yeq36TI7gsCenK0mQT
|
||||||
|
75MscvJAG0enoKVrTZez5ner9ZwLOevAKzRe4huRJZZjM8gM6sb2OKslJLqTxEVt
|
||||||
|
G3b8BLB9IUAxCeyuvGSG/3RV3MgZLnLy5MLYjh72+Kmo6HpuajJwPuvUck5ZYcGE
|
||||||
|
jjeRFZmqZj0FtCrcfStau/0liyAxU5k/43RwMvujO1uTTgOVHw1QhhMEkZ9bYhhO
|
||||||
|
JgeCEkwL1Bjjved1NSySjZbt2sFbG89as14ezHxgc4HaujJ6bGkINnkPOPWM1tk4
|
||||||
|
DjjEO/0PY9i0m/ivQUXf5ZPSnlkAR8x6Ve2S2MvQd7nFoS/YfLs=
|
||||||
|
=0pTn
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2023-2024 tecnovert
|
# Copyright (c) 2023-2024 tecnovert
|
||||||
# Copyright (c) 2024 The Basicswap developers
|
# Copyright (c) 2024-2025 The Basicswap developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -657,8 +657,8 @@ def main():
|
|||||||
offer_identity = read_json_api(
|
offer_identity = read_json_api(
|
||||||
"identities/{}".format(offer["addr_from"])
|
"identities/{}".format(offer["addr_from"])
|
||||||
)
|
)
|
||||||
if len(offer_identity) > 0:
|
if "address" in offer_identity:
|
||||||
id_offer_from = offer_identity[0]
|
id_offer_from = offer_identity
|
||||||
automation_override = id_offer_from["automation_override"]
|
automation_override = id_offer_from["automation_override"]
|
||||||
if automation_override == 2:
|
if automation_override == 2:
|
||||||
if args.debug:
|
if args.debug:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright (c) 2020-2024 tecnovert
|
# 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
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -132,6 +132,7 @@ def run_prepare(
|
|||||||
os.environ["BSX_TEST_MODE"] = "true"
|
os.environ["BSX_TEST_MODE"] = "true"
|
||||||
os.environ["PART_RPC_PORT"] = str(PARTICL_RPC_PORT_BASE)
|
os.environ["PART_RPC_PORT"] = str(PARTICL_RPC_PORT_BASE)
|
||||||
os.environ["BTC_RPC_PORT"] = str(BITCOIN_RPC_PORT_BASE)
|
os.environ["BTC_RPC_PORT"] = str(BITCOIN_RPC_PORT_BASE)
|
||||||
|
os.environ["BTC_PORT"] = str(BITCOIN_PORT_BASE)
|
||||||
os.environ["LTC_RPC_PORT"] = str(LITECOIN_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["DCR_RPC_PORT"] = str(DECRED_RPC_PORT_BASE)
|
||||||
os.environ["FIRO_RPC_PORT"] = str(FIRO_RPC_PORT_BASE)
|
os.environ["FIRO_RPC_PORT"] = str(FIRO_RPC_PORT_BASE)
|
||||||
@@ -229,8 +230,7 @@ def run_prepare(
|
|||||||
for line in lines:
|
for line in lines:
|
||||||
if not line.startswith("prune"):
|
if not line.startswith("prune"):
|
||||||
fp.write(line)
|
fp.write(line)
|
||||||
fp.write("port={}\n".format(BITCOIN_PORT_BASE + node_id + port_ofs))
|
# fp.write("bind=127.0.0.1\n") # Causes BTC v28 to try and bind to bind=127.0.0.1:8444, even with a bind...=onion present
|
||||||
fp.write("bind=127.0.0.1\n")
|
|
||||||
# listenonion=0 does not stop the node from trying to bind to the tor port
|
# listenonion=0 does not stop the node from trying to bind to the tor port
|
||||||
# https://github.com/bitcoin/bitcoin/issues/22726
|
# https://github.com/bitcoin/bitcoin/issues/22726
|
||||||
fp.write(
|
fp.write(
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ class Test(BaseTest):
|
|||||||
"walletrpcport": WOW_BASE_WALLET_RPC_PORT + node_id,
|
"walletrpcport": WOW_BASE_WALLET_RPC_PORT + node_id,
|
||||||
"walletrpcuser": "test" + str(node_id),
|
"walletrpcuser": "test" + str(node_id),
|
||||||
"walletrpcpassword": "test_pass" + str(node_id),
|
"walletrpcpassword": "test_pass" + str(node_id),
|
||||||
"walletfile": "testwallet",
|
"wallet_name": "testwallet",
|
||||||
"datadir": os.path.join(datadir, "xmr_" + str(node_id)),
|
"datadir": os.path.join(datadir, "xmr_" + str(node_id)),
|
||||||
"bindir": WOW_BINDIR,
|
"bindir": WOW_BINDIR,
|
||||||
}
|
}
|
||||||
|
|||||||
+129
-129
@@ -10,9 +10,6 @@ import random
|
|||||||
import logging
|
import logging
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from basicswap.db import (
|
|
||||||
Concepts,
|
|
||||||
)
|
|
||||||
from basicswap.basicswap import (
|
from basicswap.basicswap import (
|
||||||
BidStates,
|
BidStates,
|
||||||
Coins,
|
Coins,
|
||||||
@@ -23,6 +20,9 @@ from basicswap.basicswap_util import (
|
|||||||
TxLockTypes,
|
TxLockTypes,
|
||||||
EventLogTypes,
|
EventLogTypes,
|
||||||
)
|
)
|
||||||
|
from basicswap.db import (
|
||||||
|
Concepts,
|
||||||
|
)
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
make_int,
|
make_int,
|
||||||
)
|
)
|
||||||
@@ -32,10 +32,10 @@ from tests.basicswap.util import (
|
|||||||
)
|
)
|
||||||
from tests.basicswap.common import (
|
from tests.basicswap.common import (
|
||||||
abandon_all_swaps,
|
abandon_all_swaps,
|
||||||
|
wait_for_balance,
|
||||||
wait_for_bid,
|
wait_for_bid,
|
||||||
wait_for_event,
|
wait_for_event,
|
||||||
wait_for_offer,
|
wait_for_offer,
|
||||||
wait_for_balance,
|
|
||||||
wait_for_unspent,
|
wait_for_unspent,
|
||||||
wait_for_none_active,
|
wait_for_none_active,
|
||||||
BTC_BASE_RPC_PORT,
|
BTC_BASE_RPC_PORT,
|
||||||
@@ -640,6 +640,129 @@ class TestFunctions(BaseTest):
|
|||||||
wait_for=(self.extra_wait_time + 180),
|
wait_for=(self.extra_wait_time + 180),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def do_test_08_insufficient_funds(self, coin_from, coin_to):
|
||||||
|
logging.info(
|
||||||
|
"---------- Test {} to {} Insufficient Funds".format(
|
||||||
|
coin_from.name, coin_to.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
|
||||||
|
|
||||||
|
id_offerer: int = self.node_c_id
|
||||||
|
id_bidder: int = self.node_b_id
|
||||||
|
|
||||||
|
self.prepare_balance(
|
||||||
|
coin_from,
|
||||||
|
10.0,
|
||||||
|
1800 + id_offerer,
|
||||||
|
1801 if coin_from in (Coins.XMR,) else 1800,
|
||||||
|
)
|
||||||
|
jsw = read_json_api(1800 + id_offerer, "wallets")
|
||||||
|
balance_from_before: float = self.getBalance(jsw, coin_from)
|
||||||
|
self.prepare_balance(
|
||||||
|
coin_to,
|
||||||
|
balance_from_before + 1,
|
||||||
|
1800 + id_bidder,
|
||||||
|
1801 if coin_to in (Coins.XMR,) else 1800,
|
||||||
|
)
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||||
|
ci_to = swap_clients[id_bidder].ci(coin_to)
|
||||||
|
|
||||||
|
amt_swap: int = ci_from.make_int(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"
|
||||||
|
|
||||||
|
# Test that postbid errors when offer is for the full balance
|
||||||
|
id_offerer_test_bid = id_bidder
|
||||||
|
id_bidder_test_bid = id_offerer
|
||||||
|
amt_swap_test_bid_to: int = ci_from.make_int(balance_from_before, r=1)
|
||||||
|
amt_swap_test_bid_from: int = ci_to.make_int(1.0)
|
||||||
|
offer_id = swap_clients[id_offerer_test_bid].postOffer(
|
||||||
|
coin_to,
|
||||||
|
coin_from,
|
||||||
|
amt_swap_test_bid_from,
|
||||||
|
0,
|
||||||
|
amt_swap_test_bid_from,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
extra_options={"amount_to": amt_swap_test_bid_to},
|
||||||
|
)
|
||||||
|
wait_for_offer(test_delay_event, swap_clients[id_bidder_test_bid], offer_id)
|
||||||
|
try:
|
||||||
|
bid_id = swap_clients[id_bidder_test_bid].postBid(
|
||||||
|
offer_id, amt_swap_test_bid_from
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
amt_swap,
|
||||||
|
rate_swap,
|
||||||
|
amt_swap,
|
||||||
|
SwapTypes.XMR_SWAP,
|
||||||
|
auto_accept_bids=True,
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
wait_for_bid(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_offerer],
|
||||||
|
bid_id,
|
||||||
|
(
|
||||||
|
(BidStates.SWAP_COMPLETED, BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED)
|
||||||
|
if reverse_bid
|
||||||
|
else (BidStates.BID_ACCEPTED, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED)
|
||||||
|
),
|
||||||
|
wait_for=120,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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,
|
||||||
|
)
|
||||||
|
event = wait_for_event(
|
||||||
|
test_delay_event,
|
||||||
|
swap_clients[id_offerer],
|
||||||
|
Concepts.BID,
|
||||||
|
bid_id,
|
||||||
|
event_type=EventLogTypes.AUTOMATION_CONSTRAINT,
|
||||||
|
)
|
||||||
|
assert "Over remaining offer value" in event.event_msg
|
||||||
|
try:
|
||||||
|
swap_clients[id_offerer].acceptBid(bid_id)
|
||||||
|
except Exception as e:
|
||||||
|
assert "Insufficient funds" in str(e) or "Balance too low" in str(e)
|
||||||
|
else:
|
||||||
|
assert False, "Should fail"
|
||||||
|
|
||||||
|
|
||||||
class BasicSwapTest(TestFunctions):
|
class BasicSwapTest(TestFunctions):
|
||||||
|
|
||||||
@@ -1714,133 +1837,10 @@ class BasicSwapTest(TestFunctions):
|
|||||||
swap_clients[0].setMockTimeOffset(0)
|
swap_clients[0].setMockTimeOffset(0)
|
||||||
|
|
||||||
def test_08_insufficient_funds(self):
|
def test_08_insufficient_funds(self):
|
||||||
tla_from = self.test_coin_from.name
|
self.do_test_08_insufficient_funds(self.test_coin_from, Coins.XMR)
|
||||||
logging.info("---------- Test {} Insufficient Funds".format(tla_from))
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
coin_from = self.test_coin_from
|
|
||||||
coin_to = Coins.XMR
|
|
||||||
|
|
||||||
self.prepare_balance(coin_from, 10.0, 1802, 1800)
|
|
||||||
|
|
||||||
id_offerer: int = self.node_c_id
|
|
||||||
id_bidder: int = self.node_b_id
|
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
ci_from = swap_clients[id_offerer].ci(coin_from)
|
|
||||||
ci_to = swap_clients[id_bidder].ci(coin_to)
|
|
||||||
|
|
||||||
jsw = read_json_api(1800 + id_offerer, "wallets")
|
|
||||||
balance_from_before: float = self.getBalance(jsw, coin_from)
|
|
||||||
|
|
||||||
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,
|
|
||||||
amt_swap,
|
|
||||||
rate_swap,
|
|
||||||
amt_swap,
|
|
||||||
SwapTypes.XMR_SWAP,
|
|
||||||
auto_accept_bids=True,
|
|
||||||
)
|
|
||||||
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)
|
|
||||||
wait_for_bid(
|
|
||||||
test_delay_event,
|
|
||||||
swap_clients[id_offerer],
|
|
||||||
bid_id,
|
|
||||||
(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):
|
def test_08_insufficient_funds_rev(self):
|
||||||
tla_from = self.test_coin_from.name
|
self.do_test_08_insufficient_funds(Coins.XMR, self.test_coin_from)
|
||||||
logging.info("---------- Test {} Insufficient Funds (reverse)".format(tla_from))
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
coin_from = Coins.XMR
|
|
||||||
coin_to = self.test_coin_from
|
|
||||||
|
|
||||||
self.prepare_balance(coin_to, 10.0, 1802, 1800)
|
|
||||||
|
|
||||||
id_offerer: int = self.node_b_id
|
|
||||||
id_bidder: int = self.node_c_id
|
|
||||||
|
|
||||||
swap_clients = self.swap_clients
|
|
||||||
ci_from = swap_clients[id_offerer].ci(coin_from)
|
|
||||||
ci_to = swap_clients[id_bidder].ci(coin_to)
|
|
||||||
|
|
||||||
jsw = read_json_api(1800 + id_bidder, "wallets")
|
|
||||||
balance_to_before: float = self.getBalance(jsw, coin_to)
|
|
||||||
|
|
||||||
amt_swap: int = ci_from.make_int(balance_to_before, r=1)
|
|
||||||
rate_swap: int = ci_to.make_int(1.0, r=1)
|
|
||||||
|
|
||||||
amt_swap -= 1
|
|
||||||
offer_id = swap_clients[id_offerer].postOffer(
|
|
||||||
coin_from,
|
|
||||||
coin_to,
|
|
||||||
amt_swap,
|
|
||||||
rate_swap,
|
|
||||||
amt_swap,
|
|
||||||
SwapTypes.XMR_SWAP,
|
|
||||||
auto_accept_bids=True,
|
|
||||||
)
|
|
||||||
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
|
|
||||||
|
|
||||||
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap)
|
|
||||||
|
|
||||||
event = wait_for_event(
|
|
||||||
test_delay_event,
|
|
||||||
swap_clients[id_bidder],
|
|
||||||
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_bidder],
|
|
||||||
bid_id,
|
|
||||||
BidStates.BID_ERROR,
|
|
||||||
sent=True,
|
|
||||||
wait_for=20,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestBTC(BasicSwapTest):
|
class TestBTC(BasicSwapTest):
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ def prepare_swapclient_dir(
|
|||||||
"walletrpcport": XMR_BASE_WALLET_RPC_PORT + node_id,
|
"walletrpcport": XMR_BASE_WALLET_RPC_PORT + node_id,
|
||||||
"walletrpcuser": "test" + str(node_id),
|
"walletrpcuser": "test" + str(node_id),
|
||||||
"walletrpcpassword": "test_pass" + str(node_id),
|
"walletrpcpassword": "test_pass" + str(node_id),
|
||||||
"walletfile": "testwallet",
|
"wallet_name": "testwallet",
|
||||||
"datadir": os.path.join(datadir, "xmr_" + str(node_id)),
|
"datadir": os.path.join(datadir, "xmr_" + str(node_id)),
|
||||||
"bindir": cfg.XMR_BINDIR,
|
"bindir": cfg.XMR_BINDIR,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user