94 Commits

Author SHA1 Message Date
Cryptoguard
631ccea626 Added NMC to README 2025-04-07 16:35:51 -04:00
tecnovert
a5c3c692a0 Merge pull request #269 from gerlofvanek/version-1
GUI v3.2.0
2025-02-27 16:48:03 +00:00
gerlofvanek
b2df4ea80d GUI v3.2.0 2025-02-27 17:27:50 +01:00
Gerlof van Ek
18a7105f20 New Swaps in Progress page + various fixes + CSV export on bids page. (#267)
* New Swaps in Progress page + various fixes.

* LINT

* Fix small memory leak in bids page.

* Fix coin filter logic.

* Add CSV export on bids page + various fixes.

* Update basicswap/static/js/bids_sentreceived.js

Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com>

* Update basicswap/static/js/bids_sentreceived.js

Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com>

* Update basicswap/static/js/bids_sentreceived.js

Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com>

* Update basicswap/static/js/bids_sentreceived.js

Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com>

* Various fixes.

---------

Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com>
2025-02-25 19:20:55 +00:00
Gerlof van Ek
fcdb2e7dfe Merge pull request #265 from nahuhh/offertweaks
bids/offers: responsive and styling tweaks
2025-02-22 23:11:34 +01:00
nahuhh
3c5e8481cd bids/offers: responsive and styling tweaks 2025-02-22 22:05:31 +00:00
Gerlof van Ek
97bb615176 New bids pages + various fixes. (#266)
* New bids pages + various fixes.

* LINT

* Fix styling.
2025-02-22 15:55:12 +00:00
tecnovert
f1c2b41714 Add safe_logs option to anonymise logs. (#264)
* Add safe_logs option to anonymise logs.

* Extend logger class.
2025-02-22 15:54:13 +00:00
tecnovert
8d317e4b67 Merge pull request #262 from gerlofvanek/ws
JS: Fix websocket delay / loading tables faster.
2025-02-17 09:57:17 +00:00
tecnovert
45ed2cdb87 Merge pull request #254 from tecnovert/local_pgp
Import signing pubkeys from local filesystem.
2025-02-17 09:57:03 +00:00
tecnovert
d64e3f4be9 Merge pull request #259 from basicswap/dependabot/pip/dev/pyzmq-26.2.1
build(deps): bump pyzmq from 26.2.0 to 26.2.1
2025-02-17 09:56:38 +00:00
gerlofvanek
57d885bc0c JS: Fix websocket delay / loading tables faster. 2025-02-12 20:23:55 +01:00
Gerlof van Ek
205c6e2b58 Merge pull request #260 from gerlofvanek/readme
Updated README.md with DOGE
2025-02-08 22:03:28 +01:00
tecnovert
d95f3ccd24 Fix checkWallets regression, must rename watchonly wallet also. 2025-02-08 00:10:39 +02:00
gerlofvanek
d6a9425b22 Updated README.md with DOGE 2025-02-03 11:07:06 +01:00
dependabot[bot]
e4cc5da490 build(deps): bump pyzmq from 26.2.0 to 26.2.1
Bumps [pyzmq](https://github.com/zeromq/pyzmq) from 26.2.0 to 26.2.1.
- [Release notes](https://github.com/zeromq/pyzmq/releases)
- [Commits](https://github.com/zeromq/pyzmq/compare/v26.2.0...v26.2.1)

---
updated-dependencies:
- dependency-name: pyzmq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 07:58:31 +00:00
tecnovert
e177d36bd4 Silence python deprecation warning. 2025-02-02 11:14:01 +02:00
tecnovert
05ffa5e3ac prepare: Can use original UTXO snapshot signature.
Prints UTXO snapshot hashing progress.
Add signature of snapshot hashes.
Add PGP keys for Nicolas Dorier.
2025-02-02 11:14:01 +02:00
tecnovert
6165cbc4c3 Change ADD_PUBKEY_URL to per coin. 2025-02-02 11:14:01 +02:00
tecnovert
7e6f94319d Import signing pubkeys from local filesystem. 2025-02-02 11:14:00 +02:00
tecnovert
71fd3d10aa Merge pull request #252 from tecnovert/descriptors
Add BTC descriptor wallet support.
2025-01-31 19:06:22 +00:00
tecnovert
e4ed9aebdf Merge pull request #256 from tecnovert/scripts
scripts: Periodically prune old state data.
2025-01-31 19:03:14 +00:00
Gerlof van Ek
b97a9f4a27 Merge pull request #258 from nahuhh/pr/scroll
js: contain scroll
2025-01-30 23:03:40 +01:00
nahuhh
510eff6163 js: contain scroll 2025-01-30 21:54:59 +00:00
tecnovert
efb84f58af scripts: Periodically prune old state data.
Set "prune_state_delay" to 0 to disable.
Removes entires over two weeks old by default.
2025-01-30 15:58:54 +02:00
tecnovert
831ef40977 tests: Intercept signals in test_scripts.py 2025-01-30 15:56:25 +02:00
tecnovert
a0456cb689 Avoid reentrant error in signal_handler. 2025-01-30 15:21:56 +02:00
tecnovert
c7818f5fac Merge branch 'dev' 2025-01-30 14:18:24 +02:00
Gerlof van Ek
713577d868 JS/UI: Tooltips + Sorting table + Memory fix and new header. (#253)
* JS/UI: Tooltips + Sorting table + Memory fix and new header.

* LINT

* Light theme fix

* JS: Global / standalone Tooltips.

* Unminimized versions of tippy and popper js libs

* Formatting / Cleanup
2025-01-30 12:16:41 +00:00
tecnovert
37be3bcab5 Add BTC descriptor wallet support.
Set BTC_USE_DESCRIPTORS env var to true to enable descriptors in the prepare script and test_btc_xmr
A separate watchonly wallet is created when using descriptor wallets.
2025-01-29 10:16:07 +02:00
tecnovert
4ae97790aa Merge pull request #255 from tecnovert/ci
Fix CI caching
2025-01-29 07:27:41 +00:00
tecnovert
8928451af0 Merge pull request #251 from tecnovert/wallet_name
Add wallet_name option to basicswap.json.
2025-01-29 07:27:28 +00:00
tecnovert
473e4fd400 Fix CI caching 2025-01-29 09:16:23 +02:00
tecnovert
ff2fc35f72 Add wallet_name option to basicswap.json.
Removed "walletfile" setting for XMR and WOW, replaced with "wallet_name".
Set wallet_name in prepare script with eg: BTC_WALLET_NAME env var.
2025-01-28 09:40:29 +02:00
tecnovert
edb3b19dcf Merge pull request #248 from nahuhh/pr/transient
xmr: make " failed to get earliest fork height" a transient error
2025-01-23 08:53:08 +00:00
nahuhh
aac2f51b88 xmr: make earliest fork height a transient error 2025-01-22 23:30:33 +00:00
nahuhh
57b96cd985 wallet: fix reseed regression 2025-01-22 21:21:55 +02:00
tecnovert
4d5551cd84 Merge pull request #247 from nahuhh/pr/reseed
wallet: fix reseed regression
2025-01-22 19:00:07 +00:00
nahuhh
7ee4720738 wallet: fix reseed regression 2025-01-22 18:24:21 +00:00
tecnovert
c76fe79848 scripts: Fix createoffers, identities api changed. 2025-01-22 20:13:33 +02:00
tecnovert
f13c481b51 tests: Fix test_xmr_persistent with BTC v28. 2025-01-22 20:08:46 +02:00
tecnovert
6f776971b1 Merge pull request #245 from tecnovert/fastsync
prepare: Update BTC fastsync file.
2025-01-22 16:10:05 +00:00
tecnovert
c79ed493aa Add estimated tx fee to amount check when posting bid.
Add more log messages around balance checks.
2025-01-22 18:07:20 +02:00
tecnovert
b6709d0cdc prepare: Update BTC fastsync file.
Allow specifying a custom URL to look for the snapshot signature with: BITCOIN_FASTSYNC_SIG_URL.

Reduce gnupg module logging level.
2025-01-22 00:48:27 +02:00
tecnovert
c945e267e7 Merge pull request #239 from nahuhh/pr/filters
offers: align filters
2025-01-21 19:12:20 +00:00
tecnovert
ef65420978 Merge pull request #235 from nahuhh/pr/wallet
wallet: resposive ui & cleanup
2025-01-21 19:11:56 +00:00
tecnovert
6da4bf6aaf Merge pull request #225 from nahuhh/cores
help: add --upgradecores
2025-01-21 19:11:29 +00:00
tecnovert
6e56b7f421 Merge pull request #224 from nahuhh/pr/minbid
ui: reword min bid -> min purchase
2025-01-21 19:11:13 +00:00
Gerlof van Ek
f084c6f538 JS/UI: Fix scrolling lag / tooltips + Various fixes and cleanup. (#236)
* JS/UI: Fix scrolling lag + Various fixes and cleanup.

* Fix clear button

* JS: Fix when page is hidden, reconnect and proper pause/resume logic.

* JS: Fix tooltips bugs.

* JS: Various fixes.

* JS: Fix fetch system.

* JS: Cleanup
2025-01-21 19:10:52 +00:00
nahuhh
443bd6917f prepare: fix mweb wallet generation (#238)
* prepare: fix mweb wallet generation

* Restore interface_type on LTC MWEB and send it through initialiseWallet.

---------

Co-authored-by: tecnovert <tecnovert@tecnovert.net>
2025-01-21 19:09:04 +00:00
nahuhh
b55d126a0a ui: reword min bid -> min purchase 2025-01-21 13:23:31 +00:00
nahuhh
586ff3288f offers: align filters 2025-01-21 11:53:38 +00:00
nahuhh
0398fce5a8 wallet: responsive 2025-01-20 22:50:35 +00:00
nahuhh
ef082ff7be xmr: remove inaccurate fee rate, hide sweep all checkbox 2025-01-20 22:50:35 +00:00
nahuhh
168284ce25 wallet: cleanup, deduplicate, djlints 2025-01-20 22:50:27 +00:00
Gerlof van Ek
e797e23625 Merge pull request #234 from gerlofvanek/decimals
JS: Decimals
2025-01-18 22:22:28 +01:00
gerlofvanek
d3fcdc8052 JS: Decimals 2025-01-18 22:21:24 +01:00
Gerlof van Ek
2c176a8c86 Merge pull request #232 from gerlofvanek/cleanup-4
JS: Final tweaks 429
2025-01-18 21:06:54 +01:00
gerlofvanek
e92d5560af JS: Final tweaks 429 2025-01-18 20:53:13 +01:00
Gerlof van Ek
0171ad6889 Merge pull request #231 from gerlofvanek/eslint
Fix: Eslint.
2025-01-18 20:38:02 +01:00
gerlofvanek
5d381d4b73 Fix: Eslint. 2025-01-18 20:22:51 +01:00
Gerlof van Ek
9e24d9a12a Merge pull request #230 from nahuhh/pr/overflow
ui: missing character
2025-01-18 18:58:19 +01:00
nahuhh
21ef6f3129 ui: missing character 2025-01-18 17:31:40 +00:00
Gerlof van Ek
67d808cbe4 JS: Enhanced 429 fix + better error handle. Updated refresh button. (#229)
* JS: Enhanced 429 fix + better error handle. Updated refresh button.

* Update offerstable.js
2025-01-18 18:14:49 +01:00
Gerlof van Ek
5d1bed6423 Merge pull request #228 from nahuhh/pr/overflow
ui: responsive offers page
2025-01-18 18:13:47 +01:00
nahuhh
edc11b4c96 ui: offers responsive ui
ui: offers avoid squishing tiles
2025-01-18 16:55:48 +00:00
Gerlof van Ek
5daf591985 Merge pull request #226 from gerlofvanek/cleanup-2
JS: Fix HTTP Error 429
2025-01-17 22:50:39 +01:00
Gerlof van Ek
aee66712b8 Update pricechart.js 2025-01-17 22:14:19 +01:00
Gerlof van Ek
8de365f9d3 Update offerstable.js 2025-01-17 22:13:37 +01:00
gerlofvanek
765ef9571a JS: Fix HTTP Error 429 2025-01-17 20:15:58 +01:00
nahuhh
c575625097 help: add --upgradecores 2025-01-17 14:54:56 +00:00
tecnovert
fe02441619 Merge pull request #223 from gerlofvanek/cleanup-1
JS: Cleanup + Fixes
2025-01-17 10:46:07 +00:00
gerlofvanek
c992ef571a JS: Cleanup + Fixes 2025-01-17 11:34:28 +01:00
tecnovert
5f275132de Merge pull request #218 from basicswap/dependabot/pip/dev/python-gnupg-0.5.4
build(deps): bump python-gnupg from 0.5.3 to 0.5.4
2025-01-17 07:42:13 +00:00
tecnovert
64151f4203 Merge pull request #216 from nahuhh/bch
bch: v28.0.1
2025-01-17 07:39:51 +00:00
tecnovert
734214af53 Merge pull request #222 from nahuhh/pr/overflow
offers: fix overflow bar
2025-01-17 07:39:09 +00:00
tecnovert
1cb8ffb632 Merge pull request #220 from nahuhh/pr/eslint
lint: eslinting suggestions
2025-01-17 07:34:36 +00:00
nahuhh
40d06df325 offers: fix overflow bar 2025-01-17 02:55:20 +00:00
nahuhh
62031173f5 lint: manual eslint 2025-01-16 21:36:08 +00:00
nahuhh
f473d66de5 lint: auto eslint 2025-01-16 21:26:20 +00:00
tecnovert
e548cf2b3b Merge pull request #219 from gerlofvanek/memory
Fix potential sources of mem leaks + Various related fixes.
2025-01-16 19:28:13 +00:00
gerlofvanek
d1baf4bc10 Improve identity fetching + hor/ver bar fix. 2025-01-16 19:14:58 +01:00
gerlofvanek
3b8e084b2e Simplified API requests and remove debug. 2025-01-16 16:34:36 +01:00
gerlofvanek
0a697c61e8 Fix scroll up / down memory increase bug. 2025-01-16 16:26:48 +01:00
gerlofvanek
5af59dd8da Pricechart fix potential mem leaks. 2025-01-16 14:17:46 +01:00
gerlofvanek
a75cd28995 Reduced retry delay. 2025-01-16 13:23:18 +01:00
gerlofvanek
f40d98ef23 Fix API issue with Firo and various small fixes. 2025-01-16 13:08:52 +01:00
gerlofvanek
b14fba0e1f Fix small API bug and better status feedback. 2025-01-16 12:00:50 +01:00
gerlofvanek
4d928dc98e Better error handling API / Tooltips: Rate, Market. 2025-01-16 11:38:02 +01:00
gerlofvanek
1845f802a2 Update cleanup. 2025-01-16 10:25:25 +01:00
gerlofvanek
7ec9dfa35a Fix manual refresh button. 2025-01-16 01:28:10 +01:00
gerlofvanek
b70e46ffc1 Fix potential sources of mem leaks. 2025-01-16 01:17:23 +01:00
dependabot[bot]
07de2d61af build(deps): bump python-gnupg from 0.5.3 to 0.5.4
Bumps [python-gnupg](https://github.com/vsajip/python-gnupg) from 0.5.3 to 0.5.4.
- [Release notes](https://github.com/vsajip/python-gnupg/releases)
- [Changelog](https://github.com/vsajip/python-gnupg/blob/master/release)
- [Commits](https://github.com/vsajip/python-gnupg/compare/0.5.3...0.5.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-15 16:59:52 +00:00
nahuhh
b87e034719 bch: v28.0.1 2025-01-14 12:49:31 +00:00
89 changed files with 15233 additions and 4713 deletions

View File

@@ -30,6 +30,9 @@ jobs:
- name: Install
run: |
pip install .
# Print the core versions to a file for caching
basicswap-prepare --version --withcoins=bitcoin | tail -n +2 > core_versions.txt
cat core_versions.txt
- name: Running flake8
run: |
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
env:
cache-name: cache-cores
CACHE_KEY: $(printf $(python bin/basicswap-prepare.py --version --withcoins=bitcoin) | sha256sum | head -c 64)
with:
path: $BIN_DIR
key: $CACHE_KEY
path: /tmp/cached_bin
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
run: |
basicswap-prepare --bindir="$BIN_DIR" --preparebinonly --withcoins=particl,bitcoin,monero
- name: Running test_xmr
run: |
export PYTHONPATH=$(pwd)
export PARTICL_BINDIR="$BIN_DIR/particl";
export BITCOIN_BINDIR="$BIN_DIR/bitcoin";
export XMR_BINDIR="$BIN_DIR/monero";
export PARTICL_BINDIR="$BIN_DIR/particl"
export BITCOIN_BINDIR="$BIN_DIR/bitcoin"
export XMR_BINDIR="$BIN_DIR/monero"
pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx"
- name: Running test_encrypted_xmr_reload
run: |

View File

@@ -112,6 +112,18 @@ BasicSwap is compatible with the following digital assets.
<td>PART
</td>
</tr>
<tr>
<td>Dogecoin
</td>
<td>DOGE
</td>
</tr>
<tr>
<td>Namecoin
</td>
<td>NMC
</td>
</tr>
</table>
If youd like to add a cryptocurrency to BasicSwap, refer to how other cryptocurrencies have been integrated to the DEX by following [this link](https://academy.particl.io/en/latest/basicswap-guides/basicswapguides_apply.html).

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -28,6 +28,9 @@ from .rpc import (
from .util import (
TemporaryError,
)
from .util.logging import (
BSXLogger,
)
from .chainparams import (
Coins,
chainparams,
@@ -75,6 +78,7 @@ class BaseApp(DBMethods):
self.delay_event.set()
def prepareLogging(self):
logging.setLoggerClass(BSXLogger)
self.log = logging.getLogger(self.log_name)
self.log.propagate = False

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@ import basicswap.config as cfg
from basicswap import __version__
from basicswap.base import getaddrinfo_tor
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.ui.util import getCoinName
from basicswap.util import toBool
@@ -47,6 +47,7 @@ from basicswap.bin.run import (
getWalletBinName,
)
PARTICL_VERSION = os.getenv("PARTICL_VERSION", "23.2.7.0")
PARTICL_VERSION_TAG = os.getenv("PARTICL_VERSION_TAG", "")
PARTICL_LINUX_EXTRA = os.getenv("PARTICL_LINUX_EXTRA", "nousb")
@@ -84,15 +85,13 @@ NAV_VERSION_TAG = os.getenv("NAV_VERSION_TAG", "")
DCR_VERSION = os.getenv("DCR_VERSION", "1.8.1")
DCR_VERSION_TAG = os.getenv("DCR_VERSION_TAG", "")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "27.1.0")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "28.0.1")
BITCOINCASH_VERSION_TAG = os.getenv("BITCOINCASH_VERSION_TAG", "")
DOGECOIN_VERSION = os.getenv("DOGECOIN_VERSION", "23.2.1")
DOGECOIN_VERSION_TAG = os.getenv("DOGECOIN_VERSION_TAG", "")
GUIX_SSL_CERT_DIR = None
ADD_PUBKEY_URL = os.getenv("ADD_PUBKEY_URL", "")
OVERRIDE_DISABLED_COINS = toBool(os.getenv("OVERRIDE_DISABLED_COINS", "false"))
# If SKIP_GPG_VALIDATION is set to true the script will check hashes but not signatures
@@ -134,6 +133,7 @@ expected_key_ids = {
"pasta": ("52527BEDABE87984", "E2F3D7916E722D38"),
"reuben": ("1290A1D0FA7EE109",),
"nav_builder": ("2782262BF6E7FADB",),
"nicolasdorier": ("6618763EF09186FE", "223FDA69DEBEA82D", "62FE85647DEDDA2E"),
"decred_release": ("6D897EDF518A031D",),
"Calin_Culianu": ("21810A542031C02C",),
}
@@ -162,6 +162,7 @@ LOG_LEVEL = logging.DEBUG
logger.level = LOG_LEVEL
if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout))
logging.getLogger("gnupg").setLevel(logging.INFO)
BSX_DOCKER_MODE = toBool(os.getenv("BSX_DOCKER_MODE", "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_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_RPC_USER = os.getenv("BTC_RPC_USER", "")
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_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_ONION_PORT = int(os.getenv("BCH_ONION_PORT", 8335))
BCH_RPC_USER = os.getenv("BCH_RPC_USER", "")
BCH_RPC_PWD = os.getenv("BCH_RPC_PWD", "")
@@ -268,10 +270,18 @@ TOR_PROXY_HOST = os.getenv("TOR_PROXY_HOST", "127.0.0.1")
TOR_PROXY_PORT = int(os.getenv("TOR_PROXY_PORT", 9050))
TOR_CONTROL_PORT = int(os.getenv("TOR_CONTROL_PORT", 9051))
TOR_DNS_PORT = int(os.getenv("TOR_DNS_PORT", 5353))
TOR_CONTROL_LISTEN_INTERFACE = os.getenv("TOR_CONTROL_LISTEN_INTERFACE", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TORRC_PROXY_HOST = os.getenv("TORRC_PROXY_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TORRC_CONTROL_HOST = os.getenv("TORRC_CONTROL_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TORRC_DNS_HOST = os.getenv("TORRC_DNS_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TOR_CONTROL_LISTEN_INTERFACE = os.getenv(
"TOR_CONTROL_LISTEN_INTERFACE", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
)
TORRC_PROXY_HOST = os.getenv(
"TORRC_PROXY_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
)
TORRC_CONTROL_HOST = os.getenv(
"TORRC_CONTROL_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
)
TORRC_DNS_HOST = os.getenv(
"TORRC_DNS_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
)
TEST_TOR_PROXY = toBool(
os.getenv("TEST_TOR_PROXY", "true")
@@ -280,10 +290,14 @@ TEST_ONION_LINK = toBool(os.getenv("TEST_ONION_LINK", "false"))
BITCOIN_FASTSYNC_URL = os.getenv(
"BITCOIN_FASTSYNC_URL",
"https://eu2.contabostorage.com/1f50a74c9dc14888a8664415dad3d020:utxosets/",
"https://snapshots.btcpay.tech/",
)
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
@@ -349,6 +363,18 @@ def shouldManageDaemon(prefix: str) -> bool:
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:
version, version_tag, _ = known_coins[coin_name]
return version + version_tag
@@ -447,7 +473,27 @@ def downloadBytes(url) -> None:
popConnectionParameters()
def importPubkeyFromUrls(gpg, pubkeyurls):
def getBasePath():
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if os.path.exists(os.path.join(base_path, "basicswap", "pgp")):
base_path = os.path.join(base_path, "basicswap")
return base_path
def importPubkey(gpg, pubkey_filename, pubkeyurls):
base_path = getBasePath()
local_path = os.path.join(base_path, "pgp", "keys", pubkey_filename)
if os.path.exists(local_path):
logger.info("Importing public key from file: " + pubkey_filename)
try:
with open(local_path, "rb") as fp:
rv = gpg.import_keys(fp.read())
for key in rv.fingerprints:
gpg.trust_keys(key, "TRUST_FULLY")
return
except Exception as e:
logging.warning(f"Import from file failed: {e}")
for url in pubkeyurls:
try:
logger.info("Importing public key from url: " + url)
@@ -456,7 +502,7 @@ def importPubkeyFromUrls(gpg, pubkeyurls):
gpg.trust_keys(key, "TRUST_FULLY")
break
except Exception as e:
logging.warning("Import from url failed: %s", str(e))
logging.warning(f"Import from url failed: {e}")
def testTorConnection():
@@ -485,6 +531,23 @@ def havePubkey(gpg, key_id):
return False
def getFileHash(file_path, print_progress: bool = False) -> str:
h = hashlib.sha256()
if print_progress:
reporthook = make_reporthook(0, logger)
total_size: int = os.stat(file_path).st_size
block_num: int = 0
block_size: int = 1024 * 1024
with open(file_path, "rb") as fp:
while data_chunk := fp.read(block_size):
h.update(data_chunk)
block_num += 1
if print_progress:
reporthook(block_num, block_size, total_size)
return h.hexdigest()
def downloadPIVXParams(output_dir):
# util/fetch-params.sh
@@ -505,10 +568,8 @@ def downloadPIVXParams(output_dir):
url = urllib.parse.urljoin(source_url, k)
path = os.path.join(output_dir, k)
downloadFile(url, path)
hasher = hashlib.sha256()
with open(path, "rb") as fp:
hasher.update(fp.read())
file_hash = hasher.hexdigest()
file_hash = getFileHash(path)
logger.info("%s hash: %s", k, file_hash)
assert file_hash == v
finally:
@@ -531,6 +592,8 @@ def ensureValidSignatureBy(result, 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)
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={}):
version, version_tag, signers = version_data
@@ -964,23 +1027,17 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if not os.path.exists(assert_sig_path):
downloadFile(assert_sig_url, assert_sig_path)
hasher = hashlib.sha256()
with open(release_path, "rb") as fp:
hasher.update(fp.read())
release_hash = hasher.digest()
logger.info("%s hash: %s", release_filename, release_hash.hex())
release_hash = getFileHash(release_path)
logger.info(f"{release_filename} hash: {release_hash}")
with (
open(assert_path, "rb", 0) as fp,
mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) as s,
):
if s.find(bytes(release_hash.hex(), "utf-8")) == -1:
if s.find(bytes(release_hash, "utf-8")) == -1:
raise ValueError(
"Error: release hash %s not found in assert file."
% (release_hash.hex())
f"Error: Release hash {release_hash} not found in assert file."
)
else:
logger.info("Found release hash in assert file.")
logger.info("Found release hash in assert file.")
if SKIP_GPG_VALIDATION:
logger.warning(
@@ -1014,11 +1071,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
pubkey_filename = "particl_{}.pgp".format(signing_key_name)
else:
pubkey_filename = "{}_{}.pgp".format(coin, signing_key_name)
pubkeyurls = [
"https://raw.githubusercontent.com/basicswap/basicswap/master/pgp/keys/"
+ pubkey_filename,
"https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/" + pubkey_filename,
]
pubkeyurls = []
if coin == "dash":
pubkeyurls.append(
"https://raw.githubusercontent.com/dashpay/dash/master/contrib/gitian-keys/pasta.pgp"
@@ -1038,8 +1091,11 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
"https://gitlab.com/bitcoin-cash-node/bitcoin-cash-node/-/raw/master/contrib/gitian-signing/pubkeys.txt"
)
if ADD_PUBKEY_URL != "":
pubkeyurls.append(ADD_PUBKEY_URL + "/" + pubkey_filename)
coin_id = getCoinIdFromName(coin)
ticker: str = chainparams[coin_id]["ticker"]
extra_pubkey_url: str = os.getenv(f"{ticker}_ADD_PUBKEY_URL", "")
if extra_pubkey_url != "":
pubkeyurls.append(extra_pubkey_url)
if coin in (
"monero",
@@ -1052,7 +1108,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if not isValidSignature(verified) and verified.username is None:
logger.warning("Signature made by unknown key.")
importPubkeyFromUrls(gpg, pubkeyurls)
importPubkey(gpg, pubkey_filename, pubkeyurls)
with open(assert_path, "rb") as fp:
verified = gpg.verify_file(fp)
elif coin in ("navcoin"):
@@ -1061,7 +1117,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
if not isValidSignature(verified) and verified.username is None:
logger.warning("Signature made by unknown key.")
importPubkeyFromUrls(gpg, pubkeyurls)
importPubkey(gpg, pubkey_filename, pubkeyurls)
with open(assert_sig_path, "rb") as fp:
verified = gpg.verify_file(fp)
@@ -1073,10 +1129,9 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
else:
with open(assert_sig_path, "rb") as fp:
verified = gpg.verify_file(fp, assert_path)
if not isValidSignature(verified) and verified.username is None:
logger.warning("Signature made by unknown key.")
importPubkeyFromUrls(gpg, pubkeyurls)
importPubkey(gpg, pubkey_filename, pubkeyurls)
with open(assert_sig_path, "rb") as fp:
verified = gpg.verify_file(fp, assert_path)
@@ -1105,6 +1160,8 @@ def writeTorSettings(fp, coin, coin_settings, tor_control_password):
def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
core_settings = settings["chainclients"][coin]
wallet_name = core_settings.get("wallet_name", "wallet.dat")
assert len(wallet_name) > 0
data_dir = core_settings["datadir"]
tor_control_password = extra_opts.get("tor_control_password", None)
@@ -1285,7 +1342,7 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
fp.write("rpcport={}\n".format(core_settings["rpcport"]))
fp.write("printtoconsole=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:
writeTorSettings(fp, coin, core_settings, tor_control_password)
@@ -1408,10 +1465,14 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
# Double check
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:
ft.extractall(path=data_dir)
if hasattr(tarfile, "data_filter"):
ft.extractall(path=data_dir, filter="data")
else:
# TODO: Remove when minimum python version is >= 3.12
ft.extractall(path=data_dir)
def write_torrc(data_dir, tor_control_password):
@@ -1593,6 +1654,9 @@ def printHelp():
print("--withoutcoin= Do not prepare system to run daemon for coin.")
print("--addcoin= Add coin to existing setup.")
print("--disablecoin= Make coin inactive.")
print(
"--upgradecores Upgrade all coin cores present in basicswap.json. Optionally use alongside --withcoin= or --withoutcoin="
)
print("--preparebinonly Don't prepare settings or datadirs.")
print("--nocores Don't download and extract any coin clients.")
print("--usecontainers Expect each core to run in a unique container.")
@@ -1752,6 +1816,7 @@ def initialise_wallets(
] + [c for c in with_coins if c != "particl"]
for coin_name in start_daemons:
coin_settings = settings["chainclients"][coin_name]
wallet_name = coin_settings.get("wallet_name", "wallet.dat")
c = swap_client.getCoinIdFromName(coin_name)
if c == Coins.XMR:
@@ -1843,28 +1908,51 @@ def initialise_wallets(
swap_client.waitForDaemonRPC(c, with_wallet=False)
# Create wallet if it doesn't exist yet
wallets = swap_client.callcoinrpc(c, "listwallets")
if len(wallets) < 1:
if wallet_name not in wallets:
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):
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
use_descriptors = coin_settings.get(
"use_descriptors", False
)
swap_client.callcoinrpc(
c,
"createwallet",
[
"wallet.dat",
wallet_name,
False,
True,
WALLET_ENCRYPTION_PWD,
False,
False,
use_descriptors,
],
)
if use_descriptors:
swap_client.callcoinrpc(
c,
"createwallet",
[
coin_settings["watch_wallet_name"],
True,
True,
"",
False,
use_descriptors,
],
)
swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD)
else:
swap_client.callcoinrpc(c, "createwallet", ["wallet.dat"])
swap_client.callcoinrpc(
c,
"createwallet",
[
wallet_name,
],
)
if WALLET_ENCRYPTION_PWD != "":
encrypt_wallet(swap_client, c)
@@ -1950,37 +2038,58 @@ def load_config(config_path):
def signal_handler(sig, frame):
logger.info("Signal %d detected" % (sig))
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
def check_btc_fastsync_data(base_dir, sync_file_path):
github_pgp_url = "https://raw.githubusercontent.com/basicswap/basicswap/master/pgp"
gitlab_pgp_url = "https://gitlab.com/particl/basicswap/-/raw/master/pgp"
asc_filename = BITCOIN_FASTSYNC_FILE + ".asc"
def check_btc_fastsync_data(base_dir, sync_filename):
logger.info("Validating signature for: " + sync_filename)
asc_filename = "utxo-snapshot-bitcoin-mainnet-hashes.asc"
asc_file_path = os.path.join(base_dir, asc_filename)
sync_file_path = os.path.join(base_dir, sync_filename)
if BITCOIN_FASTSYNC_SIG_URL:
try:
downloadFile(BITCOIN_FASTSYNC_SIG_URL, asc_file_path)
except Exception as e:
logging.warning(f"Download failed: {e}")
elif not os.path.exists(asc_file_path):
base_path = getBasePath()
local_path = os.path.join(base_path, "pgp", "sigs", asc_filename)
if os.path.exists(local_path):
shutil.copyfile(local_path, asc_file_path)
if not os.path.exists(asc_file_path):
asc_file_urls = (
github_pgp_url + "/sigs/" + asc_filename,
gitlab_pgp_url + "/sigs/" + asc_filename,
)
for url in asc_file_urls:
try:
downloadFile(url, asc_file_path)
break
except Exception as e:
logging.warning("Download failed: %s", str(e))
raise ValueError("Unable to find snapshot assert file.")
logger.info(f"Hashing {sync_filename}:")
utxo_snapshot_hash = getFileHash(sync_file_path, print_progress=True)
logger.info(f"{sync_filename} hash: {utxo_snapshot_hash}")
with (
open(asc_file_path, "rb", 0) as fp,
mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) as s,
):
if s.find(bytes(utxo_snapshot_hash, "utf-8")) == -1:
raise ValueError(
f"Error: Snapshot hash {utxo_snapshot_hash} not found in assert file."
)
logger.info("Found snapshot hash in assert file.")
gpg = gnupg.GPG()
pubkey_filename = "{}_{}.pgp".format("particl", "tecnovert")
pubkeyurls = [
github_pgp_url + "/keys/" + pubkey_filename,
gitlab_pgp_url + "/keys/" + pubkey_filename,
]
pubkeyurls = []
if not havePubkey(gpg, expected_key_ids["tecnovert"][0]):
importPubkeyFromUrls(gpg, pubkeyurls)
importPubkey(gpg, pubkey_filename, pubkeyurls)
with open(asc_file_path, "rb") as fp:
verified = gpg.verify_file(fp, sync_file_path)
ensureValidSignatureBy(verified, "tecnovert")
verified = gpg.verify_file(fp)
if isValidSignature(verified) and verified.key_id in expected_key_ids["tecnovert"]:
ensureValidSignatureBy(verified, "tecnovert")
else:
pubkey_filename = "nicolasdorier.asc"
if not havePubkey(gpg, expected_key_ids["nicolasdorier"][0]):
importPubkey(gpg, pubkey_filename, pubkeyurls)
with open(asc_file_path, "rb") as fp:
verified = gpg.verify_file(fp)
ensureValidSignatureBy(verified, "nicolasdorier")
def ensure_coin_valid(coin: str, test_disabled: bool = True) -> None:
@@ -2240,7 +2349,7 @@ def main():
check_sig = True
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:
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}"
@@ -2271,6 +2380,7 @@ def main():
"onionport": BTC_ONION_PORT + port_offset,
"datadir": os.getenv("BTC_DATA_DIR", os.path.join(data_dir, "bitcoin")),
"bindir": os.path.join(bin_dir, "bitcoin"),
"port": BTC_PORT + port_offset,
"use_segwit": True,
"blocks_confirmed": 1,
"conf_target": 2,
@@ -2373,7 +2483,6 @@ def main():
"walletrpchost": XMR_WALLET_RPC_HOST,
"walletrpcuser": XMR_WALLET_RPC_USER,
"walletrpcpassword": XMR_WALLET_RPC_PWD,
"walletfile": "swap_wallet",
"datadir": os.getenv("XMR_DATA_DIR", os.path.join(data_dir, "monero")),
"bindir": os.path.join(bin_dir, "monero"),
"restore_height": xmr_restore_height,
@@ -2460,7 +2569,6 @@ def main():
"walletrpchost": WOW_WALLET_RPC_HOST,
"walletrpcuser": WOW_WALLET_RPC_USER,
"walletrpcpassword": WOW_WALLET_RPC_PWD,
"walletfile": "swap_wallet",
"datadir": os.getenv("WOW_DATA_DIR", os.path.join(data_dir, "wownero")),
"bindir": os.path.join(bin_dir, "wownero"),
"restore_height": wow_restore_height,
@@ -2473,6 +2581,36 @@ 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
ticker: str = coin_params["ticker"]
if toBool(os.getenv(ticker + "_USE_DESCRIPTORS", False)):
if coin_id not in (Coins.BTC,):
raise ValueError(f"Descriptor wallet unavailable for {coin_name}")
coin_settings["use_descriptors"] = True
coin_settings["watch_wallet_name"] = getWalletName(
coin_params, "bsx_watch", prefix_override=f"{ticker}_WATCH"
)
if PART_RPC_USER != "":
chainclients["particl"]["rpcuser"] = PART_RPC_USER
chainclients["particl"]["rpcpassword"] = PART_RPC_PWD

View File

@@ -6,14 +6,14 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import sys
import json
import logging
import os
import shutil
import signal
import logging
import traceback
import subprocess
import sys
import traceback
import basicswap.config as cfg
from basicswap import __version__
@@ -24,10 +24,11 @@ from basicswap.http_server import HttpThread
from basicswap.contrib.websocket_server import WebsocketServer
logger = logging.getLogger()
logger.level = logging.DEBUG
if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout))
initial_logger = logging.getLogger()
initial_logger.level = logging.DEBUG
if not len(initial_logger.handlers):
initial_logger.addHandler(initial_logger.StreamHandler(sys.stdout))
logger = initial_logger
swap_client = None
@@ -48,9 +49,10 @@ def is_known_coin(coin_name: str) -> bool:
def signal_handler(sig, frame):
global swap_client
logger.info("Signal %d detected, ending program." % (sig))
if swap_client is not None:
os.write(
sys.stdout.fileno(), f"Signal {sig} detected, ending program.\n".encode("utf-8")
)
if swap_client is not None and not swap_client.chainstate_delay_event.is_set():
swap_client.stopRunning()
@@ -251,7 +253,7 @@ def getCoreBinArgs(coin_id: int, coin_settings, prepare=False, use_tor_proxy=Fal
extra_args = []
if "config_filename" in coin_settings:
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 coin_id == Coins.BCH:
# Without this BCH (27.1) will bind to the default BTC port, even with proxy set
@@ -440,7 +442,9 @@ def runClient(fp, data_dir, chain, start_only_coins):
swap_client.log.info(f"Starting {display_name} daemon")
filename: str = getCoreBinName(coin_id, v, c + "d")
extra_opts = getCoreBinArgs(coin_id, v, use_tor_proxy=swap_client.use_tor_proxy)
extra_opts = getCoreBinArgs(
coin_id, v, use_tor_proxy=swap_client.use_tor_proxy
)
daemons.append(
startDaemon(v["datadir"], v["bindir"], filename, opts=extra_opts)
)
@@ -531,7 +535,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
signal.CTRL_C_EVENT if os.name == "nt" else signal.SIGINT
)
except Exception as e:
swap_client.log.info("Interrupting %d, error %s", d.handle.pid, str(e))
swap_client.log.info(f"Interrupting {d.handle.pid}, error {e}")
for d in daemons:
try:
d.handle.wait(timeout=120)
@@ -539,8 +543,8 @@ def runClient(fp, data_dir, chain, start_only_coins):
if fp:
fp.close()
closed_pids.append(d.handle.pid)
except Exception as ex:
swap_client.log.error("Error: {}".format(ex))
except Exception as e:
swap_client.log.error(f"Error: {e}")
if os.path.exists(pids_path):
with open(pids_path) as fd:

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -91,6 +91,8 @@ chainparams = {
"bip44": 0,
"min_amount": 100000,
"max_amount": 10000000 * COIN,
"ext_public_key_prefix": 0x0488B21E,
"ext_secret_key_prefix": 0x0488ADE4,
},
"testnet": {
"rpcport": 18332,
@@ -102,6 +104,8 @@ chainparams = {
"min_amount": 100000,
"max_amount": 10000000 * COIN,
"name": "testnet3",
"ext_public_key_prefix": 0x043587CF,
"ext_secret_key_prefix": 0x04358394,
},
"regtest": {
"rpcport": 18443,
@@ -112,6 +116,8 @@ chainparams = {
"bip44": 1,
"min_amount": 100000,
"max_amount": 10000000 * COIN,
"ext_public_key_prefix": 0x043587CF,
"ext_secret_key_prefix": 0x04358394,
},
},
Coins.LTC: {
@@ -199,6 +205,7 @@ chainparams = {
"message_magic": "Decred Signed Message:\n",
"blocks_target": 60 * 5,
"decimal_places": 8,
"has_multiwallet": False,
"mainnet": {
"rpcport": 9109,
"pubkey_address": 0x073F,
@@ -404,6 +411,7 @@ chainparams = {
"has_cltv": False,
"has_csv": False,
"has_segwit": False,
"has_multiwallet": False,
"mainnet": {
"rpcport": 8888,
"pubkey_address": 82,
@@ -443,6 +451,7 @@ chainparams = {
"decimal_places": 8,
"has_csv": True,
"has_segwit": True,
"has_multiwallet": False,
"mainnet": {
"rpcport": 44444,
"pubkey_address": 53,
@@ -519,10 +528,13 @@ chainparams = {
},
},
}
name_map = {}
ticker_map = {}
for c, params in chainparams.items():
name_map[params["name"].lower()] = c
ticker_map[params["ticker"].lower()] = c
@@ -530,4 +542,11 @@ def getCoinIdFromTicker(ticker: str) -> str:
try:
return ticker_map[ticker.lower()]
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}")

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
# Copyright (c) 2019 Pieter Wuille
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Utility functions related to output descriptors"""
import re
INPUT_CHARSET = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "
CHECKSUM_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
GENERATOR = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd]
def descsum_polymod(symbols):
"""Internal function that computes the descriptor checksum."""
chk = 1
for value in symbols:
top = chk >> 35
chk = (chk & 0x7ffffffff) << 5 ^ value
for i in range(5):
chk ^= GENERATOR[i] if ((top >> i) & 1) else 0
return chk
def descsum_expand(s):
"""Internal function that does the character to symbol expansion"""
groups = []
symbols = []
for c in s:
if not c in INPUT_CHARSET:
return None
v = INPUT_CHARSET.find(c)
symbols.append(v & 31)
groups.append(v >> 5)
if len(groups) == 3:
symbols.append(groups[0] * 9 + groups[1] * 3 + groups[2])
groups = []
if len(groups) == 1:
symbols.append(groups[0])
elif len(groups) == 2:
symbols.append(groups[0] * 3 + groups[1])
return symbols
def descsum_create(s):
"""Add a checksum to a descriptor without"""
symbols = descsum_expand(s) + [0, 0, 0, 0, 0, 0, 0, 0]
checksum = descsum_polymod(symbols) ^ 1
return s + '#' + ''.join(CHECKSUM_CHARSET[(checksum >> (5 * (7 - i))) & 31] for i in range(8))
def descsum_check(s, require=True):
"""Verify that the checksum is correct in a descriptor"""
if not '#' in s:
return not require
if s[-9] != '#':
return False
if not all(x in CHECKSUM_CHARSET for x in s[-8:]):
return False
symbols = descsum_expand(s[:-9]) + [CHECKSUM_CHARSET.find(x) for x in s[-8:]]
return descsum_polymod(symbols) == 1
def drop_origins(s):
'''Drop the key origins from a descriptor'''
desc = re.sub(r'\[.+?\]', '', s)
if '#' in s:
desc = desc[:desc.index('#')]
return descsum_create(desc)

View File

@@ -578,10 +578,8 @@ class HttpHandler(BaseHTTPRequestHandler):
return page_offers(self, url_split, post_string, sent=True)
if page == "bid":
return page_bid(self, url_split, post_string)
if page == "receivedbids":
return page_bids(self, url_split, post_string, received=True)
if page == "sentbids":
return page_bids(self, url_split, post_string, sent=True)
if page == "bids":
return page_bids(self, url_split, post_string)
if page == "availablebids":
return page_bids(self, url_split, post_string, available=True)
if page == "watched":

View File

@@ -1,13 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from typing import Union
from basicswap.contrib.test_framework.messages import COutPoint, CTransaction, CTxIn
from basicswap.util import b2h, b2i, ensure, i2h
from basicswap.util import b2i, ensure, i2b
from basicswap.util.script import decodePushData, decodeScriptNum
from .btc import BTCInterface, ensure_op, findOutput
from basicswap.rpc import make_rpc_func
@@ -454,11 +454,14 @@ class BCHInterface(BTCInterface):
tx.rehash()
self._log.info(
"createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
size,
pay_fee,
"createSCLockSpendTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {size}, {pay_fee}"
),
)
)
return tx.serialize_without_witness()
@@ -506,11 +509,14 @@ class BCHInterface(BTCInterface):
tx.rehash()
self._log.info(
"createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockRefundTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize_without_witness(), refund_script, tx.vout[0].nValue
@@ -582,11 +588,14 @@ class BCHInterface(BTCInterface):
tx.rehash()
self._log.info(
"createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockRefundSpendToFTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize_without_witness()
@@ -780,7 +789,7 @@ class BCHInterface(BTCInterface):
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock tx: {}.".format(self._log.id(txid)))
ensure(tx.nVersion == self.txVersion(), "Bad version")
ensure(tx.nLockTime == 0, "Bad nLockTime") # TODO match txns created by cores
@@ -835,7 +844,7 @@ class BCHInterface(BTCInterface):
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock refund tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock refund tx: {}.".format(self._log.id(txid)))
ensure(tx.nVersion == self.txVersion(), "Bad version")
ensure(tx.nLockTime == 0, "nLockTime not 0")
@@ -881,7 +890,7 @@ class BCHInterface(BTCInterface):
size = self.getTxSize(tx)
vsize = size
self._log.info(
self._log.info_s(
"tx amount, vsize, fee: %ld, %ld, %ld", locked_coin, vsize, fee_paid
)
@@ -905,7 +914,7 @@ class BCHInterface(BTCInterface):
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock refund spend tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock refund spend tx: {}.".format(self._log.id(txid)))
ensure(tx.nVersion == self.txVersion(), "Bad version")
ensure(tx.nLockTime == 0, "nLockTime not 0")
@@ -947,9 +956,7 @@ class BCHInterface(BTCInterface):
size = self.getTxSize(tx)
vsize = size
self._log.info(
"tx amount, vsize, fee: %ld, %ld, %ld", tx_value, vsize, fee_paid
)
self._log.info_s(f"tx amount, vsize, fee: {tx_value}, {vsize}, {fee_paid}")
return True
@@ -962,7 +969,7 @@ class BCHInterface(BTCInterface):
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock spend tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock spend tx: {}.".format(self._log.id(txid)))
ensure(tx.nVersion == self.txVersion(), "Bad version")
ensure(tx.nLockTime == 0, "nLockTime not 0")
@@ -995,7 +1002,7 @@ class BCHInterface(BTCInterface):
size = self.getTxSize(tx)
vsize = size
self._log.info(
self._log.info_s(
"tx amount, vsize, fee: %ld, %ld, %ld", tx.vout[0].nValue, vsize, fee_paid
)
@@ -1115,11 +1122,14 @@ class BCHInterface(BTCInterface):
tx.rehash()
self._log.info(
"createMercyTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
1,
vsize,
pay_fee,
"createMercyTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {1}, {vsize}, {pay_fee}"
),
)
)
txHex = tx.serialize_without_witness()

View File

@@ -18,15 +18,9 @@ from basicswap.basicswap_util import (
getVoutByAddress,
getVoutByScriptPubKey,
)
from basicswap.contrib.test_framework import (
segwit_addr,
)
from basicswap.interface.base import (
Secp256k1Interface,
)
from basicswap.interface.base import Secp256k1Interface
from basicswap.util import (
ensure,
b2h,
i2b,
b2i,
i2h,
@@ -35,6 +29,7 @@ from basicswap.util.ecc import (
pointToCPK,
CPKToPoint,
)
from basicswap.util.extkey import ExtKeyPair
from basicswap.util.script import (
decodeScriptNum,
getCompactSizeLen,
@@ -44,6 +39,7 @@ from basicswap.util.script import (
from basicswap.util.address import (
toWIF,
b58encode,
b58decode,
decodeWif,
decodeAddress,
pubkeyToAddress,
@@ -63,6 +59,8 @@ from coincurve.ecdsaotves import (
ecdsaotves_rec_enc_key,
)
from basicswap.contrib.test_framework import segwit_addr
from basicswap.contrib.test_framework.descriptors import descsum_create
from basicswap.contrib.test_framework.messages import (
COIN,
COutPoint,
@@ -217,6 +215,10 @@ class BTCInterface(Secp256k1Interface):
rv += output.nValue
return rv
@staticmethod
def est_lock_tx_vsize() -> int:
return 110
@staticmethod
def xmr_swap_a_lock_spend_tx_vsize() -> int:
return 147
@@ -262,10 +264,22 @@ class BTCInterface(Secp256k1Interface):
self._rpcport = coin_settings["rpcport"]
self._rpcauth = coin_settings["rpcauth"]
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_watch = coin_settings.get(
"watch_wallet_name", self._rpc_wallet
)
self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
)
if self._rpc_wallet_watch == self._rpc_wallet:
self.rpc_wallet_watch = self.rpc_wallet
else:
self.rpc_wallet_watch = make_rpc_func(
self._rpcport,
self._rpcauth,
host=self._rpc_host,
wallet=self._rpc_wallet_watch,
)
self.blocks_confirmed = coin_settings["blocks_confirmed"]
self.setConfTarget(coin_settings["conf_target"])
self._use_segwit = coin_settings["use_segwit"]
@@ -274,6 +288,7 @@ class BTCInterface(Secp256k1Interface):
self._log = self._sc.log if self._sc and self._sc.log else logging
self._expect_seedid_hex = None
self._altruistic = coin_settings.get("altruistic", True)
self._use_descriptors = coin_settings.get("use_descriptors", False)
def open_rpc(self, wallet=None):
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
@@ -297,16 +312,19 @@ class BTCInterface(Secp256k1Interface):
# Wallet name is "" for some LTC and PART installs on older cores
if self._rpc_wallet not in wallets and len(wallets) > 0:
self._log.debug("Changing {} wallet name.".format(self.ticker()))
self._log.warning(f"Changing {self.ticker()} wallet name.")
for wallet_name in wallets:
# Skip over other expected wallets
if wallet_name in ("mweb",):
continue
change_watchonly_wallet: bool = (
self._rpc_wallet_watch == self._rpc_wallet
)
self._rpc_wallet = wallet_name
self._log.info(
"Switched {} wallet name to {}.".format(
self.ticker(), self._rpc_wallet
)
f"Switched {self.ticker()} wallet name to {self._rpc_wallet}."
)
self.rpc_wallet = make_rpc_func(
self._rpcport,
@@ -314,6 +332,8 @@ class BTCInterface(Secp256k1Interface):
host=self._rpc_host,
wallet=self._rpc_wallet,
)
if change_watchonly_wallet:
self.rpc_wallet_watch = self.rpc_wallet
break
return len(wallets)
@@ -358,9 +378,40 @@ class BTCInterface(Secp256k1Interface):
raise ValueError(f"Block header not found at time: {time}")
def initialiseWallet(self, key_bytes: bytes) -> None:
key_wif = self.encodeKey(key_bytes)
self.rpc_wallet("sethdseed", [True, key_wif])
assert len(key_bytes) == 32
self._have_checked_seed = False
if self._use_descriptors:
self._log.info("Importing descriptors")
ek = ExtKeyPair()
ek.set_seed(key_bytes)
ek_encoded: str = self.encode_secret_extkey(ek.encode_v())
desc_external = descsum_create(f"wpkh({ek_encoded}/0h/0h/*h)")
desc_internal = descsum_create(f"wpkh({ek_encoded}/0h/1h/*h)")
rv = self.rpc_wallet(
"importdescriptors",
[
[
{"desc": desc_external, "timestamp": "now", "active": True},
{
"desc": desc_internal,
"timestamp": "now",
"active": True,
"internal": True,
},
],
],
)
num_successful: int = 0
for entry in rv:
if entry.get("success", False) is True:
num_successful += 1
if num_successful != 2:
self._log.error(f"Failed to import descriptors: {rv}.")
raise ValueError("Failed to import descriptors.")
else:
key_wif = self.encodeKey(key_bytes)
self.rpc_wallet("sethdseed", [True, key_wif])
def getWalletInfo(self):
rv = self.rpc_wallet("getwalletinfo")
@@ -370,16 +421,23 @@ class BTCInterface(Secp256k1Interface):
return rv
def getWalletRestoreHeight(self) -> int:
start_time = self.rpc_wallet("getwalletinfo")["keypoololdest"]
if self._use_descriptors:
descriptor = self.getActiveDescriptor()
if descriptor is None:
start_time = 0
else:
start_time = descriptor["timestamp"]
else:
start_time = self.rpc_wallet("getwalletinfo")["keypoololdest"]
blockchaininfo = self.getBlockchainInfo()
best_block = blockchaininfo["bestblockhash"]
chain_synced = round(blockchaininfo["verificationprogress"], 3)
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()
try:
@@ -390,16 +448,43 @@ class BTCInterface(Secp256k1Interface):
)
if block_header["time"] < start_time:
return block_header["height"]
if "previousblockhash" not in block_header: # Genesis block
return block_header["height"]
block_hash = block_header["previousblockhash"]
finally:
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:
wi = self.rpc_wallet("getwalletinfo")
return "Not found" if "hdseedid" not in wi else wi["hdseedid"]
def getActiveDescriptor(self):
descriptors = self.rpc_wallet("listdescriptors")["descriptors"]
for descriptor in descriptors:
if (
descriptor["desc"].startswith("wpkh")
and descriptor["active"] is True
and descriptor["internal"] is False
):
return descriptor
return None
def checkExpectedSeed(self, expect_seedid: str) -> bool:
if self._use_descriptors:
descriptor = self.getActiveDescriptor()
if descriptor is None:
self._log.debug("Could not find active descriptor.")
return False
end = descriptor["desc"].find("/")
if end < 10:
return False
extkey = descriptor["desc"][5:end]
extkey_data = b58decode(extkey)[4:-4]
extkey_data_hash: bytes = hash160(extkey_data)
return True if extkey_data_hash.hex() == expect_seedid else False
wallet_seed_id = self.getWalletSeedID()
self._expect_seedid_hex = expect_seedid
self._have_checked_seed = True
@@ -424,6 +509,10 @@ class BTCInterface(Secp256k1Interface):
addr_info = self.rpc_wallet("getaddressinfo", [address])
if not or_watch_only:
return addr_info["ismine"]
if self._use_descriptors:
addr_info = self.rpc_wallet_watch("getaddressinfo", [address])
return addr_info["ismine"] or addr_info["iswatchonly"]
def checkAddressMine(self, address: str) -> None:
@@ -491,6 +580,20 @@ class BTCInterface(Secp256k1Interface):
pkh = hash160(pk)
return segwit_addr.encode(bech32_prefix, version, pkh)
def encode_secret_extkey(self, ek_data: bytes) -> str:
assert len(ek_data) == 74
prefix = self.chainparams_network()["ext_secret_key_prefix"]
data: bytes = prefix.to_bytes(4, "big") + ek_data
checksum = sha256(sha256(data))
return b58encode(data + checksum[0:4])
def encode_public_extkey(self, ek_data: bytes) -> str:
assert len(ek_data) == 74
prefix = self.chainparams_network()["ext_public_key_prefix"]
data: bytes = prefix.to_bytes(4, "big") + ek_data
checksum = sha256(sha256(data))
return b58encode(data + checksum[0:4])
def pkh_to_address(self, pkh: bytes) -> str:
# pkh is ripemd160(sha256(pk))
assert len(pkh) == 20
@@ -526,7 +629,12 @@ class BTCInterface(Secp256k1Interface):
pk = self.getPubkey(key)
return hash160(pk)
def getSeedHash(self, seed) -> bytes:
def getSeedHash(self, seed: bytes) -> bytes:
if self._use_descriptors:
ek = ExtKeyPair()
ek.set_seed(seed)
return hash160(ek.encode_p())
return self.getAddressHashFromKey(seed)[::-1]
def encodeKey(self, key_bytes: bytes) -> str:
@@ -626,11 +734,14 @@ class BTCInterface(Secp256k1Interface):
tx.rehash()
self._log.info(
"createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockRefundTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize(), refund_script, tx.vout[0].nValue
@@ -681,11 +792,14 @@ class BTCInterface(Secp256k1Interface):
tx.rehash()
self._log.info(
"createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockRefundSpendTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize()
@@ -748,11 +862,14 @@ class BTCInterface(Secp256k1Interface):
tx.rehash()
self._log.info(
"createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockRefundSpendToFTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize()
@@ -795,11 +912,14 @@ class BTCInterface(Secp256k1Interface):
tx.rehash()
self._log.info(
"createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockSpendTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize()
@@ -824,7 +944,7 @@ class BTCInterface(Secp256k1Interface):
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock tx: {}.".format(self._log.id(txid)))
ensure(tx.nVersion == self.txVersion(), "Bad version")
ensure(tx.nLockTime == 0, "Bad nLockTime") # TODO match txns created by cores
@@ -912,7 +1032,7 @@ class BTCInterface(Secp256k1Interface):
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock refund tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock refund tx: {}.".format(self._log.id(txid)))
ensure(tx.nVersion == self.txVersion(), "Bad version")
ensure(tx.nLockTime == 0, "nLockTime not 0")
@@ -951,7 +1071,7 @@ class BTCInterface(Secp256k1Interface):
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
fee_rate_paid = fee_paid * 1000 // vsize
self._log.info(
self._log.info_s(
"tx amount, vsize, feerate: %ld, %ld, %ld",
locked_coin,
vsize,
@@ -980,7 +1100,7 @@ class BTCInterface(Secp256k1Interface):
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock refund spend tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock refund spend tx: {}.".format(self._log.id(txid)))
ensure(tx.nVersion == self.txVersion(), "Bad version")
ensure(tx.nLockTime == 0, "nLockTime not 0")
@@ -1017,7 +1137,7 @@ class BTCInterface(Secp256k1Interface):
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
fee_rate_paid = fee_paid * 1000 // vsize
self._log.info(
self._log.info_s(
"tx amount, vsize, feerate: %ld, %ld, %ld", tx_value, vsize, fee_rate_paid
)
@@ -1035,7 +1155,7 @@ class BTCInterface(Secp256k1Interface):
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock spend tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock spend tx: {}.".format(self._log.id(txid)))
ensure(tx.nVersion == self.txVersion(), "Bad version")
ensure(tx.nLockTime == 0, "nLockTime not 0")
@@ -1073,7 +1193,7 @@ class BTCInterface(Secp256k1Interface):
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
fee_rate_paid = fee_paid * 1000 // vsize
self._log.info(
self._log.info_s(
"tx amount, vsize, feerate: %ld, %ld, %ld",
tx.vout[0].nValue,
vsize,
@@ -1383,7 +1503,7 @@ class BTCInterface(Secp256k1Interface):
witness_bytes = 109
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
pay_fee = round(fee_rate * vsize / 1000)
self._log.info(
self._log.info_s(
f"BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}."
)
return pay_fee
@@ -1401,7 +1521,9 @@ class BTCInterface(Secp256k1Interface):
lock_tx_vout=None,
) -> bytes:
self._log.info(
"spendBLockTx: {} {}\n".format(chain_b_lock_txid.hex(), lock_tx_vout)
"spendBLockTx: {} {}\n".format(
self._log.id(chain_b_lock_txid), lock_tx_vout
)
)
locked_n = lock_tx_vout
@@ -1409,7 +1531,7 @@ class BTCInterface(Secp256k1Interface):
script_pk = self.getPkDest(Kbs)
if locked_n is None:
wtx = self.rpc_wallet(
wtx = self.rpc_wallet_watch(
"gettransaction",
[
chain_b_lock_txid.hex(),
@@ -1446,10 +1568,23 @@ class BTCInterface(Secp256k1Interface):
return bytes.fromhex(self.publishTx(b_lock_spend_tx))
def importWatchOnlyAddress(self, address: str, label: str):
def importWatchOnlyAddress(self, address: str, label: str) -> None:
if self._use_descriptors:
desc_watch = descsum_create(f"addr({address})")
rv = self.rpc_wallet_watch(
"importdescriptors",
[
[
{"desc": desc_watch, "timestamp": "now", "active": False},
],
],
)
ensure(rv[0]["success"] is True, "importdescriptors failed for watchonly")
return
self.rpc_wallet("importaddress", [address, label, False])
def isWatchOnlyAddress(self, address: str):
def isWatchOnlyAddress(self, address: str) -> bool:
addr_info = self.rpc_wallet("getaddressinfo", [address])
return addr_info["iswatchonly"]
@@ -1469,7 +1604,9 @@ class BTCInterface(Secp256k1Interface):
# Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True):
self.importWatchOnlyAddress(dest_address, "bid")
self._log.info("Imported watch-only addr: {}".format(dest_address))
self._log.info(
"Imported watch-only addr: {}".format(self._log.addr(dest_address))
)
self._log.info(
"Rescanning {} chain from height: {}".format(
self.coin_name(), rescan_from
@@ -1479,7 +1616,7 @@ class BTCInterface(Secp256k1Interface):
return_txid = True if txid is None else False
if txid is None:
txns = self.rpc_wallet(
txns = self.rpc_wallet_watch(
"listunspent",
[
0,
@@ -1500,7 +1637,7 @@ class BTCInterface(Secp256k1Interface):
try:
# set `include_watchonly` explicitly to `True` to get transactions for watchonly addresses also in BCH
tx = self.rpc_wallet("gettransaction", [txid.hex(), True])
tx = self.rpc_wallet_watch("gettransaction", [txid.hex(), True])
block_height = 0
if "blockhash" in tx:
@@ -1802,16 +1939,20 @@ class BTCInterface(Secp256k1Interface):
def unlockWallet(self, password: str):
if password == "":
return
self._log.info("unlockWallet - {}".format(self.ticker()))
self._log.info(f"unlockWallet - {self.ticker()}")
if self.coin_type() == Coins.BTC:
# Recreate wallet if none found
# Required when encrypting an existing btc wallet, workaround is to delete the btc wallet and recreate
wallets = self.rpc("listwallets")
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
self.rpc("createwallet", ["wallet.dat", False, True, "", False, False])
self.rpc(
"createwallet", [self._rpc_wallet, False, True, "", False, False]
)
self.rpc_wallet("encryptwallet", [password])
# Max timeout value, ~3 years
@@ -1819,7 +1960,7 @@ class BTCInterface(Secp256k1Interface):
self._sc.checkWalletSeed(self.coin_type())
def lockWallet(self):
self._log.info("lockWallet - {}".format(self.ticker()))
self._log.info(f"lockWallet - {self.ticker()}")
self.rpc_wallet("walletlock")
def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray:

View File

@@ -27,7 +27,6 @@ from basicswap.interface.btc import (
)
from basicswap.util import (
ensure,
b2h,
b2i,
i2b,
i2h,
@@ -211,6 +210,10 @@ class DCRInterface(Secp256k1Interface):
def txoType():
return CTxOut
@staticmethod
def est_lock_tx_vsize() -> int:
return 224
@staticmethod
def xmr_swap_a_lock_spend_tx_vsize() -> int:
return 327
@@ -273,6 +276,9 @@ class DCRInterface(Secp256k1Interface):
self._connection_type = coin_settings["connection_type"]
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):
return openrpc(self._rpcport, self._rpcauth, host=self._rpc_host)
@@ -1123,11 +1129,14 @@ class DCRInterface(Secp256k1Interface):
fee_info["size"] = size
self._log.info(
"createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.",
tx.TxHash().hex(),
tx_fee_rate,
size,
pay_fee,
"createSCLockSpendTx {}{}.".format(
self._log.id(tx.TxHash()),
(
""
if self._log.safe_logs
else f":\n fee_rate, size, fee: {tx_fee_rate}, {size}, {pay_fee}"
),
)
)
return tx.serialize(TxSerializeType.NoWitness)
@@ -1167,11 +1176,14 @@ class DCRInterface(Secp256k1Interface):
tx.vout[0].value = locked_coin - pay_fee
self._log.info(
"createSCLockRefundTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.",
tx.TxHash().hex(),
tx_fee_rate,
size,
pay_fee,
"createSCLockRefundTx {}{}.".format(
self._log.id(tx.TxHash()),
(
""
if self._log.safe_logs
else f":\n fee_rate, size, fee: {tx_fee_rate}, {size}, {pay_fee}"
),
)
)
return tx.serialize(TxSerializeType.NoWitness), refund_script, tx.vout[0].value
@@ -1215,11 +1227,14 @@ class DCRInterface(Secp256k1Interface):
tx.vout[0].value = locked_coin - pay_fee
self._log.info(
"createSCLockRefundSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.",
tx.TxHash().hex(),
tx_fee_rate,
size,
pay_fee,
"createSCLockRefundSpendTx {}{}.".format(
self._log.id(tx.TxHash()),
(
""
if self._log.safe_logs
else f":\n fee_rate, size, fee: {tx_fee_rate}, {size}, {pay_fee}"
),
)
)
return tx.serialize(TxSerializeType.NoWitness)
@@ -1244,7 +1259,7 @@ class DCRInterface(Secp256k1Interface):
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock tx: {}.".format(self._log.id(txid)))
ensure(tx.version == self.txVersion(), "Bad version")
ensure(tx.locktime == 0, "Bad locktime")
@@ -1320,7 +1335,7 @@ class DCRInterface(Secp256k1Interface):
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock spend tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock spend tx: {}.".format(self._log.id(txid)))
ensure(tx.version == self.txVersion(), "Bad version")
ensure(tx.locktime == 0, "Bad locktime")
@@ -1390,7 +1405,7 @@ class DCRInterface(Secp256k1Interface):
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock refund tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock refund tx: {}.".format(self._log.id(txid)))
ensure(tx.version == self.txVersion(), "Bad version")
ensure(tx.locktime == 0, "locktime not 0")
@@ -1453,7 +1468,7 @@ class DCRInterface(Secp256k1Interface):
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
tx = self.loadTx(tx_bytes)
txid = self.getTxid(tx)
self._log.info("Verifying lock refund spend tx: {}.".format(b2h(txid)))
self._log.info("Verifying lock refund spend tx: {}.".format(self._log.id(txid)))
ensure(tx.version == self.txVersion(), "Bad version")
ensure(tx.locktime == 0, "locktime not 0")
@@ -1539,11 +1554,14 @@ class DCRInterface(Secp256k1Interface):
tx.vout[0].value = locked_amount - pay_fee
self._log.info(
"createSCLockRefundSpendToFTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.",
tx.TxHash().hex(),
tx_fee_rate,
size,
pay_fee,
"createSCLockRefundSpendToFTx {}{}.".format(
self._log.id(tx.TxHash()),
(
""
if self._log.safe_logs
else f":\n fee_rate, size, fee: {tx_fee_rate}, {size}, {pay_fee}"
),
)
)
return tx.serialize(TxSerializeType.NoWitness)
@@ -1712,7 +1730,7 @@ class DCRInterface(Secp256k1Interface):
witness_bytes = 115
size = len(tx.serialize()) + witness_bytes
pay_fee = round(fee_rate * size / 1000)
self._log.info(
self._log.info_s(
f"BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {size}, {pay_fee}."
)
return pay_fee

View File

@@ -24,6 +24,10 @@ class DOGEInterface(BTCInterface):
def coin_type():
return Coins.DOGE
@staticmethod
def est_lock_tx_vsize() -> int:
return 192
@staticmethod
def xmr_swap_b_lock_spend_tx_vsize() -> int:
return 192

View File

@@ -45,6 +45,9 @@ class FIROInterface(BTCInterface):
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:
return "zcoin"
@@ -102,7 +105,9 @@ class FIROInterface(BTCInterface):
if not self.isAddressMine(dest_address, or_watch_only=True):
self.importWatchOnlyAddress(dest_address, "bid")
self._log.info("Imported watch-only addr: {}".format(dest_address))
self._log.info(
"Imported watch-only addr: {}".format(self._log.addr(dest_address))
)
self._log.info(
"Rescanning {} chain from height: {}".format(
self.coin_name(), rescan_from

View File

@@ -18,7 +18,7 @@ class LTCInterface(BTCInterface):
def __init__(self, coin_settings, network, swap_client=None):
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._rpcport,
self._rpcauth,
@@ -94,7 +94,7 @@ class LTCInterfaceMWEB(LTCInterface):
def __init__(self, coin_settings, network, swap_client=None):
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._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("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
self.rpc("createwallet", ["mweb", False, True, password, False, False, True])
@@ -137,7 +137,7 @@ class LTCInterfaceMWEB(LTCInterface):
self.rpc_wallet("walletpassphrase", [password, 100000000])
if self.getWalletSeedID() == "Not found":
self._sc.initialiseWallet(self.coin_type())
self._sc.initialiseWallet(self.interface_type())
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
self.rpc("unloadwallet", ["mweb"])

View File

@@ -41,7 +41,6 @@ from basicswap.util.address import (
from basicswap.util import (
b2i,
i2b,
i2h,
ensure,
)
from basicswap.basicswap_util import (
@@ -81,6 +80,9 @@ class NAVInterface(BTCInterface):
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:
# p2sh-p2wsh
return True
@@ -549,7 +551,9 @@ class NAVInterface(BTCInterface):
if not self.isAddressMine(dest_address, or_watch_only=True):
self.importWatchOnlyAddress(dest_address, "bid")
self._log.info("Imported watch-only addr: {}".format(dest_address))
self._log.info(
"Imported watch-only addr: {}".format(self._log.addr(dest_address))
)
self._log.info(
"Rescanning {} chain from height: {}".format(
self.coin_name(), rescan_from
@@ -813,11 +817,14 @@ class NAVInterface(BTCInterface):
tx.rehash()
self._log.info(
"createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockRefundTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize(), refund_script, tx.vout[0].nValue
@@ -868,11 +875,14 @@ class NAVInterface(BTCInterface):
tx.rehash()
self._log.info(
"createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockRefundSpendTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize()
@@ -925,11 +935,14 @@ class NAVInterface(BTCInterface):
tx.rehash()
self._log.info(
"createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockRefundSpendToFTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize()
@@ -972,11 +985,14 @@ class NAVInterface(BTCInterface):
tx.rehash()
self._log.info(
"createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
i2h(tx.sha256),
tx_fee_rate,
vsize,
pay_fee,
"createSCLockSpendTx {}{}.".format(
self._log.id(i2b(tx.sha256)),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
return tx.serialize()

View File

@@ -66,6 +66,10 @@ class PARTInterface(BTCInterface):
def txVersion() -> int:
return 0xA0
@staticmethod
def est_lock_tx_vsize() -> int:
return 138
@staticmethod
def xmr_swap_a_lock_spend_tx_vsize() -> int:
return 200
@@ -195,6 +199,10 @@ class PARTInterfaceBlind(PARTInterface):
def balance_type():
return BalanceTypes.BLIND
@staticmethod
def est_lock_tx_vsize() -> int:
return 980
@staticmethod
def xmr_swap_a_lock_spend_tx_vsize() -> int:
return 1032
@@ -469,7 +477,7 @@ class PARTInterfaceBlind(PARTInterface):
):
lock_tx_obj = self.rpc("decoderawtransaction", [tx_bytes.hex()])
lock_txid_hex = lock_tx_obj["txid"]
self._log.info("Verifying lock tx: {}.".format(lock_txid_hex))
self._log.info("Verifying lock tx: {}.".format(self._log.id(lock_txid_hex)))
ensure(lock_tx_obj["version"] == self.txVersion(), "Bad version")
ensure(lock_tx_obj["locktime"] == 0, "Bad nLockTime")
@@ -533,7 +541,9 @@ class PARTInterfaceBlind(PARTInterface):
):
lock_refund_tx_obj = self.rpc("decoderawtransaction", [tx_bytes.hex()])
lock_refund_txid_hex = lock_refund_tx_obj["txid"]
self._log.info("Verifying lock refund tx: {}.".format(lock_refund_txid_hex))
self._log.info(
"Verifying lock refund tx: {}.".format(self._log.id(lock_refund_txid_hex))
)
ensure(lock_refund_tx_obj["version"] == self.txVersion(), "Bad version")
ensure(lock_refund_tx_obj["locktime"] == 0, "Bad nLockTime")
@@ -622,7 +632,9 @@ class PARTInterfaceBlind(PARTInterface):
lock_refund_spend_tx_obj = self.rpc("decoderawtransaction", [tx_bytes.hex()])
lock_refund_spend_txid_hex = lock_refund_spend_tx_obj["txid"]
self._log.info(
"Verifying lock refund spend tx: {}.".format(lock_refund_spend_txid_hex)
"Verifying lock refund spend tx: {}.".format(
self._log.id(lock_refund_spend_txid_hex)
)
)
ensure(lock_refund_spend_tx_obj["version"] == self.txVersion(), "Bad version")
@@ -781,11 +793,14 @@ class PARTInterfaceBlind(PARTInterface):
)
actual_tx_fee_rate = pay_fee * 1000 // vsize
self._log.info(
"createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.",
lock_spend_tx_obj["txid"],
actual_tx_fee_rate,
vsize,
pay_fee,
"createSCLockSpendTx {}{}.".format(
self._log.id(lock_spend_tx_obj["txid"]),
(
""
if self._log.safe_logs
else f":\n fee_rate, vsize, fee: {actual_tx_fee_rate}, {vsize}, {pay_fee}"
),
)
)
fee_info["vsize"] = vsize
@@ -800,7 +815,9 @@ class PARTInterfaceBlind(PARTInterface):
):
lock_spend_tx_obj = self.rpc("decoderawtransaction", [tx_bytes.hex()])
lock_spend_txid_hex = lock_spend_tx_obj["txid"]
self._log.info("Verifying lock spend tx: {}.".format(lock_spend_txid_hex))
self._log.info(
"Verifying lock spend tx: {}.".format(self._log.id(lock_spend_txid_hex))
)
ensure(lock_spend_tx_obj["version"] == self.txVersion(), "Bad version")
ensure(lock_spend_tx_obj["locktime"] == 0, "Bad nLockTime")
@@ -1220,6 +1237,10 @@ class PARTInterfaceAnon(PARTInterface):
def balance_type():
return BalanceTypes.ANON
@staticmethod
def est_lock_tx_vsize() -> int:
return 1153
@staticmethod
def xmr_swap_a_lock_spend_tx_vsize() -> int:
raise ValueError("Not possible")

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2024 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -71,15 +71,20 @@ class XMRInterface(CoinInterface):
def xmr_swap_a_lock_spend_tx_vsize() -> int:
raise ValueError("Not possible")
@staticmethod
def est_lock_tx_vsize() -> int:
# TODO: Estimate with ringsize
return 1604
@staticmethod
def xmr_swap_b_lock_spend_tx_vsize() -> int:
# TODO: Estimate with ringsize
return 1604
def is_transient_error(self, ex) -> bool:
# str_error: str = str(ex).lower()
# if "failed to get output distribution" in str_error:
# return True
str_error: str = str(ex).lower()
if "failed to get earliest fork height" in str_error:
return True
return super().is_transient_error(ex)
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._wallet_password = None
self._have_checked_seed = False
self._wallet_filename = coin_settings.get("wallet_name", "swap_wallet")
daemon_login = None
if coin_settings.get("rpcuser", "") != "":
@@ -170,14 +176,23 @@ class XMRInterface(CoinInterface):
ensure(new_priority >= 0 and new_priority < 4, "Invalid fee_priority value")
self._fee_priority = new_priority
def setWalletFilename(self, wallet_filename):
self._wallet_filename = wallet_filename
def createWallet(self, params):
if self._wallet_password is not None:
params["password"] = self._wallet_password
rv = self.rpc_wallet("generate_from_keys", params)
self._log.info("generate_from_keys %s", dumpj(rv))
if "address" in rv:
new_address: str = rv["address"]
is_watch_only: bool = "Watch-only" in rv.get("info", "")
self._log.info(
"Generated{} {} wallet: {}".format(
" watch-only" if is_watch_only else "",
self.coin_name(),
self._log.addr(new_address),
)
)
else:
self._log.debug("generate_from_keys %s", dumpj(rv))
raise ValueError("generate_from_keys failed")
def openWallet(self, filename):
params = {"filename": filename}
@@ -403,7 +418,9 @@ class XMRInterface(CoinInterface):
params["priority"] = self._fee_priority
rv = self.rpc_wallet("transfer", params)
self._log.info(
"publishBLockTx %s to address_b58 %s", rv["tx_hash"], shared_addr
"publishBLockTx %s to address_b58 %s",
self._log.id(rv["tx_hash"]),
self._log.addr(shared_addr),
)
tx_hash = bytes.fromhex(rv["tx_hash"])

View File

@@ -15,6 +15,7 @@ from .util import (
)
from .basicswap_util import (
strBidState,
strTxState,
SwapTypes,
NotificationTypes as NT,
)
@@ -320,18 +321,36 @@ def formatBids(swap_client, bids, filters) -> bytes:
with_extra_info = filters.get("with_extra_info", False)
rv = []
for b in bids:
ci_from = swap_client.ci(b[9])
offer = swap_client.getOffer(b[3])
ci_to = swap_client.ci(offer.coin_to) if offer else None
amount_to = None
if ci_to:
amount_to = ci_to.format_amount(
(b[4] * b[10]) // ci_from.COIN()
)
bid_data = {
"bid_id": b[2].hex(),
"offer_id": b[3].hex(),
"created_at": b[0],
"expire_at": b[1],
"coin_from": b[9],
"amount_from": swap_client.ci(b[9]).format_amount(b[4]),
"coin_from": ci_from.coin_name(),
"coin_to": ci_to.coin_name() if ci_to else "Unknown",
"amount_from": ci_from.format_amount(b[4]),
"amount_to": amount_to,
"bid_rate": swap_client.ci(b[14]).format_amount(b[10]),
"bid_state": strBidState(b[5]),
"addr_from": b[11],
"addr_to": offer.addr_to if offer else None
}
if with_extra_info:
bid_data["addr_from"] = b[11]
bid_data.update({
"tx_state_a": strTxState(b[7]),
"tx_state_b": strTxState(b[8])
})
rv.append(bid_data)
return bytes(json.dumps(rv), "UTF-8")
@@ -961,6 +980,67 @@ def js_readurl(self, url_split, post_string, is_json) -> bytes:
raise ValueError("Requires URL.")
def js_active(self, url_split, post_string, is_json) -> bytes:
swap_client = self.server.swap_client
swap_client.checkSystemStatus()
filters = {
"sort_by": "created_at",
"sort_dir": "desc"
}
EXCLUDED_STATES = [
'Completed',
'Expired',
'Timed-out',
'Abandoned',
'Failed, refunded',
'Failed, swiped',
'Failed',
'Error',
'received'
]
all_bids = []
try:
received_bids = swap_client.listBids(filters=filters)
sent_bids = swap_client.listBids(sent=True, filters=filters)
for bid in received_bids + sent_bids:
try:
bid_state = strBidState(bid[5])
tx_state_a = strTxState(bid[7])
tx_state_b = strTxState(bid[8])
if bid_state in EXCLUDED_STATES:
continue
offer = swap_client.getOffer(bid[3])
if not offer:
continue
swap_data = {
"bid_id": bid[2].hex(),
"offer_id": bid[3].hex(),
"created_at": bid[0],
"bid_state": bid_state,
"tx_state_a": tx_state_a if tx_state_a else 'None',
"tx_state_b": tx_state_b if tx_state_b else 'None',
"coin_from": swap_client.ci(bid[9]).coin_name(),
"coin_to": swap_client.ci(offer.coin_to).coin_name(),
"amount_from": swap_client.ci(bid[9]).format_amount(bid[4]),
"amount_to": swap_client.ci(offer.coin_to).format_amount(
(bid[4] * bid[10]) // swap_client.ci(bid[9]).COIN()
),
"addr_from": bid[11],
"status": {
"main": bid_state,
"initial_tx": tx_state_a if tx_state_a else 'None',
"payment_tx": tx_state_b if tx_state_b else 'None'
}
}
all_bids.append(swap_data)
except Exception:
continue
except Exception:
return bytes(json.dumps([]), "UTF-8")
return bytes(json.dumps(all_bids), "UTF-8")
pages = {
"coins": js_coins,
"wallets": js_wallets,
@@ -986,6 +1066,7 @@ pages = {
"lock": js_lock,
"help": js_help,
"readurl": js_readurl,
"active": js_active,
}

View File

@@ -0,0 +1,161 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: https://keybase.io/nicolasdorier
Version: Keybase Go 2.6.0 (linux)
xsFNBFuPQQEBEADWe0DHzPvxOuiRAlUyvoQm/+P6jiCqZ4XjFfPIthPh4lnj9ZC6
oK4XfFgU5Z1YLcXWg/3Ven5GZzcz/V82Q8MoDAuf2cNjmG+hHuoLMCwECGE8GcoN
gqBhNGcUp8UykEUjMx6B+B1kBH/Z563Id82y4MssIWwVZA2roGvrLZKSTA0m7rhu
JHLmO8rOsBZymEtRvGFhnVBTrSw13RIgUpr0D+nYU8s/ahnLwf5EAA0l9AgQcMQ+
VQFMV3zPMnhVHIXpcw1dmfiLMiOHhonQ9uu4x/kLroq2zGRHqetV0Ix9pbx4cxKw
idXt0KbFi2lNX+Xh2s47mC3oJSJyOTLxoIyj073nMPwFE+fZrByop+qYYmLvq9BM
q75ocJIr+O41/IdL0/R4l3rwD+dfwYDHITfwcYMfrI0GZYC8igoeBtQiHx+9bHyV
spmAH6W4pJeo8jkEdWvu8xbBHP37+ELVrabz4DpYnGga1fBGoHGVwTOlIzmtOCJ7
hIS5tpjC0njfiJJRq15bwFeUoWhzr4fngA2pqE5LX1bvH9HwoYJ7nbNZcsXhYFoW
0lXxYJA/6wPoxC5FWFBZ2goq/qPiVLfnp7XPgDJu3UkYn9Mqi1MTJk4nDviUb5iZ
1wFoEFw9QZIpBpIaQKeRCVOa88FGQxP3Ud8CRMsGy1TyOiN/ZkiWxvB1/wARAQAB
zSlOaWNvbGFzIERvcmllciA8bmljb2xhcy5kb3JpZXJAZ21haWwuY29tPsLBeAQT
AQgALAUCW49BAQkQZhh2PvCRhv4CGwMFCR4TOAACGQEECwcJAwUVCAoCAwQWAAEC
AAAmRBAANTErDJqg7Qh2gIEJFS+LVOBF427Bmj+DNTEb/XeMDB1QAbVw/ItM5LEa
WW499HFgG+jBMohIVNcmtKIOGdrQSBc2B8Ox4KUnDLO2TXrzMW+EveMIDjBGjxSZ
n2QAVaeemY19cENZfqmYkBTF2kcJzpzlTLsN9FpjOWYjdebjA/plM8W29rUqLE7R
RRqkayXhkkkou6m3diblDiboWj26V+79Rd4iXYE/S/nzbJfNIUjUTj1geVWVgW+7
Gh26H1c5IkeNrsTx/oSA6PN1Zk8/B8q6ftpt6tN1ksrvW6ErxivaxKQJsxM1RO0f
9tfZlUPCuf6Qsjg/IFayZhzi3U+5KBTpJeupBUPqTDtF8byD/iSi0/s0s3ogEFu7
ibMkmGnPu3W3n74qZpl7dNJysu1J7X1bzbeUb4CTgYl/hmsEu+nj7E82knckNXiI
cqSUlHTGsEywGiEkuGTP2N7qikWdggvDsBVE18OfQnBnzOxEXAVe0rCbRSqtgrqc
CSAG/pXdTfNTAo3ScTJ34DYTrZ3EohUwYuSc77e4nkec6+CdUg/IIGX7rB+Iz6RY
Py/24lRp9AJOG6Pzb3K8evE1o3kZjrU/vYyWEo1kiyJJmQa1toBnvJBVIUrcjk7A
603GGU0yFNXfGG31WxudDNMXaIbFG+s6SUC5H+eA+A9HHMM9/vHOwU0EW49BAQEQ
ALDfCek420s6nTWd0lqhJxpaYbGzw44KekwIyOqiA9BZ9W6/DJ4VJoHHK0tBplhQ
J9yrpfuIPTx+TG/2qShNShWv3zLjtGc1JIjYlJGzofmglo/zXP4HdXIfq5bhC2pP
9F0gVmnVNdSN4nA1/FuMJ3raST23F0Q5hieM2znPRoCxNdy6eGo5+Pn8Hssyvr/1
rRjRmTUIEyB4v5uVlPbqfvEMBtVOy8AS8+sWiW9PCojWV/NQpJ8DEP4NPfZG4sNu
rhUN6wTYTc1YpqHp2ZjSCFgscgXOBXpbhj8wRvfuOR7PQjBMW5Trz1yFvaOXIRHN
Srtoldmt8QyHXwIPVn1Z6byULWGsWw2hSKV4kgCep0djb4cncY04f1hCFHKtycv/
32pKdzya3nd8455wS755L2cQBMRs5tS71EpjkZwiwAHdQ8csXLZ3F+JwveavNp+K
cn4eYhfFx0TejQuryvrPx4le51iH6ozVOM37gIUftNGx537yWYBTBTsspz3fau13
s7NicSKc00GNfdGw2CP5NfcLOosUntk5CK/ZMQcnY2YT2FPdmIdX2iF100Ai+be6
xbbYB3tWbRbnvI5JUIuOPuNeZcFQUEd4mr+XRpGLhzkGi5XqTPaAXiwjfZie7tYO
/ZCuAWmpNo2VWOlBJO/QvN/sHyHwIBAkJ123fQtUystPABEBAAHCwXUEGAEIACkF
AluPQQEJEGYYdj7wkYb+AhsMBQkeEzgABAsHCQMFFQgKAgMEFgABAgAAiKIQANI2
RDk4L33EjOS0abxB8h5tR9ca1P2BIKCnXb/IfiqlDcoKR0RVAy1dOHlmyH/5K7lh
5cp9LsqY3/XuPZoN9MRcWmav6HWWvWKdtpg0RbRqDyiqh0uiwwB8QZ7Hf4uWmLPj
V+tficTqyFhNn7RdU5DrcVhvuueh1fJrTqaizB88QMvYW+xGuuIBYIFrkibH3UFS
/L8Qj7CBgfWNAsC47t8DtBKKX/i07bJnlFyv+0dOpxNAFIROlXw33sbTM8SkZ7jR
jIeKhS+fEowjA8R3rSJLBEadIwUaD+uIACaFVh+o/ogssXWZX3GZ2IgwPhiAFcJT
qDzDu5nsIu8/QwN+TH0zPLoVjfg56HqPAsJHYLOSqO5xCE8lhyQuMh3PPF47kUoS
6QGNkASgSAGEq5RMBpUWqS8TYkYU/mk+b94nJnhhvXQPAEUHIqY7R7EPduHldyBh
e9eF6GZLUj9iA7uUY8m5CrLNl+axKxRhyMqUNOAos58z5bg6pqvrJIy7J26pWjnF
qNj7ylvjGakY3WR+EjPmgU2KGdcKloZLMOOSLq+4kwWPr0+q3dBI0qqXssVPZAtJ
b+lEWZtwBM0n3d8RcNEGywqeZIiAfgvyUQ6rNosDhE51q9nWoJW1i3r9X0ATe+aV
avYCWTKM5AQ7bEIvuVW/4M8PLFClJ2GmI7+YY7gl
=sNb2
-----END PGP PUBLIC KEY BLOCK-----
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: https://keybase.io/nicolasdorier
Version: Keybase Go 5.0.0 (windows)
xsFNBF3clT4BEAC65tyMgP9NWzaUyNlbvbT8LlFRd/QsbxTElVILwdlypB/HInSt
18P0d5Px381cTN6QQnfRaE5cvbghqL94qVg4Ycc/tW71XxS4GT/xujzbNfol0unC
DAo1NqYWESrIAlosvgZBU2L4M88ASE2psHVdo2Dc6NRmdcit7G/RD9Js4MgGi9Kf
8bu4Xwk+vwGDvHDjPbDjlyx+djkGenQeuBVsIwJqXyFrr4WYkpFfBcGtMiBM986Z
lCMZ/Y8+WeGMHoq16uOuauIiE10RCAjSMkpLbqNcAFY5/qIImaHlQFpUxRewX/04
RQ00QrKYmToMB4VT+b0JSMVpHZAKaITFfSB3QbOSJrblZXyC1cTSGaDnTzhuvVeF
0S1eD1v4ZPDW5egxEKe/ckCxq4O/j39oj3oiYWcVmS+kceiIyETuXlgWyB2meG69
AAFfPisv0jUN/xrQJ7+TNBD86Cs53GvlghqHHWOZyLEDrNlkFOd/f7uN08cYJcCH
HLWwysLxBFhFUE9PXBT+83EkgsU1nCysB7kvodXkAS7rjCtrXuBuE3z3HOyfrQVZ
geOAlyAlLdbL/IQeQWe2k4Mz1ej90k4kqjfzZxSS8zBN3kvBW56/4W1LSA5pPhjl
5BSRUxk/nSrNMfc2u8ZmcD//mNZJ2d9yVJfOAjXJPEDQXAebWRZaWJw/hwARAQAB
zSlOaWNvbGFzIERvcmllciA8bmljb2xhcy5kb3JpZXJAZ21haWwuY29tPsLBeAQT
AQgALAUCXdyVPgkQIj/aad6+qC0CGwMFCR4TOAACGQEECwcJAwUVCAoCAwQWAAEC
AABCERAAFi2eSIRh9kpkERD1NYCMf6NfuPC1y6vf0xNYnIodPkAyv4xthEl4esdJ
xeltVIQ5BcPNUrHitcwO6TmtQa/a/4E8RgFzKDbGo/Wgr7shVAs0YUnQ6Tk07fL6
OVuwRCc1uTpUAgcv8ESNUyUgMeThcTmPChDRhhWn2Imy7pi8NPzM0X+/QCA0yj3p
Fa6Y+03WrqWbv9+OdqRysCwNPtOSAfbT4XXifn4efkOtBk4vx2oGr/NxxUOw5CgR
DAp8hEL76b5yZzvex75JFjCUwKqeYf2GjZrv94XgWXWZderlW2MHM+R/ON2K60/Y
SkafrGg4GdorwJIaLR8OVGV2nuBeUJXg75taOEzTtm8siEmiF1cvlfyEO15lTUuZ
7rIb9CILwCJ79nlON21MFax3bMqWP55GuC8Z79dSl3uSHaJg28NiB1iFVO0xAOlT
wQ++qeWQXpWUviNbHJ57+jgK80PLn6alXvfGSDovNZfO2UvRD5lpDmN6VyqrDB5z
ibPZmfR5SR+G9XqR03i5mG6/ynjWmXDzL4t3trrBPwLeyppvRXA9QY444Tm9OdH/
yj06mNGcQMLqsbd+9KS/veKDl9yJDxhqJe/nauq4vV0a+oMjFGKM+7waLc2n851N
yqdToaKfwt9FocDy4Xh54WPx+xaCfi9tDJMmKPjJP87oys2EdlXOwU0EXdyVPgEQ
AOyufiiUouX9yBrfeLOt3vLMVY3swP1KEosa/EZn+7zNJ+VZzfQFcmrNJ6lfzoIk
WNTYhqhCwPWLyw89wYhXNHEedICzRuOsET2CMP9bYXe0GcMi5vXCOs3QZDD5bNau
VnqnjM/sT25GHJb5IPdE/jOtAO3/WnwtlclfqNBgI1n0UUak4QZM03B7fFmVldXg
G1FydusZ0cH5vn2O8yQkvY7IcgNhgsQRPahrrpfDnfRd/CuX1yP4xbgULrgMjs3P
98HW+vwsx3IS8uFfxMUOftjXBUvCWoz+rc6fNqCS9lUIKdmpN0J+wtvbgcwXlde/
C2j3gzHBg8uGnRyVgygTUZceLeIxYjfwgCoRuGK70EfV4TAKkT9ODivA00D4mQm1
Bkh39hl4dCZ3xMVlVthT4BK1nEEM5DtwRAkVjR7wrv+fHR90yoHH/zDA/wFCGaD+
ML4v3578bctkJcmIJq32pbiP2jS36xnjxSRsDhQcbJjfeSm9qtMAOwF36GyGRVF6
fgxkRh04gzpE7d+fugRM9aTaaSBvr4oU5OmR9Aw066SC0nGGSnGehuvH5Ov/QtpC
Wl95tCviMaW28MSudwdYAfwgzKpCbe6sRi9tH0D6z2ZSLsykwby29wVfdPKVqUZt
LLSHhlRdw/eJDt7vCoxHR/TOJxOQZWCzJma+idz3NBkXABEBAAHCwXUEGAEIACkF
Al3clT4JECI/2mnevqgtAhsMBQkeEzgABAsHCQMFFQgKAgMEFgABAgAAersQAKm/
I45krs/U4OWfru8FA5auuGgdiFThzk2Z+iE3XZ/TcJDSZfcECil8eFvjycL7JSRy
VUDY8GOmxL9oZyW9YY7EuvpsSBq6b7x6r8Cz40hBuP59DD+V1qtIokvc+kh2XJlS
GYKjggKaKTwrUazFtLur+XipPEL6yLYabaJaOiM5sMPmGc8raovIrh5IsVsEgEA2
bLbtaBiQqSR8Czh8pznijT/qw2ZLKqHkD+YQWf0xxwt/jMj/eG0yWzBam7YoqzM9
9GX411vmJNImNnLLrwA+LhN5A+m9oyf2KINHhq9xmyP2cRmXUcLDejMIIaISFWxT
aBrcmDSdztzsDzGaAz389bPUheSnOE6iK3zxbaUx67Tcmt1UjIWEZW1jyO4zmeXI
JG+0rdxZJU+wxa0jZcjF4C4IjgV6mXm+hN8F9jKBXu42ayqBHH2FAQLJQkD7mGSy
YJKo6eiJUfwI6DfDTlYF3QCWGi9bpdKZsaWj6+sgzhsHrENEEd1UnXm3W31wzYew
YtnmykETkCW0tnYf6tW5zJqpH6Y1zTS2+oSE2CRLjIPhWqRw6gfIk7g54mgNXf4D
ppHvGVduPErEE5WWH8iUVWYtk/yA7LhyRfvRjAezjtK7uzqQNqZirQjf6coqrV+Q
/+7CvHSsc6GjkqB7bFx5phZPRpt7OLzVKszDroyv
=ut7t
-----END PGP PUBLIC KEY BLOCK-----
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: https://keybase.io/nicolasdorier
Version: Keybase Go 5.0.0 (windows)
xsFNBF3ec/EBEAC5sbWmzhP1hoLQ2/gm8Tds+v/p6DmY+vVNIgiBz1/XG+glRkna
qqwmVe71CE+nYtrxlzzc70PfxvrfWzfoavYGMgIkIQhEcst3ST6Qqo7IglAcXL0z
Vwqq5QcmCfyz2kr9wxUUrwofznKQch/7dZATkTl18ci5bzKTgENzHFKJx6EHN6aF
0meUW6tmIVSxva/tmkQK+dZtjfYHZvlDC0AUTNv8nWGEVNtvJvN+KKrXpHjiSjp4
lHGXp6QZEA4Xmbo/5RMoy7FtHAjT8QXG3kmmWAQSN8TYrI0KMWoSIfZMVhytTgqc
1S2G4nmUmkLVJgJ1p2/plLwY3ORpmQHgTrmttYnh/y9h3wNEje/8QQKlLncCLP4b
GVfIfBjuKSoYAU6UqDBV8wgyCbgysdhDDxlt6hkF1lMljc9xlj1pUlYqdMCn8Nvt
rQ21mpaMOcyAKu0qZPgSBJR9W15hdAS7Y3RCHBDi8TraLnl+pvhRy4q2e9qYsMIO
w8kmrRVtXHTdPCyAfVKU93mn8A1MUbISr3f4AmP623NOK8MVP/J0Khx3tHpJ1Hdr
L5Erg0N4n7lA+eUiYthwdxG1JaGQaCRVeqUZJ/TwuLvAsknDOCdZAn/jrjjaxRJ8
EwVnu8kJUuxYIix4CuydLKCS3QXey3jbRccEn8Ybzz4nPcoZoWmJianrRQARAQAB
zS1CVENQYXlTZXJ2ZXIgVmF1bHQgPG5pY29sYXMuZG9yaWVyQGdtYWlsLmNvbT7C
wXgEEwEIACwFAl3ec/EJEGL+hWR97douAhsDBQkeEzgAAhkBBAsHCQMFFQgKAgME
FgABAgAAVGkQABOWW9mCyBOdWaJ7JBFGraUv9qQ3Q9EXFfOCXHDJdiY6WSWyvhMG
0KluY6h0kVMGkc5MXl5D04+UuCrVIn7ucQ3FR5E3pkROJ/ZqGuXXBY/G7JVJsJz2
TGjRD5PxQD2SkfLQ/ZscqhmwcZPtmyVcyfKsLrtSPmDp25xYo/InJ0BDh2M6jvs7
WNRX4O/jQNl2WnAx8e8W/BtTQr23PC5+y6jsi2GVo+ePubqS+nz+O5MD0+0FJ2ov
2i9MAwJZUez4z7w11SRO2QT1MX4FzgIe+YcnnU5DeO+WTQci6cuv2+l1heDysRto
oZlWFL8bNNCKtGC46ZyJ4jmsMUp2eP5st32bpHQPf0yIhFvvKzPkm7u1fZIPPbXM
bmREBJWNiCNWOnCLr7yiO9ATVIzvvnK713oQYHpAHRoIuYgUiVxLVveBSY4ERE8F
IfOu2VUXyi+c/ottTd07dDrLpy8DJ25891ovE883NZcFR/rW1+0ymTDFyl/fPEDM
DNq/NxVKFfrIaGFvRoDLpOJPGbUgHsU3+xxndorFnrWIiOpLk9dIGxKSdVs67Hmx
YiRDuw/2j1QhR4dk1l8ySD75Hs7FFrLrUDfDWbipFHjrKti/V7zgUsgWYxmscAGs
cRd1Q/59vX7GFyyWYvMsEAMob1oIfSA+2SgpVDP55AXoqbo9iWUfJePYzsFNBF3e
c/EBEADQCD6OD21aTYARADbEfnCysxD1l/tDbhmjbJNgw5v5YzvVs2GCovhPzQmC
aLybwzuOvsh+dh2cnOjlWoYaQK/8JXolH0ZAh4z3oJca9UUdcOcBt6poYjPUYCjA
NLNFIS4CH05yr4CECu/GBGM9dSbizmbl/tJ7EcZO8xlxg85XOFT8fz/KhEhElyb8
KrCC46gtWnXYSBQ1XljfcZOUXRhv7ROAe1BAw3j9sdZ34RZ79xXx4rMyna2BBbzn
Gki4hV2qVAgXwcn8gq8Qhux/Y6XeZuJhjFCS6FCk8JgK7BFrThZi2z6FTHFM+7HR
eAkoJBcg/JoqyBauZx0UJ+JckxQb8dqImDiPc+2WJ8ENCTU8xobWAZUT0Hj8HhJi
kQ6URScpty1VushBtU4GHsPfLJoU2mLI7YQQ6b0VJD3ZT3eQuYchNjE44eSGx8M5
XVZjunbrrZjq2gzxd8+iK7vj9mnQ5M/kiFA2ptwPUVHjGmVS/omOI89AtPpLENwC
yFwKqOgOGPy92tVF/FFqKveFnic6U1M/3FWZamU0A3BxUFHrXrY9MWFul9AVLTud
lbrNluOIxmSsRAJXkkTs0JLam4ubgoSAg4XOHe1Y9w/BRC6huIRs72HBNUuDtACS
oMWfPOgt66rl0CW6/qBDh4gSLxxni2PhGehJOEc+ls6K6k+b4QARAQABwsF1BBgB
CAApBQJd3nPxCRBi/oVkfe3aLgIbDAUJHhM4AAQLBwkDBRUICgIDBBYAAQIAACWW
EAB510r8zce3r4bspcj/A/WFAPHgoGlMUeJQkoxsgE3tfcZBLPWkInTGnUHsLPMw
olE+pmqbS3XV3FjC4yGOGPOQYLeF+o/64+EabTzDomi9Hs0rV7GzpuYqSRQ/j8/j
H1qo5iuWwJnvvr5rGy3+mN1O6I88AZDRGHiLS1oG+mFXhNVp0dXPeDMsbGnztgNJ
zmIAWMeWqsC852ZmXa0VosTEE1Jb3s48otblwBwOWzNXBs+J+amuA71DridQYNWR
l3ixirH9/D+tpXOd+zOXwyczoYgf14Yz/lgKT+wlSfOQeMRbqTY5oijIxeLDJbeX
eYZoCss6gX1ue5yqgT0+haI9FAPrnJ/Jq9cPmwXuBmjQ7869JvDWUNgoQ8sP5GoH
vRGjaEzKkH8ibQTLtP2VKPENKsNjikKCaLsmWGvfC1CzAuw0JHQ8fNgwuqIXGs0L
MBCOUgynVqhHQKnApGcbnkCrRjr1wuAydPCQ7xbIaKdhbN3qzj1rUcvkG0GEjs9C
R4VB8G0zcLXMoqwKxPLAeR2cnSiIUW0JEcjxxBb+6poj9kQKaee97cxXP1qq2D8d
hsZHpy1Q/HSyaKYK4gId5/eZ7IsbPH60L61OJ2NC7xRcM9P09/EDz08dbt8IKqrq
bhogEBf9UyDmPn6DW8jC1nkVbE8ODYDaOuLW3PKrthoKVQ==
=n82A
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -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-----

View File

@@ -0,0 +1,21 @@
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
725a049bc5a9fd60b05bba4d4825d35115d99f05ab5b7716d4507c295d05172d utxo-snapshot-bitcoin-mainnet-820852.tar
744c42885df700513331a978b289d9c9d5b27e0cf1147f2f5a287b4492ff940c utxo-snapshot-bitcoin-mainnet-867690.tar
-----BEGIN PGP SIGNATURE-----
iQIzBAEBCAAdFiEEjlF9wS7BzDf2QjqKE/E2UcnPDWsFAmedUAYACgkQE/E2UcnP
DWs1Vw/+P3CGP9LLVv2deNocBFunUz+7aDZsQiykSI8ws50ssJ5PsAg5VSl4CbCl
owWOdQVJiDUh7daP0jr+bt3X2FY5ORBb1TGlvfCHE+vLfEFDnTpLXouSCclP0cv8
Ci8zQFKSI5Pf6uSMpALgQZxBgNU/0IegAQbpuJI4nrQXTKHJcMqtw1LtnmcreESO
MsSiGCXnC1R+xGQjptfvbzXaQVrin7ctYA9zjN4CGbjNChzr+ywT8dht2RKoLYyP
OrEys7d8EIaw/ktRvRmyk6O7KmnvUhf0uuFlDq+eTiBIpQoUEovCow1YYKaWkIRB
r4JBJJ34AB+XC2hgi5jpJNub/wKgVBm0iy79zZOSILP3ymbn3iJGg4ifUF0YeZCU
ufYkYi3iTJDpwYr0tylZmBiwsWNcbUhB+WTNX7ogCW70ZuhrF0PJQRPmhI34vsE/
qg3n0/hNNsypy0epRd33KSOvrSmaoTKLtCax9Osnt+F+yTYjD5EPqkQuzlJl+fDe
VvjWO5XHuaRvzijBrJQz6r5V4e/0ioNa8FTRqWmMTO1wHmxF5glpozyKycv9+bsB
IL9F1IQjhPkSVI7Hw8bsURpfH4mV+9eZJJDIvBf1/0gDctsBdsI5+5jxZjup769Q
AmMsGeZoplm/eUofQ9hItWcVitPhisDmC3wDR71UKM0b9FF6IUY=
=YUjt
-----END PGP SIGNATURE-----

View File

@@ -44,7 +44,7 @@ def addLockRefundSigs(self, xmr_swap, ci):
def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
self.log.info(f"Manually recovering {bid_id.hex()}")
self.log.info(f"Manually recovering {self.log.id(bid_id)}")
# Manually recover txn if other key is known
try:
use_cursor = self.openDB(cursor)
@@ -119,10 +119,7 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, cursor=None):
lock_tx_vout=lock_tx_vout,
)
self.log.debug(
"Submitted lock B spend txn %s to %s chain for bid %s",
txid.hex(),
ci_follower.coin_name(),
bid_id.hex(),
f"Submitted lock B spend txn {self.log.id(txid)} to {ci_follower.coin_name()} chain for bid {self.log.id(bid_id)}."
)
self.logBidEvent(
bid.bid_id,

View File

@@ -1,153 +1,157 @@
/* General Styles */
.bold {
font-weight: bold;
font-weight: bold;
}
.monospace {
font-family: monospace;
font-family: monospace;
}
.floatright {
position: fixed;
top: 1.25rem;
right: 1.25rem;
z-index: 9999;
position: fixed;
top: 1.25rem;
right: 1.25rem;
z-index: 9999;
}
/* Table Styles */
.padded_row td {
padding-top: 1.5em;
padding-top: 1.5em;
}
/* Modal Styles */
.modal-highest {
z-index: 9999;
z-index: 9999;
}
/* Animation */
#hide {
-moz-animation: cssAnimation 0s ease-in 15s forwards;
-webkit-animation: cssAnimation 0s ease-in 15s forwards;
-o-animation: cssAnimation 0s ease-in 15s forwards;
animation: cssAnimation 0s ease-in 15s forwards;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
-moz-animation: cssAnimation 0s ease-in 15s forwards;
-webkit-animation: cssAnimation 0s ease-in 15s forwards;
-o-animation: cssAnimation 0s ease-in 15s forwards;
animation: cssAnimation 0s ease-in 15s forwards;
-webkit-animation-fill-mode: forwards;
animation-fill-mode: forwards;
}
@keyframes cssAnimation {
to {
width: 0;
height: 0;
overflow: hidden;
}
to {
width: 0;
height: 0;
overflow: hidden;
}
}
@-webkit-keyframes cssAnimation {
to {
width: 0;
height: 0;
visibility: hidden;
}
to {
width: 0;
height: 0;
visibility: hidden;
}
}
/* Custom Select Styles */
.custom-select .select {
appearance: none;
background-image: url('/static/images/other/coin.png');
background-position: 10px center;
background-repeat: no-repeat;
position: relative;
appearance: none;
background-image: url('/static/images/other/coin.png');
background-position: 10px center;
background-repeat: no-repeat;
position: relative;
}
.custom-select select::-webkit-scrollbar {
width: 0;
width: 0;
}
.custom-select .select option {
padding-left: 0;
text-indent: 0;
background-repeat: no-repeat;
background-position: 0 50%;
padding-left: 0;
text-indent: 0;
background-repeat: no-repeat;
background-position: 0 50%;
}
.custom-select .select option.no-space {
padding-left: 0;
padding-left: 0;
}
.custom-select .select option[data-image] {
background-image: url('');
background-image: url('');
}
.custom-select .select-icon {
position: absolute;
top: 50%;
left: 10px;
transform: translateY(-50%);
position: absolute;
top: 50%;
left: 10px;
transform: translateY(-50%);
}
.custom-select .select-image {
display: none;
margin-top: 10px;
display: none;
margin-top: 10px;
}
.custom-select .select:focus + .select-dropdown .select-image {
display: block;
display: block;
}
/* Blur and Overlay Styles */
.blurred {
filter: blur(3px);
pointer-events: none;
user-select: none;
filter: blur(3px);
pointer-events: none;
user-select: none;
}
.error-overlay.non-blurred {
filter: none;
pointer-events: auto;
user-select: auto;
filter: none;
pointer-events: auto;
user-select: auto;
}
/* Form Element Styles */
@media screen and (-webkit-min-device-pixel-ratio:0) {
select:disabled,
input:disabled,
textarea:disabled {
opacity: 1 !important;
}
@media screen and (-webkit-min-device-pixel-ratio: 0) {
select:disabled,
input:disabled,
textarea:disabled {
opacity: 1 !important;
}
}
.error {
border: 1px solid red !important;
border: 1px solid red !important;
}
/* Active Container Styles */
.active-container {
position: relative;
border-radius: 10px;
position: relative;
border-radius: 10px;
}
.active-container::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid rgb(77, 132, 240);
border-radius: inherit;
pointer-events: none;
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid rgb(77, 132, 240);
border-radius: inherit;
pointer-events: none;
}
/* Center Spin Animation */
.center-spin {
display: flex;
justify-content: center;
align-items: center;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Hover Container Styles */
@@ -155,215 +159,209 @@
.hover-container:hover #coin_to,
.hover-container:hover #coin_from_button,
.hover-container:hover #coin_from {
border-color: #3b82f6;
border-color: #3b82f6;
}
#coin_to_button, #coin_from_button {
background-repeat: no-repeat;
background-position: center;
background-size: 20px 20px;
#coin_to_button,
#coin_from_button {
background-repeat: no-repeat;
background-position: center;
background-size: 20px 20px;
}
/* Input-like Container Styles */
.input-like-container {
max-width: 100%;
background-color: #ffffff;
width: 360px;
padding: 1rem;
color: #374151;
border-radius: 0.375rem;
font-size: 0.875rem;
line-height: 1.25rem;
outline: none;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-all;
height: auto;
min-height: 90px;
max-height: 150px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow-y: auto;
max-width: 100%;
background-color: #ffffff;
width: 360px;
padding: 1rem;
color: #374151;
border-radius: 0.375rem;
font-size: 0.875rem;
line-height: 1.25rem;
outline: none;
word-wrap: break-word;
overflow-wrap: break-word;
word-break: break-all;
height: auto;
min-height: 90px;
max-height: 150px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow-y: auto;
}
.input-like-container.dark {
background-color: #374151;
color: #ffffff;
background-color: #374151;
color: #ffffff;
}
.input-like-container.copying {
width: inherit;
width: inherit;
}
/* QR Code Styles */
.qrcode {
position: relative;
display: inline-block;
padding: 10px;
overflow: hidden;
position: relative;
display: inline-block;
padding: 10px;
overflow: hidden;
}
.qrcode-border {
border: 2px solid;
background-color: #ffffff;
border-radius: 0px;
border: 2px solid;
background-color: #ffffff;
border-radius: 0px;
}
.qrcode img {
width: 100%;
height: auto;
border-radius: 0px;
width: 100%;
height: auto;
border-radius: 0px;
}
#showQR {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 25px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 25px;
}
.qrcode-container {
margin-top: 25px;
margin-top: 25px;
}
/* Disabled Element Styles */
select.select-disabled,
.disabled-input-enabled,
select.disabled-select-enabled {
opacity: 0.40 !important;
opacity: 0.40 !important;
}
/* Shutdown Modal Styles */
#shutdownModal {
z-index: 50;
z-index: 50;
}
#shutdownModal > div:first-child {
z-index: 40;
z-index: 40;
}
#shutdownModal > div:last-child {
z-index: 50;
z-index: 50;
}
#shutdownModal > div {
transition: opacity 0.3s ease-out;
transition: opacity 0.3s ease-out;
}
#shutdownModal.hidden > div {
opacity: 0;
opacity: 0;
}
#shutdownModal:not(.hidden) > div {
opacity: 1;
opacity: 1;
}
.shutdown-button {
transition: all 0.3s ease;
transition: all 0.3s ease;
}
.shutdown-button.shutdown-disabled {
opacity: 0.6;
cursor: not-allowed;
color: #a0aec0;
opacity: 0.6;
cursor: not-allowed;
color: #a0aec0;
}
.shutdown-button.shutdown-disabled:hover {
background-color: #4a5568;
background-color: #4a5568;
}
.shutdown-button.shutdown-disabled svg {
opacity: 0.5;
opacity: 0.5;
}
/* Loading line animation */
/* Loading Line Animation */
.loading-line {
width: 100%;
height: 2px;
background-color: #ccc;
overflow: hidden;
position: relative;
width: 100%;
height: 2px;
background-color: #ccc;
overflow: hidden;
position: relative;
}
.loading-line::before {
content: '';
display: block;
width: 100%;
height: 100%;
background: linear-gradient(to right, transparent, #007bff, transparent);
animation: loading 1.5s infinite;
content: '';
display: block;
width: 100%;
height: 100%;
background: linear-gradient(to right, transparent, #007bff, transparent);
animation: loading 1.5s infinite;
}
@keyframes loading {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
/* Hide the loading line once data is loaded */
.usd-value:not(.loading) .loading-line,
.profit-loss:not(.loading) .loading-line {
display: none;
display: none;
}
.resolution-button {
/* Resolution Button Styles */
.resolution-button {
background: none;
border: none;
color: #4B5563; /* gray-600 */
font-size: 0.875rem; /* text-sm */
font-weight: 500; /* font-medium */
color: #4B5563;
font-size: 0.875rem;
font-weight: 500;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
transition: all 0.2s;
outline: 2px solid transparent;
outline-offset: 2px;
}
}
.resolution-button:hover {
color: #1F2937; /* gray-800 */
}
.resolution-button:hover {
color: #1F2937;
}
.resolution-button:focus {
outline: 2px solid #3B82F6; /* blue-500 */
}
.resolution-button:focus {
outline: 2px solid #3B82F6;
}
.resolution-button.active {
color: #3B82F6; /* blue-500 */
outline: 2px solid #3B82F6; /* blue-500 */
}
.resolution-button.active {
color: #3B82F6;
outline: 2px solid #3B82F6;
}
.dark .resolution-button {
color: #9CA3AF; /* gray-400 */
}
.dark .resolution-button {
color: #9CA3AF;
}
.dark .resolution-button:hover {
color: #F3F4F6; /* gray-100 */
}
.dark .resolution-button:hover {
color: #F3F4F6;
}
.dark .resolution-button.active {
color: #60A5FA; /* blue-400 */
outline-color: #60A5FA; /* blue-400 */
.dark .resolution-button.active {
color: #60A5FA;
outline-color: #60A5FA;
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 {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
/* Toggle Button Styles */
#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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,872 @@
// Constants and State
const PAGE_SIZE = 50;
const COIN_NAME_TO_SYMBOL = {
'Bitcoin': 'BTC',
'Litecoin': 'LTC',
'Monero': 'XMR',
'Particl': 'PART',
'Particl Blind': 'PART',
'Particl Anon': 'PART',
'PIVX': 'PIVX',
'Firo': 'FIRO',
'Dash': 'DASH',
'Decred': 'DCR',
'Wownero': 'WOW',
'Bitcoin Cash': 'BCH',
'Dogecoin': 'DOGE'
};
// Global state
const state = {
identities: new Map(),
currentPage: 1,
wsConnected: false,
swapsData: [],
isLoading: false,
isRefreshing: false,
refreshPromise: null
};
// DOM
const elements = {
swapsBody: document.getElementById('active-swaps-body'),
prevPageButton: document.getElementById('prevPage'),
nextPageButton: document.getElementById('nextPage'),
currentPageSpan: document.getElementById('currentPage'),
paginationControls: document.getElementById('pagination-controls'),
activeSwapsCount: document.getElementById('activeSwapsCount'),
refreshSwapsButton: document.getElementById('refreshSwaps'),
statusDot: document.getElementById('status-dot'),
statusText: document.getElementById('status-text')
};
// Identity Manager
const IdentityManager = {
cache: new Map(),
pendingRequests: new Map(),
retryDelay: 2000,
maxRetries: 3,
cacheTimeout: 5 * 60 * 1000, // 5 minutes
async getIdentityData(address) {
if (!address) {
return { address: '' };
}
const cachedData = this.getCachedIdentity(address);
if (cachedData) {
return { ...cachedData, address };
}
if (this.pendingRequests.has(address)) {
const pendingData = await this.pendingRequests.get(address);
return { ...pendingData, address };
}
const request = this.fetchWithRetry(address);
this.pendingRequests.set(address, request);
try {
const data = await request;
this.cache.set(address, {
data,
timestamp: Date.now()
});
return { ...data, address };
} catch (error) {
console.warn(`Error fetching identity for ${address}:`, error);
return { address };
} finally {
this.pendingRequests.delete(address);
}
},
getCachedIdentity(address) {
const cached = this.cache.get(address);
if (cached && (Date.now() - cached.timestamp) < this.cacheTimeout) {
return cached.data;
}
if (cached) {
this.cache.delete(address);
}
return null;
},
async fetchWithRetry(address, attempt = 1) {
try {
const response = await fetch(`/json/identities/${address}`, {
signal: AbortSignal.timeout(5000)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return {
...data,
address,
num_sent_bids_successful: safeParseInt(data.num_sent_bids_successful),
num_recv_bids_successful: safeParseInt(data.num_recv_bids_successful),
num_sent_bids_failed: safeParseInt(data.num_sent_bids_failed),
num_recv_bids_failed: safeParseInt(data.num_recv_bids_failed),
num_sent_bids_rejected: safeParseInt(data.num_sent_bids_rejected),
num_recv_bids_rejected: safeParseInt(data.num_recv_bids_rejected),
label: data.label || '',
note: data.note || '',
automation_override: safeParseInt(data.automation_override)
};
} catch (error) {
if (attempt >= this.maxRetries) {
console.warn(`Failed to fetch identity for ${address} after ${attempt} attempts`);
return {
address,
num_sent_bids_successful: 0,
num_recv_bids_successful: 0,
num_sent_bids_failed: 0,
num_recv_bids_failed: 0,
num_sent_bids_rejected: 0,
num_recv_bids_rejected: 0,
label: '',
note: '',
automation_override: 0
};
}
await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
return this.fetchWithRetry(address, attempt + 1);
}
}
};
const safeParseInt = (value) => {
const parsed = parseInt(value);
return isNaN(parsed) ? 0 : parsed;
};
const getStatusClass = (status, tx_a, tx_b) => {
switch (status) {
case 'Completed':
return 'bg-green-300 text-black dark:bg-green-600 dark:text-white';
case 'Expired':
case 'Timed-out':
return 'bg-gray-200 text-black dark:bg-gray-400 dark:text-white';
case 'Error':
case 'Failed':
return 'bg-red-300 text-black dark:bg-red-600 dark:text-white';
case 'Failed, swiped':
case 'Failed, refunded':
return 'bg-gray-200 text-black dark:bg-gray-400 dark:text-red-500';
case 'InProgress':
case 'Script coin locked':
case 'Scriptless coin locked':
case 'Script coin lock released':
case 'SendingInitialTx':
case 'SendingPaymentTx':
return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
case 'Received':
case 'Exchanged script lock tx sigs msg':
case 'Exchanged script lock spend tx msg':
case 'Script tx redeemed':
case 'Scriptless tx redeemed':
case 'Scriptless tx recovered':
return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
case 'Accepted':
case 'Request accepted':
return 'bg-green-300 text-black dark:bg-green-600 dark:text-white';
case 'Delaying':
case 'Auto accept delay':
return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
case 'Abandoned':
case 'Rejected':
return 'bg-red-300 text-black dark:bg-red-600 dark:text-white';
default:
return 'bg-blue-300 text-black dark:bg-blue-500 dark:text-white';
}
};
const getTxStatusClass = (status) => {
if (!status || status === 'None') return 'text-gray-400';
if (status.includes('Complete') || status.includes('Confirmed')) {
return 'text-green-500';
}
if (status.includes('Error') || status.includes('Failed')) {
return 'text-red-500';
}
if (status.includes('Progress') || status.includes('Sending')) {
return 'text-yellow-500';
}
return 'text-blue-500';
};
// Util
const formatTimeAgo = (timestamp) => {
const now = Math.floor(Date.now() / 1000);
const diff = now - timestamp;
if (diff < 60) return `${diff} seconds ago`;
if (diff < 3600) return `${Math.floor(diff / 60)} minutes ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)} hours ago`;
return `${Math.floor(diff / 86400)} days ago`;
};
const formatTime = (timestamp) => {
if (!timestamp) return '';
const date = new Date(timestamp * 1000);
return date.toLocaleString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
const formatAddress = (address, displayLength = 15) => {
if (!address) return '';
if (address.length <= displayLength) return address;
return `${address.slice(0, displayLength)}...`;
};
const getStatusColor = (status) => {
const statusColors = {
'Received': 'text-blue-500',
'Accepted': 'text-green-500',
'InProgress': 'text-yellow-500',
'Complete': 'text-green-600',
'Failed': 'text-red-500',
'Expired': 'text-gray-500'
};
return statusColors[status] || 'text-gray-500';
};
const getTimeStrokeColor = (expireTime) => {
const now = Math.floor(Date.now() / 1000);
const timeLeft = expireTime - now;
if (timeLeft <= 300) return '#9CA3AF'; // 5 minutes or less
if (timeLeft <= 1800) return '#3B82F6'; // 30 minutes or less
return '#10B981'; // More than 30 minutes
};
// WebSocket Manager
const WebSocketManager = {
ws: null,
processingQueue: false,
reconnectTimeout: null,
maxReconnectAttempts: 5,
reconnectAttempts: 0,
reconnectDelay: 5000,
initialize() {
this.connect();
this.startHealthCheck();
},
connect() {
if (this.ws?.readyState === WebSocket.OPEN) return;
try {
const wsPort = window.ws_port || '11700';
this.ws = new WebSocket(`ws://${window.location.hostname}:${wsPort}`);
this.setupEventHandlers();
} catch (error) {
console.error('WebSocket connection error:', error);
this.handleReconnect();
}
},
setupEventHandlers() {
this.ws.onopen = () => {
state.wsConnected = true;
this.reconnectAttempts = 0;
updateConnectionStatus('connected');
console.log('🟢 WebSocket connection established for Swaps in Progress');
updateSwapsTable({ resetPage: true, refreshData: true });
};
this.ws.onmessage = () => {
if (!this.processingQueue) {
this.processingQueue = true;
setTimeout(async () => {
try {
if (!state.isRefreshing) {
await updateSwapsTable({ resetPage: false, refreshData: true });
}
} finally {
this.processingQueue = false;
}
}, 200);
}
};
this.ws.onclose = () => {
state.wsConnected = false;
updateConnectionStatus('disconnected');
this.handleReconnect();
};
this.ws.onerror = () => {
updateConnectionStatus('error');
};
},
startHealthCheck() {
setInterval(() => {
if (this.ws?.readyState !== WebSocket.OPEN) {
this.handleReconnect();
}
}, 30000);
},
handleReconnect() {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
this.reconnectAttempts++;
if (this.reconnectAttempts <= this.maxReconnectAttempts) {
const delay = this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1);
this.reconnectTimeout = setTimeout(() => this.connect(), delay);
} else {
updateConnectionStatus('error');
setTimeout(() => {
this.reconnectAttempts = 0;
this.connect();
}, 60000);
}
}
};
// UI
const updateConnectionStatus = (status) => {
const { statusDot, statusText } = elements;
if (!statusDot || !statusText) return;
const statusConfig = {
connected: {
dotClass: 'w-2.5 h-2.5 rounded-full bg-green-500 mr-2',
textClass: 'text-sm text-green-500',
message: 'Connected'
},
disconnected: {
dotClass: 'w-2.5 h-2.5 rounded-full bg-red-500 mr-2',
textClass: 'text-sm text-red-500',
message: 'Disconnected - Reconnecting...'
},
error: {
dotClass: 'w-2.5 h-2.5 rounded-full bg-yellow-500 mr-2',
textClass: 'text-sm text-yellow-500',
message: 'Connection Error'
},
default: {
dotClass: 'w-2.5 h-2.5 rounded-full bg-gray-500 mr-2',
textClass: 'text-sm text-gray-500',
message: 'Connecting...'
}
};
const config = statusConfig[status] || statusConfig.default;
statusDot.className = config.dotClass;
statusText.className = config.textClass;
statusText.textContent = config.message;
};
const updateLoadingState = (isLoading) => {
state.isLoading = isLoading;
if (elements.refreshSwapsButton) {
elements.refreshSwapsButton.disabled = isLoading;
elements.refreshSwapsButton.classList.toggle('opacity-75', isLoading);
elements.refreshSwapsButton.classList.toggle('cursor-wait', isLoading);
const refreshIcon = elements.refreshSwapsButton.querySelector('svg');
const refreshText = elements.refreshSwapsButton.querySelector('#refreshText');
if (refreshIcon) {
refreshIcon.style.transition = 'transform 0.3s ease';
refreshIcon.classList.toggle('animate-spin', isLoading);
}
if (refreshText) {
refreshText.textContent = isLoading ? 'Refreshing...' : 'Refresh';
}
}
};
const processIdentityStats = (identity) => {
if (!identity) return null;
const stats = {
sentSuccessful: safeParseInt(identity.num_sent_bids_successful),
recvSuccessful: safeParseInt(identity.num_recv_bids_successful),
sentFailed: safeParseInt(identity.num_sent_bids_failed),
recvFailed: safeParseInt(identity.num_recv_bids_failed),
sentRejected: safeParseInt(identity.num_sent_bids_rejected),
recvRejected: safeParseInt(identity.num_recv_bids_rejected)
};
stats.totalSuccessful = stats.sentSuccessful + stats.recvSuccessful;
stats.totalFailed = stats.sentFailed + stats.recvFailed;
stats.totalRejected = stats.sentRejected + stats.recvRejected;
stats.totalBids = stats.totalSuccessful + stats.totalFailed + stats.totalRejected;
stats.successRate = stats.totalBids > 0
? ((stats.totalSuccessful / stats.totalBids) * 100).toFixed(1)
: '0.0';
return stats;
};
const createIdentityTooltip = (identity) => {
if (!identity) return '';
const stats = processIdentityStats(identity);
if (!stats) return '';
const getSuccessRateColor = (rate) => {
const numRate = parseFloat(rate);
if (numRate >= 80) return 'text-green-600';
if (numRate >= 60) return 'text-yellow-600';
return 'text-red-600';
};
return `
<div class="identity-info space-y-2">
${identity.label ? `
<div class="border-b border-gray-400 pb-2">
<div class="text-white text-xs tracking-wide font-semibold">Label:</div>
<div class="text-white">${identity.label}</div>
</div>
` : ''}
<div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Address:</div>
<div class="monospace text-xs break-all bg-gray-500 p-2 rounded-md text-white">
${identity.address || ''}
</div>
</div>
${identity.note ? `
<div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Note:</div>
<div class="text-white text-sm italic">${identity.note}</div>
</div>
` : ''}
<div class="pt-2 mt-2">
<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="text-center p-2 bg-gray-500 rounded-md">
<div class="text-lg font-bold ${getSuccessRateColor(stats.successRate)}">
${stats.successRate}%
</div>
<div class="text-xs text-white">Success Rate</div>
</div>
<div class="text-center p-2 bg-gray-500 rounded-md">
<div class="text-lg font-bold text-blue-500">${stats.totalBids}</div>
<div class="text-xs text-white">Total Trades</div>
</div>
</div>
<div class="grid grid-cols-3 gap-2 mt-2 text-center text-xs">
<div>
<div class="text-green-600 font-semibold">
${stats.totalSuccessful}
</div>
<div class="text-white">Successful</div>
</div>
<div>
<div class="text-yellow-600 font-semibold">
${stats.totalRejected}
</div>
<div class="text-white">Rejected</div>
</div>
<div>
<div class="text-red-600 font-semibold">
${stats.totalFailed}
</div>
<div class="text-white">Failed</div>
</div>
</div>
</div>
</div>
`;
};
const createSwapTableRow = async (swap) => {
if (!swap || !swap.bid_id) {
console.warn('Invalid swap data:', swap);
return '';
}
const identity = await IdentityManager.getIdentityData(swap.addr_from);
const uniqueId = `${swap.bid_id}_${swap.created_at}`;
const fromSymbol = COIN_NAME_TO_SYMBOL[swap.coin_from] || swap.coin_from;
const toSymbol = COIN_NAME_TO_SYMBOL[swap.coin_to] || swap.coin_to;
const timeColor = getTimeStrokeColor(swap.expire_at);
const fromAmount = parseFloat(swap.amount_from) || 0;
const toAmount = parseFloat(swap.amount_to) || 0;
return `
<tr class="relative opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600" data-bid-id="${swap.bid_id}">
<td class="relative w-0 p-0 m-0">
<div class="absolute top-0 bottom-0 left-0 w-1"></div>
</td>
<!-- Time Column -->
<td class="py-3 pl-1 pr-2 text-xs whitespace-nowrap">
<div class="flex items-center">
<div class="relative" data-tooltip-target="tooltip-time-${uniqueId}">
<svg class="w-5 h-5 rounded-full mr-4 cursor-pointer" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="${timeColor}" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12"></polyline>
</g>
</svg>
</div>
<div class="flex flex-col hidden xl:block">
<div class="text-xs whitespace-nowrap">
<span class="bold">Posted:</span> ${formatTimeAgo(swap.created_at)}
</div>
</div>
</div>
</td>
<!-- Details Column -->
<td class="py-8 px-4 text-xs text-left hidden xl:block">
<div class="flex flex-col gap-2 relative">
<div class="flex items-center">
<a href="/identity/${swap.addr_from}" data-tooltip-target="tooltip-identity-${uniqueId}" class="flex items-center">
<svg class="w-4 h-4 mr-2 text-gray-400 dark:text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path>
</svg>
<span class="monospace ${identity?.label ? 'dark:text-white' : 'dark:text-white'}">
${identity?.label || formatAddress(swap.addr_from)}
</span>
</a>
</div>
<div class="monospace text-xs text-gray-500 dark:text-gray-300">
<span class="font-semibold">Bid ID:</span>
<a href="/bid/${swap.bid_id}" data-tooltip-target="tooltip-bid-${uniqueId}" class="hover:underline">
${formatAddress(swap.bid_id)}
</a>
</div>
<div class="monospace text-xs text-gray-500 dark:text-gray-300">
<span class="font-semibold">Offer ID:</span>
<a href="/offer/${swap.offer_id}" data-tooltip-target="tooltip-offer-${uniqueId}" class="hover:underline">
${formatAddress(swap.offer_id)}
</a>
</div>
</div>
</td>
<!-- You Send Column -->
<td class="py-0">
<div class="py-3 px-4 text-left">
<div class="items-center monospace">
<div class="pr-2">
<div class="text-sm font-semibold">${fromAmount.toFixed(8)}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${fromSymbol}</div>
</div>
</div>
</div>
</td>
<!-- Swap Column -->
<td class="py-0">
<div class="py-3 px-4 text-center">
<div class="flex items-center justify-center">
<span class="inline-flex mr-3 align-middle items-center justify-center w-18 h-20 rounded">
<img class="h-12"
src="/static/images/coins/${swap.coin_from.replace(' ', '-')}.png"
alt="${swap.coin_from}"
onerror="this.src='/static/images/coins/default.png'">
</span>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"></path>
</svg>
<span class="inline-flex ml-3 align-middle items-center justify-center w-18 h-20 rounded">
<img class="h-12"
src="/static/images/coins/${swap.coin_to.replace(' ', '-')}.png"
alt="${swap.coin_to}"
onerror="this.src='/static/images/coins/default.png'">
</span>
</div>
</div>
</td>
<!-- You Receive Column -->
<td class="py-0">
<div class="py-3 px-4 text-right">
<div class="items-center monospace">
<div class="text-sm font-semibold">${toAmount.toFixed(8)}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${toSymbol}</div>
</div>
</div>
</td>
<!-- Status Column -->
<td class="py-3 px-4 text-center">
<div data-tooltip-target="tooltip-status-${uniqueId}" class="flex justify-center">
<span class="px-2.5 py-1 text-xs font-medium rounded-full ${getStatusClass(swap.bid_state, swap.tx_state_a, swap.tx_state_b)}">
${swap.bid_state}
</span>
</div>
</td>
<!-- Actions Column -->
<td class="py-3 px-4 text-center">
<a href="/bid/${swap.bid_id}"
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">
Details
</a>
</td>
<!-- Tooltips -->
<div id="tooltip-time-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
<div class="active-revoked-expired">
<span class="bold">
<div class="text-xs"><span class="bold">Posted:</span> ${formatTimeAgo(swap.created_at)}</div>
<div class="text-xs"><span class="bold">Expires in:</span> ${formatTime(swap.expire_at)}</div>
</span>
</div>
<div class="mt-5 text-xs">
<p class="font-bold mb-3">Time Indicator Colors:</p>
<p class="flex items-center">
<svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#10B981" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12" stroke="#10B981"></polyline>
</g>
</svg>
Green: More than 30 minutes left
</p>
<p class="flex items-center mt-3">
<svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#3B82F6" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12" stroke="#3B82F6"></polyline>
</g>
</svg>
Blue: Between 5 and 30 minutes left
</p>
<p class="flex items-center mt-3 mb-3">
<svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#9CA3AF" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12" stroke="#9CA3AF"></polyline>
</g>
</svg>
Grey: Less than 5 minutes left or expired
</p>
</div>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-identity-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
${createIdentityTooltip(identity)}
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-offer-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
<div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Offer ID:</div>
<div class="monospace text-xs break-all">
${swap.offer_id}
</div>
</div>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-bid-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
<div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Bid ID:</div>
<div class="monospace text-xs break-all">
${swap.bid_id}
</div>
</div>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-status-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
<div class="text-white">
<p class="font-bold mb-2">Transaction Status</p>
<div class="grid grid-cols-2 gap-2">
<div class="bg-gray-500 p-2 rounded">
<p class="text-xs font-bold">ITX:</p>
<p>${swap.tx_state_a || 'N/A'}</p>
</div>
<div class="bg-gray-500 p-2 rounded">
<p class="text-xs font-bold">PTX:</p>
<p>${swap.tx_state_b || 'N/A'}</p>
</div>
</div>
</div>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</tr>
`;
};
async function updateSwapsTable(options = {}) {
const { resetPage = false, refreshData = true } = options;
if (state.refreshPromise) {
await state.refreshPromise;
return;
}
try {
updateLoadingState(true);
if (refreshData) {
state.refreshPromise = (async () => {
try {
const response = await fetch('/json/active', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sort_by: "created_at",
sort_dir: "desc"
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
state.swapsData = Array.isArray(data) ? data : [];
} catch (error) {
console.error('Error fetching swap data:', error);
state.swapsData = [];
} finally {
state.refreshPromise = null;
}
})();
await state.refreshPromise;
}
if (elements.activeSwapsCount) {
elements.activeSwapsCount.textContent = state.swapsData.length;
}
const totalPages = Math.ceil(state.swapsData.length / PAGE_SIZE);
if (resetPage && state.swapsData.length > 0) {
state.currentPage = 1;
}
state.currentPage = Math.min(Math.max(1, state.currentPage), Math.max(1, totalPages));
const startIndex = (state.currentPage - 1) * PAGE_SIZE;
const endIndex = startIndex + PAGE_SIZE;
const currentPageSwaps = state.swapsData.slice(startIndex, endIndex);
if (elements.swapsBody) {
if (currentPageSwaps.length > 0) {
const rowPromises = currentPageSwaps.map(swap => createSwapTableRow(swap));
const rows = await Promise.all(rowPromises);
elements.swapsBody.innerHTML = rows.join('');
// Initialize tooltips
if (window.TooltipManager) {
window.TooltipManager.cleanup();
const tooltipTriggers = document.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'
});
}
});
}
} else {
elements.swapsBody.innerHTML = `
<tr>
<td colspan="8" class="text-center py-4 text-gray-500 dark:text-white">
No active swaps found
</td>
</tr>`;
}
}
if (elements.paginationControls) {
elements.paginationControls.style.display = totalPages > 1 ? 'flex' : 'none';
}
if (elements.currentPageSpan) {
elements.currentPageSpan.textContent = state.currentPage;
}
if (elements.prevPageButton) {
elements.prevPageButton.style.display = state.currentPage > 1 ? 'inline-flex' : 'none';
}
if (elements.nextPageButton) {
elements.nextPageButton.style.display = state.currentPage < totalPages ? 'inline-flex' : 'none';
}
} catch (error) {
console.error('Error updating swaps table:', error);
if (elements.swapsBody) {
elements.swapsBody.innerHTML = `
<tr>
<td colspan="8" class="text-center py-4 text-red-500">
Error loading active swaps. Please try again later.
</td>
</tr>`;
}
} finally {
updateLoadingState(false);
}
}
// Event
const setupEventListeners = () => {
if (elements.refreshSwapsButton) {
elements.refreshSwapsButton.addEventListener('click', async (e) => {
e.preventDefault();
if (state.isRefreshing) return;
updateLoadingState(true);
try {
await updateSwapsTable({ resetPage: true, refreshData: true });
} finally {
updateLoadingState(false);
}
});
}
if (elements.prevPageButton) {
elements.prevPageButton.addEventListener('click', async (e) => {
e.preventDefault();
if (state.isLoading) return;
if (state.currentPage > 1) {
state.currentPage--;
await updateSwapsTable({ resetPage: false, refreshData: false });
}
});
}
if (elements.nextPageButton) {
elements.nextPageButton.addEventListener('click', async (e) => {
e.preventDefault();
if (state.isLoading) return;
const totalPages = Math.ceil(state.swapsData.length / PAGE_SIZE);
if (state.currentPage < totalPages) {
state.currentPage++;
await updateSwapsTable({ resetPage: false, refreshData: false });
}
});
}
};
// Init
document.addEventListener('DOMContentLoaded', () => {
WebSocketManager.initialize();
setupEventListeners();
});

View File

@@ -0,0 +1,899 @@
// Constants and State
const PAGE_SIZE = 50;
const COIN_NAME_TO_SYMBOL = {
'Bitcoin': 'BTC',
'Litecoin': 'LTC',
'Monero': 'XMR',
'Particl': 'PART',
'Particl Blind': 'PART',
'Particl Anon': 'PART',
'PIVX': 'PIVX',
'Firo': 'FIRO',
'Dash': 'DASH',
'Decred': 'DCR',
'Wownero': 'WOW',
'Bitcoin Cash': 'BCH',
'Dogecoin': 'DOGE'
};
// Global state
const state = {
dentities: new Map(),
currentPage: 1,
wsConnected: false,
jsonData: [],
isLoading: false,
isRefreshing: false,
refreshPromise: null
};
// DOM
const elements = {
bidsBody: document.getElementById('bids-body'),
prevPageButton: document.getElementById('prevPage'),
nextPageButton: document.getElementById('nextPage'),
currentPageSpan: document.getElementById('currentPage'),
paginationControls: document.getElementById('pagination-controls'),
availableBidsCount: document.getElementById('availableBidsCount'),
refreshBidsButton: document.getElementById('refreshBids'),
statusDot: document.getElementById('status-dot'),
statusText: document.getElementById('status-text')
};
// Identity Manager
const IdentityManager = {
cache: new Map(),
pendingRequests: new Map(),
retryDelay: 2000,
maxRetries: 3,
cacheTimeout: 5 * 60 * 1000, // 5 minutes
async getIdentityData(address) {
if (!address) {
return { address: '' };
}
const cachedData = this.getCachedIdentity(address);
if (cachedData) {
return { ...cachedData, address };
}
if (this.pendingRequests.has(address)) {
const pendingData = await this.pendingRequests.get(address);
return { ...pendingData, address };
}
const request = this.fetchWithRetry(address);
this.pendingRequests.set(address, request);
try {
const data = await request;
this.cache.set(address, {
data,
timestamp: Date.now()
});
return { ...data, address };
} catch (error) {
console.warn(`Error fetching identity for ${address}:`, error);
return { address };
} finally {
this.pendingRequests.delete(address);
}
},
getCachedIdentity(address) {
const cached = this.cache.get(address);
if (cached && (Date.now() - cached.timestamp) < this.cacheTimeout) {
return cached.data;
}
if (cached) {
this.cache.delete(address);
}
return null;
},
async fetchWithRetry(address, attempt = 1) {
try {
const response = await fetch(`/json/identities/${address}`, {
signal: AbortSignal.timeout(5000)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return {
...data,
address,
num_sent_bids_successful: safeParseInt(data.num_sent_bids_successful),
num_recv_bids_successful: safeParseInt(data.num_recv_bids_successful),
num_sent_bids_failed: safeParseInt(data.num_sent_bids_failed),
num_recv_bids_failed: safeParseInt(data.num_recv_bids_failed),
num_sent_bids_rejected: safeParseInt(data.num_sent_bids_rejected),
num_recv_bids_rejected: safeParseInt(data.num_recv_bids_rejected),
label: data.label || '',
note: data.note || '',
automation_override: safeParseInt(data.automation_override)
};
} catch (error) {
if (attempt >= this.maxRetries) {
console.warn(`Failed to fetch identity for ${address} after ${attempt} attempts`);
return {
address,
num_sent_bids_successful: 0,
num_recv_bids_successful: 0,
num_sent_bids_failed: 0,
num_recv_bids_failed: 0,
num_sent_bids_rejected: 0,
num_recv_bids_rejected: 0,
label: '',
note: '',
automation_override: 0
};
}
await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
return this.fetchWithRetry(address, attempt + 1);
}
},
clearCache() {
this.cache.clear();
this.pendingRequests.clear();
},
removeFromCache(address) {
this.cache.delete(address);
this.pendingRequests.delete(address);
},
cleanup() {
const now = Date.now();
for (const [address, cached] of this.cache.entries()) {
if (now - cached.timestamp >= this.cacheTimeout) {
this.cache.delete(address);
}
}
}
};
// Util
const formatTimeAgo = (timestamp) => {
const now = Math.floor(Date.now() / 1000);
const diff = now - timestamp;
if (diff < 60) return `${diff} seconds ago`;
if (diff < 3600) return `${Math.floor(diff / 60)} minutes ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)} hours ago`;
return `${Math.floor(diff / 86400)} days ago`;
};
const formatTime = (timestamp) => {
const now = Math.floor(Date.now() / 1000);
const diff = timestamp - now;
if (diff <= 0) return "Expired";
if (diff < 60) return `${diff} seconds`;
if (diff < 3600) return `${Math.floor(diff / 60)} minutes`;
if (diff < 86400) return `${Math.floor(diff / 3600)} hours`;
return `${Math.floor(diff / 86400)} days`;
};
const formatAddress = (address, displayLength = 15) => {
if (!address) return '';
if (address.length <= displayLength) return address;
return `${address.slice(0, displayLength)}...`;
};
const getTimeStrokeColor = (expireTime) => {
const now = Math.floor(Date.now() / 1000);
const timeLeft = expireTime - now;
if (timeLeft <= 300) return '#9CA3AF'; // 5 minutes or less
if (timeLeft <= 1800) return '#3B82F6'; // 30 minutes or less
return '#10B981'; // More than 30 minutes
};
const createTimeTooltip = (bid) => {
const postedTime = formatTimeAgo(bid.created_at);
const expiresIn = formatTime(bid.expire_at);
return `
<div class="active-revoked-expired">
<span class="bold">
<div class="text-xs"><span class="bold">Posted:</span> ${postedTime}</div>
<div class="text-xs"><span class="bold">Expires in:</span> ${expiresIn}</div>
</span>
</div>
<div class="mt-5 text-xs">
<p class="font-bold mb-3">Time Indicator Colors:</p>
<p class="flex items-center">
<svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#10B981" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12" stroke="#10B981"></polyline>
</g>
</svg>
Green: More than 30 minutes left
</p>
<p class="flex items-center mt-3">
<svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#3B82F6" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12" stroke="#3B82F6"></polyline>
</g>
</svg>
Blue: Between 5 and 30 minutes left
</p>
<p class="flex items-center mt-3 mb-3">
<svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#9CA3AF" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12" stroke="#9CA3AF"></polyline>
</g>
</svg>
Grey: Less than 5 minutes left or expired
</p>
</div>
`;
};
const safeParseInt = (value) => {
const parsed = parseInt(value);
return isNaN(parsed) ? 0 : parsed;
};
const processIdentityStats = (identity) => {
if (!identity) return null;
const stats = {
sentSuccessful: safeParseInt(identity.num_sent_bids_successful),
recvSuccessful: safeParseInt(identity.num_recv_bids_successful),
sentFailed: safeParseInt(identity.num_sent_bids_failed),
recvFailed: safeParseInt(identity.num_recv_bids_failed),
sentRejected: safeParseInt(identity.num_sent_bids_rejected),
recvRejected: safeParseInt(identity.num_recv_bids_rejected)
};
stats.totalSuccessful = stats.sentSuccessful + stats.recvSuccessful;
stats.totalFailed = stats.sentFailed + stats.recvFailed;
stats.totalRejected = stats.sentRejected + stats.recvRejected;
stats.totalBids = stats.totalSuccessful + stats.totalFailed + stats.totalRejected;
stats.successRate = stats.totalBids > 0
? ((stats.totalSuccessful / stats.totalBids) * 100).toFixed(1)
: '0.0';
return stats;
};
const createIdentityTooltip = (identity) => {
if (!identity) return '';
const stats = processIdentityStats(identity);
if (!stats) return '';
const getSuccessRateColor = (rate) => {
const numRate = parseFloat(rate);
if (numRate >= 80) return 'text-green-600';
if (numRate >= 60) return 'text-yellow-600';
return 'text-red-600';
};
return `
<div class="identity-info space-y-2">
${identity.label ? `
<div class="border-b border-gray-400 pb-2">
<div class="text-white text-xs tracking-wide font-semibold">Label:</div>
<div class="text-white">${identity.label}</div>
</div>
` : ''}
<div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Bid From Address:</div>
<div class="monospace text-xs break-all bg-gray-500 p-2 rounded-md text-white">
${identity.address || ''}
</div>
</div>
${identity.note ? `
<div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Note:</div>
<div class="text-white text-sm italic">${identity.note}</div>
</div>
` : ''}
<div class="pt-2 mt-2">
<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="text-center p-2 bg-gray-500 rounded-md">
<div class="text-lg font-bold ${getSuccessRateColor(stats.successRate)}">
${stats.successRate}%
</div>
<div class="text-xs text-white">Success Rate</div>
</div>
<div class="text-center p-2 bg-gray-500 rounded-md">
<div class="text-lg font-bold text-blue-500">${stats.totalBids}</div>
<div class="text-xs text-white">Total Trades</div>
</div>
</div>
<div class="grid grid-cols-3 gap-2 mt-2 text-center text-xs">
<div>
<div class="text-green-600 font-semibold">
${stats.totalSuccessful}
</div>
<div class="text-white">Successful</div>
</div>
<div>
<div class="text-yellow-600 font-semibold">
${stats.totalRejected}
</div>
<div class="text-white">Rejected</div>
</div>
<div>
<div class="text-red-600 font-semibold">
${stats.totalFailed}
</div>
<div class="text-white">Failed</div>
</div>
</div>
</div>
</div>
`;
};
// WebSocket Manager
const WebSocketManager = {
ws: null,
processingQueue: false,
reconnectTimeout: null,
maxReconnectAttempts: 5,
reconnectAttempts: 0,
reconnectDelay: 5000,
initialize() {
this.connect();
this.startHealthCheck();
},
connect() {
if (this.ws?.readyState === WebSocket.OPEN) return;
try {
const wsPort = window.ws_port || '11700';
this.ws = new WebSocket(`ws://${window.location.hostname}:${wsPort}`);
this.setupEventHandlers();
} catch (error) {
console.error('WebSocket connection error:', error);
this.handleReconnect();
}
},
setupEventHandlers() {
this.ws.onopen = () => {
state.wsConnected = true;
this.reconnectAttempts = 0;
updateConnectionStatus('connected');
console.log('🟢 WebSocket connection established for Bid Requests');
updateBidsTable({ resetPage: true, refreshData: true });
};
this.ws.onmessage = () => {
if (!this.processingQueue) {
this.processingQueue = true;
setTimeout(async () => {
try {
if (!state.isRefreshing) {
await updateBidsTable({ resetPage: false, refreshData: true });
}
} finally {
this.processingQueue = false;
}
}, 200);
}
};
this.ws.onclose = () => {
state.wsConnected = false;
updateConnectionStatus('disconnected');
this.handleReconnect();
};
this.ws.onerror = () => {
updateConnectionStatus('error');
};
},
startHealthCheck() {
setInterval(() => {
if (this.ws?.readyState !== WebSocket.OPEN) {
this.handleReconnect();
}
}, 30000);
},
handleReconnect() {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
this.reconnectAttempts++;
if (this.reconnectAttempts <= this.maxReconnectAttempts) {
const delay = this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1);
this.reconnectTimeout = setTimeout(() => this.connect(), delay);
} else {
updateConnectionStatus('error');
setTimeout(() => {
this.reconnectAttempts = 0;
this.connect();
}, 60000);
}
}
};
// UI
const updateConnectionStatus = (status) => {
const { statusDot, statusText } = elements;
if (!statusDot || !statusText) return;
const statusConfig = {
connected: {
dotClass: 'w-2.5 h-2.5 rounded-full bg-green-500 mr-2',
textClass: 'text-sm text-green-500',
message: 'Connected'
},
disconnected: {
dotClass: 'w-2.5 h-2.5 rounded-full bg-red-500 mr-2',
textClass: 'text-sm text-red-500',
message: 'Disconnected - Reconnecting...'
},
error: {
dotClass: 'w-2.5 h-2.5 rounded-full bg-yellow-500 mr-2',
textClass: 'text-sm text-yellow-500',
message: 'Connection Error'
},
default: {
dotClass: 'w-2.5 h-2.5 rounded-full bg-gray-500 mr-2',
textClass: 'text-sm text-gray-500',
message: 'Connecting...'
}
};
const config = statusConfig[status] || statusConfig.default;
statusDot.className = config.dotClass;
statusText.className = config.textClass;
statusText.textContent = config.message;
};
const updateLoadingState = (isLoading) => {
state.isLoading = isLoading;
if (elements.refreshBidsButton) {
elements.refreshBidsButton.disabled = isLoading;
elements.refreshBidsButton.classList.toggle('opacity-75', isLoading);
elements.refreshBidsButton.classList.toggle('cursor-wait', isLoading);
const refreshIcon = elements.refreshBidsButton.querySelector('svg');
const refreshText = elements.refreshBidsButton.querySelector('#refreshText');
if (refreshIcon) {
// Add CSS transition for smoother animation
refreshIcon.style.transition = 'transform 0.3s ease';
refreshIcon.classList.toggle('animate-spin', isLoading);
}
if (refreshText) {
refreshText.textContent = isLoading ? 'Refreshing...' : 'Refresh';
}
}
};
const createBidTableRow = async (bid) => {
if (!bid || !bid.bid_id) {
console.error('Invalid bid data:', bid);
return '';
}
const identity = await IdentityManager.getIdentityData(bid.addr_from);
const fromAmount = parseFloat(bid.amount_from) || 0;
const toAmount = parseFloat(bid.amount_to) || 0;
const rate = toAmount > 0 ? toAmount / fromAmount : 0;
const inverseRate = fromAmount > 0 ? fromAmount / toAmount : 0;
const fromSymbol = COIN_NAME_TO_SYMBOL[bid.coin_from] || bid.coin_from;
const toSymbol = COIN_NAME_TO_SYMBOL[bid.coin_to] || bid.coin_to;
const timeColor = getTimeStrokeColor(bid.expire_at);
const uniqueId = `${bid.bid_id}_${bid.created_at}`;
return `
<tr class="relative opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600" data-bid-id="${bid.bid_id}">
<td class="relative w-0 p-0 m-0">
<div class="absolute top-0 bottom-0 left-0 w-1"></div>
</td>
<!-- Time Column -->
<td class="py-3 pl-1 pr-2 text-xs whitespace-nowrap">
<div class="flex items-center">
<div class="relative" data-tooltip-target="tooltip-time-${uniqueId}">
<svg class="w-5 h-5 rounded-full mr-4 cursor-pointer" 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="${timeColor}" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12"></polyline>
</g>
</svg>
</div>
<div class="flex flex-col hidden xl:block">
<div class="text-xs whitespace-nowrap">
<span class="bold">Posted:</span> ${formatTimeAgo(bid.created_at)}
</div>
<div class="text-xs whitespace-nowrap">
<span class="bold">Expires in:</span> ${formatTime(bid.expire_at)}
</div>
</div>
</div>
</td>
<!-- Details Column -->
<td class="py-8 px-4 text-xs text-left hidden xl:block">
<div class="flex flex-col gap-2 relative">
<div class="flex items-center">
<a href="/identity/${bid.addr_from}" data-tooltip-target="tooltip-identity-${uniqueId}" class="flex items-center">
<svg class="w-4 h-4 mr-2 text-gray-400 dark:text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path>
</svg>
<span class="monospace ${identity?.label ? 'dark:text-white' : 'dark:text-white'}">
${identity?.label || formatAddress(bid.addr_from)}
</span>
</a>
</div>
<div class="monospace text-xs text-gray-500 dark:text-gray-300">
<span class="font-semibold">Offer ID:</span>
<a href="/offer/${bid.offer_id}" data-tooltip-target="tooltip-offer-${uniqueId}" class="hover:underline">
${formatAddress(bid.offer_id)}
</a>
</div>
<div class="monospace text-xs text-gray-500 dark:text-gray-300">
<span class="font-semibold">Bid ID:</span>
<a href="/bid/${bid.bid_id}" data-tooltip-target="tooltip-bid-${uniqueId}" class="hover:underline">
${formatAddress(bid.bid_id)}
</a>
</div>
</div>
</td>
<!-- You Send Column -->
<td class="py-0">
<div class="py-3 px-4 text-left">
<div class="items-center monospace">
<div class="pr-2">
<div class="text-sm font-semibold">${fromAmount.toFixed(8)}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${bid.coin_from}</div>
</div>
</div>
</div>
</td>
<!-- Swap Column -->
<td class="py-0">
<div class="py-3 px-4 text-center">
<div class="flex items-center justify-center">
<span class="inline-flex mr-3 align-middle items-center justify-center w-18 h-20 rounded">
<img class="h-12"
src="/static/images/coins/${bid.coin_from.replace(' ', '-')}.png"
alt="${bid.coin_from}"
onerror="this.src='/static/images/coins/default.png'">
</span>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"></path>
</svg>
<span class="inline-flex ml-3 align-middle items-center justify-center w-18 h-20 rounded">
<img class="h-12"
src="/static/images/coins/${bid.coin_to.replace(' ', '-')}.png"
alt="${bid.coin_to}"
onerror="this.src='/static/images/coins/default.png'">
</span>
</div>
</div>
</td>
<!-- You Get Column -->
<td class="py-0">
<div class="py-3 px-4 text-right">
<div class="items-center monospace">
<div class="text-sm font-semibold">${toAmount.toFixed(8)}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${bid.coin_to}</div>
</div>
</div>
</td>
<!-- Rate Column -->
<td class="py-3 px-4 text-right semibold monospace item-center text-xs">
<div class="relative">
<div class="flex flex-col items-end">
<span class="bold text-gray-700 dark:text-white">
${rate.toFixed(8)} ${toSymbol}/${fromSymbol}
</span>
<span class="semibold text-gray-400 dark:text-gray-300">
${inverseRate.toFixed(8)} ${fromSymbol}/${toSymbol}
</span>
</div>
</div>
</td>
<!-- Actions Column -->
<td class="py-3 px-4 text-center">
<a href="/bid/${bid.bid_id}/accept"
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">
Accept
</a>
</td>
</tr>
<!-- Tooltips -->
<div id="tooltip-time-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
<div class="active-revoked-expired">
<span class="bold">
<div class="text-xs"><span class="bold">Posted:</span> ${formatTimeAgo(bid.created_at)}</div>
<div class="text-xs"><span class="bold">Expires in:</span> ${formatTime(bid.expire_at)}</div>
</span>
</div>
<div class="mt-5 text-xs">
<p class="font-bold mb-3">Time Indicator Colors:</p>
<p class="flex items-center">
<svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#10B981" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12" stroke="#10B981"></polyline>
</g>
</svg>
Green: More than 30 minutes left
</p>
<p class="flex items-center mt-3">
<svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#3B82F6" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12" stroke="#3B82F6"></polyline>
</g>
</svg>
Blue: Between 5 and 30 minutes left
</p>
<p class="flex items-center mt-3 mb-3">
<svg class="w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g stroke-linecap="round" stroke-width="2" fill="none" stroke="#9CA3AF" stroke-linejoin="round">
<circle cx="12" cy="12" r="11"></circle>
<polyline points="12,6 12,12 18,12" stroke="#9CA3AF"></polyline>
</g>
</svg>
Grey: Less than 5 minutes left or expired
</p>
</div>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-identity-${uniqueId}" role="tooltip" class="fixed z-50 py-3 px-4 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600 max-w-sm pointer-events-none">
${createIdentityTooltip(identity)}
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-offer-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
<div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Offer ID:</div>
<div class="monospace text-xs break-all">
${bid.offer_id}
</div>
</div>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-bid-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip dark:bg-gray-600">
<div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Bid ID:</div>
<div class="monospace text-xs break-all">
${bid.bid_id}
</div>
</div>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
`;
};
const getDisplayText = (identity, address) => {
if (identity?.label) {
return identity.label;
}
return formatAddress(address);
};
const createDetailsColumn = (bid, identity, uniqueId) => `
<td class="py-8 px-4 text-xs text-left hidden xl:block">
<div class="flex flex-col gap-2 relative">
<div class="flex items-center">
<a href="/identity/${bid.addr_from}" data-tooltip-target="tooltip-identity-${uniqueId}" class="flex items-center">
<svg class="w-4 h-4 mr-2 text-gray-400 dark:text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path>
</svg>
<span class="monospace ${identity?.label ? 'dark:text-white' : 'dark:text-white'}">
${getDisplayText(identity, bid.addr_from)}
</span>
</a>
</div>
<div class="monospace text-xs text-gray-500 dark:text-gray-300">
<span class="font-semibold">Offer ID:</span>
<a href="/offer/${bid.offer_id}" data-tooltip-target="tooltip-offer-${uniqueId}" class="hover:underline">
${formatAddress(bid.offer_id)}
</a>
</div>
<div class="monospace text-xs text-gray-500 dark:text-gray-300">
<span class="font-semibold">Bid ID:</span>
<a href="/bid/${bid.bid_id}" data-tooltip-target="tooltip-bid-${uniqueId}" class="hover:underline">
${formatAddress(bid.bid_id)}
</a>
</div>
</div>
</td>
`;
async function updateBidsTable(options = {}) {
const { resetPage = false, refreshData = true } = options;
if (state.refreshPromise) {
await state.refreshPromise;
return;
}
try {
updateLoadingState(true);
if (refreshData) {
state.refreshPromise = (async () => {
try {
const response = await fetch('/json/bids', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sort_by: "created_at",
sort_dir: "desc",
with_available_or_active: true
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const allBids = await response.json();
if (!Array.isArray(allBids)) {
throw new Error('Invalid response format');
}
state.jsonData = allBids.filter(bid => bid.bid_state === "Received");
state.originalJsonData = [...state.jsonData];
} finally {
state.refreshPromise = null;
}
})();
await state.refreshPromise;
}
if (elements.availableBidsCount) {
elements.availableBidsCount.textContent = state.jsonData.length;
}
const totalPages = Math.ceil(state.jsonData.length / PAGE_SIZE);
if (resetPage && state.jsonData.length > 0) {
state.currentPage = 1;
}
state.currentPage = Math.min(Math.max(1, state.currentPage), Math.max(1, totalPages));
const startIndex = (state.currentPage - 1) * PAGE_SIZE;
const endIndex = startIndex + PAGE_SIZE;
const currentPageBids = state.jsonData.slice(startIndex, endIndex);
if (elements.bidsBody) {
if (currentPageBids.length > 0) {
const rowPromises = currentPageBids.map(bid => createBidTableRow(bid));
const rows = await Promise.all(rowPromises);
elements.bidsBody.innerHTML = rows.join('');
if (window.TooltipManager) {
window.TooltipManager.cleanup();
const tooltipTriggers = document.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'
});
}
});
}
} else {
elements.bidsBody.innerHTML = `
<tr>
<td colspan="8" class="text-center py-4 text-gray-500 dark:text-white">
No available bids requests found
</td>
</tr>`;
}
}
if (elements.paginationControls) {
elements.paginationControls.style.display = totalPages > 1 ? 'flex' : 'none';
}
if (elements.currentPageSpan) {
elements.currentPageSpan.textContent = state.currentPage;
}
if (elements.prevPageButton) {
elements.prevPageButton.style.display = state.currentPage > 1 ? 'inline-flex' : 'none';
}
if (elements.nextPageButton) {
elements.nextPageButton.style.display = state.currentPage < totalPages ? 'inline-flex' : 'none';
}
} catch (error) {
console.error('Error updating bids table:', error);
if (elements.bidsBody) {
elements.bidsBody.innerHTML = `
<tr>
<td colspan="8" class="text-center py-4 text-red-500">
Error loading bids. Please try again later.
</td>
</tr>`;
}
} finally {
updateLoadingState(false);
}
}
// Event
const setupEventListeners = () => {
if (elements.refreshBidsButton) {
elements.refreshBidsButton.addEventListener('click', async () => {
if (state.isRefreshing) return;
updateLoadingState(true);
await new Promise(resolve => setTimeout(resolve, 500));
try {
await updateBidsTable({ resetPage: true, refreshData: true });
} finally {
updateLoadingState(false);
}
});
}
if (elements.prevPageButton) {
elements.prevPageButton.addEventListener('click', async () => {
if (state.isLoading) return;
if (state.currentPage > 1) {
state.currentPage--;
await updateBidsTable({ resetPage: false, refreshData: false });
}
});
}
if (elements.nextPageButton) {
elements.nextPageButton.addEventListener('click', async () => {
if (state.isLoading) return;
const totalPages = Math.ceil(state.jsonData.length / PAGE_SIZE);
if (state.currentPage < totalPages) {
state.currentPage++;
await updateBidsTable({ resetPage: false, refreshData: false });
}
});
}
};
// Init
document.addEventListener('DOMContentLoaded', () => {
WebSocketManager.initialize();
setupEventListeners();
});

View File

@@ -0,0 +1,141 @@
const BidExporter = {
toCSV(bids, type) {
if (!bids || !bids.length) {
return 'No data to export';
}
const isSent = type === 'sent';
const headers = [
'Date/Time',
'Bid ID',
'Offer ID',
'From Address',
isSent ? 'You Send Amount' : 'You Receive Amount',
isSent ? 'You Send Coin' : 'You Receive Coin',
isSent ? 'You Receive Amount' : 'You Send Amount',
isSent ? 'You Receive Coin' : 'You Send Coin',
'Status',
'Created At',
'Expires At'
];
let csvContent = headers.join(',') + '\n';
bids.forEach(bid => {
const row = [
`"${formatTime(bid.created_at)}"`,
`"${bid.bid_id}"`,
`"${bid.offer_id}"`,
`"${bid.addr_from}"`,
isSent ? bid.amount_from : bid.amount_to,
`"${isSent ? bid.coin_from : bid.coin_to}"`,
isSent ? bid.amount_to : bid.amount_from,
`"${isSent ? bid.coin_to : bid.coin_from}"`,
`"${bid.bid_state}"`,
bid.created_at,
bid.expire_at
];
csvContent += row.join(',') + '\n';
});
return csvContent;
},
download(content, filename) {
try {
const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, filename);
return;
}
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
setTimeout(() => {
URL.revokeObjectURL(url);
}, 100);
} catch (error) {
console.error('Error downloading CSV:', error);
const csvData = 'data:text/csv;charset=utf-8,' + encodeURIComponent(content);
const link = document.createElement('a');
link.setAttribute('href', csvData);
link.setAttribute('download', filename);
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
},
exportCurrentView() {
const type = state.currentTab;
const data = state.data[type];
if (!data || !data.length) {
alert('No data to export');
return;
}
const csvContent = this.toCSV(data, type);
const now = new Date();
const dateStr = now.toISOString().split('T')[0];
const filename = `bsx_${type}_bids_${dateStr}.csv`;
this.download(csvContent, filename);
}
};
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
if (typeof state !== 'undefined' && typeof EventManager !== 'undefined') {
const exportSentButton = document.getElementById('exportSentBids');
if (exportSentButton) {
EventManager.add(exportSentButton, 'click', (e) => {
e.preventDefault();
state.currentTab = 'sent';
BidExporter.exportCurrentView();
});
}
const exportReceivedButton = document.getElementById('exportReceivedBids');
if (exportReceivedButton) {
EventManager.add(exportReceivedButton, 'click', (e) => {
e.preventDefault();
state.currentTab = 'received';
BidExporter.exportCurrentView();
});
}
}
}, 500);
});
const originalCleanup = window.cleanup || function(){};
window.cleanup = function() {
originalCleanup();
const exportSentButton = document.getElementById('exportSentBids');
const exportReceivedButton = document.getElementById('exportReceivedBids');
if (exportSentButton && typeof EventManager !== 'undefined') {
EventManager.remove(exportSentButton, 'click');
}
if (exportReceivedButton && typeof EventManager !== 'undefined') {
EventManager.remove(exportReceivedButton, 'click');
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@ document.addEventListener('DOMContentLoaded', () => {
const image = selectedOption.getAttribute('data-image') || '';
const name = selectedOption.textContent.trim();
select.style.backgroundImage = image ? `url(${image}?${new Date().getTime()})` : '';
const selectImage = select.nextElementSibling.querySelector('.select-image');
if (selectImage) {
selectImage.src = image;

View File

@@ -0,0 +1,190 @@
(function(window) {
'use strict';
function positionElement(targetEl, triggerEl, placement = 'bottom', offsetDistance = 8) {
targetEl.style.visibility = 'hidden';
targetEl.style.display = 'block';
const triggerRect = triggerEl.getBoundingClientRect();
const targetRect = targetEl.getBoundingClientRect();
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
let top, left;
top = triggerRect.bottom + offsetDistance;
left = triggerRect.left + (triggerRect.width - targetRect.width) / 2;
switch (placement) {
case 'bottom-start':
left = triggerRect.left;
break;
case 'bottom-end':
left = triggerRect.right - targetRect.width;
break;
}
const viewport = {
width: window.innerWidth,
height: window.innerHeight
};
if (left < 10) left = 10;
if (left + targetRect.width > viewport.width - 10) {
left = viewport.width - targetRect.width - 10;
}
targetEl.style.position = 'fixed';
targetEl.style.top = `${Math.round(top)}px`;
targetEl.style.left = `${Math.round(left)}px`;
targetEl.style.margin = '0';
targetEl.style.maxHeight = `${viewport.height - top - 10}px`;
targetEl.style.overflow = 'auto';
targetEl.style.visibility = 'visible';
}
class Dropdown {
constructor(targetEl, triggerEl, options = {}) {
this._targetEl = targetEl;
this._triggerEl = triggerEl;
this._options = {
placement: options.placement || 'bottom',
offset: options.offset || 5,
onShow: options.onShow || function() {},
onHide: options.onHide || function() {}
};
this._visible = false;
this._initialized = false;
this._handleScroll = this._handleScroll.bind(this);
this._handleResize = this._handleResize.bind(this);
this._handleOutsideClick = this._handleOutsideClick.bind(this);
this.init();
}
init() {
if (!this._initialized) {
this._targetEl.style.margin = '0';
this._targetEl.style.display = 'none';
this._targetEl.style.position = 'fixed';
this._targetEl.style.zIndex = '50';
this._setupEventListeners();
this._initialized = true;
}
}
_setupEventListeners() {
this._triggerEl.addEventListener('click', (e) => {
e.stopPropagation();
this.toggle();
});
document.addEventListener('click', this._handleOutsideClick);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') this.hide();
});
window.addEventListener('scroll', this._handleScroll, true);
window.addEventListener('resize', this._handleResize);
}
_handleScroll() {
if (this._visible) {
requestAnimationFrame(() => {
positionElement(
this._targetEl,
this._triggerEl,
this._options.placement,
this._options.offset
);
});
}
}
_handleResize() {
if (this._visible) {
requestAnimationFrame(() => {
positionElement(
this._targetEl,
this._triggerEl,
this._options.placement,
this._options.offset
);
});
}
}
_handleOutsideClick(e) {
if (this._visible &&
!this._targetEl.contains(e.target) &&
!this._triggerEl.contains(e.target)) {
this.hide();
}
}
show() {
if (!this._visible) {
this._targetEl.style.display = 'block';
this._targetEl.style.visibility = 'hidden';
requestAnimationFrame(() => {
positionElement(
this._targetEl,
this._triggerEl,
this._options.placement,
this._options.offset
);
this._visible = true;
this._options.onShow();
});
}
}
hide() {
if (this._visible) {
this._targetEl.style.display = 'none';
this._visible = false;
this._options.onHide();
}
}
toggle() {
if (this._visible) {
this.hide();
} else {
this.show();
}
}
destroy() {
document.removeEventListener('click', this._handleOutsideClick);
window.removeEventListener('scroll', this._handleScroll, true);
window.removeEventListener('resize', this._handleResize);
this._initialized = false;
}
}
function initDropdowns() {
document.querySelectorAll('[data-dropdown-toggle]').forEach(triggerEl => {
const targetId = triggerEl.getAttribute('data-dropdown-toggle');
const targetEl = document.getElementById(targetId);
if (targetEl) {
const placement = triggerEl.getAttribute('data-dropdown-placement');
new Dropdown(targetEl, triggerEl, {
placement: placement || 'bottom-start'
});
}
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initDropdowns);
} else {
initDropdowns();
}
window.Dropdown = Dropdown;
window.initDropdowns = initDropdowns;
})(window);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -19,8 +19,8 @@ document.addEventListener('DOMContentLoaded', function() {
const backdrop = document.querySelectorAll('.navbar-backdrop');
if (close.length) {
for (var i = 0; i < close.length; i++) {
close[i].addEventListener('click', function() {
for (var k = 0; k < close.length; k++) {
close[k].addEventListener('click', function() {
for (var j = 0; j < menu.length; j++) {
menu[j].classList.toggle('hidden');
}
@@ -29,12 +29,12 @@ document.addEventListener('DOMContentLoaded', function() {
}
if (backdrop.length) {
for (var i = 0; i < backdrop.length; i++) {
backdrop[i].addEventListener('click', function() {
for (var l = 0; l < backdrop.length; l++) {
backdrop[l].addEventListener('click', function() {
for (var j = 0; j < menu.length; j++) {
menu[j].classList.toggle('hidden');
}
});
}
}
});
});

View File

@@ -1,5 +1,5 @@
window.addEventListener('DOMContentLoaded', (event) => {
let err_msgs = document.querySelectorAll('p.error_msg');
window.addEventListener('DOMContentLoaded', () => {
const err_msgs = document.querySelectorAll('p.error_msg');
for (let i = 0; i < err_msgs.length; i++) {
err_msg = err_msgs[i].innerText;
if (err_msg.indexOf('coin_to') >= 0 || err_msg.indexOf('Coin To') >= 0) {
@@ -29,9 +29,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
}
// remove error class on input or select focus
let inputs = document.querySelectorAll('input.error');
let selects = document.querySelectorAll('select.error');
let elements = [...inputs, ...selects];
const inputs = document.querySelectorAll('input.error');
const selects = document.querySelectorAll('select.error');
const elements = [...inputs, ...selects];
elements.forEach((element) => {
element.addEventListener('focus', (event) => {
event.target.classList.remove('error');

File diff suppressed because it is too large Load Diff

115
basicswap/static/js/tabs.js Normal file
View File

@@ -0,0 +1,115 @@
(function(window) {
'use strict';
class Tabs {
constructor(tabsEl, items = [], options = {}) {
this._tabsEl = tabsEl;
this._items = items;
this._activeTab = options.defaultTabId ? this.getTab(options.defaultTabId) : null;
this._options = {
defaultTabId: options.defaultTabId || null,
activeClasses: options.activeClasses || 'text-blue-600 hover:text-blue-600 dark:text-blue-500 dark:hover:text-blue-500 border-blue-600 dark:border-blue-500',
inactiveClasses: options.inactiveClasses || 'dark:border-transparent text-gray-500 hover:text-gray-600 dark:text-gray-400 border-gray-100 hover:border-gray-300 dark:border-gray-700 dark:hover:text-gray-300',
onShow: options.onShow || function() {}
};
this._initialized = false;
this.init();
}
init() {
if (this._items.length && !this._initialized) {
if (!this._activeTab) {
this.setActiveTab(this._items[0]);
}
this.show(this._activeTab.id, true);
this._items.forEach(tab => {
tab.triggerEl.addEventListener('click', () => {
this.show(tab.id);
});
});
this._initialized = true;
}
}
show(tabId, force = false) {
const tab = this.getTab(tabId);
if ((tab !== this._activeTab) || force) {
this._items.forEach(t => {
if (t !== tab) {
t.triggerEl.classList.remove(...this._options.activeClasses.split(' '));
t.triggerEl.classList.add(...this._options.inactiveClasses.split(' '));
t.targetEl.classList.add('hidden');
t.triggerEl.setAttribute('aria-selected', false);
}
});
tab.triggerEl.classList.add(...this._options.activeClasses.split(' '));
tab.triggerEl.classList.remove(...this._options.inactiveClasses.split(' '));
tab.triggerEl.setAttribute('aria-selected', true);
tab.targetEl.classList.remove('hidden');
this.setActiveTab(tab);
this._options.onShow(this, tab);
}
}
getTab(id) {
return this._items.find(t => t.id === id);
}
getActiveTab() {
return this._activeTab;
}
setActiveTab(tab) {
this._activeTab = tab;
}
}
function initTabs() {
document.querySelectorAll('[data-tabs-toggle]').forEach(tabsEl => {
const items = [];
let defaultTabId = null;
tabsEl.querySelectorAll('[role="tab"]').forEach(triggerEl => {
const isActive = triggerEl.getAttribute('aria-selected') === 'true';
const tab = {
id: triggerEl.getAttribute('data-tabs-target'),
triggerEl: triggerEl,
targetEl: document.querySelector(triggerEl.getAttribute('data-tabs-target'))
};
items.push(tab);
if (isActive) {
defaultTabId = tab.id;
}
});
new Tabs(tabsEl, items, {
defaultTabId: defaultTabId
});
});
}
const style = document.createElement('style');
style.textContent = `
[data-tabs-toggle] [role="tab"] {
cursor: pointer;
}
`;
document.head.appendChild(style);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTabs);
} else {
initTabs();
}
window.Tabs = Tabs;
window.initTabs = initTabs;
})(window);

View File

@@ -0,0 +1,306 @@
class TooltipManager {
constructor() {
this.activeTooltips = new Map();
this.sizeCheckIntervals = new Map();
this.setupStyles();
this.setupCleanupEvents();
}
static initialize() {
if (!window.TooltipManager) {
window.TooltipManager = new TooltipManager();
}
return window.TooltipManager;
}
create(element, content, options = {}) {
if (!element) return null;
this.destroy(element);
const checkSize = () => {
const rect = element.getBoundingClientRect();
if (rect.width && rect.height) {
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;
}
createTooltip(element, content, options, rect) {
const targetId = element.getAttribute('data-tooltip-target');
let bgClass = 'bg-gray-400';
let arrowColor = 'rgb(156 163 175)';
if (targetId?.includes('tooltip-offer-')) {
const offerId = targetId.split('tooltip-offer-')[1];
const [actualOfferId] = offerId.split('_');
if (window.jsonData) {
const offer = window.jsonData.find(o =>
o.unique_id === offerId ||
o.offer_id === actualOfferId
);
if (offer) {
if (offer.is_revoked) {
bgClass = 'bg-red-500';
arrowColor = 'rgb(239 68 68)';
} else if (offer.is_own_offer) {
bgClass = 'bg-gray-300';
arrowColor = 'rgb(209 213 219)';
} else {
bgClass = 'bg-green-700';
arrowColor = 'rgb(21 128 61)';
}
}
}
}
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;
}
const rect = element.getBoundingClientRect();
if (!rect.width || !rect.height) {
return false;
}
instance.setProps({
placement: instance._originalPlacement
});
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);
}
});
}
setupStyles() {
if (document.getElementById('tooltip-styles')) return;
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>
`);
}
setupCleanupEvents() {
window.addEventListener('beforeunload', () => this.cleanup());
window.addEventListener('unload', () => this.cleanup());
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.cleanup();
}
});
}
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();
});

View File

@@ -1,115 +1,118 @@
{% include 'header.html' %}
{% from 'style.html' import breadcrumb_line_svg, circular_arrows_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="/active">Swaps In Progress</a>
</li>
<li>{{ breadcrumb_line_svg | safe }}</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">Swaps in Progress</h2>
<p class="font-normal text-coolGray-200 dark:text-white">Your swaps that are currently in progress.</p>
</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="/active" class="rounded-full 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 30 seconds</span>
</a>
{% else %}
<a id="refresh" href="/active" class="flex flex-wrap justify-center px-5 py-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white borderdark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
{{ circular_arrows_svg | safe }}
<span>Refresh</span>
</a>
{% endif %}
</div>
{% from 'style.html' import breadcrumb_line_svg, page_back_svg, page_forwards_svg, filter_clear_svg, filter_apply_svg, input_arrow_down_svg %}
<section class="py-3 px-4 mt-6">
<div class="lg:container mx-auto">
<div class="relative py-8 px-8 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-3 text-2xl font-bold text-white tracking-tighter">Swaps in Progress</h2>
<p class="font-normal text-coolGray-200 dark:text-white">Monitor your currently active swap transactions.</p>
</div>
</div>
</div>
</section>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
<div class="pb-6 border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mt-5 mx-auto">
<div class="pt-6 pb-8 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 text-sm">
<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">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 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 rounded-tr-xl 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>
</tr>
</thead>
{% for s in active_swaps %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 monospace">
<a href=/bid/{{ s[0] }}>{{ s[0]|truncate(50,true,'...',0) }}</a>
</td>
<td class="py-3 px-6 monospace">
<a href=/offer/{{ s[1] }}>{{ s[1]|truncate(50,true,'...',0) }}</a>
</td>
<td class="py-3 px-6 w-52 whitespace-normal break-words">{{ s[2] }}</td>
<td class="py-3 px-6">{{ s[3] }}</td>
<td class="py-3 px-6">{{ s[4] }}</td>
</tr>
{% endfor %}
</table>
</div>
</section>
{% include 'inc_messages.html' %}
<section>
<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="px-0">
<div class="w-auto mt-6 overflow-auto lg:overflow-hidden">
<table class="w-full min-w-max">
<thead class="uppercase">
<tr>
<th class="p-0" data-sortable="true" data-column-index="0">
<div class="py-3 pl-4 justify-center rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm mr-1 text-gray-600 dark:text-gray-300 font-semibold"></span>
</div>
</div>
</th>
<th class="p-0">
<div class="py-3 pl-6 pr-3 justify-center bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm mr-1 text-gray-600 dark:text-gray-300 font-semibold">Time</span>
</div>
</th>
<th class="p-0 hidden xl:block">
<div class="py-3 px-4 text-left bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Details</span>
</div>
</th>
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-left">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Send</span>
</div>
</th>
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-center">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Swap</span>
</div>
</th>
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-right">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Receive</span>
</div>
</th>
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-center">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Status</span>
</div>
</th>
<th class="p-0">
<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">Actions</span>
</div>
</th>
</tr>
</thead>
<tbody id="active-swaps-body"></tbody>
</table>
</div>
</div>
<div class="rounded-b-md">
<div class="w-full">
<div class="flex flex-wrap justify-between items-center pl-6 pt-6 pr-6 border-t border-gray-100 dark:border-gray-400">
<div class="flex items-center">
<div class="flex items-center mr-4">
<span id="status-dot" class="w-2.5 h-2.5 rounded-full bg-gray-500 mr-2"></span>
<span id="status-text" class="text-sm text-gray-500">Connecting...</span>
</div>
<p class="text-sm font-heading dark:text-gray-400 mr-4">Active Swaps: <span id="activeSwapsCount">0</span></p>
{% if debug_ui_mode == true %}
<button type="button" id="refreshSwaps" class="inline-flex items-center 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">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<span id="refreshText">Refresh</span>
</button>
{% endif %}
<div id="pagination-controls" class="flex items-center space-x-2" style="display: none;">
<button id="prevPage" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
Previous
</button>
<p class="text-sm font-heading dark:text-white">Page <span id="currentPage">1</span></p>
<button id="nextPage" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200">
Next
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
<script src="/static/js/active.js"></script>
{% include 'footer.html' %}
</body>
</html>

View File

@@ -1,220 +1,368 @@
{% 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 %}
<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="#">{{ page_type_available }} {{ page_type_received }} {{ page_type_sent }}</a> </li>
<li> {{ breadcrumb_line_svg | safe }} </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">{{ page_type_available }} {{ page_type_received }} {{ page_type_sent }}</h2>
<p class="font-normal text-coolGray-200 dark:text-white">{{ page_type_available_description }} {{ page_type_received_description }} {{ page_type_sent_description }}</p>
</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>
</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="flex items-center">
<div class="w-full md:w-auto p-1.5">
<p class="text-sm font-heading bold">State:</p>
</div>
</div>
<div class="w-full md:w-auto p-1.5">
<div class="relative">
{{ input_arrow_down_svg | safe }}
<select name="state" 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="-1" {% if filters.bid_state_ind==-1 %} selected{% endif %}>Any</option>
{% for s in data.bid_states %}
<option value="{{ s[0] }}" {% if filters.bid_state_ind==s[0] %} selected{% endif %}>{{ s[1] }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="flex items-center">
<div class="w-full md:w-auto p-1.5">
<p class="text-sm font-heading bold">Include Expired:</p>
</div>
</div>
<div class="w-full md:w-auto p-1.5">
<div class="relative">
{{ input_arrow_down_svg | safe }}
<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="false" {% if filters.with_expired==false %} selected{% endif %}>Exclude</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">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>
{% if bids_count > 20 %}
<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>
</form>
</div>
</div>
{% 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, arrow_right_svg %}
<section class="py-3 px-4 mt-6">
<div class="lg:container mx-auto">
<div class="relative py-8 px-8 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-3 text-2xl font-bold text-white tracking-tighter">Sent Bids / Received Bids</h2>
<p class="font-normal text-coolGray-200 dark:text-white">View, and manage bids.</p>
</div>
</div>
</div>
</div>
</section>
</section>
{% include 'inc_messages.html' %}
<div class="xl:container mx-auto">
<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 <span class="text-gray-500 dark:text-gray-400">({{ sent_bids_count }})</span>
</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 <span class="text-gray-500 dark:text-gray-400">({{ received_bids_count }})</span>
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
</section>
</div>
<section>
<div class="px-6 py-0 h-full overflow-hidden">
<div class="pb-6 mt-6 border-coolGray-100">
<div class="flex flex-wrap justify-center -m-1.5">
<div class="w-full md:w-auto p-1.5">
<div class="relative">
<input type="text"
id="searchInput"
name="search" autocomplete="off" placeholder="Search bid ID, offer ID, address or label..."
class="w-full md:w-96 hover:border-blue-500 dark:hover:bg-gray-50 text-gray-900 pl-4 pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none block p-2.5 focus:ring-blue-500 focus:border-blue-500 focus:ring-0 dark:focus:bg-gray-500 dark:focus:text-white">
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg class="w-5 h-5 text-gray-500 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
</div>
</div>
<div class="p-1.5 md:w-auto hover-container">
<div class="flex">
<button id="coin_from_button" class="bg-gray-50 text-gray-900 appearance-none w-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-l-lg flex items-center" disabled></button>
<div class="relative">
{{ input_arrow_down_svg | safe }}
<select name="coin_from" id="coin_from" class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-r-lg outline-none block w-full p-2.5 focus:ring-0 border-l-0">
<option value="any" {% if filters.coin_from==-1 %} selected{% endif %}>You Send</option>
{% for c in coins_from %}
<option class="text-sm" value="{{ c[0] }}" {% if filters.coin_from==c[0] %} selected{% endif %} data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}.png">{{ c[1] }}</option>
{% endfor %}
</select>
</div>
<div class="flex items-center">
<div class="w-full md:w-auto p-1.5">
<p class="text-sm font-heading text-gray-500 dark:text-white">{{ arrow_right_svg | safe }}</p>
</div>
</div>
<button id="coin_to_button" class="bg-gray-50 text-gray-900 appearance-none w-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-l-lg flex items-center" disabled></button>
<div class="relative">
{{ input_arrow_down_svg | safe }}
<select name="coin_to" id="coin_to" class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-r-lg outline-none block w-full p-2.5 focus:ring-0 border-l-0">
<option value="any" {% if filters.coin_to==-1 %} selected{% endif %}>You Receive</option>
{% for c in coins %}
<option class="text-sm" value="{{ c[0] }}" {% if filters.coin_to==c[0] %} selected{% endif %} data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}.png">{{ c[1] }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="w-full md:w-auto p-1.5">
<div class="relative">
{{ input_arrow_down_svg | safe }}
<select name="state" id="state" 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="-1" selected="">Any State</option>
<optgroup label="Active States">
<option value="1">Sent</option>
<option value="2">Receiving</option>
<option value="3">Received</option>
<option value="4">Receiving accept</option>
<option value="5">Accepted</option>
<option value="6">Initiated</option>
<option value="7">Participating</option>
</optgroup>
<optgroup label="Completed States">
<option value="8">Completed</option>
<option value="15">Scriptless tx redeemed</option>
<option value="13">Script tx redeemed</option>
</optgroup>
<optgroup label="Failed States">
<option value="17">Failed, refunded</option>
<option value="18">Failed, swiped</option>
<option value="19">Failed</option>
<option value="22">Abandoned</option>
<option value="23">Error</option>
<option value="31">Expired</option>
</optgroup>
<optgroup label="Other States">
<option value="9">Script coin locked</option>
<option value="10">Script coin spend tx valid</option>
<option value="11">Scriptless coin locked</option>
<option value="12">Script coin lock released</option>
<option value="14">Script pre-refund tx in chain</option>
<option value="16">Scriptless tx recovered</option>
<option value="20">Delaying</option>
<option value="21">Timed-out</option>
<option value="24">Stalled (debug)</option>
<option value="25">Rejected</option>
<option value="26">Unknown bid state</option>
<option value="27">Exchanged script lock tx sigs msg</option>
<option value="28">Exchanged script lock spend tx msg</option>
<option value="29">Request sent</option>
<option value="30">Request accepted</option>
<option value="32">Auto accept delay</option>
<option value="33">Auto accept failed</option>
</optgroup>
</select>
</div>
</div>
<!-- todo
<div class="w-full md:w-auto p-1.5">
<div class="relative">
{{ input_arrow_down_svg | safe }}
<select name="with_expired" id="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">Include Expired</option>
<option value="false">Exclude Expired</option>
</select>
</div>
</div>-->
<div class="w-full md:w-auto p-1.5">
<div class="relative">
<button type="button" id="clearFilters" 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>
</div>
</div>
</section>
<div id="bidstab">
<div class="rounded-lg lg:px-6" id="sent" role="tabpanel" aria-labelledby="sent-tab">
<div id="sent-content">
<div class="xl:container mx-auto lg:px-0">
<div class="pt-0 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-0">
<div class="w-auto overflow-auto lg:overflow-hidden">
<table class="w-full lg:min-w-max">
<thead class="uppercase">
<tr class="text-left">
<th class="p-0">
<div class="py-3 pl-16 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Date/Time</span>
</div>
</th>
<th class="p-0 hidden lg:block">
<div class="p-3 bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Details</span>
</div>
</th>
<th class="p-0">
<div class="p-3 bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Send</span>
</div>
</th>
<th class="p-0">
<div class="p-3 bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Receive</span>
</div>
</th>
<th class="p-0">
<div class="p-3 text-center bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Status</span>
</div>
</th>
<th class="p-0">
<div class="p-3 pr-6 text-center rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Actions</span>
</div>
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="rounded-b-md">
<div class="w-full">
<div class="flex flex-wrap justify-between items-center pl-6 pt-6 pr-6 border-t border-gray-100 dark:border-gray-400">
<div class="flex items-center">
<div class="flex items-center mr-4">
<span id="status-dot-sent" class="w-2.5 h-2.5 rounded-full bg-gray-500 mr-2"></span>
<span id="status-text-sent" class="text-sm text-gray-500">Connecting...</span>
</div>
<p class="text-sm font-heading dark:text-gray-400">
Sent Bids: <span id="sentBidsCount">0</span>
</p>
{% if debug_ui_mode == true %}
<button id="refreshSentBids" class="ml-4 inline-flex items-center 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">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<span id="refreshSentText">Refresh</span>
</button>
{% endif %}
<button id="exportSentBids" class="ml-4 inline-flex items-center px-4 py-2.5 font-medium text-sm text-white bg-green-600 hover:bg-green-700 hover:border-green-700 rounded-lg transition duration-200 border border-green-600 rounded-md shadow-button focus:ring-0 focus:outline-none">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<span>Export CSV</span>
</button>
</div>
<div id="pagination-controls-sent" class="flex items-center space-x-2" style="display: none;">
<button id="prevPageSent" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
Previous
</button>
<p class="text-sm font-heading dark:text-white">Page <span id="currentPageSent">1</span></p>
<button id="nextPageSent" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
Next
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="hidden rounded-lg lg:px-6" id="received" role="tabpanel" aria-labelledby="received-tab">
<div id="received-content">
<div class="xl:container mx-auto lg:px-0">
<div class="pt-0 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-0">
<div class="w-auto overflow-auto lg:overflow-hidden">
<table class="w-full lg:min-w-max">
<thead class="uppercase">
<tr class="text-left">
<th class="p-0">
<div class="p-3 pl-16 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Date/Time</span>
</div>
</th>
<th class="p-0">
<div class="p-3 bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Details</span>
</div>
</th>
<th class="p-0">
<div class="p-3 bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Send</span>
</div>
</th>
<th class="p-0">
<div class="p-3 bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Receive</span>
</div>
</th>
<th class="p-0">
<div class="p-3 text-center bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Status</span>
</div>
</th>
<th class="p-0">
<div class="p-3 pr-6 text-center rounded-tr-xl bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Actions</span>
</div>
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class="rounded-b-md">
<div class="w-full">
<div class="flex flex-wrap justify-between items-center pl-6 pt-6 pr-6 border-t border-gray-100 dark:border-gray-400">
<div class="flex items-center">
<div class="flex items-center mr-4">
<span id="status-dot-received" class="w-2.5 h-2.5 rounded-full bg-gray-500 mr-2"></span>
<span id="status-text-received" class="text-sm text-gray-500">Connecting...</span>
</div>
<p class="text-sm font-heading dark:text-gray-400">
Received Bids: <span id="receivedBidsCount">0</span>
</p>
{% if debug_ui_mode == true %}
<button id="refreshReceivedBids" class="ml-4 inline-flex items-center 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">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<span id="refreshReceivedText">Refresh</span>
</button>
{% endif %}
<button id="exportReceivedBids" class="ml-4 inline-flex items-center px-4 py-2.5 font-medium text-sm text-white bg-green-600 hover:bg-green-700 hover:border-green-700 rounded-lg transition duration-200 border border-green-600 rounded-md shadow-button focus:ring-0 focus:outline-none">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<span>Export CSV</span>
</button>
</div>
<div id="pagination-controls-received" class="flex items-center space-x-2" style="display: none;">
<button id="prevPageReceived" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
Previous
</button>
<p class="text-sm font-heading dark:text-white">Page <span id="currentPageReceived">1</span></p>
<button id="nextPageReceived" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200 focus:ring-0 focus:outline-none">
Next
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/static/js/bids_sentreceived.js"></script>
<script src="/static/js/bids_export.js"></script>
{% include 'footer.html' %}
</body>
</html>

View File

@@ -0,0 +1,118 @@
{% 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 %}
<section class="py-3 px-4 mt-6">
<div class="lg:container mx-auto">
<div class="relative py-8 px-8 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-3 text-2xl font-bold text-white tracking-tighter">Bid 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' %}
<section>
<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="px-0">
<div class="w-auto mt-6 overflow-auto lg:overflow-hidden">
<table class="w-full min-w-max">
<thead class="uppercase">
<tr>
<th class="p-0" data-sortable="true" data-column-index="0">
<div class="py-3 pl-4 justify-center rounded-tl-xl bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm mr-1 text-gray-600 dark:text-gray-300 font-semibold"></span>
</div>
</th>
<th class="p-0">
<div class="py-3 pl-4 justify-center bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm mr-1 text-gray-600 dark:text-gray-300 font-semibold">Time</span>
</div>
</th>
<th class="p-0 hidden xl:block">
<div class="py-3 px-4 text-left bg-coolGray-200 dark:bg-gray-600">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Details</span>
</div>
</th>
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-left">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Send</span>
</div>
</th>
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-center">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Swap</span>
</div>
</th>
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-right">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">You Get</span>
</div>
</th>
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-right">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Rate</span>
</div>
</th>
<th class="p-0">
<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">Actions</span>
</div>
</th>
</tr>
</thead>
<tbody id="bids-body"></tbody>
</table>
</div>
</div>
<div class="rounded-b-md">
<div class="w-full">
<div class="flex flex-wrap justify-between items-center pl-6 pt-6 pr-6 border-t border-gray-100 dark:border-gray-400">
<div class="flex items-center">
<div class="flex items-center mr-4">
<span id="status-dot" class="w-2.5 h-2.5 rounded-full bg-gray-500 mr-2"></span>
<span id="status-text" class="text-sm text-gray-500">Connecting...</span>
</div>
<p class="text-sm font-heading dark:text-gray-400 mr-4">Available Bids: <span id="availableBidsCount">0</span></p>
{% if debug_ui_mode == true %}
<button id="refreshBids" class="inline-flex items-center 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">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
<span id="refreshText">Refresh</span>
</button>
{% endif %}
<div id="pagination-controls" class="flex items-center space-x-2" style="display: none;">
<button id="prevPage" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
Previous
</button>
<p class="text-sm font-heading dark:text-white">Page <span id="currentPage">1</span></p>
<button id="nextPage" class="inline-flex items-center h-9 py-1 px-4 text-xs text-blue-50 font-semibold bg-blue-500 hover:bg-green-600 rounded-lg transition duration-200">
Next
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<script src="/static/js/bids_available.js"></script>
{% include 'footer.html' %}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,73 +1,4 @@
{% include 'header.html' %}
{% from 'style.html' import index_wallet_svg, index_trading_svg, index_support_svg %}
<div class="container mx-auto">
<section class="relative py-24 overflow-hidden">
<div class="container px-4 mx-auto mb-16 md:mb-0">
<div class="md:w-1/2 pl-4">
<span class="inline-block py-1 px-3 mb-4 text-xs leading-5 bg-blue-500 text-white font-medium rounded-full shadow-sm">(BSX) BasicSwap v{{ version }} - (GUI) v.3.1.2</span>
<h3 class="mb-6 text-4xl md:text-5xl leading-tight text-coolGray-900 font-bold tracking-tighter dark:text-white">Welcome to BasicSwap DEX</h3>
<p class="mb-12 text-lg md:text-xl text-coolGray-500 dark:text-gray-300 font-medium">The World's Most Secure and Decentralized DEX, Safely swap cryptocurrencies without central points of failure.
Its free, completely trustless, and highly secure.</p>
<div class="flex flex-wrap mb-10 text-center md:text-left">
<div class="w-full md:w-auto mb-6 md:mb-0 md:pr-6">
<a href="/wallets">
<div class="inline-flex h-14 w-14 mx-auto items-center justify-center text-white bg-blue-500 rounded-lg">
{{ index_wallet_svg | safe }}
</div>
</div>
<div class="w-full md:flex-1 md:pt-3">
<div class="md:max-w-sm">
<h3 class="mb-4 text-xl md:text-2xl leading-tight text-coolGray-900 dark:text-white font-bold">Your Wallet</h3>
<p class="text-coolGray-500 dark:text-gray-300 font-medium">Manage your cryptocurrency wallets.</p>
</a>
</div>
</div>
</div>
<div class="flex flex-wrap mb-10 text-center md:text-left">
<div class="w-full md:w-auto mb-6 md:mb-0 md:pr-6">
<a href="/offers">
<div class="inline-flex h-14 w-14 mx-auto items-center justify-center text-white bg-blue-500 rounded-lg">
{{ index_trading_svg | safe }}
</div>
</div>
<div class="w-full md:flex-1 md:pt-3">
<div class="md:max-w-sm">
<h3 class="mb-4 text-xl md:text-2xl leading-tight text-coolGray-900 dark:text-white font-bold">Start Trading</h3>
<p class="text-coolGray-500 dark:text-gray-300 font-medium">Browse available swap offers placed by others.</p>
</a>
</div>
</div>
</div>
<div class="flex flex-wrap text-center md:text-left">
<div class="w-full md:w-auto mb-6 md:mb-0 md:pr-6">
<a href="https://academy.particl.io/en/latest/faq/get_support.html" target="_blank">
<div class="inline-flex h-14 w-14 mx-auto items-center justify-center text-white bg-blue-500 rounded-lg">
{{ index_support_svg | safe }}
</div>
</div>
<div class="w-full md:flex-1 md:pt-3">
<div class="md:max-w-sm">
<h3 class="mb-4 text-xl md:text-2xl leading-tight text-coolGray-900 dark:text-white font-bold">Help / Tutorials</h3>
<p class="text-coolGray-500 dark:text-gray-300 font-medium">Learn how to use BasicSwap with the Particl Academy.</p>
</div>
</div>
</a>
</div>
</div>
</div>
<div class="md:absolute md:top-28 lg:top-1/2 md:-right-96 xl:-right-80 md:-mr-56 lg:-mr-20 xl:-mr-0 md:transform lg:-translate-y-1/2 px-4 mb-16 md:mb-0">
<div class="relative max-w-max">
<img class="absolute p-7 -mt-1 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-10/12 z-20 imageshow light-image" src="/static/images/gfx/dashboard.jpg" alt="">
<img class="absolute p-7 -mt-1 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-10/12 z-20 imageshow dark-image" src="/static/images/gfx/dashboard2.jpg" alt="">
<img class="relative z-10 imageshow light-image" src="/static/images/gfx/macbook.png" alt="">
<img class="relative z-10 imageshow dark-image" src="/static/images/gfx/macbook2.png" alt="">
<img class="absolute -top-24 right-0 md:mt-px md:right-96 md:mr-52 lg:mr-16 xl:-mr-20 w-28 md:w-auto text-blue-500" src="/static/images/elements/dots2-red.svg">
<img class="absolute -bottom-24 left-0 md:left-auto md:mt-px md:right-96 md:mr-52 lg:mr-16 xl:-mr-20 w-28 md:w-auto text-red-500" src="/static/images/elements/dots2-red.svg">
<img class="absolute left-0 top-1/2 transform -translate-y-1/2 w-28 md:w-auto text-yellow-400" src="/static/images/elements/circle2-violet.svg">
</div>
</div>
</section>
</div>
{% include 'footer.html' %}
</body>
</html>

View File

@@ -137,7 +137,7 @@
<td class="py-3 px-6 bold">{{ data.amt_to }} {{ data.tla_to }}</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Minimum Bid Amount</td>
<td class="py-3 px-6 bold">Minimum Purchase</td>
<td class="py-3 px-6">{{ data.amt_bid_min }} {{ data.tla_from }}</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
@@ -747,40 +747,24 @@ function resetForm() {
const bidAmountInput = document.getElementById('bid_amount');
const bidRateInput = document.getElementById('bid_rate');
const validMinsInput = document.querySelector('input[name="validmins"]');
const addrFromSelect = document.querySelector('select[name="addr_from"]');
const amtVar = document.getElementById('amt_var')?.value === 'True';
if (bidAmountSendInput) {
const defaultSendAmount = bidAmountSendInput.getAttribute('max');
bidAmountSendInput.value = defaultSendAmount;
bidAmountSendInput.value = amtVar ? '' : bidAmountSendInput.getAttribute('max');
}
if (bidAmountInput) {
const defaultReceiveAmount = bidAmountInput.getAttribute('max');
bidAmountInput.value = defaultReceiveAmount;
bidAmountInput.value = amtVar ? '' : bidAmountInput.getAttribute('max');
}
if (bidRateInput && !bidRateInput.disabled) {
const defaultRate = document.getElementById('offer_rate')?.value || '';
bidRateInput.value = defaultRate;
}
if (validMinsInput) {
validMinsInput.value = "60";
}
if (addrFromSelect) {
if (addrFromSelect.options.length > 1) {
addrFromSelect.selectedIndex = 1;
} else {
addrFromSelect.selectedIndex = 0;
}
const selectedOption = addrFromSelect.options[addrFromSelect.selectedIndex];
saveAddress(selectedOption.value, selectedOption.text);
if (!amtVar) {
updateBidParams('rate');
}
updateBidParams('rate');
updateModalValues();
const errorMessages = document.querySelectorAll('.error-message');
errorMessages.forEach(msg => msg.remove());

View File

@@ -293,7 +293,7 @@
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Minimum Bid Amount</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Minimum Purchase</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_bid_amount_svg | safe }}

View File

@@ -274,7 +274,7 @@ if (document.readyState === 'loading') {
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base dark:text-white text-coolGray-800">Minimum Bid Amount</p>
<p class="mb-1.5 font-medium text-base dark:text-white text-coolGray-800">Minimum Purchase</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_bid_amount_svg | safe }}

View File

@@ -279,7 +279,7 @@
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Minimum Bid Amount</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Minimum Purchase</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_bid_amount_svg | safe }}

View File

@@ -24,102 +24,75 @@ function getWebSocketConfig() {
}
</script>
{% if sent_offers %}
<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="/">Home</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="{% if page_type == 'offers' %}/offers{% elif page_type == 'sentoffers' %}/sentoffers{% endif %}">
{{ page_type }}
</a>
</li>
<li>{{ breadcrumb_line_svg | safe }}</li>
</ul>
</div>
</div>
</section>
</div>
{% endif %}
{% if sent_offers %}
<section class="py-5">
{% else %}
<section class="py-5 mt-5">
{% endif %}
<div class="container px-4 mx-auto">
<div class="relative py-11 px-16 bg-coolGray-900 dark:bg-gray-500 rounded-md overflow-hidden">
<img class="absolute z-10 left-4 top-4 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="dots-red">
<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="wave">
<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">{{ page_type }}</h2>
<p class="font-normal text-coolGray-200 dark:text-white">{{ page_type_description }}</p>
</div>
<div class="rounded-full{{ page_button }} w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
<a id="refresh" href="/newoffer" class="rounded-full flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-green-600 hover:border-green-600 font-medium text-sm text-white border border-blue-500 rounded-md focus:ring-0 focus:outline-none">{{ place_new_offer_svg | safe }}<span>Place new Offer</span></a>
</div>
<section class="py-3 px-4 mt-6">
<div class="lg:container mx-auto">
<div class="relative py-8 px-8 bg-coolGray-900 dark:bg-gray-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-3 text-2xl font-bold text-white tracking-tighter">{{ page_type }}</h2>
<p class="font-normal text-coolGray-200 dark:text-white">{{ page_type_description }}</p>
</div>
<div class="w-full md:w-1/2 p-3 flex justify-end items-center hidden">
<a id="refresh" href="/newoffer"
class="rounded-full flex items-center justify-center px-4 py-2 bg-blue-500 hover:bg-green-600 hover:border-green-600 font-medium text-sm text-white border border-blue-500 rounded-md focus:ring-0 focus:outline-none">
{{ place_new_offer_svg | safe }}
<span>Place new Offer</span>
</a>
</div>
</div>
</div>
</section>
</div>
</section>
{% include 'inc_messages.html' %}
{% if show_chart %}
<section class="relative hidden md:block">
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
<div class="px-6 py-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="flex flex-wrap items-center justify-between">
<div class="w-full pt-2">
<div class="container px-4 mx-auto">
<div class="lg:container mx-auto">
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl container-to-blur">
<div class="flex justify-between items-center mb-4 mr-10 ml-10">
<div class="flex items-center justify-between">
<h2 class="text-xl font-bold dark:text-white" id="chart-title">Price Chart</h2>
<div class="flex items-center space-x-4">
<button id="resolution-year" class="ml-5 resolution-button">1Y</button>
<button id="resolution-sixMonths" class="resolution-button">6M</button>
<button id="resolution-day" class="resolution-button">24H</button>
<button id="resolution-year" class="ml-5 resolution-button">1Y</button>
<button id="resolution-sixMonths" class="resolution-button">6M</button>
<button id="resolution-day" class="resolution-button">24H</button>
</div>
</div>
<div class="flex items-center">
<span id="load-time hidden" class="mr-2 text-sm text-gray-600 dark:text-gray-300"></span>
<span id="last-refreshed-time hidden" class="mr-2 text-sm text-gray-600 dark:text-gray-300"></span>
<span id="cache-status hidden" class="mr-4 text-sm text-gray-600 dark:text-gray-300"></span>
<span id="tor-status" class="mr-4 text-sm hidden {% if tor_established %}text-green-500{% else %}text-red-500{% endif %}"> Tor {% if tor_established %}ON{% else %}OFF{% endif %}
<span id="tor-status" class="mr-4 text-sm hidden {% if tor_established %}text-green-500{% else %}text-red-500{% endif %}">
Tor {% if tor_established %}ON{% else %}OFF{% endif %}
<a href="https://academy.particl.io/en/latest/basicswap-guides/basicswapguides_tor.html" target="_blank" rel="noopener noreferrer" class="underline">(?)</a>
</span>
</span>
<span id="next-refresh-time" class="mr-4 text-sm text-gray-600 dark:text-gray-300">
<span id="next-refresh-label"></span>
<span id="next-refresh-value"></span>
</span>
<div id="tooltip-volume" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
Toggle Coin Volume
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-volume" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">Toggle Coin Volume</div>
<button id="toggle-volume" data-tooltip-target="tooltip-volume" class="text-white font-bold py-2 px-4 rounded mr-2 focus:outline-none focus:ring-0 transition-colors duration-200" title="Toggle Volume">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z" />
</svg>
</button>
<div id="tooltip-refresh" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
Refresh Charts/Prices & Clear Cache
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-refresh" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">Refresh Charts/Prices & Clear Cache</div>
<button id="refresh-all" data-tooltip-target="tooltip-refresh" class="text-gray-600 dark:text-gray-400 font-bold py-2 px-4 rounded mr-2 focus:outline-none focus:ring-0 transition-colors duration-200" title="Refresh Data & Clear Cache">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
<circle cx="12" cy="12" r="3" />
</svg>
</button>
<div id="tooltip-auto" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
Auto Refresh Enable/Disable
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-auto" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">Auto Refresh Enable/Disable</div>
<button id="toggle-auto-refresh" data-enabled="false" data-tooltip-target="tooltip-auto" class="text-white font-bold py-2 px-4 rounded mr-2 focus:outline-none focus:ring-0 transition-colors duration-200" title="Enable Auto-Refresh">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd" />
@@ -137,18 +110,25 @@ function getWebSocketConfig() {
</div>
</div>
</div>
<div id="error-overlay" class="error-overlay hidden absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div id="error-content" class="error-content bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-2xl mx-4 relative">
<button id="close-error" class="absolute top-3 right-3 bg-red-500 text-white rounded-full p-2 hover:bg-red-600 focus:outline-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div id="error-overlay" class="notice-overlay hidden absolute inset-0 bg-opacity-30 backdrop-blur-sm flex items-center justify-center">
<div id="error-content" class="notice-content bg-white dark:bg-gray-800 rounded-xl p-6 w-full max-w-2xl mx-4 relative shadow-lg">
<div class="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-400 via-blue-500 to-blue-600"></div>
<button id="close-error" class="absolute top-3 right-3 text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-white rounded-full p-2 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors focus:outline-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<p class="text-red-600 font-semibold text-xl mb-4">Error</p>
<p id="error-message" class="text-gray-700 dark:text-gray-300 text-lg mb-6"></p>
<p class="text-sm text-gray-600 dark:text-gray-400">To review or update your Chart API Key(s), navigate to<a href="/settings" class="text-blue-500 hover:underline">Settings & Tools > Settings > General (TAB)</a>.
</p>
<div class="flex items-center gap-3 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
<p class="text-lg font-medium text-gray-900 dark:text-gray-100">Notice</p>
</div>
<p id="error-message" class="text-gray-600 dark:text-gray-300 text-base mb-6"></p>
<div class="text-sm text-gray-500 dark:text-gray-400">
Need to update your (API) settings?
<a href="/settings" class="text-blue-500 hover:text-blue-600 dark:hover:text-blue-400 font-medium ml-1 hover:underline">Go to Settings -> General</a>
</div>
</div>
</div>
</div>
@@ -157,9 +137,9 @@ function getWebSocketConfig() {
</div>
</section>
<section class="py-4 flex flex-wrap justify-center overflow-hidden container-to-blur">
<div class="container px-4 mx-auto">
<div class="flex flex-wrap justify-center -m-3" id="coin-container">
<section class="py-4 px-3 flex-wrap overflow-hidden container-to-blur">
<div class="lg:container mx-auto">
<div class="flex flex-wrap justify-center lg:justify-start xl:justify-center" id="coin-container">
{% set coin_data = {
'BTC': {'name': 'Bitcoin', 'symbol': 'BTC', 'image': 'Bitcoin.png', 'show': true},
'XMR': {'name': 'Monero', 'symbol': 'XMR', 'image': 'Monero.png', 'show': true},
@@ -192,9 +172,9 @@ function getWebSocketConfig() {
{% for coin_symbol in custom_order %}
{% if coin_symbol in display_coins and coin_data[coin_symbol]['show'] %}
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="{{ coin_symbol.lower() }}-container">
<div class="w-full sm:w-1/2 md:w-1/4 lg:w-1/5 xl:w-1/6 p-3" id="{{ coin_symbol.lower() }}-container">
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white {% if coin_symbol == 'BTC' %}active-container{% endif %}" style="min-height: 180px;">
<div class="flex items-center">
<div class="flex items-center h-10">
<img src="/static/images/coins/{{ coin_data[coin_symbol]['image'] }}" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="{{ coin_data[coin_symbol]['name'] }}">
<p class="ml-1 text-black text-sm dark:text-white">
{{ coin_data[coin_symbol]['name'] }} {% if coin_data[coin_symbol]['symbol'] != coin_data[coin_symbol]['name'] %}({{ coin_data[coin_symbol]['symbol'] }}){% endif %}
@@ -213,12 +193,13 @@ function getWebSocketConfig() {
</div>
{% if coin_symbol != 'BTC' %}
<div id="{{ coin_symbol.lower() }}-btc-price-div" class="flex items-center text-xs text-gray-600 dark:text-gray-300 mt-2 {% if coin_symbol == 'WOW' %}hidden{% endif %}">
<span class="bold mr-2">BTC:</span> <span id="{{ coin_symbol.lower() }}-price-btc"></span>
<span class="bold mr-2 ml-1">BTC:</span>
<span id="{{ coin_symbol.lower() }}-price-btc"></span>
</div>
{% endif %}
<div id="{{ coin_symbol.lower() }}-volume-div" class="flex items-center text-xs text-gray-600 dark:text-gray-300 mt-2">
<span class="bold mr-2">VOL:</span>
<div id="{{ coin_symbol.lower() }}-volume-24h"></div>
<span class="bold mr-2 ml-1">VOL:</span>
<span id="{{ coin_symbol.lower() }}-volume-24h"></span>
</div>
</div>
</div>
@@ -233,17 +214,17 @@ function getWebSocketConfig() {
<script src="/static/js/pricechart.js"></script>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
<div class="px-6 py-0 mt-5 h-full overflow-hidden">
<div class="border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="flex flex-wrap items-center justify-between">
<div class="w-full mx-auto pt-2">
<form method="post" id="filterForm">
<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="container flex flex-wrap">
<div class="md:w-auto hover-container justify-center">
<div class="flex flex-wrap justify-center">
<div class="lg:container flex flex-wrap justify-center">
<div class="md:w-auto hover-container">
<div class="flex flex-wrap">
<div class="pt-3 px-3 md:w-auto hover-container">
<div class="flex">
<button id="coin_to_button" class="bg-gray-50 text-gray-900 appearance-none w-10 dark:bg-gray-500 dark:text-white border-l border-t border-b border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-l-lg flex items-center" disabled></button>
@@ -302,14 +283,14 @@ function getWebSocketConfig() {
</div>
</div>
</div>
<div class="w-full md:w-auto pt-3 px-3">
<div class="w-full lg:w-auto pt-3 px-3">
<div class="relative">
<button type="button" id="clearFilters" class="transition-opacity duration-200 flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm hover:text-white dark:text-white dark:bg-gray-500 bg-coolGray-200 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-coolGray-200 dark:border-gray-400 rounded-md shadow-button focus:ring-0 focus:outline-none" disabled>
<span>Clear Filters</span>
</button>
</div>
</div>
<div class="w-full md:w-auto pt-3 px-3">
<div class="w-full lg:w-auto pt-3 px-3">
<div class="relative">
<button type="button" id="refreshOffers" 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">
<svg id="refreshIcon" class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
@@ -331,13 +312,10 @@ function getWebSocketConfig() {
</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="container mt-5 mx-auto px-4">
<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="px-0">
<div class="w-auto mt-6 overflow-x-auto">
<div class="w-auto mt-6 overflow-auto lg:overflow-hidden">
<table class="w-full min-w-max">
<thead class="uppercase">
<tr>
@@ -357,37 +335,29 @@ function getWebSocketConfig() {
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Details</span>
</div>
</th>
{% if sent_offers %}
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-left">
{% if sent_offers %}
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Max Recv</span>
</div>
</th>
{% else %}
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-left">
{% else %}
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Max Send</span>
{% endif %}
</div>
</th>
{% endif %}
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-center">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Swap</span>
</div>
</th>
{% if sent_offers %}
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-right">
{% if sent_offers %}
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold pr-2">Your Liq.</span>
</div>
</th>
{% else %}
<th class="p-0">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-right">
{% else %}
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold pr-2">Max Recv</span>
{% endif %}
</div>
</th>
{% endif %}
<th class="p-0" data-sortable="true" data-column-index="5">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-right flex items-center justify-end">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Rate</span>
@@ -400,10 +370,9 @@ function getWebSocketConfig() {
<span class="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-6"></span>
</div>
</th>
<th class="p-0" data-sortable="true" data-column-index="7">
<th class="p-0">
<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="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-7"></span>
</div>
</th>
</tr>
@@ -447,6 +416,5 @@ function getWebSocketConfig() {
</section>
<input type="hidden" name="formid" value="{{ form_id }}">
<script src="/static/js/offerstable.js"></script>
<script src="/static/js/offers.js"></script>
{% include 'footer.html' %}

View File

@@ -10,7 +10,6 @@
<link type="text/css" media="all" href="/static/css/libs/tailwind.min.css" rel="stylesheet">
<link type="text/css" media="all" href="/static/css/style.css" rel="stylesheet">
<script src="/static/js/main.js"></script>
<script src="/static/js/libs/flowbite.js"></script>
<script>
const isDarkMode =
localStorage.getItem('color-theme') === 'dark' ||

View File

@@ -1,36 +1,37 @@
{% include 'header.html' %}
{% from 'style.html' import select_box_arrow_svg, select_box_class, circular_arrows_svg, circular_error_svg, circular_info_svg, cross_close_svg, breadcrumb_line_svg, withdraw_svg, utxo_groups_svg, create_utxo_svg, red_cross_close_svg, blue_cross_close_svg, circular_update_messages_svg, circular_error_messages_svg %}
<script src="/static/js/libs//qrcode.js"></script>
<div class="container mx-auto">
<section class="p-5 mt-5">
<div class="container mx-auto">
<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="/">Home</a></li>
<li><a class="flex font-medium text-md lg:text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/">Home</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="/wallets">Wallets</a></li>
<li><a class="flex font-medium text-md lg:text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/wallets">Wallets</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="/wallet/{{ w.ticker }}">{{ w.ticker }}</a></li>
<li><a class="flex font-medium text-md lg:text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/wallet/{{ w.ticker }}">{{ w.ticker }}</a></li>
</ul>
</div>
</div>
</div>
</section>
<section class="py-4">
<div class="container px-4 mx-auto">
<section class="py-4 px-6">
<div class="lg:container 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 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="dots-red"> <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="wave">
<div class="relative z-20 flex flex-wrap items-center -m-3">
<div class="w-full md:w-1/2">
<h2 class="text-3xl font-bold text-white"> <span class="inline-block align-middle"><img class="mr-2 h-16" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"></span>({{ w.ticker }}) {{ w.name }} Wallet </h2>
</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"> <a 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" id="refresh" href="/wallet/{{ w.ticker }}"> {{ circular_arrows_svg | safe }}<span>Refresh</span> </a> </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"> <a class="rounded-full mr-5 flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg: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" id="refresh" href="/wallet/{{ w.ticker }}"> {{ circular_arrows_svg | safe }}<span>Refresh</span> </a> </div>
</div>
</div>
</div>
</section>
{% include 'inc_messages.html' %}
{% if w.updating %}
<section class="py-4" id="messages_updating" role="alert">
<div class="container px-4 mx-auto">
<section class="py-4 px-6" id="messages_updating" role="alert">
<div class="lg:container mx-auto">
<div class="p-6 text-green-800 rounded-lg bg-blue-50 border border-blue-500 dark:bg-gray-500 dark:text-blue-400 rounded-md">
<div class="flex flex-wrap justify-between items-center -m-2">
<div class="flex-1 p-2">
@@ -39,8 +40,8 @@
{{ circular_update_messages_svg | safe }}
</div>
<ul class="ml-4 mt-1">
<li class="font-semibold text-sm text-blue-500 error_msg"><span class="bold">UPDATING:</span></li>
<li class="font-medium text-sm text-blue-500">Please wait...</li>
<li class="font-semibold text-lg lg:text-sm text-blue-500 error_msg"><span class="bold">UPDATING:</span></li>
<li class="font-medium text-lg lg:text-sm text-blue-500">Please wait...</li>
</ul>
</div>
</div>
@@ -56,8 +57,8 @@
{% endif %}
{% if w.havedata %}
{% if w.error %}
<section class="py-4" id="messages_error" role="alert">
<div class="container px-4 mx-auto">
<section class="py-4 px-6" id="messages_error" role="alert">
<div class="lg:container mx-auto">
<div class="p-6 text-green-800 rounded-lg bg-red-50 border border-red-400 dark:bg-gray-500 dark:text-red-400 rounded-md">
<div class="flex flex-wrap justify-between items-center -m-2">
<div class="flex-1 p-2">
@@ -66,8 +67,8 @@
{{ circular_error_messages_svg | safe }}
</div>
<ul class="ml-4 mt-1">
<li class="font-semibold text-sm text-red-500 error_msg"><span class="bold">ERROR:</span></li>
<li class="font-medium text-sm text-red-500 error_msg">{{ w.error }}</li>
<li class="font-semibold text-lg lg:text-sm text-red-500 error_msg"><span class="bold">ERROR:</span></li>
<li class="font-medium text-lg lg:text-sm text-red-500 error_msg">{{ w.error }}</li>
</ul>
</div>
</div>
@@ -83,8 +84,8 @@
</section>
{% else %}
{% if w.cid == '18' %} {# DOGE #}
<section class="py-4" id="messages_notice">
<div class="container px-4 mx-auto">
<section class="py-4 px-6" id="messages_notice">
<div class="lg:container mx-auto">
<div class="p-6 rounded-lg bg-coolGray-100 dark:bg-gray-500 shadow-sm">
<div class="flex items-start">
<svg class="w-6 h-6 text-blue-500 mt-1 mr-3 flex-shrink-0" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"></path></svg>
@@ -101,24 +102,24 @@
</div>
</section>
{% endif %}
<form method="post" autocomplete="off">
<section>
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
<section>
<form method="post" autocomplete="off">
<div class="px-6 py-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 pt-2">
<div class="container mt-5 mx-auto">
<div class="lg:container mt-5 mx-auto">
<div class="pt-6 pb-8 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 text-sm">
<div class="w-full pb-6 overflow-x-auto">
<table class="w-full text-lg lg:text-sm">
<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">Wallet</span> </div>
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Wallet</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>
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Details</span> </div>
</th>
</tr>
</thead>
@@ -129,7 +130,8 @@
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.pending }} {{ w.ticker }} </span>
{% endif %}
</td>
</tr> {% if w.cid == '1' %} {# PART #}
</tr>
{% if w.cid == '1' %} {# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Blind"> </span>Blind Balance: </td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }} (<span class="usd-value"></span>)
@@ -147,11 +149,10 @@
</td>
<td class="usd-value"></td>
</tr>
{% endif %}
{# / PART #}
{% if w.cid == '3' %} {# LTC #}
{% elif w.cid == '3' %} {# LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold"> <span class="pr-3 inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} MWEB"> </span>MWEB Balance: </td>
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} MWEB"> </span>MWEB Balance: </td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }} (<span class="usd-value"></span>)
{% if w.mweb_pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }} </span>
@@ -163,7 +164,7 @@
{% if w.locked_utxos %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Locked Outputs:</td>
<td id='locked_utxos' class="py-3 px-6">{{ w.locked_utxos }}</td>
<td id="locked_utxos" class="py-3 px-6">{{ w.locked_utxos }}</td>
</tr>
{% endif %}
{# / locked_utxos #}
@@ -176,7 +177,7 @@
<td class="py-3 px-6">{{ w.version }}</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Blocks:</td>
<td class="py-3 px-6 bold">Blockheight:</td>
<td class="py-3 px-6">{{ w.blocks }}
{% if w.known_block_count %} / {{ w.known_block_count }}
{% endif %}
@@ -206,10 +207,12 @@
</tr>
{% endif %}
{# / encrypted #}
{% if w.expected_seed != true %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Expected Seed:</td>
<td class="py-3 px-6">{{ w.expected_seed }}</td>
</tr>
{% endif %}
</table>
</div>
</div>
@@ -221,16 +224,16 @@
</div>
</section>
{% if block_unknown_seeds and w.expected_seed != true %} {# Only show addresses if wallet seed is correct #}
<section class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
<section class="px-6 py-0 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 pt-2">
<div class="container mt-5 mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="lg:container mt-5 mx-auto">
<div class="py-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6">
{% if w.cid != '4' %} {# DCR #}
<div class="flex flex-wrap justify-end">
<div class="w-full md:w-auto p-1.5"> <input class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none cursor-pointer" type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"> </div>
<div class="w-full md:w-auto p-1.5"> <input class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-lg lg:text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none cursor-pointer" type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"> </div>
</div>
{% endif %}
</div>
@@ -240,92 +243,57 @@
</div>
</div>
</section>
{% else %}
<input type="hidden" name="formid" value="{{ form_id }}">
</form>
{% else %}
<section class="p-6">
<div class="lg:container mx-auto">
<div class="flex items-center">
<h4 class="font-semibold text-2xl text-black dark:text-white">Deposit</h4>
</div>
</div>
</section>
<form method="post" autocomplete="off">
<section>
<div class="pl-6 pr-6 pt-0 pb-0 overflow-hidden">
<div class="px-6 py-0 overflow-hidden">
<div class="pb-6 border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mt-5 mx-auto">
<div class="lg:container mt-5 mx-auto">
<div class="pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6">
<div class="container mx-auto">
<div class="flex flex-wrap max-w-7xl mx-auto justify-center -m-3">
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
<div class="flex flex-wrap max-w-7xl mx-auto justify-center -m-3">
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<div id="qrcode-monero-main" class="qrcode"></div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Main Address: </div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Main Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-monero-main" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="monero_main_address">{{ w.main_address }}</div>
<div data-tooltip-target="tooltip-copy-monero-main" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="monero_main_address">{{ w.main_address }}</div>
</div>
<div id="tooltip-copy-monero-main" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</div>
</div>
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
<div id="qrcode-monero-sub" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Sub Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-monero-sub" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="monero_sub_address">{{ w.deposit_address }}</div>
</div>
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
<div class="py-3 px-6 bold mt-5">
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newaddr_{{ w.cid }}" value="New Subaddress"> {{ circular_arrows_svg }} New {{ w.name }} Deposit Address</button>
</div>
</div>
<div id="tooltip-copy-monero-sub" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</div>
</div>
{% else %}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
<div id="tooltip-copy-monero-main" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{% else %}
<div id="qrcode-deposit" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Deposit Address: </div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Deposit Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-default" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="main_deposit_address">{{ w.deposit_address }}</div>
<div data-tooltip-target="tooltip-copy-default" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="main_deposit_address">{{ w.deposit_address }}</div>
</div>
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
<div class="py-3 px-6 bold mt-5">
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newaddr_{{ w.cid }}" value="New Deposit Address"> {{ circular_arrows_svg }} New {{ w.name }} Deposit Address </button>
</div>
</div>
<div id="tooltip-copy-default" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<div id="tooltip-copy-default" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{% endif %}
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
@@ -333,62 +301,67 @@
</div>
</div>
</div>
{% endif %}
{% if w.cid == '1' %} {# PART #}
{% if w.cid in '1, 3, 6, 9' %}
{# PART | LTC | XMR | WOW | #}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<div id="qrcode-monero-sub" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Subaddress: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-monero-sub" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="monero_sub_address">{{ w.deposit_address }}</div>
</div>
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
<div class="py-3 px-6 bold mt-5">
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newaddr_{{ w.cid }}" value="New Subaddress"> {{ circular_arrows_svg }} New {{ w.name }} Deposit Address</button>
</div>
</div>
<div id="tooltip-copy-monero-sub" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{% elif w.cid == '1' %}
{# PART #}
<div id="qrcode-stealth" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Stealth Address: </div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Stealth Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-particl-stealth" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-10 focus:ring-0" id="stealth_address"> {{ w.stealth_address }}
<div data-tooltip-target="tooltip-copy-particl-stealth" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-10 focus:ring-0" id="stealth_address"> {{ w.stealth_address }}
</div>
</div>
<div id="tooltip-copy-particl-stealth" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</div>
</div>
{# / PART #}
{% elif w.cid == '3' %}
{# LTC #}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
<div id="tooltip-copy-particl-stealth" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{# / PART #}
{% elif w.cid == '3' %}
{# LTC #}
<div id="qrcode-mweb" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} MWEB Address: </div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">MWEB Address: </div>
<div class="text-center relative">
<div data-tooltip-target="tooltip-copy-litecoin-mweb" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="stealth_address">{{ w.mweb_address }}</div>
<div data-tooltip-target="tooltip-copy-litecoin-mweb" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="stealth_address">{{ w.mweb_address }}</div>
<span class="absolute inset-y-0 right-0 flex items-center pr-3 cursor-pointer" id="copyIcon"></span>
</div>
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
<div class="py-3 px-6 bold mt-5">
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newmwebaddr_{{ w.cid }}" value="New MWEB Address"> {{ circular_arrows_svg }} New MWEB Address </button>
</div>
<div id="tooltip-copy-litecoin-mweb" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
</div>
<div id="tooltip-copy-litecoin-mweb" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{# / LTC #}
{% endif %}
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{# / LTC #}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
@@ -410,8 +383,7 @@
correctLevel: QRCode.CorrectLevel.L
});
</script>
{% endif %}
{% if w.cid == '3' %}
{% elif w.cid == '3' %}
{# LTC #}
<script>
// Litecoin MWEB
@@ -529,61 +501,67 @@ document.addEventListener('DOMContentLoaded', function() {
});
</script>
<section class="p-6">
<div class="lg:container mx-auto">
<div class="flex items-center">
<h4 class="font-semibold text-2xl text-black dark:text-white">Withdraw</h4>
</div>
</section>
</div>
</section>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
<div class="px-6 py-0 h-full overflow-hidden">
<div class="border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mt-5 mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="lg:container mt-5 mx-auto">
<div class="py-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 text-sm">
<div class="w-full pb-6 overflow-x-auto">
<table class="w-full text-lg lg:text-sm">
<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">Options</span> </div>
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Options</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">Input</span> </div>
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Input</span> </div>
</th>
</tr>
</thead>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span> {{ w.name }} Balance: </td>
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }} </td>
</tr>
{% if w.cid == '3' %}
{# LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span> {{ w.name }} MWEB Balance: </td>
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>MWEB Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }} </td>
{% endif %}
{% if w.cid == '1' %}
</tr>
{% elif w.cid == '1' %}
{# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span> {{ w.name }} Blind Balance: </td>
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Blind Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }} </td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span> {{ w.name }} Anon Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }} </td> {% endif %}
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Anon Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }} </td>
</tr>
{% endif %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> {{ w.name }} Address: </td>
<td class="py-3 px-6"> <input placeholder="{{ w.ticker }} Address" 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-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="to_{{ w.cid }}" value="{{ w.wd_address }}"> </td>
<td class="py-3 px-6"> <input placeholder="{{ w.ticker }} Address" 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-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="to_{{ w.cid }}" value="{{ w.wd_address }}"> </td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> {{ w.name }} Amount:
<td class="py-3 px-6">
<div class="flex"> <input placeholder="{{ w.ticker }} Amount" 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-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="amount" name="amt_{{ w.cid }}" value="{{ w.wd_value }}">
<div class="flex"> <input placeholder="{{ w.ticker }} Amount" 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-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="amount" name="amt_{{ w.cid }}" value="{{ w.wd_value }}">
<div class="ml-2 flex">
{% if w.cid == '1' %}
{# PART #}
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">25%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">100%</button>
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">25%</button>
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">100%</button>
<script>
function setAmount(percent, balance, cid, blindBalance, anonBalance) {
var amountInput = document.getElementById('amount');
@@ -618,9 +596,9 @@ document.addEventListener('DOMContentLoaded', function() {
{# / PART #}
{% elif w.cid == '3' %}
{# LTC #}
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">25%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">100%</button>
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">25%</button>
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">100%</button>
<script>
function setAmount(percent, balance, cid, mwebBalance) {
var amountInput = document.getElementById('amount');
@@ -651,9 +629,9 @@ document.addEventListener('DOMContentLoaded', function() {
</script>
{# / LTC #}
{% else %}
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }})">25%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }})">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }})">100%</button>
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }})">25%</button>
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }})">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }})">100%</button>
<script>
function setAmount(percent, balance, cid) {
var amountInput = document.getElementById('amount');
@@ -700,19 +678,21 @@ document.addEventListener('DOMContentLoaded', function() {
</script>
{% endif %}
</div>
</div>
</div>
</td>
</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
{% if w.cid in '6, 9' %} {# XMR | WOW #}
<td class="py-3 px-6 bold">Sweep All:</td>
<td class="py-3 px-6">
<input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="sweepall" name="sweepall_{{ w.cid }}" {% if w.wd_sweepall==true %} checked=checked{% endif %}>
<td class="hidden py-3 px-6 bold">Sweep All:</td>
<td class="hidden py-3 px-6">
<input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="sweepall" name="sweepall_{{ w.cid }}" {% if w.wd_sweepall==true %} checked="checked"{% endif %}>
</td>
{% else %}
<td class="py-3 px-6 bold">Subtract Fee:</td>
<td class="py-3 px-6">
<input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" name="subfee_{{ w.cid }}" {% if w.wd_subfee==true %} checked=checked{% endif %}>
<input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" name="subfee_{{ w.cid }}" {% if w.wd_subfee==true %} checked="checked"{% endif %}>
</td>
{% endif %}
<td>
@@ -746,9 +726,8 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</td>
</tr>
{% endif %}
{# / PART #}
{% if w.cid == '3' %} {# LTC #}
{% elif w.cid == '3' %} {# LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Type From:</td>
<td class="py-3 px-6">
@@ -762,14 +741,16 @@ document.addEventListener('DOMContentLoaded', function() {
</tr>
{% endif %}
{# / LTC #}
{% if w.cid not in '6,9' %} {# Not XMR WOW #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Fee Rate:</td>
<td class="py-3 px-6">{{ w.fee_rate }}</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Estimate Fee:</td>
<td class="py-3 px-6 bold">Fee Estimate:</td>
<td class="py-3 px-6"> {{ w.est_fee }} </td>
</tr>
{% endif %}
</table>
</div>
</div>
@@ -781,25 +762,23 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</section>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden ">
<div class="px-6 py-0 h-full overflow-hidden">
<div class="pb-6 ">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
<div class="lg:container mx-auto">
<div class="py-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
<div class="px-6">
<div class="flex flex-wrap justify-end">
{% if w.cid not in '6, 9' %}
{# !XMR | WOW #}
{% if w.show_utxo_groups %}
{% else %}
<div class="w-full md:w-auto p-1.5"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="showutxogroups" name="showutxogroups" value="Show UTXO Groups"> {{ utxo_groups_svg | safe }} Show UTXO Groups </button> </div>
{% endif %} {% endif %}
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<div class="w-full md:w-auto p-1.5 ml-2"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="estfee_{{ w.cid }}" value="Estimate Fee">Estimate {{ w.ticker }} Fee </button> </div>
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="estfee_{{ w.cid }}" value="Estimate Fee">Estimate {{ w.ticker }} Fee </button> </div>
{# / XMR | WOW #}
{% elif w.show_utxo_groups %}
{% else %}
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="showutxogroups" name="showutxogroups" value="Show UTXO Groups"> {{ utxo_groups_svg | safe }} Show UTXO Groups </button> </div>
{% endif %}
{# / XMR | WOW #} <div class="w-full md:w-auto p-1.5 ml-2"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="withdraw_{{ w.cid }}" value="Withdraw" onclick="return confirmWithdrawal();">{{ withdraw_svg | safe }} Withdraw {{ w.ticker }} </div>
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="withdraw_{{ w.cid }}" value="Withdraw" onclick="return confirmWithdrawal();">{{ withdraw_svg | safe }} Withdraw {{ w.ticker }} </button></div>
</div>
</div>
</div>
@@ -807,42 +786,43 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</div>
</div>
</div>
</section>
{% if w.cid not in '6, 9' %}
{# !XMR | WOW #}
{% if w.show_utxo_groups %}
<section class="p-6">
<div class="lg:container mx-auto">
<div class="flex items-center">
<h4 class="font-semibold text-2xl text-black dark:text-white">UTXO Groups</h4>
</div>
</div>
</section>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
<div class="px-6 py-0 h-full overflow-hidden">
<div class="border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mt-5 mx-auto">
<div class="lg:container mt-5 mx-auto">
<div class="pt-6 pb-8 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 text-sm">
<div class="w-full pb-6 overflow-x-auto">
<table class="w-full text-lg lg:text-sm">
<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">Options</span> </div>
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Options</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 p-10"></span> </div>
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold p-10"></span> </div>
</th>
</tr>
</thead>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 w-1/4 bold">UTXO Groups:</td>
<td class="py-3 px-6"> <textarea 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" id="tx_view" rows="10" readonly>{{ w.utxo_groups }} </textarea> </td>
<td class="py-3 px-6"> <textarea 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-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="tx_view" rows="10" readonly>{{ w.utxo_groups }} </textarea> </td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="create_utxo" name="create_utxo" value="Create UTXO" onclick="return confirmUTXOResize();"> {{ create_utxo_svg | safe }}Create UTXO </button> </td>
<td class="py-3 px-6"> <input placeholder="Amount" 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-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="utxo_value" value="{{ w.utxo_value }}"> </td>
<td class="py-3 px-6"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="create_utxo" name="create_utxo" value="Create UTXO" onclick="return confirmUTXOResize();"> {{ create_utxo_svg | safe }}Create UTXO </button> </td>
<td class="py-3 px-6"> <input placeholder="Amount" 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-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="utxo_value" value="{{ w.utxo_value }}"> </td>
</tr>
</table>
</div>
@@ -855,14 +835,14 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</section>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden ">
<div class="px-6 py-0 h-full overflow-hidden ">
<div class="pb-6 ">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
<div class="lg:container mx-auto">
<div class="py-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
<div class="px-6">
<div class="flex flex-wrap justify-end"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="closeutxogroups" name="closeutxogroups" value="Close UTXO Groups"> {{ utxo_groups_svg | safe }} Close UTXO Groups </button> </div>
<div class="flex flex-wrap justify-end"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="closeutxogroups" name="closeutxogroups" value="Close UTXO Groups"> {{ utxo_groups_svg | safe }} Close UTXO Groups </button> </div>
</div>
</div>
</div>
@@ -876,7 +856,6 @@ document.addEventListener('DOMContentLoaded', function() {
{% endif %}
{% endif %}
{% endif %}
{% endif %}
<input type="hidden" name="formid" value="{{ form_id }}">
</form>
<script>

View File

@@ -9,12 +9,14 @@ from .util import (
PAGE_LIMIT,
describeBid,
get_data_entry,
have_data_entry,
get_data_entry_or,
have_data_entry,
listAvailableCoins,
listBidActions,
listBidStates,
listOldBidStates,
set_pagination_filters,
setCoinFilter,
)
from basicswap.util import (
ensure,
@@ -149,13 +151,12 @@ def page_bid(self, url_split, post_string):
)
def page_bids(
self, url_split, post_string, sent=False, available=False, received=False
):
def page_bids(self, url_split, post_string, sent=False, available=False, received=False):
server = self.server
swap_client = server.swap_client
swap_client.checkSystemStatus()
summary = swap_client.getSummary()
filter_key = "page_available_bids" if available else "page_bids"
filters = {
"page_no": 1,
@@ -164,31 +165,25 @@ def page_bids(
"limit": PAGE_LIMIT,
"sort_by": "created_at",
"sort_dir": "desc",
"coin_from": -1,
"coin_to": -1,
}
if available:
filters["bid_state_ind"] = BidStates.BID_RECEIVED
filters["with_expired"] = False
filter_prefix = (
"page_bids_sent"
if sent
else "page_bids_available" if available else "page_bids_received"
)
messages = []
form_data = self.checkForm(post_string, "bids", messages)
if form_data:
if have_data_entry(form_data, "clearfilters"):
swap_client.clearFilters(filter_prefix)
swap_client.clearFilters(filter_key)
else:
filters["coin_from"] = setCoinFilter(form_data, "coin_from")
filters["coin_to"] = setCoinFilter(form_data, "coin_to")
if have_data_entry(form_data, "sort_by"):
sort_by = get_data_entry(form_data, "sort_by")
ensure(
sort_by
in [
"created_at",
],
"Invalid sort by",
)
ensure(sort_by in ["created_at"], "Invalid sort by")
filters["sort_by"] = sort_by
if have_data_entry(form_data, "sort_dir"):
sort_dir = get_data_entry(form_data, "sort_dir")
@@ -199,7 +194,7 @@ def page_bids(
if state_ind != -1:
try:
_ = BidStates(state_ind)
except Exception as e: # noqa: F841
except Exception:
raise ValueError("Invalid state")
filters["bid_state_ind"] = state_ind
if have_data_entry(form_data, "with_expired"):
@@ -208,38 +203,64 @@ def page_bids(
set_pagination_filters(form_data, filters)
if have_data_entry(form_data, "applyfilters"):
swap_client.setFilters(filter_prefix, filters)
swap_client.setFilters(filter_key, filters)
else:
saved_filters = swap_client.getFilters(filter_prefix)
saved_filters = swap_client.getFilters(filter_key)
if saved_filters:
filters.update(saved_filters)
bids = swap_client.listBids(sent=sent, filters=filters)
page_data = {
"bid_states": listBidStates(),
}
coins_from, coins_to = listAvailableCoins(swap_client, split_from=True)
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,
"coins_from": coins_from,
"coins": coins_to,
"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")
return self.render_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,
"filters": filters,
"data": page_data,
"summary": summary,
"bids": [
"filter_key": filter_key,
"coins_from": coins_from,
"coins": coins_to,
"sent_bids": [
(
format_timestamp(b[0]),
b[2].hex(),
@@ -249,8 +270,22 @@ def page_bids(
strTxState(b[8]),
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),
},
)

View File

@@ -131,9 +131,9 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
parsed_data["amt_bid_min"] < 0
or parsed_data["amt_bid_min"] > parsed_data["amt_from"]
):
errors.append("Minimum Bid Amount out of range")
errors.append("Minimum Purchase Quantity out of range")
except Exception:
errors.append("Minimum Bid Amount")
errors.append("Minimum Purchase Quantity")
if have_data_entry(form_data, "rate") and not have_data_entry(form_data, "amt_to"):
parsed_data["rate"] = ci_to.make_int(form_data["rate"], r=1)

View File

@@ -122,8 +122,30 @@ def set_pagination_filters(form_data, filters):
filters["page_no"] = 1
elif form_data and have_data_entry(form_data, "pageforwards"):
filters["page_no"] = int(form_data[b"pageno"][0]) + 1
if filters["page_no"] > 1:
filters["offset"] = (filters["page_no"] - 1) * PAGE_LIMIT
no_limit = False
if form_data:
if "is_json" in form_data:
no_limit = form_data.get("no_limit", False)
else:
no_limit = b"no_limit" in form_data
if no_limit:
filters["offset"] = 0
filters["limit"] = None
else:
if filters["page_no"] > 1:
filters["offset"] = (filters["page_no"] - 1) * PAGE_LIMIT
filters["limit"] = PAGE_LIMIT
def get_data_with_pagination(data, filters):
if filters.get("limit") is None:
return data
offset = filters.get("offset", 0)
limit = filters.get("limit", PAGE_LIMIT)
return data[offset:offset + limit]
def getTxIdHex(bid, tx_type, suffix):

View File

@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2024 tecnovert
# Copyright (c) 2022-2025 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from hashlib import sha256 as hashlib_sha256 # hashlib is faster than pycryptodome
from basicswap.contrib.blake256.blake256 import blake_hash
from Crypto.Hash import HMAC, RIPEMD160, SHA256, SHA512 # pycryptodome
from Crypto.Hash import HMAC, RIPEMD160, SHA512 # pycryptodome
def sha256(data: bytes) -> bytes:
h = SHA256.new()
h = hashlib_sha256()
h.update(data)
return h.digest()

42
basicswap/util/logging.py Normal file
View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import logging
from basicswap.util.crypto import (
sha256,
)
class BSXLogger(logging.Logger):
def __init__(self, name):
super().__init__(name)
self.safe_logs = False
self.safe_logs_prefix = b""
def addr(self, addr: str) -> str:
if self.safe_logs:
return (
"A_"
+ sha256(self.safe_logs_prefix + addr.encode(encoding="utf-8"))[
:8
].hex()
)
return addr
def id(self, concept_id: bytes, prefix: str = "") -> str:
if concept_id is None:
return prefix + "None"
if isinstance(concept_id, str):
concept_id = bytes.fromhex(concept_id)
if self.safe_logs:
return (prefix if len(prefix) > 0 else "_") + sha256(
self.safe_logs_prefix + concept_id
)[:8].hex()
return prefix + concept_id.hex()
def info_s(self, msg, *args, **kwargs):
if self.safe_logs is False:
self.info(msg, *args, **kwargs)

View File

@@ -1,5 +1,5 @@
pyzmq==26.2.0
python-gnupg==0.5.3
pyzmq==26.2.1
python-gnupg==0.5.4
Jinja2==3.1.5
pycryptodome==3.21.0
PySocks==1.7.1

View File

@@ -190,118 +190,118 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via -r requirements.in
python-gnupg==0.5.3 \
--hash=sha256:290d8ddb9cd63df96cfe9284b9b265f19fd6e145e5582dc58fd7271f026d0a47 \
--hash=sha256:2f8a4c6f63766feca6cc1416408f8b84e1b914fe7b54514e570fc5cbe92e9248
python-gnupg==0.5.4 \
--hash=sha256:40ce25cde9df29af91fe931ce9df3ce544e14a37f62b13ca878c897217b2de6c \
--hash=sha256:f2fdb5fb29615c77c2743e1cb3d9314353a6e87b10c37d238d91ae1c6feae086
# via -r requirements.in
pyzmq==26.2.0 \
--hash=sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6 \
--hash=sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a \
--hash=sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9 \
--hash=sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f \
--hash=sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37 \
--hash=sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc \
--hash=sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed \
--hash=sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097 \
--hash=sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d \
--hash=sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52 \
--hash=sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6 \
--hash=sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6 \
--hash=sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2 \
--hash=sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282 \
--hash=sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3 \
--hash=sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732 \
--hash=sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5 \
--hash=sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18 \
--hash=sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306 \
--hash=sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f \
--hash=sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3 \
--hash=sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b \
--hash=sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277 \
--hash=sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a \
--hash=sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797 \
--hash=sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca \
--hash=sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c \
--hash=sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f \
--hash=sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5 \
--hash=sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a \
--hash=sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44 \
--hash=sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20 \
--hash=sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4 \
--hash=sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8 \
--hash=sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780 \
--hash=sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386 \
--hash=sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5 \
--hash=sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2 \
--hash=sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0 \
--hash=sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971 \
--hash=sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b \
--hash=sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50 \
--hash=sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c \
--hash=sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f \
--hash=sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231 \
--hash=sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c \
--hash=sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08 \
--hash=sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5 \
--hash=sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6 \
--hash=sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073 \
--hash=sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e \
--hash=sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4 \
--hash=sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317 \
--hash=sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3 \
--hash=sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072 \
--hash=sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad \
--hash=sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a \
--hash=sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb \
--hash=sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd \
--hash=sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f \
--hash=sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef \
--hash=sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5 \
--hash=sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187 \
--hash=sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711 \
--hash=sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988 \
--hash=sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640 \
--hash=sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c \
--hash=sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764 \
--hash=sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1 \
--hash=sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1 \
--hash=sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289 \
--hash=sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb \
--hash=sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a \
--hash=sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218 \
--hash=sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c \
--hash=sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf \
--hash=sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7 \
--hash=sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8 \
--hash=sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726 \
--hash=sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9 \
--hash=sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93 \
--hash=sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88 \
--hash=sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115 \
--hash=sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6 \
--hash=sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672 \
--hash=sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2 \
--hash=sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea \
--hash=sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc \
--hash=sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b \
--hash=sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa \
--hash=sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003 \
--hash=sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797 \
--hash=sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940 \
--hash=sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db \
--hash=sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc \
--hash=sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27 \
--hash=sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3 \
--hash=sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e \
--hash=sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98 \
--hash=sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b \
--hash=sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629 \
--hash=sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9 \
--hash=sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6 \
--hash=sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec \
--hash=sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951 \
--hash=sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae \
--hash=sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4 \
--hash=sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6 \
--hash=sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919
pyzmq==26.2.1 \
--hash=sha256:000760e374d6f9d1a3478a42ed0c98604de68c9e94507e5452951e598ebecfba \
--hash=sha256:004837cb958988c75d8042f5dac19a881f3d9b3b75b2f574055e22573745f841 \
--hash=sha256:0250c94561f388db51fd0213cdccbd0b9ef50fd3c57ce1ac937bf3034d92d72e \
--hash=sha256:03719e424150c6395b9513f53a5faadcc1ce4b92abdf68987f55900462ac7eec \
--hash=sha256:0995fd3530f2e89d6b69a2202e340bbada3191014352af978fa795cb7a446331 \
--hash=sha256:099b56ef464bc355b14381f13355542e452619abb4c1e57a534b15a106bf8e23 \
--hash=sha256:09dac387ce62d69bec3f06d51610ca1d660e7849eb45f68e38e7f5cf1f49cbcb \
--hash=sha256:0b2007f28ce1b8acebdf4812c1aab997a22e57d6a73b5f318b708ef9bcabbe95 \
--hash=sha256:0b6a93d684278ad865fc0b9e89fe33f6ea72d36da0e842143891278ff7fd89c3 \
--hash=sha256:0f50db737d688e96ad2a083ad2b453e22865e7e19c7f17d17df416e91ddf67eb \
--hash=sha256:100a826a029c8ef3d77a1d4c97cbd6e867057b5806a7276f2bac1179f893d3bf \
--hash=sha256:1238c2448c58b9c8d6565579393148414a42488a5f916b3f322742e561f6ae0d \
--hash=sha256:160194d1034902937359c26ccfa4e276abffc94937e73add99d9471e9f555dd6 \
--hash=sha256:17d72a74e5e9ff3829deb72897a175333d3ef5b5413948cae3cf7ebf0b02ecca \
--hash=sha256:17f88622b848805d3f6427ce1ad5a2aa3cf61f12a97e684dab2979802024d460 \
--hash=sha256:1c6ae0e95d0a4b0cfe30f648a18e764352d5415279bdf34424decb33e79935b8 \
--hash=sha256:1c84c1297ff9f1cd2440da4d57237cb74be21fdfe7d01a10810acba04e79371a \
--hash=sha256:1fd4b3efc6f62199886440d5e27dd3ccbcb98dfddf330e7396f1ff421bfbb3c2 \
--hash=sha256:25e720dba5b3a3bb2ad0ad5d33440babd1b03438a7a5220511d0c8fa677e102e \
--hash=sha256:269c14904da971cb5f013100d1aaedb27c0a246728c341d5d61ddd03f463f2f3 \
--hash=sha256:290c96f479504439b6129a94cefd67a174b68ace8a8e3f551b2239a64cfa131a \
--hash=sha256:2d88ba221a07fc2c5581565f1d0fe8038c15711ae79b80d9462e080a1ac30435 \
--hash=sha256:2e1eb9d2bfdf5b4e21165b553a81b2c3bd5be06eeddcc4e08e9692156d21f1f6 \
--hash=sha256:31fff709fef3b991cfe7189d2cfe0c413a1d0e82800a182cfa0c2e3668cd450f \
--hash=sha256:361edfa350e3be1f987e592e834594422338d7174364763b7d3de5b0995b16f3 \
--hash=sha256:36d4e7307db7c847fe37413f333027d31c11d5e6b3bacbb5022661ac635942ba \
--hash=sha256:36ee4297d9e4b34b5dc1dd7ab5d5ea2cbba8511517ef44104d2915a917a56dc8 \
--hash=sha256:380816d298aed32b1a97b4973a4865ef3be402a2e760204509b52b6de79d755d \
--hash=sha256:3ef584f13820d2629326fe20cc04069c21c5557d84c26e277cfa6235e523b10f \
--hash=sha256:3fe6e28a8856aea808715f7a4fc11f682b9d29cac5d6262dd8fe4f98edc12d53 \
--hash=sha256:44dba28c34ce527cf687156c81f82bf1e51f047838d5964f6840fd87dfecf9fe \
--hash=sha256:45fad32448fd214fbe60030aa92f97e64a7140b624290834cc9b27b3a11f9473 \
--hash=sha256:46d4ebafc27081a7f73a0f151d0c38d4291656aa134344ec1f3d0199ebfbb6d4 \
--hash=sha256:49135bb327fca159262d8fd14aa1f4a919fe071b04ed08db4c7c37d2f0647162 \
--hash=sha256:4a98898fdce380c51cc3e38ebc9aa33ae1e078193f4dc641c047f88b8c690c9a \
--hash=sha256:4eb3197f694dfb0ee6af29ef14a35f30ae94ff67c02076eef8125e2d98963cd0 \
--hash=sha256:51431f6b2750eb9b9d2b2952d3cc9b15d0215e1b8f37b7a3239744d9b487325d \
--hash=sha256:574b285150afdbf0a0424dddf7ef9a0d183988eb8d22feacb7160f7515e032cb \
--hash=sha256:57dd4d91b38fa4348e237a9388b4423b24ce9c1695bbd4ba5a3eada491e09399 \
--hash=sha256:59660e15c797a3b7a571c39f8e0b62a1f385f98ae277dfe95ca7eaf05b5a0f12 \
--hash=sha256:5b4fc44f5360784cc02392f14235049665caaf7c0fe0b04d313e763d3338e463 \
--hash=sha256:632a09c6d8af17b678d84df442e9c3ad8e4949c109e48a72f805b22506c4afa7 \
--hash=sha256:637536c07d2fb6a354988b2dd1d00d02eb5dd443f4bbee021ba30881af1c28aa \
--hash=sha256:651726f37fcbce9f8dd2a6dab0f024807929780621890a4dc0c75432636871be \
--hash=sha256:6991ee6c43e0480deb1b45d0c7c2bac124a6540cba7db4c36345e8e092da47ce \
--hash=sha256:6d75fcb00a1537f8b0c0bb05322bc7e35966148ffc3e0362f0369e44a4a1de99 \
--hash=sha256:70b3a46ecd9296e725ccafc17d732bfc3cdab850b54bd913f843a0a54dfb2c04 \
--hash=sha256:786dd8a81b969c2081b31b17b326d3a499ddd1856e06d6d79ad41011a25148da \
--hash=sha256:7c6160fe513654e65665332740f63de29ce0d165e053c0c14a161fa60dd0da01 \
--hash=sha256:7ebdd96bd637fd426d60e86a29ec14b8c1ab64b8d972f6a020baf08a30d1cf46 \
--hash=sha256:7f18ce33f422d119b13c1363ed4cce245b342b2c5cbbb76753eabf6aa6f69c7d \
--hash=sha256:80a00370a2ef2159c310e662c7c0f2d030f437f35f478bb8b2f70abd07e26b24 \
--hash=sha256:817fcd3344d2a0b28622722b98500ae9c8bfee0f825b8450932ff19c0b15bebd \
--hash=sha256:8531ed35dfd1dd2af95f5d02afd6545e8650eedbf8c3d244a554cf47d8924459 \
--hash=sha256:866c12b7c90dd3a86983df7855c6f12f9407c8684db6aa3890fc8027462bda82 \
--hash=sha256:88812b3b257f80444a986b3596e5ea5c4d4ed4276d2b85c153a6fbc5ca457ae7 \
--hash=sha256:8b0f5bab40a16e708e78a0c6ee2425d27e1a5d8135c7a203b4e977cee37eb4aa \
--hash=sha256:8bacc1a10c150d58e8a9ee2b2037a70f8d903107e0f0b6e079bf494f2d09c091 \
--hash=sha256:8ec8e3aea6146b761d6c57fcf8f81fcb19f187afecc19bf1701a48db9617a217 \
--hash=sha256:8eddb3784aed95d07065bcf94d07e8c04024fdb6b2386f08c197dfe6b3528fda \
--hash=sha256:9027a7fcf690f1a3635dc9e55e38a0d6602dbbc0548935d08d46d2e7ec91f454 \
--hash=sha256:90dc731d8e3e91bcd456aa7407d2eba7ac6f7860e89f3766baabb521f2c1de4a \
--hash=sha256:91e2bfb8e9a29f709d51b208dd5f441dc98eb412c8fe75c24ea464734ccdb48e \
--hash=sha256:95f5728b367a042df146cec4340d75359ec6237beebf4a8f5cf74657c65b9257 \
--hash=sha256:95f7b01b3f275504011cf4cf21c6b885c8d627ce0867a7e83af1382ebab7b3ff \
--hash=sha256:97cbb368fd0debdbeb6ba5966aa28e9a1ae3396c7386d15569a6ca4be4572b99 \
--hash=sha256:9ec6abfb701437142ce9544bd6a236addaf803a32628d2260eb3dbd9a60e2891 \
--hash=sha256:9fbdb90b85c7624c304f72ec7854659a3bd901e1c0ffb2363163779181edeb68 \
--hash=sha256:a003200b6cd64e89b5725ff7e284a93ab24fd54bbac8b4fa46b1ed57be693c27 \
--hash=sha256:a0741edbd0adfe5f30bba6c5223b78c131b5aa4a00a223d631e5ef36e26e6d13 \
--hash=sha256:a23948554c692df95daed595fdd3b76b420a4939d7a8a28d6d7dea9711878641 \
--hash=sha256:a4bffcadfd40660f26d1b3315a6029fd4f8f5bf31a74160b151f5c577b2dc81b \
--hash=sha256:a6549ecb0041dafa55b5932dcbb6c68293e0bd5980b5b99f5ebb05f9a3b8a8f3 \
--hash=sha256:a7ad34a2921e8f76716dc7205c9bf46a53817e22b9eec2e8a3e08ee4f4a72468 \
--hash=sha256:abf7b5942c6b0dafcc2823ddd9154f419147e24f8df5b41ca8ea40a6db90615c \
--hash=sha256:b314268e716487bfb86fcd6f84ebbe3e5bec5fac75fdf42bc7d90fdb33f618ad \
--hash=sha256:baa1da72aecf6a490b51fba7a51f1ce298a1e0e86d0daef8265c8f8f9848eb77 \
--hash=sha256:bd8fdee945b877aa3bffc6a5a8816deb048dab0544f9df3731ecd0e54d8c84c9 \
--hash=sha256:bdbc78ae2065042de48a65f1421b8af6b76a0386bb487b41955818c3c1ce7bed \
--hash=sha256:c059883840e634a21c5b31d9b9a0e2b48f991b94d60a811092bc37992715146a \
--hash=sha256:c1bb37849e2294d519117dd99b613c5177934e5c04a5bb05dd573fa42026567e \
--hash=sha256:c2a9cb17fd83b7a3a3009901aca828feaf20aa2451a8a487b035455a86549c09 \
--hash=sha256:c7154d228502e18f30f150b7ce94f0789d6b689f75261b623f0fdc1eec642aab \
--hash=sha256:cdb69710e462a38e6039cf17259d328f86383a06c20482cc154327968712273c \
--hash=sha256:ceb0d78b7ef106708a7e2c2914afe68efffc0051dc6a731b0dbacd8b4aee6d68 \
--hash=sha256:d14f50d61a89b0925e4d97a0beba6053eb98c426c5815d949a43544f05a0c7ec \
--hash=sha256:d51a7bfe01a48e1064131f3416a5439872c533d756396be2b39e3977b41430f9 \
--hash=sha256:d9da0289d8201c8a29fd158aaa0dfe2f2e14a181fd45e2dc1fbf969a62c1d594 \
--hash=sha256:e5e33b1491555843ba98d5209439500556ef55b6ab635f3a01148545498355e5 \
--hash=sha256:e76ad4729c2f1cf74b6eb1bdd05f6aba6175999340bd51e6caee49a435a13bf5 \
--hash=sha256:e7eeaef81530d0b74ad0d29eec9997f1c9230c2f27242b8d17e0ee67662c8f6e \
--hash=sha256:e8e47050412f0ad3a9b2287779758073cbf10e460d9f345002d4779e43bb0136 \
--hash=sha256:ed038a921df836d2f538e509a59cb638df3e70ca0fcd70d0bf389dfcdf784d2a \
--hash=sha256:edb550616f567cd5603b53bb52a5f842c0171b78852e6fc7e392b02c2a1504bb \
--hash=sha256:ee7152f32c88e0e1b5b17beb9f0e2b14454235795ef68c0c120b6d3d23d12833 \
--hash=sha256:eeb37f65350d5c5870517f02f8bbb2ac0fbec7b416c0f4875219fef305a89a45 \
--hash=sha256:ef29630fde6022471d287c15c0a2484aba188adbfb978702624ba7a54ddfa6c1 \
--hash=sha256:ef5479fac31df4b304e96400fc67ff08231873ee3537544aa08c30f9d22fce38 \
--hash=sha256:f0019cc804ac667fb8c8eaecdb66e6d4a68acf2e155d5c7d6381a5645bd93ae4 \
--hash=sha256:f0f19c2097fffb1d5b07893d75c9ee693e9cbc809235cf3f2267f0ef6b015f24 \
--hash=sha256:f19dae58b616ac56b96f2e2290f2d18730a898a171f447f491cc059b073ca1fa \
--hash=sha256:f1f31661a80cc46aba381bed475a9135b213ba23ca7ff6797251af31510920ce \
--hash=sha256:f2c307fbe86e18ab3c885b7e01de942145f539165c3360e2af0f094dd440acd9 \
--hash=sha256:f32718ee37c07932cc336096dc7403525301fd626349b6eff8470fe0f996d8d7 \
--hash=sha256:f39d1227e8256d19899d953e6e19ed2ccb689102e6d85e024da5acf410f301eb \
--hash=sha256:f5eeeb82feec1fc5cbafa5ee9022e87ffdb3a8c48afa035b356fcd20fc7f533f \
--hash=sha256:f92a002462154c176dac63a8f1f6582ab56eb394ef4914d65a9417f5d9fde218 \
--hash=sha256:f9ba5def063243793dec6603ad1392f735255cbc7202a3a484c14f99ec290705 \
--hash=sha256:fc409c18884eaf9ddde516d53af4f2db64a8bc7d81b1a0c274b8aa4e929958e8
# via -r requirements.in

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
@@ -14,6 +14,7 @@ from urllib.request import urlopen
from .util import read_json_api
from basicswap.rpc import callrpc
from basicswap.util import toBool
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from basicswap.bin.prepare import downloadPIVXParams
@@ -44,6 +45,8 @@ PIVX_BASE_ZMQ_PORT = 36892
PREFIX_SECRET_KEY_REGTEST = 0x2E
BTC_USE_DESCRIPTORS = toBool(os.getenv("BTC_USE_DESCRIPTORS", False))
def prepareDataDir(
datadir,

View File

@@ -2,30 +2,27 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import sys
import json
import logging
import multiprocessing
import os
import shutil
import signal
import logging
import unittest
import sys
import threading
import multiprocessing
import unittest
from io import StringIO
from urllib.request import urlopen
from unittest.mock import patch
from basicswap.rpc_xmr import (
callrpc_xmr,
)
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from basicswap.rpc_xmr import callrpc_xmr
from tests.basicswap.mnemonics import mnemonics
from tests.basicswap.util import (
waitForServer,
)
from tests.basicswap.util import waitForServer
from tests.basicswap.common import (
BASE_PORT,
BASE_RPC_PORT,
@@ -35,6 +32,7 @@ from tests.basicswap.common import (
LTC_BASE_PORT,
LTC_BASE_RPC_PORT,
PIVX_BASE_PORT,
BTC_USE_DESCRIPTORS,
)
from tests.basicswap.extended.test_dcr import (
DCR_BASE_PORT,
@@ -49,8 +47,6 @@ from tests.basicswap.extended.test_doge import (
DOGE_BASE_RPC_PORT,
)
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
import basicswap.config as cfg
import basicswap.bin.run as runSystem
@@ -132,6 +128,8 @@ def run_prepare(
os.environ["BSX_TEST_MODE"] = "true"
os.environ["PART_RPC_PORT"] = str(PARTICL_RPC_PORT_BASE)
os.environ["BTC_RPC_PORT"] = str(BITCOIN_RPC_PORT_BASE)
os.environ["BTC_PORT"] = str(BITCOIN_PORT_BASE)
os.environ["BTC_USE_DESCRIPTORS"] = str(BTC_USE_DESCRIPTORS)
os.environ["LTC_RPC_PORT"] = str(LITECOIN_RPC_PORT_BASE)
os.environ["DCR_RPC_PORT"] = str(DECRED_RPC_PORT_BASE)
os.environ["FIRO_RPC_PORT"] = str(FIRO_RPC_PORT_BASE)
@@ -229,8 +227,7 @@ def run_prepare(
for line in lines:
if not line.startswith("prune"):
fp.write(line)
fp.write("port={}\n".format(BITCOIN_PORT_BASE + node_id + port_ofs))
fp.write("bind=127.0.0.1\n")
# 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
# listenonion=0 does not stop the node from trying to bind to the tor port
# https://github.com/bitcoin/bitcoin/issues/22726
fp.write(
@@ -534,7 +531,7 @@ class TestBase(unittest.TestCase):
)
def signal_handler(self, sig, frame):
logging.info("signal {} detected.".format(sig))
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
self.delay_event.set()
def wait_seconds(self, seconds):

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -11,16 +11,16 @@ basicswap]$ python tests/basicswap/extended/test_dash.py
"""
import os
import sys
import json
import time
import logging
import os
import random
import shutil
import signal
import logging
import unittest
import sys
import threading
import time
import unittest
import basicswap.config as cfg
from basicswap.basicswap import (
@@ -251,7 +251,7 @@ def dashRpc(cmd, wallet=None):
def signal_handler(sig, frame):
global stop_test
print("signal {} detected.".format(sig))
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
stop_test = True
delay_event.set()

View File

@@ -2,19 +2,20 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2021 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import json
import time
import logging
import os
import shutil
import signal
import logging
import unittest
import sys
import threading
import time
import traceback
import unittest
import basicswap.config as cfg
from basicswap.basicswap import (
@@ -150,7 +151,7 @@ def btcRpc(cmd, node_id=0):
def signal_handler(sig, frame):
global stop_test
logging.info("signal {} detected.".format(sig))
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
stop_test = True
delay_event.set()

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-2021 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -11,15 +11,15 @@ basicswap]$ python tests/basicswap/extended/test_nmc.py
"""
import os
import sys
import json
import time
import logging
import os
import shutil
import signal
import logging
import unittest
import sys
import threading
import time
import unittest
import basicswap.config as cfg
from basicswap.basicswap import (
@@ -231,7 +231,7 @@ def nmcRpc(cmd):
def signal_handler(sig, frame):
global stop_test
print("signal {} detected.".format(sig))
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
stop_test = True
delay_event.set()

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -11,16 +11,16 @@ basicswap]$ python tests/basicswap/extended/test_pivx.py
"""
import os
import sys
import json
import time
import logging
import os
import random
import shutil
import signal
import logging
import unittest
import sys
import threading
import time
import unittest
import basicswap.config as cfg
from basicswap.basicswap import (
@@ -256,7 +256,7 @@ def pivxRpc(cmd):
def signal_handler(sig, frame):
global stop_test
print("signal {} detected.".format(sig))
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
stop_test = True
delay_event.set()

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# 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
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -15,17 +15,18 @@ pytest -v -s tests/basicswap/extended/test_scripts.py::Test::test_bid_tracking
"""
import os
import sys
import json
import time
import math
import logging
import sqlite3
import unittest
import threading
import subprocess
import http.client
import json
import logging
import math
import os
import signal
import sqlite3
import subprocess
import sys
import threading
import time
import unittest
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib import parse
@@ -196,6 +197,11 @@ def get_possible_bids(rv_stdout):
return bids
def signal_handler(self, sig, frame):
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
self.delay_event.set()
class Test(unittest.TestCase):
delay_event = threading.Event()
thread_http = HttpThread()
@@ -203,6 +209,11 @@ class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(Test, cls).setUpClass()
signal.signal(
signal.SIGINT, lambda signal, frame: signal_handler(cls, signal, frame)
)
cls.thread_http.start()
script_path = "scripts/createoffers.py"

View File

@@ -189,7 +189,7 @@ class Test(BaseTest):
"walletrpcport": WOW_BASE_WALLET_RPC_PORT + node_id,
"walletrpcuser": "test" + str(node_id),
"walletrpcpassword": "test_pass" + str(node_id),
"walletfile": "testwallet",
"wallet_name": "testwallet",
"datadir": os.path.join(datadir, "xmr_" + str(node_id)),
"bindir": WOW_BINDIR,
}

View File

@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -22,16 +22,16 @@ cp -r ${TEST_PATH}/bin/ ~/tmp/basicswap_bin/
"""
import os
import sys
import json
import time
import logging
import multiprocessing
import os
import random
import signal
import logging
import unittest
import sys
import threading
import multiprocessing
import time
import unittest
from unittest.mock import patch
from basicswap.rpc_xmr import (
@@ -219,7 +219,7 @@ def updateThreadDCR(cls):
def signal_handler(self, sig, frame):
logging.info("signal {} detected.".format(sig))
os.write(sys.stdout.fileno(), f"Signal {sig} detected.\n".encode("utf-8"))
self.delay_event.set()

View File

@@ -10,9 +10,6 @@ import random
import logging
import unittest
from basicswap.db import (
Concepts,
)
from basicswap.basicswap import (
BidStates,
Coins,
@@ -23,23 +20,28 @@ from basicswap.basicswap_util import (
TxLockTypes,
EventLogTypes,
)
from basicswap.db import (
Concepts,
)
from basicswap.util import (
make_int,
)
from basicswap.util.extkey import ExtKeyPair
from basicswap.interface.base import Curves
from tests.basicswap.util import (
read_json_api,
)
from tests.basicswap.common import (
abandon_all_swaps,
wait_for_balance,
wait_for_bid,
wait_for_event,
wait_for_offer,
wait_for_balance,
wait_for_unspent,
wait_for_none_active,
BTC_BASE_RPC_PORT,
)
from basicswap.contrib.test_framework.descriptors import descsum_create
from basicswap.contrib.test_framework.messages import (
ToHex,
FromHex,
@@ -58,6 +60,8 @@ from .test_xmr import BaseTest, test_delay_event, callnoderpc
logger = logging.getLogger()
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
class TestFunctions(BaseTest):
base_rpc_port = None
@@ -640,9 +644,140 @@ class TestFunctions(BaseTest):
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):
@classmethod
def setUpClass(cls):
super(BasicSwapTest, cls).setUpClass()
if False:
for client in cls.swap_clients:
client.log.safe_logs = True
client.log.safe_logs_prefix = b"tests"
def test_001_nested_segwit(self):
# p2sh-p2wpkh
logging.info(
@@ -1043,7 +1178,6 @@ class BasicSwapTest(TestFunctions):
logging.info("---------- Test {} hdwallet".format(self.test_coin_from.name))
ci = self.swap_clients[0].ci(self.test_coin_from)
test_seed = "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
test_wif = (
self.swap_clients[0]
.ci(self.test_coin_from)
@@ -1055,10 +1189,35 @@ class BasicSwapTest(TestFunctions):
"createwallet", [new_wallet_name, False, True, "", False, False]
)
self.callnoderpc("sethdseed", [True, test_wif], wallet=new_wallet_name)
wi = self.callnoderpc("getwalletinfo", wallet=new_wallet_name)
assert wi["hdseedid"] == "3da5c0af91879e8ce97d9a843874601c08688078"
addr = self.callnoderpc("getnewaddress", wallet=new_wallet_name)
self.callnoderpc("unloadwallet", [new_wallet_name])
addr_info = self.callnoderpc(
"getaddressinfo",
[
addr,
],
wallet=new_wallet_name,
)
assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
assert addr_info["hdkeypath"] == "m/0'/0'/0'"
assert addr == "bcrt1qps7hnjd866e9ynxadgseprkc2l56m00dvwargr"
addr_change = self.callnoderpc("getrawchangeaddress", wallet=new_wallet_name)
addr_info = self.callnoderpc(
"getaddressinfo",
[
addr_change,
],
wallet=new_wallet_name,
)
assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
assert addr_info["hdkeypath"] == "m/0'/1'/0'"
assert addr_change == "bcrt1qdl9ryxkqjltv42lhfnqgdjf9tagxsjpp2xak9a"
self.callnoderpc("unloadwallet", [new_wallet_name])
self.swap_clients[0].initialiseWallet(Coins.BTC, raise_errors=True)
assert self.swap_clients[0].checkWalletSeed(Coins.BTC) is True
for i in range(1500):
@@ -1438,6 +1597,97 @@ class BasicSwapTest(TestFunctions):
)
assert len(tx_wallet["blockhash"]) == 64
def test_013_descriptor_wallet(self):
logging.info(f"---------- Test {self.test_coin_from.name} descriptor wallet")
ci = self.swap_clients[0].ci(self.test_coin_from)
ek = ExtKeyPair()
ek.set_seed(bytes.fromhex(test_seed))
ek_encoded: str = ci.encode_secret_extkey(ek.encode_v())
new_wallet_name = "descriptors_" + random.randbytes(10).hex()
new_watch_wallet_name = "watch_descriptors_" + random.randbytes(10).hex()
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
ci.rpc("createwallet", [new_wallet_name, False, True, "", False, True])
ci.rpc("createwallet", [new_watch_wallet_name, True, True, "", False, True])
desc_external = descsum_create(f"wpkh({ek_encoded}/0h/0h/*h)")
desc_internal = descsum_create(f"wpkh({ek_encoded}/0h/1h/*h)")
self.callnoderpc(
"importdescriptors",
[
[
{
"desc": desc_external,
"timestamp": "now",
"active": True,
"range": [0, 10],
"next_index": 0,
},
{
"desc": desc_internal,
"timestamp": "now",
"active": True,
"internal": True,
},
],
],
wallet=new_wallet_name,
)
addr = self.callnoderpc("getnewaddress", wallet=new_wallet_name)
addr_info = self.callnoderpc(
"getaddressinfo",
[
addr,
],
wallet=new_wallet_name,
)
assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
assert addr_info["hdkeypath"] == "m/0h/0h/0h"
assert addr == "bcrt1qps7hnjd866e9ynxadgseprkc2l56m00dvwargr"
addr_change = self.callnoderpc("getrawchangeaddress", wallet=new_wallet_name)
addr_info = self.callnoderpc(
"getaddressinfo",
[
addr_change,
],
wallet=new_wallet_name,
)
assert addr_info["hdmasterfingerprint"] == "a55b7ea9"
assert addr_info["hdkeypath"] == "m/0h/1h/0h"
assert addr_change == "bcrt1qdl9ryxkqjltv42lhfnqgdjf9tagxsjpp2xak9a"
desc_watch = descsum_create(f"addr({addr})")
self.callnoderpc(
"importdescriptors",
[
[
{"desc": desc_watch, "timestamp": "now", "active": False},
],
],
wallet=new_watch_wallet_name,
)
ci.rpc_wallet("sendtoaddress", [addr, 1])
found: bool = False
for i in range(10):
txn_list = self.callnoderpc(
"listtransactions", ["*", 100, 0, True], wallet=new_watch_wallet_name
)
test_delay_event.wait(1)
if len(txn_list) > 0:
found = True
break
assert found
# Test that addresses can be generated beyond range in listdescriptors
for i in range(2000):
self.callnoderpc("getnewaddress", wallet=new_wallet_name)
self.callnoderpc("unloadwallet", [new_wallet_name])
self.callnoderpc("unloadwallet", [new_watch_wallet_name])
def test_01_0_lock_bad_prevouts(self):
logging.info(
"---------- Test {} lock_bad_prevouts".format(self.test_coin_from.name)
@@ -1714,133 +1964,10 @@ class BasicSwapTest(TestFunctions):
swap_clients[0].setMockTimeOffset(0)
def test_08_insufficient_funds(self):
tla_from = self.test_coin_from.name
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"
self.do_test_08_insufficient_funds(self.test_coin_from, Coins.XMR)
def test_08_insufficient_funds_rev(self):
tla_from = self.test_coin_from.name
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,
)
self.do_test_08_insufficient_funds(Coins.XMR, self.test_coin_from)
class TestBTC(BasicSwapTest):
@@ -1862,11 +1989,11 @@ class TestBTC(BasicSwapTest):
assert "seed is set from the Basicswap mnemonic" in rv["error"]
rv = read_json_api(1800, "getcoinseed", {"coin": "BTC"})
assert (
rv["seed"]
== "8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b"
assert rv["seed"] == test_seed
assert rv["seed_id"] in (
"3da5c0af91879e8ce97d9a843874601c08688078",
"4a231080ec6f4078e543d39cc6dcf0b922c9b16b",
)
assert rv["seed_id"] == "3da5c0af91879e8ce97d9a843874601c08688078"
assert rv["seed_id"] == rv["expected_seed_id"]
rv = read_json_api(
@@ -2022,6 +2149,14 @@ class TestBTC_PARTB(TestFunctions):
start_ltc_nodes = False
base_rpc_port = BTC_BASE_RPC_PORT
@classmethod
def setUpClass(cls):
super(TestBTC_PARTB, cls).setUpClass()
if False:
for client in cls.swap_clients:
client.log.safe_logs = True
client.log.safe_logs_prefix = b"tests"
def test_01_a_full_swap(self):
self.prepare_balance(self.test_coin_to, 100.0, 1801, 1800)
self.do_test_01_full_swap(self.test_coin_from, self.test_coin_to)

View File

@@ -6,16 +6,16 @@
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import json
import time
import logging
import os
import random
import shutil
import signal
import logging
import unittest
import traceback
import threading
import time
import traceback
import unittest
import basicswap.config as cfg
from basicswap.db import (
@@ -83,6 +83,7 @@ from tests.basicswap.common import (
LTC_BASE_PORT,
LTC_BASE_RPC_PORT,
PREFIX_SECRET_KEY_REGTEST,
BTC_USE_DESCRIPTORS,
)
from basicswap.db_util import (
remove_expired_data,
@@ -172,6 +173,7 @@ def prepare_swapclient_dir(
"datadir": os.path.join(datadir, "btc_" + str(node_id)),
"bindir": cfg.BITCOIN_BINDIR,
"use_segwit": True,
"use_descriptors": BTC_USE_DESCRIPTORS,
},
},
"check_progress_seconds": 2,
@@ -189,6 +191,9 @@ def prepare_swapclient_dir(
"restrict_unknown_seed_wallets": False,
}
if BTC_USE_DESCRIPTORS:
settings["chainclients"]["bitcoin"]["watch_wallet_name"] = "bsx_watch"
if Coins.XMR in with_coins:
settings["chainclients"]["monero"] = {
"connection_type": "rpc",
@@ -197,7 +202,7 @@ def prepare_swapclient_dir(
"walletrpcport": XMR_BASE_WALLET_RPC_PORT + node_id,
"walletrpcuser": "test" + str(node_id),
"walletrpcpassword": "test_pass" + str(node_id),
"walletfile": "testwallet",
"wallet_name": "testwallet",
"datadir": os.path.join(datadir, "xmr_" + str(node_id)),
"bindir": cfg.XMR_BINDIR,
}
@@ -474,25 +479,29 @@ class BaseTest(unittest.TestCase):
if os.path.exists(
os.path.join(cfg.BITCOIN_BINDIR, "bitcoin-wallet")
):
try:
callrpc_cli(
cfg.BITCOIN_BINDIR,
data_dir,
"regtest",
"-wallet=wallet.dat -legacy create",
"bitcoin-wallet",
)
except Exception as e:
logging.warning(
f"bitcoin-wallet create failed {e}, retrying without -legacy"
)
callrpc_cli(
cfg.BITCOIN_BINDIR,
data_dir,
"regtest",
"-wallet=wallet.dat create",
"bitcoin-wallet",
)
if BTC_USE_DESCRIPTORS:
# How to set blank and disable_private_keys with wallet util?
pass
else:
try:
callrpc_cli(
cfg.BITCOIN_BINDIR,
data_dir,
"regtest",
"-wallet=wallet.dat -legacy create",
"bitcoin-wallet",
)
except Exception as e:
logging.warning(
f"bitcoin-wallet create failed {e}, retrying without -legacy"
)
callrpc_cli(
cfg.BITCOIN_BINDIR,
data_dir,
"regtest",
"-wallet=wallet.dat create",
"bitcoin-wallet",
)
cls.btc_daemons.append(
startDaemon(
@@ -505,9 +514,21 @@ class BaseTest(unittest.TestCase):
"Started %s %d", cfg.BITCOIND, cls.part_daemons[-1].handle.pid
)
waitForRPC(
make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT), test_delay_event
)
if BTC_USE_DESCRIPTORS:
rpc_func = make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT)
waitForRPC(
rpc_func, test_delay_event, rpc_command="getblockchaininfo"
)
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
rpc_func(
"createwallet", ["wallet.dat", False, True, "", False, True]
)
rpc_func("createwallet", ["bsx_watch", True, True, "", False, True])
else:
waitForRPC(
make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT),
test_delay_event,
)
if cls.start_ltc_nodes:
for i in range(NUM_LTC_NODES):
@@ -658,6 +679,11 @@ class BaseTest(unittest.TestCase):
xmr_ci.getMainWalletAddress(),
)
if BTC_USE_DESCRIPTORS:
# sc.initialiseWallet(Coins.BTC)
# Import a random seed to keep the existing test behaviour. BTC core rescans even with timestamp: now.
sc.ci(Coins.BTC).initialiseWallet(random.randbytes(32))
t = HttpThread(sc.fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, sc)
cls.http_threads.append(t)
t.start()
@@ -685,6 +711,7 @@ class BaseTest(unittest.TestCase):
"getnewaddress",
["mining_addr", "bech32"],
base_rpc_port=BTC_BASE_RPC_PORT,
wallet="wallet.dat",
)
num_blocks = 400 # Mine enough to activate segwit
logging.info("Mining %d Bitcoin blocks to %s", num_blocks, cls.btc_addr)
@@ -700,6 +727,7 @@ class BaseTest(unittest.TestCase):
"getnewaddress",
["initial addr"],
base_rpc_port=BTC_BASE_RPC_PORT,
wallet="wallet.dat",
)
for i in range(5):
callnoderpc(
@@ -707,6 +735,7 @@ class BaseTest(unittest.TestCase):
"sendtoaddress",
[btc_addr1, 100],
base_rpc_port=BTC_BASE_RPC_PORT,
wallet="wallet.dat",
)
# Switch addresses so wallet amounts stay constant