Merge pull request #185 from tecnovert/doge_23
Some checks are pending
ci / ci (3.12) (push) Waiting to run

Doge 23
This commit is contained in:
tecnovert 2025-01-03 21:03:53 +00:00 committed by GitHub
commit 34eb5900fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1383 additions and 373 deletions

View file

@ -355,7 +355,13 @@ class BasicSwap(BaseApp):
# TODO: Set dynamically
self.balance_only_coins = (Coins.LTC_MWEB,)
self.scriptless_coins = (Coins.XMR, Coins.WOW, Coins.PART_ANON, Coins.FIRO)
self.scriptless_coins = (
Coins.XMR,
Coins.WOW,
Coins.PART_ANON,
Coins.FIRO,
Coins.DOGE,
)
self.adaptor_swap_only_coins = self.scriptless_coins + (
Coins.PART_BLIND,
Coins.BCH,
@ -822,6 +828,10 @@ class BasicSwap(BaseApp):
self.coin_clients[coin], self.chain, self
)
return interface
elif coin == Coins.DOGE:
from .interface.doge import DOGEInterface
return DOGEInterface(self.coin_clients[coin], self.chain, self)
elif coin == Coins.DCR:
from .interface.dcr import DCRInterface
@ -882,6 +892,7 @@ class BasicSwap(BaseApp):
if cc["name"] in (
"bitcoin",
"litecoin",
"dogecoin",
"namecoin",
"dash",
"firo",

View file

@ -56,9 +56,6 @@ LITECOIN_VERSION_TAG = os.getenv("LITECOIN_VERSION_TAG", "")
BITCOIN_VERSION = os.getenv("BITCOIN_VERSION", "26.0")
BITCOIN_VERSION_TAG = os.getenv("BITCOIN_VERSION_TAG", "")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "27.1.0")
BITCOINCASH_VERSION_TAG = os.getenv("BITCOINCASH_VERSION_TAG", "")
MONERO_VERSION = os.getenv("MONERO_VERSION", "0.18.3.4")
MONERO_VERSION_TAG = os.getenv("MONERO_VERSION_TAG", "")
XMR_SITE_COMMIT = (
@ -86,6 +83,12 @@ NAV_VERSION_TAG = os.getenv("NAV_VERSION_TAG", "")
DCR_VERSION = os.getenv("DCR_VERSION", "1.8.1")
DCR_VERSION_TAG = os.getenv("DCR_VERSION_TAG", "")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "27.1.0")
BITCOINCASH_VERSION_TAG = os.getenv("BITCOINCASH_VERSION_TAG", "")
DOGECOIN_VERSION = os.getenv("DOGECOIN_VERSION", "23.2.1")
DOGECOIN_VERSION_TAG = os.getenv("DOGECOIN_VERSION_TAG", "")
GUIX_SSL_CERT_DIR = None
ADD_PUBKEY_URL = os.getenv("ADD_PUBKEY_URL", "")
@ -98,7 +101,6 @@ SKIP_GPG_VALIDATION = toBool(os.getenv("SKIP_GPG_VALIDATION", "false"))
known_coins = {
"particl": (PARTICL_VERSION, PARTICL_VERSION_TAG, ("tecnovert",)),
"bitcoin": (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ("laanwj",)),
"bitcoincash": (BITCOINCASH_VERSION, BITCOINCASH_VERSION_TAG, ("Calin_Culianu",)),
"litecoin": (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ("davidburkett38",)),
"decred": (DCR_VERSION, DCR_VERSION_TAG, ("decred_release",)),
"namecoin": ("0.18.0", "", ("JeremyRand",)),
@ -108,6 +110,8 @@ known_coins = {
"dash": (DASH_VERSION, DASH_VERSION_TAG, ("pasta",)),
"firo": (FIRO_VERSION, FIRO_VERSION_TAG, ("reuben",)),
"navcoin": (NAV_VERSION, NAV_VERSION_TAG, ("nav_builder",)),
"bitcoincash": (BITCOINCASH_VERSION, BITCOINCASH_VERSION_TAG, ("Calin_Culianu",)),
"dogecoin": (DOGECOIN_VERSION, DOGECOIN_VERSION_TAG, ("tecnovert",)),
}
disabled_coins = [
@ -123,6 +127,8 @@ expected_key_ids = {
"binaryfate": ("F0AF4D462A0BDF92",),
"wowario": ("793504B449C69220",),
"davidburkett38": ("3620E9D387E55666",),
"xanimo": ("6E8F17C1B1BCDCBE",),
"patricklodder": ("2D3A345B98D0DC1F",),
"fuzzbawls": ("C1ABA64407731FD9",),
"pasta": ("52527BEDABE87984",),
"reuben": ("1290A1D0FA7EE109",),
@ -203,13 +209,6 @@ BTC_ONION_PORT = int(os.getenv("BTC_ONION_PORT", 8334))
BTC_RPC_USER = os.getenv("BTC_RPC_USER", "")
BTC_RPC_PWD = os.getenv("BTC_RPC_PWD", "")
BCH_RPC_HOST = os.getenv("BCH_RPC_HOST", "127.0.0.1")
BCH_RPC_PORT = int(os.getenv("BCH_RPC_PORT", 19997))
BCH_ONION_PORT = int(os.getenv("BCH_ONION_PORT", 8335))
BCH_PORT = int(os.getenv("BCH_PORT", 19798))
BCH_RPC_USER = os.getenv("BCH_RPC_USER", "")
BCH_RPC_PWD = os.getenv("BCH_RPC_PWD", "")
DCR_RPC_HOST = os.getenv("DCR_RPC_HOST", "127.0.0.1")
DCR_RPC_PORT = int(os.getenv("DCR_RPC_PORT", 9109))
DCR_WALLET_RPC_HOST = os.getenv("DCR_WALLET_RPC_HOST", "127.0.0.1")
@ -247,6 +246,19 @@ NAV_ONION_PORT = int(os.getenv("NAV_ONION_PORT", 8334)) # TODO?
NAV_RPC_USER = os.getenv("NAV_RPC_USER", "")
NAV_RPC_PWD = os.getenv("NAV_RPC_PWD", "")
BCH_RPC_HOST = os.getenv("BCH_RPC_HOST", "127.0.0.1")
BCH_RPC_PORT = int(os.getenv("BCH_RPC_PORT", 19997))
BCH_ONION_PORT = int(os.getenv("BCH_ONION_PORT", 8335))
BCH_PORT = int(os.getenv("BCH_PORT", 19798))
BCH_RPC_USER = os.getenv("BCH_RPC_USER", "")
BCH_RPC_PWD = os.getenv("BCH_RPC_PWD", "")
DOGE_RPC_HOST = os.getenv("DOGE_RPC_HOST", "127.0.0.1")
DOGE_RPC_PORT = int(os.getenv("DOGE_RPC_PORT", 42069))
DOGE_ONION_PORT = int(os.getenv("DOGE_ONION_PORT", 6969))
DOGE_RPC_USER = os.getenv("DOGE_RPC_USER", "")
DOGE_RPC_PWD = os.getenv("DOGE_RPC_PWD", "")
TOR_PROXY_HOST = os.getenv("TOR_PROXY_HOST", "127.0.0.1")
TOR_PROXY_PORT = int(os.getenv("TOR_PROXY_PORT", 9050))
TOR_CONTROL_PORT = int(os.getenv("TOR_CONTROL_PORT", 9051))
@ -802,6 +814,15 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
"https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s"
% (version, os_dir_name, signing_key_name, assert_filename)
)
elif coin == "dogecoin":
release_url = (
"https://github.com/tecnovert/dogecoin/releases/download/v{}/{}".format(
version + version_tag, release_filename
)
)
assert_filename = "{}-{}-{}-build.assert".format(coin, os_name, version)
assert_url = f"https://raw.githubusercontent.com/tecnovert/guix.sigs/dogecoin/{version}/{signing_key_name}/noncodesigned.SHA256SUMS"
elif coin == "bitcoin":
release_url = "https://bitcoincore.org/bin/bitcoin-core-{}/{}".format(
version, release_filename
@ -973,6 +994,8 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
pubkey_filename = "{}_builder.pgp".format(coin)
elif coin in ("decred",):
pubkey_filename = "{}_release.pgp".format(coin)
elif coin in ("dogecoin",):
pubkey_filename = "particl_{}.pgp".format(signing_key_name)
else:
pubkey_filename = "{}_{}.pgp".format(coin, signing_key_name)
pubkeyurls = [
@ -1276,6 +1299,14 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
LTC_RPC_USER, salt, password_to_hmac(salt, LTC_RPC_PWD)
)
)
elif coin == "dogecoin":
fp.write("prune=4000\n")
if DOGE_RPC_USER != "":
fp.write(
"rpcauth={}:{}${}\n".format(
DOGE_RPC_USER, salt, password_to_hmac(salt, DOGE_RPC_PWD)
)
)
elif coin == "bitcoin":
fp.write("deprecatedrpc=create_bdb\n")
fp.write("prune=2000\n")
@ -1485,6 +1516,8 @@ def modify_tor_config(
default_onionport = PART_ONION_PORT
elif coin == "litecoin":
default_onionport = LTC_ONION_PORT
elif coin == "dogecoin":
default_onionport = DOGE_ONION_PORT
elif coin in ("decred",):
pass
else:
@ -1697,6 +1730,7 @@ def initialise_wallets(
Coins.PART,
Coins.BTC,
Coins.LTC,
Coins.DOGE,
Coins.DCR,
Coins.DASH,
)
@ -1803,7 +1837,7 @@ def initialise_wallets(
"Creating wallet.dat for {}.".format(getCoinName(c))
)
if c in (Coins.BTC, Coins.LTC, Coins.DASH):
if c in (Coins.BTC, Coins.LTC, Coins.DOGE, Coins.DASH):
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
swap_client.callcoinrpc(
c,
@ -2256,6 +2290,21 @@ def main():
"core_version_group": 21,
"min_relay_fee": 0.00001,
},
"dogecoin": {
"connection_type": "rpc",
"manage_daemon": shouldManageDaemon("DOGE"),
"rpchost": DOGE_RPC_HOST,
"rpcport": DOGE_RPC_PORT + port_offset,
"onionport": DOGE_ONION_PORT + port_offset,
"datadir": os.getenv("DOGE_DATA_DIR", os.path.join(data_dir, "dogecoin")),
"bindir": os.path.join(bin_dir, "dogecoin"),
"use_segwit": False,
"use_csv": False,
"blocks_confirmed": 2,
"conf_target": 2,
"core_version_group": 23,
"min_relay_fee": 0.01, # RECOMMENDED_MIN_TX_FEE
},
"decred": {
"connection_type": "rpc",
"manage_daemon": shouldManageDaemon("DCR"),
@ -2403,6 +2452,9 @@ def main():
if LTC_RPC_USER != "":
chainclients["litecoin"]["rpcuser"] = LTC_RPC_USER
chainclients["litecoin"]["rpcpassword"] = LTC_RPC_PWD
if DOGE_RPC_USER != "":
chainclients["dogecoin"]["rpcuser"] = DOGE_RPC_USER
chainclients["dogecoin"]["rpcpassword"] = DOGE_RPC_PWD
if BTC_RPC_USER != "":
chainclients["bitcoin"]["rpcuser"] = BTC_RPC_USER
chainclients["bitcoin"]["rpcpassword"] = BTC_RPC_PWD

View file

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

View file

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

View file

@ -188,7 +188,7 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
def curve_type():
return Curves.secp256k1
def getNewSecretKey(self) -> bytes:
def getNewRandomKey(self) -> bytes:
return i2b(getSecretInt())
def getPubkey(self, privkey: bytes) -> bytes:

View file

@ -1296,7 +1296,7 @@ class BTCInterface(Secp256k1Interface):
def getWalletTransaction(self, txid: bytes):
try:
return bytes.fromhex(self.rpc_wallet("gettransaction", [txid.hex()]))
return bytes.fromhex(self.rpc_wallet("gettransaction", [txid.hex()])["hex"])
except Exception as e: # noqa: F841
# TODO: filter errors
return None
@ -1466,7 +1466,6 @@ class BTCInterface(Secp256k1Interface):
vout: int = -1,
):
# Add watchonly address and rescan if required
if not self.isAddressMine(dest_address, or_watch_only=True):
self.importWatchOnlyAddress(dest_address, "bid")
self._log.info("Imported watch-only addr: {}".format(dest_address))

View file

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

View file

@ -240,7 +240,7 @@ class PARTInterfaceBlind(PARTInterface):
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes) -> bytes:
# Nonce is derived from vkbv, ephemeral_key isn't used
ephemeral_key = self.getNewSecretKey()
ephemeral_key = self.getNewRandomKey()
ephemeral_pubkey = self.getPubkey(ephemeral_key)
assert len(ephemeral_pubkey) == 33
nonce = self.getScriptLockTxNonce(vkbv)
@ -307,7 +307,7 @@ class PARTInterfaceBlind(PARTInterface):
lock_tx_obj = self.rpc("decoderawtransaction", [tx_lock_bytes.hex()])
assert self.getTxid(tx_lock_bytes).hex() == lock_tx_obj["txid"]
# Nonce is derived from vkbv, ephemeral_key isn't used
ephemeral_key = self.getNewSecretKey()
ephemeral_key = self.getNewRandomKey()
ephemeral_pubkey = self.getPubkey(ephemeral_key)
assert len(ephemeral_pubkey) == 33
nonce = self.getScriptLockTxNonce(vkbv)
@ -348,7 +348,7 @@ class PARTInterfaceBlind(PARTInterface):
dummy_witness_stack = [x.hex() for x in dummy_witness_stack]
# Use a junk change pubkey to avoid adding unused keys to the wallet
zero_change_key = self.getNewSecretKey()
zero_change_key = self.getNewRandomKey()
zero_change_pubkey = self.getPubkey(zero_change_key)
inputs_info = {
"0": {
@ -428,7 +428,7 @@ class PARTInterfaceBlind(PARTInterface):
dummy_witness_stack = [x.hex() for x in dummy_witness_stack]
# Use a junk change pubkey to avoid adding unused keys to the wallet
zero_change_key = self.getNewSecretKey()
zero_change_key = self.getNewRandomKey()
zero_change_pubkey = self.getPubkey(zero_change_key)
inputs_info = {
"0": {
@ -745,7 +745,7 @@ class PARTInterfaceBlind(PARTInterface):
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
# Use a junk change pubkey to avoid adding unused keys to the wallet
zero_change_key = self.getNewSecretKey()
zero_change_key = self.getNewRandomKey()
zero_change_pubkey = self.getPubkey(zero_change_key)
inputs_info = {
"0": {
@ -949,7 +949,7 @@ class PARTInterfaceBlind(PARTInterface):
dummy_witness_stack = [x.hex() for x in dummy_witness_stack]
# Use a junk change pubkey to avoid adding unused keys to the wallet
zero_change_key = self.getNewSecretKey()
zero_change_key = self.getNewRandomKey()
zero_change_pubkey = self.getPubkey(zero_change_key)
inputs_info = {
"0": {

View file

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

View file

@ -12,7 +12,7 @@ let filterTimeout = null;
// Time Constants
const MIN_REFRESH_INTERVAL = 60; // 60 sec
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
const FALLBACK_CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
// Application Constants
@ -67,7 +67,8 @@ const coinNameToDisplayName = {
const coinIdToName = {
1: 'particl', 2: 'bitcoin', 3: 'litecoin', 4: 'decred',
6: 'monero', 7: 'particl blind', 8: 'particl anon',
9: 'wownero', 11: 'pivx', 13: 'firo', 17: 'bitcoincash'
9: 'wownero', 11: 'pivx', 13: 'firo', 17: 'bitcoincash',
18: 'dogecoin'
};
// DOM ELEMENT REFERENCES
@ -92,7 +93,7 @@ const WebSocketManager = {
reconnectDelay: 5000,
maxQueueSize: 1000,
isIntentionallyClosed: false,
connectionState: {
isConnecting: false,
lastConnectAttempt: null,
@ -271,7 +272,7 @@ const WebSocketManager = {
try {
const response = await fetch(endpoint);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const newData = await response.json();
const fetchedOffers = Array.isArray(newData) ? newData : Object.values(newData);
@ -300,7 +301,7 @@ const WebSocketManager = {
this.reconnectAttempts++;
if (this.reconnectAttempts <= this.maxReconnectAttempts) {
console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
const delay = Math.min(
this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1),
30000
@ -324,11 +325,11 @@ const WebSocketManager = {
cleanup() {
console.log('Cleaning up WebSocket resources');
clearTimeout(this.debounceTimeout);
clearTimeout(this.reconnectTimeout);
clearTimeout(this.connectionState.connectTimeout);
this.messageQueue = [];
this.processingQueue = false;
this.connectionState.isConnecting = false;
@ -365,7 +366,7 @@ const CacheManager = {
set: function(key, value, customTtl = null) {
try {
this.cleanup();
const item = {
value: value,
timestamp: Date.now(),
@ -396,7 +397,7 @@ const CacheManager = {
return false;
}
},
get: function(key) {
try {
const itemStr = localStorage.getItem(key);
@ -404,14 +405,14 @@ const CacheManager = {
const item = JSON.parse(itemStr);
const now = Date.now();
if (now < item.expiresAt) {
return {
value: item.value,
remainingTime: item.expiresAt - now
};
}
localStorage.removeItem(key);
} catch (error) {
localStorage.removeItem(key);
@ -433,7 +434,7 @@ const CacheManager = {
const itemStr = localStorage.getItem(key);
const size = new Blob([itemStr]).size;
const item = JSON.parse(itemStr);
if (now >= item.expiresAt) {
localStorage.removeItem(key);
continue;
@ -445,7 +446,7 @@ const CacheManager = {
expiresAt: item.expiresAt,
timestamp: item.timestamp
});
totalSize += size;
itemCount++;
} catch (error) {
@ -455,7 +456,7 @@ const CacheManager = {
if (aggressive || totalSize > this.maxSize || itemCount > this.maxItems) {
items.sort((a, b) => b.timestamp - a.timestamp);
while ((totalSize > this.maxSize || itemCount > this.maxItems) && items.length > 0) {
const item = items.pop();
localStorage.removeItem(item.key);
@ -473,7 +474,7 @@ const CacheManager = {
keys.push(key);
}
}
keys.forEach(key => localStorage.removeItem(key));
},
@ -491,10 +492,10 @@ const CacheManager = {
const itemStr = localStorage.getItem(key);
const size = new Blob([itemStr]).size;
const item = JSON.parse(itemStr);
totalSize += size;
itemCount++;
if (now >= item.expiresAt) {
expiredCount++;
}
@ -528,7 +529,7 @@ window.tableRateModule = {
'Bitcoin Cash': 'BCH',
'Dogecoin': 'DOGE'
},
cache: {},
processedOffers: new Set(),
@ -564,7 +565,7 @@ window.tableRateModule = {
this.processedOffers.add(offerId);
return true;
},
formatUSD(value) {
if (Math.abs(value) < 0.000001) {
return value.toExponential(8) + ' USD';
@ -654,23 +655,23 @@ async function initializePriceData() {
while (retryCount < PRICE_INIT_RETRIES) {
try {
prices = await fetchLatestPrices();
if (prices && Object.keys(prices).length > 0) {
console.log('Successfully fetched initial price data');
latestPrices = prices;
CacheManager.set(PRICES_CACHE_KEY, prices, CACHE_DURATION);
return true;
}
retryCount++;
if (retryCount < PRICE_INIT_RETRIES) {
await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY));
}
} catch (error) {
console.error(`Error fetching prices (attempt ${retryCount + 1}):`, error);
retryCount++;
if (retryCount < PRICE_INIT_RETRIES) {
await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY));
}
@ -706,7 +707,7 @@ function checkOfferAgainstFilters(offer, filters) {
const currentTime = Math.floor(Date.now() / 1000);
const isExpired = offer.expire_at <= currentTime;
const isRevoked = Boolean(offer.is_revoked);
switch (filters.status) {
case 'active':
return !isExpired && !isRevoked;
@ -726,7 +727,7 @@ function initializeFlowbiteTooltips() {
console.warn('Tooltip is not defined. Make sure the required library is loaded.');
return;
}
const tooltipElements = document.querySelectorAll('[data-tooltip-target]');
tooltipElements.forEach((el) => {
const tooltipId = el.getAttribute('data-tooltip-target');
@ -740,10 +741,10 @@ function initializeFlowbiteTooltips() {
// DATA PROCESSING FUNCTIONS
async function checkExpiredAndFetchNew() {
if (isSentOffers) return Promise.resolve();
console.log('Starting checkExpiredAndFetchNew');
const OFFERS_CACHE_KEY = 'offers_received';
try {
const response = await fetch('/json/offers');
const data = await response.json();
@ -772,9 +773,9 @@ async function checkExpiredAndFetchNew() {
CacheManager.set(OFFERS_CACHE_KEY, newListings, CACHE_DURATION);
const currentFilters = new FormData(filterForm);
const hasActiveFilters = currentFilters.get('coin_to') !== 'any' ||
const hasActiveFilters = currentFilters.get('coin_to') !== 'any' ||
currentFilters.get('coin_from') !== 'any';
if (hasActiveFilters) {
jsonData = filterAndSortData();
} else {
@ -784,7 +785,7 @@ async function checkExpiredAndFetchNew() {
updateOffersTable();
updateJsonView();
updatePaginationInfo();
if (jsonData.length === 0) {
handleNoOffersScenario();
}
@ -810,7 +811,7 @@ function getValidOffers() {
function filterAndSortData() {
//console.log('[Debug] Starting filter with data length:', originalJsonData.length);
const formData = new FormData(filterForm);
const filters = Object.fromEntries(formData);
//console.log('[Debug] Active filters:', filters);
@ -825,7 +826,7 @@ function filterAndSortData() {
let filteredData = [...originalJsonData];
const sentFromFilter = filters.sent_from || 'any';
filteredData = filteredData.filter(offer => {
if (sentFromFilter === 'public') {
return offer.is_public;
@ -842,13 +843,13 @@ function filterAndSortData() {
const coinFrom = (offer.coin_from || '').toLowerCase();
const coinTo = (offer.coin_to || '').toLowerCase();
if (filters.coin_to !== 'any') {
if (!coinMatches(coinTo, filters.coin_to)) {
return false;
}
}
if (filters.coin_from !== 'any') {
if (!coinMatches(coinFrom, filters.coin_from)) {
return false;
@ -859,7 +860,7 @@ function filterAndSortData() {
const currentTime = Math.floor(Date.now() / 1000);
const isExpired = offer.expire_at <= currentTime;
const isRevoked = Boolean(offer.is_revoked);
switch (filters.status) {
case 'active':
return !isExpired && !isRevoked;
@ -878,7 +879,7 @@ function filterAndSortData() {
if (currentSortColumn !== null) {
filteredData.sort((a, b) => {
let comparison = 0;
switch(currentSortColumn) {
case 0: // Time
comparison = a.created_at - b.created_at;
@ -891,32 +892,32 @@ function filterAndSortData() {
const aToSymbol = getCoinSymbolLowercase(a.coin_to);
const bFromSymbol = getCoinSymbolLowercase(b.coin_from);
const bToSymbol = getCoinSymbolLowercase(b.coin_to);
const aFromPrice = latestPrices[aFromSymbol]?.usd || 0;
const aToPrice = latestPrices[aToSymbol]?.usd || 0;
const bFromPrice = latestPrices[bFromSymbol]?.usd || 0;
const bToPrice = latestPrices[bToSymbol]?.usd || 0;
const aMarketRate = aToPrice / aFromPrice;
const bMarketRate = bToPrice / bFromPrice;
const aOfferedRate = parseFloat(a.rate);
const bOfferedRate = parseFloat(b.rate);
const aPercentDiff = ((aOfferedRate - aMarketRate) / aMarketRate) * 100;
const bPercentDiff = ((bOfferedRate - bMarketRate) / bMarketRate) * 100;
comparison = aPercentDiff - bPercentDiff;
break;
case 7: // Trade
comparison = a.offer_id.localeCompare(b.offer_id);
break;
}
return currentSortDirection === 'desc' ? -comparison : comparison;
});
}
//console.log(`[Debug] Filtered data length: ${filteredData.length}`);
return filteredData;
}
@ -1019,7 +1020,7 @@ async function fetchLatestPrices() {
}
const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zano,wownero,zcoin&vs_currencies=USD,BTC&api_key=${config.apiKeys.coinGecko}`;
try {
console.log('Fetching fresh price data...');
const response = await fetch('/json/readurl', {
@ -1041,7 +1042,7 @@ async function fetchLatestPrices() {
if (data.Error) {
throw new Error(data.Error);
}
if (data && Object.keys(data).length > 0) {
console.log('Fresh price data received');
@ -1052,7 +1053,7 @@ async function fetchLatestPrices() {
Object.entries(data).forEach(([coin, prices]) => {
tableRateModule.setFallbackValue(coin, prices.usd);
});
return data;
} else {
//console.warn('Received empty price data');
@ -1075,18 +1076,18 @@ async function fetchOffers(manualRefresh = false) {
refreshIcon.classList.add('animate-spin');
refreshText.textContent = 'Refreshing...';
refreshButton.classList.add('opacity-75', 'cursor-wait');
const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers';
const response = await fetch(endpoint);
const data = await response.json();
jsonData = formatInitialData(data);
originalJsonData = [...jsonData];
await updateOffersTable();
updateJsonView();
updatePaginationInfo();
} catch (error) {
console.error('[Debug] Error fetching offers:', error);
ui.displayErrorMessage('Failed to fetch offers. Please try again later.');
@ -1120,12 +1121,12 @@ function formatInitialData(data) {
function updateConnectionStatus(status) {
const dot = document.getElementById('status-dot');
const text = document.getElementById('status-text');
if (!dot || !text) {
//console.warn('Status indicators not found in DOM');
return;
}
switch(status) {
case 'connected':
dot.className = 'w-2.5 h-2.5 rounded-full bg-green-500 mr-2';
@ -1159,10 +1160,10 @@ function updateRowTimes() {
const newPostedTime = formatTime(offer.created_at, true);
const newExpiresIn = formatTimeLeft(offer.expire_at);
const postedElement = row.querySelector('.text-xs:first-child');
const expiresElement = row.querySelector('.text-xs:last-child');
if (postedElement && postedElement.textContent !== `Posted: ${newPostedTime}`) {
postedElement.textContent = `Posted: ${newPostedTime}`;
}
@ -1212,14 +1213,14 @@ function updatePaginationInfo() {
const showPrev = currentPage > 1;
const showNext = currentPage < totalPages && totalItems > 0;
prevPageButton.style.display = showPrev ? 'inline-flex' : 'none';
nextPageButton.style.display = showNext ? 'inline-flex' : 'none';
if (lastRefreshTime) {
lastRefreshTimeSpan.textContent = new Date(lastRefreshTime).toLocaleTimeString();
}
if (newEntriesCountSpan) {
newEntriesCountSpan.textContent = totalItems;
}
@ -1255,13 +1256,13 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffe
}
const formattedPercentDiff = percentDiff.toFixed(2);
const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
(percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff);
const colorClass = getProfitColorClass(percentDiff);
profitLossElement.textContent = `${percentDiffDisplay}%`;
profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`;
const tooltipId = `percentage-tooltip-${row.getAttribute('data-offer-id')}`;
const tooltipElement = document.getElementById(tooltipId);
if (tooltipElement) {
@ -1310,7 +1311,7 @@ function updateClearFiltersButton() {
const hasFilters = hasActiveFilters();
clearButton.classList.toggle('opacity-50', !hasFilters);
clearButton.disabled = !hasFilters;
// Update button styles based on state
if (hasFilters) {
clearButton.classList.add('hover:bg-green-600', 'hover:text-white');
@ -1325,10 +1326,10 @@ function updateClearFiltersButton() {
function handleNoOffersScenario() {
const formData = new FormData(filterForm);
const filters = Object.fromEntries(formData);
const hasActiveFilters = filters.coin_to !== 'any' ||
const hasActiveFilters = filters.coin_to !== 'any' ||
filters.coin_from !== 'any' ||
(filters.status && filters.status !== 'any');
stopRefreshAnimation();
if (hasActiveFilters) {
@ -1336,7 +1337,7 @@ function handleNoOffersScenario() {
<tr>
<td colspan="9" class="text-center py-8">
<div class="flex items-center justify-center text-gray-500 dark:text-white">
No offers match the selected filters. Try different filter options or
No offers match the selected filters. Try different filter options or
<button onclick="clearFilters()" class="ml-1 text-blue-500 hover:text-blue-700 font-semibold">
clear filters
</button>
@ -1357,7 +1358,7 @@ async function updateOffersTable() {
try {
const PRICES_CACHE_KEY = 'prices_coingecko';
const cachedPrices = CacheManager.get(PRICES_CACHE_KEY);
if (!cachedPrices || !cachedPrices.remainingTime || cachedPrices.remainingTime < 60000) {
console.log('Fetching fresh price data...');
const priceData = await fetchLatestPrices();
@ -1374,12 +1375,12 @@ async function updateOffersTable() {
const endIndex = Math.min(startIndex + itemsPerPage, validOffers.length);
const itemsToDisplay = validOffers.slice(startIndex, endIndex);
const identityPromises = itemsToDisplay.map(offer =>
const identityPromises = itemsToDisplay.map(offer =>
offer.addr_from ? getIdentityData(offer.addr_from) : Promise.resolve(null)
);
const identities = await Promise.all(identityPromises);
if (validOffers.length === 0) {
handleNoOffersScenario();
return;
@ -1405,7 +1406,7 @@ async function updateOffersTable() {
initializeFlowbiteTooltips();
updateRowTimes();
updatePaginationControls(totalPages);
if (tableRateModule?.initializeTable) {
tableRateModule.initializeTable();
}
@ -1482,7 +1483,7 @@ function getIdentityInfo(address, identity) {
function createTableRow(offer, identity = null) {
const row = document.createElement('tr');
const uniqueId = `${offer.offer_id}_${offer.created_at}`;
row.className = 'relative opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600';
row.setAttribute('data-offer-id', uniqueId);
@ -1504,7 +1505,7 @@ function createTableRow(offer, identity = null) {
const coinToDisplay = getDisplayName(coinTo);
const postedTime = formatTime(createdAt, true);
const expiresIn = formatTime(expireAt);
const currentTime = Math.floor(Date.now() / 1000);
const isActuallyExpired = currentTime > expireAt;
const fromAmount = parseFloat(amountFrom) || 0;
@ -1585,19 +1586,19 @@ function shouldShowPublicTag(offers) {
function truncateText(text, maxLength = 15) {
if (typeof text !== 'string') return '';
return text.length > maxLength
? text.slice(0, maxLength) + '...'
return text.length > maxLength
? text.slice(0, maxLength) + '...'
: text;
}
function createDetailsColumn(offer, identity = null) {
const addrFrom = offer.addr_from || '';
const identityInfo = getIdentityInfo(addrFrom, identity);
const showPublicPrivateTags = originalJsonData.some(o => o.is_public !== offer.is_public);
const tagClass = offer.is_public
? 'bg-green-600 dark:bg-green-600'
const tagClass = offer.is_public
? 'bg-green-600 dark:bg-green-600'
: 'bg-red-500 dark:bg-red-500';
const tagText = offer.is_public ? 'Public' : 'Private';
@ -1605,16 +1606,16 @@ function createDetailsColumn(offer, identity = null) {
identityInfo.label || addrFrom || 'Unspecified'
);
const identifierTextClass = identityInfo.label
? 'text-white dark:text-white'
const identifierTextClass = identityInfo.label
? 'text-white dark:text-white'
: 'monospace';
return `
<td class="py-8 px-4 text-xs text-left hidden xl:block">
<div class="flex flex-col gap-2 relative">
${showPublicPrivateTags ? `<span class="inline-flex pl-6 pr-6 py-1 justify-center text-[10px] w-1/4 font-medium text-gray-100 rounded-md ${tagClass}">${tagText}</span>
` : ''}
<a data-tooltip-target="tooltip-recipient-${escapeHtml(offer.offer_id)}" href="/identity/${escapeHtml(addrFrom)}" 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" xmlns="http://www.w3.org/2000/svg">
<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>
@ -1635,8 +1636,8 @@ function createTakerAmountColumn(offer, coinTo, coinFrom) {
<td class="py-0">
<div class="py-3 px-4 text-left">
<a data-tooltip-target="tooltip-wallet${escapeHtml(offer.offer_id)}" href="/wallet/${escapeHtml(toSymbol)}" class="items-center monospace">
<div class="pr-2">
<div class="text-sm font-semibold">${fromAmount.toFixed(4)}</div>
<div class="pr-2">
<div class="text-sm font-semibold">${fromAmount.toFixed(4)}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${coinTo}</div>
</div>
</a>
@ -1679,8 +1680,8 @@ function createOrderbookColumn(offer, coinFrom, coinTo) {
<td class="p-0">
<div class="py-3 px-4 text-right">
<a data-tooltip-target="tooltip-wallet-maker${escapeHtml(offer.offer_id)}" href="/wallet/${escapeHtml(fromSymbol)}" class="items-center monospace">
<div class="pr-2">
<div class="text-sm font-semibold">${toAmount.toFixed(4)}</div>
<div class="pr-2">
<div class="text-sm font-semibold">${toAmount.toFixed(4)}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${coinFrom}</div>
</div>
</a>
@ -1694,7 +1695,7 @@ function createRateColumn(offer, coinFrom, coinTo) {
const inverseRate = 1 / rate;
const fromSymbol = getCoinSymbol(coinFrom);
const toSymbol = getCoinSymbol(coinTo);
const getPriceKey = (coin) => {
const lowerCoin = coin.toLowerCase();
if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
@ -1746,9 +1747,9 @@ function createPercentageColumn(offer) {
function createActionColumn(offer, isActuallyExpired = false) {
const isRevoked = Boolean(offer.is_revoked);
const isTreatedAsSentOffer = offer.is_own_offer;
let buttonClass, buttonText;
if (isRevoked) {
buttonClass = 'bg-red-500 text-white hover:bg-red-600 transition duration-200';
buttonText = 'Revoked';
@ -1786,14 +1787,14 @@ function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, t
const identityInfo = getIdentityInfo(addrFrom, identity);
const totalBids = identity ? (
identityInfo.stats.sentBidsSuccessful +
identityInfo.stats.recvBidsSuccessful +
identityInfo.stats.sentBidsFailed +
identityInfo.stats.recvBidsFailed +
identityInfo.stats.sentBidsRejected +
identityInfo.stats.sentBidsSuccessful +
identityInfo.stats.recvBidsSuccessful +
identityInfo.stats.sentBidsFailed +
identityInfo.stats.recvBidsFailed +
identityInfo.stats.sentBidsRejected +
identityInfo.stats.recvBidsRejected
) : 0;
const successRate = totalBids ? (
((identityInfo.stats.sentBidsSuccessful + identityInfo.stats.recvBidsSuccessful) / totalBids) * 100
).toFixed(1) : 0;
@ -1846,14 +1847,14 @@ function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, t
</div>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-wallet-${uniqueId}" role="tooltip" class="inline-block absolute invisible 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">
<div class="active-revoked-expired">
<span class="bold">${treatAsSentOffer ? 'My' : ''} ${coinTo} Wallet</span>
</div>
<div class="tooltip-arrow pl-1" 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 ${isRevoked ? 'bg-red-500' : (offer.is_own_offer ? 'bg-gray-300' : 'bg-green-700')} rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<div class="active-revoked-expired">
<span class="bold">
@ -1862,14 +1863,14 @@ function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, t
</div>
<div class="tooltip-arrow pr-6" data-popper-arrow></div>
</div>
<div id="tooltip-wallet-maker-${uniqueId}" role="tooltip" class="inline-block absolute invisible 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">
<div class="active-revoked-expired">
<span class="bold">${treatAsSentOffer ? 'My' : ''} ${coinFrom} Wallet</span>
</div>
<div class="tooltip-arrow pl-1" data-popper-arrow></div>
</div>
<div id="tooltip-rate-${uniqueId}" role="tooltip" class="inline-block absolute invisible 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">
<div class="tooltip-content">
${combinedRateTooltip}
@ -1903,7 +1904,7 @@ function createRecipientTooltip(uniqueId, identityInfo, identity, successRate, t
};
return `
<div id="tooltip-recipient-${uniqueId}" role="tooltip"
<div id="tooltip-recipient-${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 max-w-sm pointer-events-none">
<div class="identity-info space-y-2">
${identityInfo.label ? `
@ -1912,7 +1913,7 @@ function createRecipientTooltip(uniqueId, identityInfo, identity, successRate, t
<div class="text-white">${escapeHtml(identityInfo.label)}</div>
</div>
` : ''}
<div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Recipient Address:</div>
<div class="monospace text-xs break-all bg-gray-500 p-2 rounded-md text-white">
@ -2001,7 +2002,7 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
const marketRate = fromPriceUSD / toPriceUSD;
const offerRate = toAmount / fromAmount;
let percentDiff;
if (isSentOffers || isOwnOffer) {
percentDiff = ((toValueUSD / fromValueUSD) - 1) * 100;
} else {
@ -2009,7 +2010,7 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
}
const formattedPercentDiff = percentDiff.toFixed(2);
const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
(percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff);
const profitLabel = (isSentOffers || isOwnOffer) ? "Max Profit" : "Max Loss";
@ -2022,8 +2023,8 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
<p class="mt-1">Percentage difference: ${percentDiffDisplay}%</p>
<p>${profitLabel}: ${profitUSD > 0 ? '' : '-'}$${Math.abs(profitUSD).toFixed(2)} USD</p>
<p class="font-bold mt-2">Calculation:</p>
<p>Percentage = ${(isSentOffers || isOwnOffer) ?
"((To Amount in USD / From Amount in USD) - 1) * 100" :
<p>Percentage = ${(isSentOffers || isOwnOffer) ?
"((To Amount in USD / From Amount in USD) - 1) * 100" :
"((From Amount in USD / To Amount in USD) - 1) * 100"}</p>
<p>USD ${profitLabel} = To Amount in USD - From Amount in USD</p>
<p class="font-bold mt-1">Interpretation:</p>
@ -2034,8 +2035,8 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
<p><span class="text-green-500">Positive percentage:</span> You're buying below market rate (savings)</p>
<p><span class="text-red-500">Negative percentage:</span> You're buying above market rate (premium)</p>
`}
<p class="mt-1"><strong>Note:</strong> ${(isSentOffers || isOwnOffer) ?
"As a seller, a positive percentage means <br/> you're selling for more than the current market value." :
<p class="mt-1"><strong>Note:</strong> ${(isSentOffers || isOwnOffer) ?
"As a seller, a positive percentage means <br/> you're selling for more than the current market value." :
"As a buyer, a positive percentage indicates </br> potential savings compared to current market rates."}</p>
<p class="mt-1"><strong>Market Rate:</strong> 1 ${coinFrom} = ${marketRate.toFixed(8)} ${coinTo}</p>
<p><strong>Offer Rate:</strong> 1 ${coinFrom} = ${offerRate.toFixed(8)} ${coinTo}</p>
@ -2045,7 +2046,7 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
function createCombinedRateTooltip(offer, coinFrom, coinTo, isSentOffers, treatAsSentOffer) {
const rate = parseFloat(offer.rate);
const inverseRate = 1 / rate;
const getPriceKey = (coin) => {
const lowerCoin = coin.toLowerCase();
if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
@ -2059,16 +2060,16 @@ function createCombinedRateTooltip(offer, coinFrom, coinTo, isSentOffers, treatA
const fromSymbol = getPriceKey(coinFrom);
const toSymbol = getPriceKey(coinTo);
const fromPriceUSD = latestPrices[fromSymbol]?.usd || 0;
const toPriceUSD = latestPrices[toSymbol]?.usd || 0;
const rateInUSD = rate * toPriceUSD;
const marketRate = fromPriceUSD / toPriceUSD;
const percentDiff = ((rate - marketRate) / marketRate) * 100;
const formattedPercentDiff = percentDiff.toFixed(2);
const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
(percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff);
const aboveOrBelow = percentDiff > 0 ? "above" : percentDiff < 0 ? "below" : "at";
@ -2166,7 +2167,7 @@ function hasActiveFilters() {
const selectElements = filterForm.querySelectorAll('select');
let hasChangedFilters = false;
selectElements.forEach(select => {
if (select.value !== 'any') {
hasChangedFilters = true;
@ -2205,13 +2206,13 @@ function getCoinSymbolLowercase(coin) {
function coinMatches(offerCoin, filterCoin) {
if (!offerCoin || !filterCoin) return false;
offerCoin = offerCoin.toLowerCase();
filterCoin = filterCoin.toLowerCase();
if (offerCoin === filterCoin) return true;
if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
(filterCoin === 'firo' || filterCoin === 'zcoin')) {
return true;
}
@ -2229,7 +2230,7 @@ function coinMatches(offerCoin, filterCoin) {
if (particlVariants.includes(filterCoin)) {
return offerCoin === filterCoin;
}
return false;
}
@ -2259,7 +2260,7 @@ function getTimeUntilNextExpiration() {
const timeUntilExpiration = offer.expire_at - currentTime;
return timeUntilExpiration > 0 && timeUntilExpiration < earliest ? timeUntilExpiration : earliest;
}, Infinity);
return Math.max(MIN_REFRESH_INTERVAL, Math.min(nextExpiration, 300));
}
@ -2270,7 +2271,7 @@ function calculateInverseRate(rate) {
function formatTime(timestamp, addAgoSuffix = false) {
const now = Math.floor(Date.now() / 1000);
const diff = Math.abs(now - timestamp);
let timeString;
if (diff < 60) {
timeString = `${diff} seconds`;
@ -2308,7 +2309,7 @@ function getCoinSymbol(fullName) {
'Particl': 'PART', 'Particl Blind': 'PART', 'Particl Anon': 'PART',
'PIVX': 'PIVX', 'Firo': 'FIRO', 'Zcoin': 'FIRO',
'Dash': 'DASH', 'Decred': 'DCR', 'Wownero': 'WOW',
'Bitcoin Cash': 'BCH'
'Bitcoin Cash': 'BCH', 'Dogecoin': 'DOGE'
};
return symbolMap[fullName] || fullName;
}
@ -2317,7 +2318,7 @@ function getCoinSymbol(fullName) {
document.querySelectorAll('th[data-sortable="true"]').forEach(header => {
header.addEventListener('click', () => {
const columnIndex = parseInt(header.getAttribute('data-column-index'));
if (currentSortColumn === columnIndex) {
currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';
} else {
@ -2348,7 +2349,7 @@ document.querySelectorAll('th[data-sortable="true"]').forEach(header => {
localStorage.setItem('tableSortColumn', currentSortColumn);
localStorage.setItem('tableSortDirection', currentSortDirection);
applyFilters();
});
@ -2357,19 +2358,19 @@ document.querySelectorAll('th[data-sortable="true"]').forEach(header => {
const eventListeners = {
listeners: [],
add(element, eventType, handler, options = false) {
element.addEventListener(eventType, handler, options);
this.listeners.push({ element, eventType, handler, options });
// console.log(`Added ${eventType} listener to`, element);
},
addWindowListener(eventType, handler, options = false) {
window.addEventListener(eventType, handler, options);
this.listeners.push({ element: window, eventType, handler, options });
// console.log(`Added ${eventType} window listener`);
},
removeAll() {
console.log('Removing all event listeners...');
this.listeners.forEach(({ element, eventType, handler, options }) => {
@ -2378,7 +2379,7 @@ const eventListeners = {
});
this.listeners = [];
},
removeByElement(element) {
const remainingListeners = [];
this.listeners = this.listeners.filter(listener => {
@ -2400,29 +2401,29 @@ const eventListeners = {
const timerManager = {
intervals: [],
timeouts: [],
addInterval(callback, delay) {
const intervalId = setInterval(callback, delay);
this.intervals.push(intervalId);
return intervalId;
},
addTimeout(callback, delay) {
const timeoutId = setTimeout(callback, delay);
this.timeouts.push(timeoutId);
return timeoutId;
},
clearAllIntervals() {
this.intervals.forEach(clearInterval);
this.intervals = [];
},
clearAllTimeouts() {
this.timeouts.forEach(clearTimeout);
this.timeouts = [];
},
clearAll() {
this.clearAllIntervals();
this.clearAllTimeouts();
@ -2435,7 +2436,7 @@ document.addEventListener('DOMContentLoaded', () => {
console.log('View type:', isSentOffers ? 'sent offers' : 'received offers');
updateClearFiltersButton();
// Add event listeners for filter controls
const selectElements = filterForm.querySelectorAll('select');
selectElements.forEach(select => {
@ -2443,7 +2444,7 @@ document.addEventListener('DOMContentLoaded', () => {
updateClearFiltersButton();
});
});
filterForm.addEventListener('change', () => {
applyFilters();
updateClearFiltersButton();
@ -2506,7 +2507,7 @@ document.addEventListener('DOMContentLoaded', () => {
eventListeners.add(document.getElementById('refreshOffers'), 'click', async () => {
console.log('Manual refresh initiated');
const refreshButton = document.getElementById('refreshOffers');
const refreshIcon = document.getElementById('refreshIcon');
const refreshText = document.getElementById('refreshText');
@ -2529,13 +2530,13 @@ document.addEventListener('DOMContentLoaded', () => {
jsonData = formatInitialData(processedNewData);
originalJsonData = [...jsonData];
await updateOffersTable();
updateJsonView();
updatePaginationInfo();
console.log(' Manual refresh completed successfully');
} catch (error) {
console.error('Error during manual refresh:', error);
ui.displayErrorMessage('Failed to refresh offers. Please try again later.');

View file

@ -170,7 +170,7 @@ function getWebSocketConfig() {
'PIVX': {'name': 'PIVX', 'symbol': 'PIVX', 'image': 'PIVX.png', 'show': true},
'DASH': {'name': 'Dash', 'symbol': 'DASH', 'image': 'Dash.png', 'show': true},
'ETH': {'name': 'Ethereum', 'symbol': 'ETH', 'image': 'Ethereum.png', 'show': false},
'DOGE': {'name': 'Dogecoin', 'symbol': 'DOGE', 'image': 'Doge.png', 'show': false},
'DOGE': {'name': 'Dogecoin', 'symbol': 'DOGE', 'image': 'Doge.png', 'show': true},
'DCR': {'name': 'Decred', 'symbol': 'DCR', 'image': 'Decred.png', 'show': true},
'ZANO': {'name': 'Zano', 'symbol': 'ZANO', 'image': 'Zano.png', 'show': false},
'WOW': {'name': 'Wownero', 'symbol': 'WOW', 'image': 'Wownero.png', 'show': true}

View file

@ -879,6 +879,7 @@ const coinNameToSymbol = {
'Monero': 'XMR',
'Wownero': 'WOW',
'Litecoin': 'LTC',
'Dogecoin': 'DOGE',
'Firo': 'FIRO',
'Dash': 'DASH',
'PIVX': 'PIVX',

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBGZeJLEBDADPy6SAx5JEA00ft1Lfv0Luy0/r2/9gH0qf+eJWCAZHltnGTt7f
exSY81Lq9UnCwrAOglkUTkMRnW/RDHEi+DEr4QRSwomq6F/J6VjmJnq02b1O/xSw
nW9EO2dOUjqSasOA+h16QBeTzod7PhkEH3acKWsWx9EraCukp9OAe7rhuMXRCkVj
CHVGqKnHcQGRHG/DlRtKRzHK/OJuki3tzr4z/DWqbdvBPJahpkiH6sjY6RzQ7IIk
WJoqjUyl5+KbVQ/nb2QDfvmbc2Ivn5wH5sOa1vblJsNsCCNhEwsLPaiaieZHNDhp
to9F93v9wxVQOKXu39+tblabs9tpfpkka2z1osAT7Ut6n2cbkw0i95suKqlxyO+3
Fe/V1Uv+WekFq6ijcX36ZA3/lmT3d9tnWkw+F9c5OalipoHxxymNzsD/sU1FIMJJ
dnOaO99Rc5X7gRPagYzliZXgkZthB0TcO65y+oxwieOYnbQIVAgWQIz6TKCOrv6T
ZC07NPkTc0uNvcMAEQEAAbQaeGFuaW1vIDxkYWtvZGFAeGFuaW1vLm5ldD6JAdQE
EwEKAD4WIQQuqosQIcca1RhsoH9ujxfBsbzcvgUCZl4ksQIbAwUJA8JnAAULCQgH
AgYVCgkICwIEFgIDAQIeAQIXgAAKCRBujxfBsbzcvqxmC/45/OsRL14S6G8DrxsC
/Awrke/OYDlmOrvBnXRQOlxzmj6lPFhIT3pkowi59wokRs+9wynqt5Pm3z90/d+2
jW1r5Hucm+PQmZUu2wIbVB0L4f6baBxKrucbQfqBqBMZ5p+D8IJJV+9ZKn00r4nq
7ahq7e4nWH3YN+G2RrR4mRpUyIUIGJLcR5YL1MQ3Q/rC0+u056KiXBv29vY++K4R
gpKQOWPFIxeK/Pl2BNZ18JfTwXeM9lZQSabgtehXshOAERLjf1KRL+X4QLc4tok5
lYwQwSTp3sK4erTAGCY3Exe6M0TC9xeyR1241YgtvAYWdFkcVPpfJl2SygWhnLzc
VFaPXYbz6RASRcCFKA3LCA6uWtdcbaCRRVPue+MeyabX+Cow74T/kTV2cYp/v1ds
XYTKd8VyFG6N2cwuvBKf5THXslT+6YFuE2Gw5vO2GuLvxai+Ny5b9bTE23l41JKW
Zp1MxGEcdezuwxjF4ZC/+oiQ1SJfUWBIUfB/4C1NRPL19U25AY0EZl4ksQEMAKf2
JMAKZ815s7Fxw6cHt7o2J2HAg1rMtY9GoRv54jCbvoc2sULvR3xeRsOD+Ii9N3TR
kDf0IRpfE6oUd+JudY8wzKfAdYLDhGk6zNtw98SmDaWauLYTkEL8NkfygPN1NowC
DRuiXVixlOVqZ1ZuLgJ74xVd6v1rRj+iyGwqGWe5YHWTfJlQ2LTcCYkXhBE5bpGS
EOhh1BnFI2JaEQ8W+TqisFz9kr/rEiiPvJcXPG2gBCVn+tOv+8CHaSK8ZcqFEhei
JPUBXCWGpWzSMSmZvC66fIfLcd/tmKwN41ZP97cnWZrKTGGmToaJNHPC7o6nLMyZ
oiSf1tqCD+ZkrLt3fEo5znTVtiyjXd4VMXBwVbruUgxDx+rjIUDNuOgYOudkZrRd
2ubNt6/hInePCMxgk5iJdGxZ90q2j1S2YDaFxjizcPtzmsyFoaiASWa+b5VoQT1D
pBD23J2oIZM1iUQOfI6H7VIMHl1Q/nm7+aSlGjoJACAz1nsei6XtzOzay59E4wAR
AQABiQG8BBgBCgAmFiEELqqLECHHGtUYbKB/bo8XwbG83L4FAmZeJLECGwwFCQPC
ZwAACgkQbo8XwbG83L7B0wwAqF9fGfrW2c3Y+Q3wfj0Euhs/gQw5vInN9nG8P8Cr
XMftO7s54lWrC/av5AMM17ltbmReVWBukKKty4nD5clKBsqlRU4UVk0gwdSceEZ0
HzILQVeJCv+1QtDWgbbCv+LK/alPbfTT5gNLPsFrD0S0gvm2CxJ7WfYCU5To6Qi1
QtQUZViCsKe1iKdi+VWUn56rUKGePgL1FpGAGMfZRvaLhk5bs5076EIS5ihEppvm
PAko2Mr+eO9aIy6NY/i5B+lMZcp2QGDofSTuFt3JE+GBiw8TQtIfN1rEpY/sKqCR
IR+K0MZ/2ifp8uUeH2NMTU1iQ49w8x2kpNVX7SR1KXiwLdAVItZNkGZQry3UwEm1
RhVeiO3c7Jdalgpr1dhEIi7dUFhcF7QEBs/fGNnId1jadAF9EdHDtFLoA0BFIeTw
ub29S0WSw+nidqYwhzDLMHMsGG3p1U5aKxfJA3PFTRe6iYEjI7O5tOZGxpVbIJBU
tS35OCTSJzNMoXtTZqCkDLc9
=Z8rt
-----END PGP PUBLIC KEY BLOCK-----

View file

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

View file

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

View file

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

View file

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

View file

@ -147,6 +147,8 @@ def wait_for_bids(delay_event, node_id, num_bids, offer_id=None) -> None:
logging.info(f"Waiting for {num_bids} bids on node {node_id}")
for i in range(20):
delay_event.wait(1)
if delay_event.is_set():
raise ValueError("Test stopped.")
if offer_id is not None:
bids = read_json_api(UI_PORT + node_id, "bids", {"offer_id": offer_id})
else:

View file

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

View file

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

View file

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

View file

@ -377,6 +377,9 @@ class TestFunctions(BaseTest):
id_offerer: int = self.node_a_id
id_bidder: int = self.node_b_id
abandon_all_swaps(test_delay_event, self.swap_clients[id_offerer])
abandon_all_swaps(test_delay_event, self.swap_clients[id_bidder])
swap_clients = self.swap_clients
reverse_bid: bool = swap_clients[0].is_reverse_ads_bid(coin_from, coin_to)
ci_from = swap_clients[id_offerer].ci(coin_from)
@ -1144,8 +1147,8 @@ class BasicSwapTest(TestFunctions):
# fee_rate is in sats/kvB
fee_rate: int = 1000
a = ci.getNewSecretKey()
b = ci.getNewSecretKey()
a = ci.getNewRandomKey()
b = ci.getNewRandomKey()
A = ci.getPubkey(a)
B = ci.getPubkey(b)
@ -1214,8 +1217,8 @@ class BasicSwapTest(TestFunctions):
assert expect_vsize - vsize_actual < 10
# Test chain b (no-script) lock tx size
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)

View file

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

View file

@ -141,9 +141,9 @@ class Test(BaseTest):
# fee_rate is in sats/kvB
fee_rate: int = 1000
vkbv = ci.getNewSecretKey()
a = ci.getNewSecretKey()
b = ci.getNewSecretKey()
vkbv = ci.getNewRandomKey()
a = ci.getNewRandomKey()
b = ci.getNewRandomKey()
A = ci.getPubkey(a)
B = ci.getPubkey(b)
@ -210,8 +210,8 @@ class Test(BaseTest):
assert ci.rpc("sendrawtransaction", [lock_spend_tx.hex()]) == txid
# Test chain b (no-script) lock tx size
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)

View file

@ -1077,8 +1077,8 @@ class Test(BaseTest):
# fee_rate is in sats/kvB
fee_rate: int = 1000
a = ci.getNewSecretKey()
b = ci.getNewSecretKey()
a = ci.getNewRandomKey()
b = ci.getNewRandomKey()
A = ci.getPubkey(a)
B = ci.getPubkey(b)
@ -1147,8 +1147,8 @@ class Test(BaseTest):
assert expect_vsize - vsize_actual < 10
# Test chain b (no-script) lock tx size
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
@ -1176,8 +1176,8 @@ class Test(BaseTest):
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
fee_rate: int = 1000 # TODO: How to set feerate for rpc functions?
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)
@ -2083,8 +2083,8 @@ class Test(BaseTest):
ci = swap_clients[1].ci(Coins.PART_ANON)
amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1)
fee_rate: int = 1000
v = ci.getNewSecretKey()
s = ci.getNewSecretKey()
v = ci.getNewRandomKey()
s = ci.getNewRandomKey()
S = ci.getPubkey(s)
lock_tx_b_txid = ci.publishBLockTx(v, S, amount, fee_rate)