wownero: integration

This commit is contained in:
nahuhh 2024-03-31 17:47:14 -05:00 committed by tecnovert
parent c3cd1871ef
commit ee2f462ee9
27 changed files with 1251 additions and 88 deletions

View file

@ -34,6 +34,7 @@ from .interface.part import PARTInterface, PARTInterfaceAnon, PARTInterfaceBlind
from . import __version__
from .rpc import escape_rpcauth
from .rpc_xmr import make_xmr_rpc2_func
from .rpc_wow import make_wow_rpc2_func
from .ui.util import getCoinName, known_chart_coins
from .util import (
AutomationConstraint,
@ -180,6 +181,21 @@ def threadPollXMRChainState(swap_client, coin_type):
swap_client.chainstate_delay_event.wait(random.randrange(20, 30)) # Random to stagger updates
def threadPollWOWChainState(swap_client, coin_type):
ci = swap_client.ci(coin_type)
cc = swap_client.coin_clients[coin_type]
while not swap_client.chainstate_delay_event.is_set():
try:
new_height = ci.getChainHeight()
if new_height != cc['chain_height']:
swap_client.log.debug('New {} block at height: {}'.format(ci.ticker(), new_height))
with swap_client.mxDB:
cc['chain_height'] = new_height
except Exception as e:
swap_client.log.warning('threadPollWOWChainState {}, error: {}'.format(ci.ticker(), str(e)))
swap_client.chainstate_delay_event.wait(random.randrange(20, 30)) # Random to stagger updates
def threadPollChainState(swap_client, coin_type):
ci = swap_client.ci(coin_type)
cc = swap_client.coin_clients[coin_type]
@ -281,7 +297,7 @@ class BasicSwap(BaseApp):
# TODO: Set dynamically
self.balance_only_coins = (Coins.LTC_MWEB, )
self.scriptless_coins = (Coins.XMR, Coins.PART_ANON, Coins.FIRO)
self.scriptless_coins = (Coins.XMR, Coins.WOW, Coins.PART_ANON, Coins.FIRO)
self.adaptor_swap_only_coins = self.scriptless_coins + (Coins.PART_BLIND, )
self.coins_without_segwit = (Coins.PIVX, Coins.DASH, Coins.NMC)
@ -523,7 +539,7 @@ class BasicSwap(BaseApp):
if self.coin_clients[coin]['connection_type'] == 'rpc':
if coin == Coins.DCR:
self.coin_clients[coin]['walletrpcport'] = chain_client_settings['walletrpcport']
elif coin == Coins.XMR:
elif coin in (Coins.XMR, Coins.WOW):
self.coin_clients[coin]['rpctimeout'] = chain_client_settings.get('rpctimeout', 60)
self.coin_clients[coin]['walletrpctimeout'] = chain_client_settings.get('walletrpctimeout', 120)
self.coin_clients[coin]['walletrpctimeoutlong'] = chain_client_settings.get('walletrpctimeoutlong', 600)
@ -561,9 +577,9 @@ class BasicSwap(BaseApp):
if self.use_tor_proxy:
have_cc_tor_opt = 'use_tor' in chain_client_settings
if have_cc_tor_opt and chain_client_settings['use_tor'] is False:
self.log.warning('use_tor is true for system but false for XMR.')
self.log.warning('use_tor is true for system but false for ' + coin + '.')
elif have_cc_tor_opt is False and is_private_ip_address(node_host):
self.log.warning(f'Not using proxy for XMR node at private ip address {node_host}.')
self.log.warning(f'Not using proxy for {coin} node at private ip address {node_host}.')
else:
proxy_host = self.tor_proxy_host
proxy_port = self.tor_proxy_port
@ -585,7 +601,10 @@ class BasicSwap(BaseApp):
if proxy_host:
self.log.info(f'Connecting through proxy at {proxy_host}.')
return make_xmr_rpc2_func(rpcport, daemon_login, rpchost, proxy_host=proxy_host, proxy_port=proxy_port)
if coin == Coins.XMR:
return make_xmr_rpc2_func(rpcport, daemon_login, rpchost, proxy_host=proxy_host, proxy_port=proxy_port)
if coin == Coins.WOW:
return make_wow_rpc2_func(rpcport, daemon_login, rpchost, proxy_host=proxy_host, proxy_port=proxy_port)
daemon_login = None
if coin_settings.get('rpcuser', '') != '':
@ -687,6 +706,12 @@ class BasicSwap(BaseApp):
chain_client_settings = self.getChainClientSettings(coin)
xmr_i.setWalletFilename(chain_client_settings['walletfile'])
return xmr_i
elif coin == Coins.WOW:
from .interface.wow import WOWInterface
wow_i = WOWInterface(self.coin_clients[coin], self.chain, self)
chain_client_settings = self.getChainClientSettings(coin)
wow_i.setWalletFilename(chain_client_settings['walletfile'])
return wow_i
elif coin == Coins.PIVX:
from .interface.pivx import PIVXInterface
return PIVXInterface(self.coin_clients[coin], self.chain, self)
@ -711,7 +736,7 @@ class BasicSwap(BaseApp):
def setCoinRunParams(self, coin):
cc = self.coin_clients[coin]
if coin == Coins.XMR:
if coin in (Coins.XMR, Coins.WOW):
return
if cc['connection_type'] == 'rpc' and cc['rpcauth'] is None:
chain_client_settings = self.getChainClientSettings(coin)
@ -782,8 +807,14 @@ class BasicSwap(BaseApp):
core_version = ci.getDaemonVersion()
self.log.info('%s Core version %d', ci.coin_name(), core_version)
self.coin_clients[c]['core_version'] = core_version
# thread_func = threadPollXMRChainState if c in (Coins.XMR, Coins.WOW) else threadPollChainState
if c == Coins.XMR:
thread_func = threadPollXMRChainState
elif c == Coins.WOW:
thread_func = threadPollWOWChainState
else:
thread_func = threadPollChainState
thread_func = threadPollXMRChainState if c == Coins.XMR else threadPollChainState
t = threading.Thread(target=thread_func, args=(self, c))
self.threads.append(t)
t.start()
@ -808,6 +839,12 @@ class BasicSwap(BaseApp):
except Exception as e:
self.log.warning('Can\'t open XMR wallet, could be locked.')
continue
elif c == Coins.WOW:
try:
ci.ensureWalletExists()
except Exception as e:
self.log.warning('Can\'t open WOW wallet, could be locked.')
continue
elif c == Coins.LTC:
ci_mweb = self.ci(Coins.LTC_MWEB)
is_encrypted, _ = self.getLockedState()
@ -858,7 +895,7 @@ class BasicSwap(BaseApp):
self.log.info('Scanned %d unread messages.', nm)
def stopDaemon(self, coin) -> None:
if coin in (Coins.XMR, Coins.DCR):
if coin in (Coins.XMR, Coins.DCR, Coins.WOW):
return
num_tries = 10
authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie')
@ -897,7 +934,7 @@ class BasicSwap(BaseApp):
if with_wallet:
self.waitForDaemonRPC(coin_type, with_wallet=False)
if coin_type in (Coins.XMR,):
if coin_type in (Coins.XMR, Coins.WOW):
return
ci = self.ci(coin_type)
# checkWallets can adjust the wallet name.
@ -932,7 +969,7 @@ class BasicSwap(BaseApp):
raise ValueError('{} has an unexpected wallet seed and "restrict_unknown_seed_wallets" is enabled.'.format(ci.coin_name()))
if self.coin_clients[c]['connection_type'] != 'rpc':
continue
if c == Coins.XMR:
if c in (Coins.XMR, Coins.WOW):
continue # TODO
synced = round(ci.getBlockchainInfo()['verificationprogress'], 3)
if synced < 1.0:
@ -1035,7 +1072,7 @@ class BasicSwap(BaseApp):
db_key_coin_name = ci.coin_name().lower()
self.log.info('Initialising {} wallet.'.format(ci.coin_name()))
if coin_type == Coins.XMR:
if coin_type in (Coins.XMR, Coins.WOW):
key_view = self.getWalletKey(coin_type, 1, for_ed25519=True)
key_spend = self.getWalletKey(coin_type, 2, for_ed25519=True)
ci.initialiseWallet(key_view, key_spend)
@ -1951,7 +1988,7 @@ class BasicSwap(BaseApp):
return self.ci(coin_type).get_fee_rate(conf_target)
def estimateWithdrawFee(self, coin_type, fee_rate):
if coin_type == Coins.XMR:
if coin_type in (Coins.XMR, Coins.WOW):
# Fee estimate must be manually initiated
return None
tx_vsize = self.ci(coin_type).getHTLCSpendTxVSize()
@ -1960,7 +1997,7 @@ class BasicSwap(BaseApp):
def withdrawCoin(self, coin_type, value, addr_to, subfee: bool) -> str:
ci = self.ci(coin_type)
if subfee and coin_type == Coins.XMR:
if subfee and coin_type in (Coins.XMR, Coins.WOW):
self.log.info('withdrawCoin sweep all {} to {}'.format(ci.ticker(), addr_to))
else:
self.log.info('withdrawCoin {} {} to {} {}'.format(value, ci.ticker(), addr_to, ' subfee' if subfee else ''))
@ -2012,7 +2049,7 @@ class BasicSwap(BaseApp):
if c == Coins.PART:
ci.setWalletSeedWarning(False) # All keys should be be derived from the Particl mnemonic
return True # TODO
if c == Coins.XMR:
if c in (Coins.XMR, Coins.WOW):
expect_address = self.getCachedMainWalletAddress(ci)
if expect_address is None:
self.log.warning('Can\'t find expected main wallet address for coin {}'.format(ci.coin_name()))
@ -2056,7 +2093,7 @@ class BasicSwap(BaseApp):
# TODO: How to scan pruned blocks?
if not self.checkWalletSeed(coin_type):
if coin_type == Coins.XMR:
if coin_type in (Coins.XMR, Coins.WOW):
raise ValueError('TODO: How to reseed XMR wallet?')
else:
raise ValueError('Wallet seed doesn\'t match expected.')
@ -5865,7 +5902,7 @@ class BasicSwap(BaseApp):
kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSL, for_ed25519)
vkbs = ci_to.sumKeys(kbsl, kbsf)
if coin_to == Coins.XMR:
if coin_to == (Coins.XMR, Coins.WOW):
address_to = self.getCachedMainWalletAddress(ci_to, session)
elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON):
address_to = self.getCachedStealthAddressForCoin(coin_to, session)
@ -5932,7 +5969,7 @@ class BasicSwap(BaseApp):
vkbs = ci_to.sumKeys(kbsl, kbsf)
try:
if offer.coin_to == Coins.XMR:
if offer.coin_to in (Coins.XMR, Coins.WOW):
address_to = self.getCachedMainWalletAddress(ci_to, session)
elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON):
address_to = self.getCachedStealthAddressForCoin(coin_to, session)
@ -6984,7 +7021,7 @@ class BasicSwap(BaseApp):
rv['anon_pending'] = walletinfo['unconfirmed_anon'] + walletinfo['immature_anon_balance']
rv['blind_balance'] = walletinfo['blind_balance']
rv['blind_unconfirmed'] = walletinfo['unconfirmed_blind']
elif coin == Coins.XMR:
elif coin in (Coins.XMR, Coins.WOW):
rv['main_address'] = self.getCachedMainWalletAddress(ci)
elif coin == Coins.NAV:
rv['immature'] = walletinfo['immature_balance']

View file

@ -10,6 +10,7 @@ from .util import (
)
XMR_COIN = 10 ** 12
WOW_COIN = 10 ** 11
class Coins(IntEnum):
@ -21,13 +22,14 @@ class Coins(IntEnum):
XMR = 6
PART_BLIND = 7
PART_ANON = 8
# ZANO = 9
WOW = 9
# NDAU = 10
PIVX = 11
DASH = 12
FIRO = 13
NAV = 14
LTC_MWEB = 15
# ZANO = 16 was 9
chainparams = {
@ -247,6 +249,33 @@ chainparams = {
'address_prefix': 18,
}
},
Coins.WOW: {
'name': 'wownero',
'ticker': 'WOW',
'client': 'wow',
'decimal_places': 11,
'mainnet': {
'rpcport': 34568,
'walletrpcport': 34572, # todo
'min_amount': 100000,
'max_amount': 10000 * WOW_COIN,
'address_prefix': 4146,
},
'testnet': {
'rpcport': 44568,
'walletrpcport': 44572,
'min_amount': 100000,
'max_amount': 10000 * WOW_COIN,
'address_prefix': 4146,
},
'regtest': {
'rpcport': 54568,
'walletrpcport': 54572,
'min_amount': 100000,
'max_amount': 10000 * WOW_COIN,
'address_prefix': 4146,
}
},
Coins.PIVX: {
'name': 'pivx',
'ticker': 'PIVX',

View file

@ -36,3 +36,7 @@ NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix)
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
WOW_BINDIR = os.path.expanduser(os.getenv('WOW_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'wownero')))
WOWD = os.getenv('WOWD', 'wownerod' + bin_suffix)
WOW_WALLET_RPC = os.getenv('WOW_WALLET_RPC', 'wownero-wallet-rpc' + bin_suffix)

View file

@ -311,6 +311,22 @@ class HttpHandler(BaseHTTPRequestHandler):
else:
raise ValueError('Unknown RPC variant')
result = json.dumps(rv, indent=4)
elif coin_type == Coins.WOW:
ci = swap_client.ci(coin_type)
arr = cmd.split(None, 1)
method = arr[0]
params = json.loads(arr[1]) if len(arr) > 1 else []
if coin_id == -8:
rv = ci.rpc_wallet(method, params)
elif coin_id == -7:
rv = ci.rpc(method, params)
elif coin_id == -6:
if params == []:
params = None
rv = ci.rpc2(method, params)
else:
raise ValueError('Unknown WOW RPC variant')
result = json.dumps(rv, indent=4)
else:
if call_type == 'http':
ci = swap_client.ci(coin_type)
@ -338,6 +354,7 @@ class HttpHandler(BaseHTTPRequestHandler):
coin_available = listAvailableCoins(swap_client, with_variants=False)
with_xmr: bool = any(c[0] == Coins.XMR for c in coin_available)
with_wow: bool = any(c[0] == Coins.WOW for c in coin_available)
coins = [(str(c[0]) + ',0', c[1]) for c in coin_available if c[0] not in (Coins.XMR, )]
if any(c[0] == Coins.DCR for c in coin_available):
@ -348,6 +365,10 @@ class HttpHandler(BaseHTTPRequestHandler):
coins.append((str(int(Coins.XMR)) + ',0', 'Monero'))
coins.append((str(int(Coins.XMR)) + ',1', 'Monero JSON'))
coins.append((str(int(Coins.XMR)) + ',2', 'Monero Wallet'))
if with_wow:
coins.append((str(int(Coins.WOW)) + ',0', 'Wownero'))
coins.append((str(int(Coins.WOW)) + ',1', 'Wownero JSON'))
coins.append((str(int(Coins.WOW)) + ',2', 'Wownero Wallet'))
return self.render_template(template, {
'messages': messages,

577
basicswap/interface/wow.py Normal file
View file

@ -0,0 +1,577 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2024 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json
import logging
import basicswap.contrib.ed25519_fast as edf
import basicswap.ed25519_fast_util as edu
import basicswap.util_xmr as wow_util
from coincurve.ed25519 import (
ed25519_add,
ed25519_get_pubkey,
ed25519_scalar_add,
)
from coincurve.keys import PrivateKey
from coincurve.dleag import (
dleag_prove,
dleag_verify,
dleag_proof_len,
verify_ed25519_point,
)
from basicswap.interface import (
Curves)
from basicswap.util import (
i2b, b2i, b2h,
dumpj,
ensure,
make_int,
TemporaryError)
from basicswap.util.network import (
is_private_ip_address)
from basicswap.rpc_wow import (
make_wow_rpc_func,
make_wow_rpc2_func)
from basicswap.chainparams import WOW_COIN, CoinInterface, Coins
class WOWInterface(CoinInterface):
@staticmethod
def curve_type():
return Curves.ed25519
@staticmethod
def coin_type():
return Coins.WOW
@staticmethod
def COIN():
return WOW_COIN
@staticmethod
def exp() -> int:
return 11
@staticmethod
def nbk() -> int:
return 32
@staticmethod
def nbK() -> int: # No. of bytes requires to encode a public key
return 32
@staticmethod
def depth_spendable() -> int:
return 10
@staticmethod
def xmr_swap_a_lock_spend_tx_vsize() -> int:
raise ValueError('Not possible')
@staticmethod
def xmr_swap_b_lock_spend_tx_vsize() -> int:
# TODO: Estimate with ringsize
return 1604
def __init__(self, coin_settings, network, swap_client=None):
super().__init__(network)
self._addr_prefix = self.chainparams_network()['address_prefix']
self.blocks_confirmed = coin_settings['blocks_confirmed']
self._restore_height = coin_settings.get('restore_height', 0)
self.setFeePriority(coin_settings.get('fee_priority', 0))
self._sc = swap_client
self._log = self._sc.log if self._sc and self._sc.log else logging
self._wallet_password = None
self._have_checked_seed = False
daemon_login = None
if coin_settings.get('rpcuser', '') != '':
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', ''))
rpchost = coin_settings.get('rpchost', '127.0.0.1')
proxy_host = None
proxy_port = None
# Connect to the daemon over a proxy if not running locally
if swap_client:
chain_client_settings = swap_client.getChainClientSettings(self.coin_type())
manage_daemon: bool = chain_client_settings['manage_daemon']
if swap_client.use_tor_proxy:
if manage_daemon is False:
log_str: str = ''
have_cc_tor_opt = 'use_tor' in chain_client_settings
if have_cc_tor_opt and chain_client_settings['use_tor'] is False:
log_str = ' bypassing proxy (use_tor false for WOW)'
elif have_cc_tor_opt is False and is_private_ip_address(rpchost):
log_str = ' bypassing proxy (private ip address)'
else:
proxy_host = swap_client.tor_proxy_host
proxy_port = swap_client.tor_proxy_port
log_str = f' through proxy at {proxy_host}'
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}.')
else:
self._log.info(f'Not connecting to local {self.coin_name()} daemon through proxy.')
elif manage_daemon is False:
self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}.')
self._rpctimeout = coin_settings.get('rpctimeout', 60)
self._walletrpctimeout = coin_settings.get('walletrpctimeout', 120)
self._walletrpctimeoutlong = coin_settings.get('walletrpctimeoutlong', 600)
self.rpc = make_wow_rpc_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node(j) ')
self.rpc2 = make_wow_rpc2_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node ') # non-json endpoint
self.rpc_wallet = make_wow_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'), default_timeout=self._walletrpctimeout, tag='Wallet ')
def checkWallets(self) -> int:
return 1
def setFeePriority(self, new_priority):
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))
def openWallet(self, filename):
params = {'filename': filename}
if self._wallet_password is not None:
params['password'] = self._wallet_password
try:
# Can't reopen the same wallet in windows, !is_keys_file_locked()
self.rpc_wallet('close_wallet')
except Exception:
pass
self.rpc_wallet('open_wallet', params)
def initialiseWallet(self, key_view, key_spend, restore_height=None):
with self._mx_wallet:
try:
self.openWallet(self._wallet_filename)
# TODO: Check address
return # Wallet exists
except Exception as e:
pass
Kbv = self.getPubkey(key_view)
Kbs = self.getPubkey(key_spend)
address_b58 = wow_util.encode_address(Kbv, Kbs, self._addr_prefix)
params = {
'filename': self._wallet_filename,
'address': address_b58,
'viewkey': b2h(key_view[::-1]),
'spendkey': b2h(key_spend[::-1]),
'restore_height': self._restore_height,
}
self.createWallet(params)
self.openWallet(self._wallet_filename)
def ensureWalletExists(self) -> None:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
def testDaemonRPC(self, with_wallet=True) -> None:
self.rpc_wallet('get_languages')
def getDaemonVersion(self):
return self.rpc_wallet('get_version')['version']
def getBlockchainInfo(self):
get_height = self.rpc2('get_height', timeout=self._rpctimeout)
rv = {
'blocks': get_height['height'],
'verificationprogress': 0.0,
}
try:
# get_block_count.block_count is how many blocks are in the longest chain known to the node.
# get_block_count returns "Internal error" if bootstrap-daemon is active
if get_height['untrusted'] is True:
rv['bootstrapping'] = True
get_info = self.rpc2('get_info', timeout=self._rpctimeout)
if 'height_without_bootstrap' in get_info:
rv['blocks'] = get_info['height_without_bootstrap']
rv['known_block_count'] = get_info['height']
if rv['known_block_count'] > rv['blocks']:
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
else:
rv['known_block_count'] = self.rpc('get_block_count', timeout=self._rpctimeout)['count']
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
except Exception as e:
self._log.warning('WOW get_block_count failed with: %s', str(e))
rv['verificationprogress'] = 0.0
return rv
def getChainHeight(self):
return self.rpc2('get_height', timeout=self._rpctimeout)['height']
def getWalletInfo(self):
with self._mx_wallet:
try:
self.openWallet(self._wallet_filename)
except Exception as e:
if 'Failed to open wallet' in str(e):
rv = {'encrypted': True, 'locked': True, 'balance': 0, 'unconfirmed_balance': 0}
return rv
raise e
rv = {}
self.rpc_wallet('refresh')
balance_info = self.rpc_wallet('get_balance')
rv['balance'] = self.format_amount(balance_info['unlocked_balance'])
rv['unconfirmed_balance'] = self.format_amount(balance_info['balance'] - balance_info['unlocked_balance'])
rv['encrypted'] = False if self._wallet_password is None else True
rv['locked'] = False
return rv
def walletRestoreHeight(self):
return self._restore_height
def getMainWalletAddress(self) -> str:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
return self.rpc_wallet('get_address')['address']
def getNewAddress(self, placeholder) -> str:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
new_address = self.rpc_wallet('create_address', {'account_index': 0})['address']
self.rpc_wallet('store')
return new_address
def get_fee_rate(self, conf_target: int = 2):
# fees - array of unsigned int; Represents the base fees at different priorities [slow, normal, fast, fastest].
fee_est = self.rpc('get_fee_estimate')
if conf_target <= 1:
conf_target = 1 # normal
else:
conf_target = 0 # slow
fee_per_k_bytes = fee_est['fees'][conf_target] * 1000
return float(self.format_amount(fee_per_k_bytes)), 'get_fee_estimate'
def getNewSecretKey(self) -> bytes:
# Note: Returned bytes are in big endian order
return i2b(edu.get_secret())
def pubkey(self, key: bytes) -> bytes:
return edf.scalarmult_B(key)
def encodeKey(self, vk: bytes) -> str:
return vk[::-1].hex()
def decodeKey(self, k_hex: str) -> bytes:
return bytes.fromhex(k_hex)[::-1]
def encodePubkey(self, pk: bytes) -> str:
return edu.encodepoint(pk)
def decodePubkey(self, pke):
return edf.decodepoint(pke)
def getPubkey(self, privkey):
return ed25519_get_pubkey(privkey)
def getAddressFromKeys(self, key_view: bytes, key_spend: bytes) -> str:
pk_view = self.getPubkey(key_view)
pk_spend = self.getPubkey(key_spend)
return wow_util.encode_address(pk_view, pk_spend, self._addr_prefix)
def verifyKey(self, k: int) -> bool:
i = b2i(k)
return (i < edf.l and i > 8)
def verifyPubkey(self, pubkey_bytes):
# Calls ed25519_decode_check_point() in secp256k1
# Checks for small order
return verify_ed25519_point(pubkey_bytes)
def proveDLEAG(self, key: bytes) -> bytes:
privkey = PrivateKey(key)
return dleag_prove(privkey)
def verifyDLEAG(self, dleag_bytes: bytes) -> bool:
return dleag_verify(dleag_bytes)
def lengthDLEAG(self) -> int:
return dleag_proof_len()
def sumKeys(self, ka: bytes, kb: bytes) -> bytes:
return ed25519_scalar_add(ka, kb)
def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes:
return ed25519_add(Ka, Kb)
def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str:
return wow_util.encode_address(Kbv, Kbs, self._addr_prefix)
def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, unlock_time: int = 0) -> bytes:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh')
Kbv = self.getPubkey(kbv)
shared_addr = wow_util.encode_address(Kbv, Kbs, self._addr_prefix)
params = {'destinations': [{'amount': output_amount, 'address': shared_addr}], 'unlock_time': unlock_time}
if self._fee_priority > 0:
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)
tx_hash = bytes.fromhex(rv['tx_hash'])
return tx_hash
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
with self._mx_wallet:
Kbv = self.getPubkey(kbv)
address_b58 = wow_util.encode_address(Kbv, Kbs, self._addr_prefix)
kbv_le = kbv[::-1]
params = {
'restore_height': restore_height,
'filename': address_b58,
'address': address_b58,
'viewkey': b2h(kbv_le),
}
try:
self.openWallet(address_b58)
except Exception as e:
self.createWallet(params)
self.openWallet(address_b58)
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
'''
# Debug
try:
current_height = self.rpc_wallet('get_height')['height']
self._log.info('findTxB WOW current_height %d\nAddress: %s', current_height, address_b58)
except Exception as e:
self._log.info('rpc failed %s', str(e))
current_height = None # If the transfer is available it will be deep enough
# and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
'''
params = {'transfer_type': 'available'}
transfers = self.rpc_wallet('incoming_transfers', params)
rv = None
if 'transfers' in transfers:
for transfer in transfers['transfers']:
# unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE
if not transfer['unlocked']:
full_tx = self.rpc_wallet('get_transfer_by_txid', {'txid': transfer['tx_hash']})
unlock_time = full_tx['transfer']['unlock_time']
if unlock_time != 0:
self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time))
rv = -1
continue
if transfer['amount'] == cb_swap_value:
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']}
else:
self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash']))
rv = -1
return rv
def findTxnByHash(self, txid):
with self._mx_wallet:
self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong)
try:
current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height']
self._log.info('findTxnByHash WOW current_height %d\nhash: %s', current_height, txid)
except Exception as e:
self._log.info('rpc failed %s', str(e))
current_height = None # If the transfer is available it will be deep enough
params = {'transfer_type': 'available'}
rv = self.rpc_wallet('incoming_transfers', params)
if 'transfers' in rv:
for transfer in rv['transfers']:
if transfer['tx_hash'] == txid \
and (current_height is None or current_height - transfer['block_height'] > self.blocks_confirmed):
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': transfer['block_height']}
return None
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
'''
Notes:
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
'''
with self._mx_wallet:
Kbv = self.getPubkey(kbv)
Kbs = self.getPubkey(kbs)
address_b58 = wow_util.encode_address(Kbv, Kbs, self._addr_prefix)
wallet_filename = address_b58 + '_spend'
params = {
'filename': wallet_filename,
'address': address_b58,
'viewkey': b2h(kbv[::-1]),
'spendkey': b2h(kbs[::-1]),
'restore_height': restore_height,
}
try:
self.openWallet(wallet_filename)
except Exception as e:
self.createWallet(params)
self.openWallet(wallet_filename)
self.rpc_wallet('refresh')
rv = self.rpc_wallet('get_balance')
if rv['balance'] < cb_swap_value:
self._log.warning('Balance is too low, checking for existing spend.')
txns = self.rpc_wallet('get_transfers', {'out': True})
if 'out' in txns:
txns = txns['out']
if len(txns) > 0:
txid = txns[0]['txid']
self._log.warning(f'spendBLockTx detected spending tx: {txid}.')
if txns[0]['address'] == address_b58:
return bytes.fromhex(txid)
self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value))
if not spend_actual_balance:
raise TemporaryError('Invalid balance')
if spend_actual_balance and rv['balance'] != cb_swap_value:
self._log.warning('Spending actual balance {}, not swap value {}.'.format(rv['balance'], cb_swap_value))
cb_swap_value = rv['balance']
if rv['unlocked_balance'] < cb_swap_value:
self._log.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock']))
raise TemporaryError('Invalid unlocked_balance')
params = {'address': address_to}
if self._fee_priority > 0:
params['priority'] = self._fee_priority
rv = self.rpc_wallet('sweep_all', params)
self._log.debug('sweep_all {}'.format(json.dumps(rv)))
return bytes.fromhex(rv['tx_hash_list'][0])
def withdrawCoin(self, value, addr_to: str, sweepall: bool, estimate_fee: bool = False) -> str:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh')
if sweepall:
balance = self.rpc_wallet('get_balance')
if balance['balance'] != balance['unlocked_balance']:
raise ValueError('Balance must be fully confirmed to use sweep all.')
self._log.info('WOW {} sweep_all.'.format('estimate fee' if estimate_fee else 'withdraw'))
self._log.debug('WOW balance: {}'.format(balance['balance']))
params = {'address': addr_to, 'do_not_relay': estimate_fee}
if self._fee_priority > 0:
params['priority'] = self._fee_priority
rv = self.rpc_wallet('sweep_all', params)
if estimate_fee:
return {'num_txns': len(rv['fee_list']), 'sum_amount': sum(rv['amount_list']), 'sum_fee': sum(rv['fee_list']), 'sum_weight': sum(rv['weight_list'])}
return rv['tx_hash_list'][0]
value_sats: int = make_int(value, self.exp())
params = {'destinations': [{'amount': value_sats, 'address': addr_to}], 'do_not_relay': estimate_fee}
if self._fee_priority > 0:
params['priority'] = self._fee_priority
rv = self.rpc_wallet('transfer', params)
if estimate_fee:
return {'num_txns': 1, 'sum_amount': rv['amount'], 'sum_fee': rv['fee'], 'sum_weight': rv['weight']}
return rv['tx_hash']
def estimateFee(self, value: int, addr_to: str, sweepall: bool) -> str:
return self.withdrawCoin(value, addr_to, sweepall, estimate_fee=True)
def showLockTransfers(self, kbv, Kbs, restore_height):
with self._mx_wallet:
try:
Kbv = self.getPubkey(kbv)
address_b58 = wow_util.encode_address(Kbv, Kbs, self._addr_prefix)
wallet_file = address_b58 + '_spend'
try:
self.openWallet(wallet_file)
except Exception:
wallet_file = address_b58
try:
self.openWallet(wallet_file)
except Exception:
self._log.info(f'showLockTransfers trying to create wallet for address {address_b58}.')
kbv_le = kbv[::-1]
params = {
'restore_height': restore_height,
'filename': address_b58,
'address': address_b58,
'viewkey': b2h(kbv_le),
}
self.createWallet(params)
self.openWallet(address_b58)
self.rpc_wallet('refresh')
rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True})
rv['filename'] = wallet_file
return rv
except Exception as e:
return {'error': str(e)}
def getSpendableBalance(self) -> int:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
self.rpc_wallet('refresh')
balance_info = self.rpc_wallet('get_balance')
return balance_info['unlocked_balance']
def changeWalletPassword(self, old_password, new_password):
self._log.info('changeWalletPassword - {}'.format(self.ticker()))
orig_password = self._wallet_password
if old_password != '':
self._wallet_password = old_password
try:
self.openWallet(self._wallet_filename)
self.rpc_wallet('change_wallet_password', {'old_password': old_password, 'new_password': new_password})
except Exception as e:
self._wallet_password = orig_password
raise e
def unlockWallet(self, password: str) -> None:
self._log.info('unlockWallet - {}'.format(self.ticker()))
self._wallet_password = password
if not self._have_checked_seed:
self._sc.checkWalletSeed(self.coin_type())
def lockWallet(self) -> None:
self._log.info('lockWallet - {}'.format(self.ticker()))
self._wallet_password = None
def isAddressMine(self, address):
# TODO
return True
def ensureFunds(self, amount: int) -> None:
if self.getSpendableBalance() < amount:
raise ValueError('Balance too low')
def getTransaction(self, txid: bytes):
return self.rpc2('get_transactions', {'txs_hashes': [txid.hex(), ]})

View file

@ -54,7 +54,7 @@ def withdraw_coin(swap_client, coin_type, post_string, is_json):
post_data = getFormData(post_string, is_json)
address = get_data_entry(post_data, 'address')
if coin_type == Coins.XMR:
if coin_type in (Coins.XMR, Coins.WOW):
value = None
sweepall = get_data_entry(post_data, 'sweepall')
if not isinstance(sweepall, bool):
@ -74,7 +74,7 @@ def withdraw_coin(swap_client, coin_type, post_string, is_json):
elif coin_type == Coins.LTC:
type_from = get_data_entry_or(post_data, 'type_from', 'plain')
txid_hex = swap_client.withdrawLTC(type_from, value, address, subfee)
elif coin_type == Coins.XMR:
elif coin_type in (Coins.XMR, Coins.WOW):
txid_hex = swap_client.withdrawCoin(coin_type, value, address, sweepall)
else:
txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee)
@ -685,7 +685,7 @@ def js_getcoinseed(self, url_split, post_string, is_json) -> bytes:
raise ValueError('Particl wallet seed is set from the Basicswap mnemonic.')
ci = swap_client.ci(coin)
if coin == Coins.XMR:
if coin in (Coins.XMR, Coins.WOW):
key_view = swap_client.getWalletKey(coin, 1, for_ed25519=True)
key_spend = swap_client.getWalletKey(coin, 2, for_ed25519=True)
address = ci.getAddressFromKeys(key_view, key_spend)

258
basicswap/rpc_wow.py Normal file
View file

@ -0,0 +1,258 @@
# -*- coding: utf-8 -*-
import os
import json
import socks
import time
import urllib
import hashlib
from xmlrpc.client import (
Fault,
Transport,
SafeTransport,
)
from sockshandler import SocksiPyConnection
from .util import jsonDecimal
class SocksTransport(Transport):
def set_proxy(self, proxy_host, proxy_port):
self.proxy_host = proxy_host
self.proxy_port = proxy_port
self.proxy_type = socks.PROXY_TYPE_SOCKS5
self.proxy_rdns = True
self.proxy_username = None
self.proxy_password = None
def make_connection(self, host):
# return an existing connection if possible. This allows
# HTTP/1.1 keep-alive.
if self._connection and host == self._connection[0]:
return self._connection[1]
# create a HTTP connection object from a host descriptor
chost, self._extra_headers, x509 = self.get_host_info(host)
self._connection = host, SocksiPyConnection(self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_username, self.proxy_password, chost)
return self._connection[1]
class JsonrpcDigest():
# __getattr__ complicates extending ServerProxy
def __init__(self, uri, transport=None, encoding=None, verbose=False,
allow_none=False, use_datetime=False, use_builtin_types=False,
*, context=None):
parsed = urllib.parse.urlparse(uri)
if parsed.scheme not in ('http', 'https'):
raise OSError('unsupported XML-RPC protocol')
self.__host = parsed.netloc
self.__handler = parsed.path
if transport is None:
handler = SafeTransport if parsed.scheme == 'https' else Transport
extra_kwargs = {}
transport = handler(use_datetime=use_datetime,
use_builtin_types=use_builtin_types,
**extra_kwargs)
self.__transport = transport
self.__encoding = encoding or 'utf-8'
self.__verbose = verbose
self.__allow_none = allow_none
self.__request_id = 0
def close(self):
if self.__transport is not None:
self.__transport.close()
def request_id(self):
return self.__request_id
def post_request(self, method, params, timeout=None):
try:
connection = self.__transport.make_connection(self.__host)
if timeout:
connection.timeout = timeout
headers = self.__transport._extra_headers[:]
connection.putrequest('POST', self.__handler)
headers.append(('Content-Type', 'application/json'))
headers.append(('User-Agent', 'jsonrpc'))
self.__transport.send_headers(connection, headers)
self.__transport.send_content(connection, '' if params is None else json.dumps(params, default=jsonDecimal).encode('utf-8'))
self.__request_id += 1
resp = connection.getresponse()
return resp.read()
except Fault:
raise
except Exception:
self.__transport.close()
raise
def json_request(self, request_body, username='', password='', timeout=None):
try:
connection = self.__transport.make_connection(self.__host)
if timeout:
connection.timeout = timeout
headers = self.__transport._extra_headers[:]
connection.putrequest('POST', self.__handler)
headers.append(('Content-Type', 'application/json'))
headers.append(('Connection', 'keep-alive'))
self.__transport.send_headers(connection, headers)
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
resp = connection.getresponse()
if resp.status == 401:
resp_headers = resp.getheaders()
v = resp.read()
algorithm = ''
realm = ''
nonce = ''
for h in resp_headers:
if h[0] != 'WWW-authenticate':
continue
fields = h[1].split(',')
for f in fields:
key, value = f.split('=', 1)
if key == 'algorithm' and value != 'MD5':
break
if key == 'realm':
realm = value.strip('"')
if key == 'nonce':
nonce = value.strip('"')
if realm != '' and nonce != '':
break
if realm == '' or nonce == '':
raise ValueError('Authenticate header not found.')
path = self.__handler
HA1 = hashlib.md5(f'{username}:{realm}:{password}'.encode('utf-8')).hexdigest()
http_method = 'POST'
HA2 = hashlib.md5(f'{http_method}:{path}'.encode('utf-8')).hexdigest()
ncvalue = '{:08x}'.format(1)
s = ncvalue.encode('utf-8')
s += nonce.encode('utf-8')
s += time.ctime().encode('utf-8')
s += os.urandom(8)
cnonce = (hashlib.sha1(s).hexdigest()[:16])
# MD5-SESS
HA1 = hashlib.md5(f'{HA1}:{nonce}:{cnonce}'.encode('utf-8')).hexdigest()
respdig = hashlib.md5(f'{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}'.encode('utf-8')).hexdigest()
header_value = f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{path}", response="{respdig}", algorithm="MD5-sess", qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
headers = self.__transport._extra_headers[:]
headers.append(('Authorization', header_value))
connection.putrequest('POST', self.__handler)
headers.append(('Content-Type', 'application/json'))
headers.append(('Connection', 'keep-alive'))
self.__transport.send_headers(connection, headers)
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
resp = connection.getresponse()
self.__request_id += 1
return resp.read()
except Fault:
raise
except Exception:
self.__transport.close()
raise
def callrpc_wow(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120, transport=None, tag=''):
# auth is a tuple: (username, password)
try:
if rpc_host.count('://') > 0:
url = '{}:{}/{}'.format(rpc_host, rpc_port, path)
else:
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
x = JsonrpcDigest(url, transport=transport)
request_body = {
'method': method,
'params': params,
'jsonrpc': '2.0',
'id': x.request_id()
}
if auth:
v = x.json_request(request_body, username=auth[0], password=auth[1], timeout=timeout)
else:
v = x.json_request(request_body, timeout=timeout)
x.close()
r = json.loads(v.decode('utf-8'))
except Exception as ex:
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
if 'error' in r and r['error'] is not None:
raise ValueError(tag + 'RPC error ' + str(r['error']))
return r['result']
def callrpc_wow2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120, transport=None, tag=''):
try:
if rpc_host.count('://') > 0:
url = '{}:{}/{}'.format(rpc_host, rpc_port, method)
else:
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method)
x = JsonrpcDigest(url, transport=transport)
if auth:
v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout)
else:
v = x.json_request(params, timeout=timeout)
x.close()
r = json.loads(v.decode('utf-8'))
except Exception as ex:
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
return r
def make_wow_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
port = port
auth = auth
host = host
transport = None
default_timeout = default_timeout
tag = tag
if proxy_host:
transport = SocksTransport()
transport.set_proxy(proxy_host, proxy_port)
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
nonlocal port, auth, host, transport, tag
return callrpc_wow2(port, method, params, auth=auth, rpc_host=host, timeout=timeout, transport=transport, tag=tag)
return rpc_func
def make_wow_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
port = port
auth = auth
host = host
transport = None
default_timeout = default_timeout
tag = tag
if proxy_host:
transport = SocksTransport()
transport.set_proxy(proxy_host, proxy_port)
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
nonlocal port, auth, host, transport, tag
return callrpc_wow(port, method, params, rpc_host=host, auth=auth, timeout=timeout, transport=transport, tag=tag)
return rpc_func

View file

@ -409,7 +409,7 @@
document.getElementById('get_rate_inferred_button').addEventListener('click', getRateInferred);
function set_swap_type_enabled(coin_from, coin_to, swap_type) {
const adaptor_sig_only_coins = ['6' /* XMR */, '8' /* PART_ANON */, '7' /* PART_BLIND */, '13' /* FIRO */];
const adaptor_sig_only_coins = ['6' /* XMR */,'9' /* WOW */, '8' /* PART_ANON */, '7' /* PART_BLIND */, '13' /* FIRO */];
const secret_hash_only_coins = ['11' /* PIVX */, '12' /* DASH */];
let make_hidden = false;
if (adaptor_sig_only_coins.includes(coin_from) || adaptor_sig_only_coins.includes(coin_to)) {

View file

@ -105,7 +105,7 @@
</tr>
{% endif %}
{% if c.manage_daemon is defined %}
{% if c.name == 'monero' %}
{% if c.name in ('wownero', 'monero') %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Manage Daemon</td>
<td class="py-3 px-6">
@ -175,7 +175,7 @@
</div>
</td>
</tr>
{% if c.name == 'monero' %}
{% if c.name in ('wownero', 'monero') %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Transaction Fee Priority</td>
<td class="py-3 px-6">

View file

@ -266,8 +266,8 @@
</div>
{% endif %}
{# / PART #}
{% if w.cid == '6' %}
{# XMR #}
{% 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">
@ -412,8 +412,8 @@
});
</script>
{% endif %}
{% if w.cid == '6' %}
{# XMR #}
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<script>
// Monero Sub
var moneroSubAddress = "{{ w.deposit_address }}";
@ -638,8 +638,7 @@
floatBalance = parseFloat(balance);
calculatedAmount = floatBalance * percent;
if (cid === '6' && percent === 1) {
if (cid in '6, 9' && percent === 1) {
amountInput.setAttribute('data-hidden', 'true');
amountInput.placeholder = 'Sweep All';
amountInput.value = '';
@ -654,8 +653,7 @@
amountInput.placeholder = '';
amountInput.disabled = false;
}
if (cid === '6' && percent === 1) {
if (cid in '6, 9' && percent === 1) {
var sweepAllCheckbox = document.getElementById('sweepall');
if (sweepAllCheckbox) {
sweepAllCheckbox.checked = true;
@ -674,7 +672,7 @@
</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
{% if w.cid == '6' %} {# XMR #}
{% 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> {% 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 %}> </td>
@ -753,17 +751,17 @@
<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="px-6">
<div class="flex flex-wrap justify-end">
{% if w.cid != '6' %}
{# !XMR #}
{% 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 == '6' %}
{# XMR #}
{% 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>
{% endif %}
{# / XMR #} <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>
{# / 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>
</div>
</div>
@ -772,8 +770,8 @@
</div>
</div>
</section>
{% if w.cid != '6' %}
{# !XMR #}
{% if w.cid not in '6, 9' %}
{# !XMR | WOW #}
{% if w.show_utxo_groups %}
<section class="p-6">
<div class="flex items-center">
@ -979,4 +977,4 @@
</script>
{% include 'footer.html' %}
</body>
</html>
</html>

View file

@ -213,11 +213,11 @@ const coinNameToSymbol = {
'Particl Blind': 'PART',
'Particl Anon': 'PART',
'Monero': 'XMR',
'Wownero': 'WOW',
'Litecoin': 'LTC',
'Firo': 'FIRO',
'Dash': 'DASH',
'PIVX': 'PIVX',
'Wownero': 'WOW',
'Decred': 'DCR',
'Zano': 'ZANO',
};

View file

@ -89,7 +89,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
page_data['coin_from'] = getCoinType(get_data_entry(form_data, 'coin_from'))
coin_from = Coins(page_data['coin_from'])
ci_from = swap_client.ci(coin_from)
if coin_from != Coins.XMR:
if coin_from not in (Coins.XMR, Coins.WOW):
page_data['fee_from_conf'] = ci_from._conf_target # Set default value
parsed_data['coin_from'] = coin_from
except Exception:
@ -99,7 +99,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
page_data['coin_to'] = getCoinType(get_data_entry(form_data, 'coin_to'))
coin_to = Coins(page_data['coin_to'])
ci_to = swap_client.ci(coin_to)
if coin_to != Coins.XMR:
if coin_to not in (Coins.XMR, Coins.WOW):
page_data['fee_to_conf'] = ci_to._conf_target # Set default value
parsed_data['coin_to'] = coin_to
except Exception:
@ -161,7 +161,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
page_data['swap_type'] = get_data_entry(form_data, 'swap_type')
parsed_data['swap_type'] = page_data['swap_type']
swap_type = swap_type_from_string(parsed_data['swap_type'])
elif parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON):
elif parsed_data['coin_to'] in (Coins.XMR, Coins.WOW, Coins.PART_ANON):
parsed_data['swap_type'] = strSwapType(SwapTypes.XMR_SWAP)
swap_type = SwapTypes.XMR_SWAP
else:
@ -243,7 +243,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
page_data['amt_from_lock_spend_tx_fee'] = ci_from.format_amount(lock_spend_tx_fee // ci_from.COIN())
page_data['tla_from'] = ci_from.ticker()
if ci_to == Coins.XMR:
if ci_to in (Coins.XMR, Coins.WOW):
if have_data_entry(form_data, 'fee_rate_to'):
page_data['to_fee_override'] = get_data_entry(form_data, 'fee_rate_to')
parsed_data['to_fee_override'] = page_data['to_fee_override']
@ -267,7 +267,7 @@ def postNewOfferFromParsed(swap_client, parsed_data):
if 'swap_type' in parsed_data:
str_swap_type = parsed_data['swap_type'].lower()
swap_type = swap_type_from_string(str_swap_type)
elif parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON):
elif parsed_data['coin_to'] in (Coins.XMR, Coins.WOW, Coins.PART_ANON):
swap_type = SwapTypes.XMR_SWAP
if swap_type == SwapTypes.XMR_SWAP:

View file

@ -57,7 +57,7 @@ def page_settings(self, url_split, post_string):
for name, c in swap_client.settings['chainclients'].items():
if have_data_entry(form_data, 'apply_' + name):
data = {'lookups': get_data_entry(form_data, 'lookups_' + name)}
if name == 'monero':
if name in ('monero', 'wownero'):
data['fee_priority'] = int(get_data_entry(form_data, 'fee_priority_' + name))
data['manage_daemon'] = True if get_data_entry(form_data, 'managedaemon_' + name) == 'true' else False
data['rpchost'] = get_data_entry(form_data, 'rpchost_' + name)
@ -104,7 +104,7 @@ def page_settings(self, url_split, post_string):
'manage_daemon': c.get('manage_daemon', 'Unknown'),
'connection_type': c.get('connection_type', 'Unknown'),
})
if name == 'monero':
if name in ('monero', 'wownero'):
chains_formatted[-1]['fee_priority'] = c.get('fee_priority', 0)
chains_formatted[-1]['manage_wallet_daemon'] = c.get('manage_wallet_daemon', 'Unknown')
chains_formatted[-1]['rpchost'] = c.get('rpchost', 'localhost')

View file

@ -176,7 +176,7 @@ def page_wallet(self, url_split, post_string):
if estimate_fee and withdraw:
err_messages.append('Estimate fee and withdraw can\'t be used together.')
if estimate_fee and coin_id not in (Coins.XMR, ):
if estimate_fee and coin_id not in (Coins.XMR, Coins.WOW):
ci = swap_client.ci(coin_id)
ticker: str = ci.ticker()
err_messages.append(f'Estimate fee unavailable for {ticker}.')
@ -206,7 +206,7 @@ def page_wallet(self, url_split, post_string):
elif coin_id == Coins.LTC:
txid = swap_client.withdrawLTC(type_from, value, address, subfee)
messages.append('Withdrew {} {} (from {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, address, txid))
elif coin_id == Coins.XMR:
elif coin_id in (Coins.XMR, Coins.WOW):
if estimate_fee:
fee_estimate = ci.estimateFee(value, address, sweepall)
suffix = 's' if fee_estimate['num_txns'] > 1 else ''
@ -281,7 +281,7 @@ def page_wallet(self, url_split, post_string):
wallet_data['est_fee'] = 'Unknown' if est_fee is None else ci.format_amount(int(est_fee * ci.COIN()))
wallet_data['deposit_address'] = w.get('deposit_address', 'Refresh necessary')
if k == Coins.XMR:
if k in (Coins.XMR, Coins.WOW):
wallet_data['main_address'] = w.get('main_address', 'Refresh necessary')
elif k == Coins.LTC:
wallet_data['mweb_address'] = w.get('mweb_address', 'Refresh necessary')

View file

@ -55,6 +55,10 @@ MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.3.3')
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
XMR_SITE_COMMIT = 'd00169a6decd9470ebdf6a75e3351df4ebcd260a' # Lock hashes.txt to monero version
WOWNERO_VERSION = os.getenv('WOWNERO_VERSION', '0.11.1.0')
WOWNERO_VERSION_TAG = os.getenv('WOWNERO_VERSION_TAG', '')
WOW_SITE_COMMIT = '97e100e1605e9f59bc8ca82a5b237d5562c8a21c' # todo
PIVX_VERSION = os.getenv('PIVX_VERSION', '5.6.1')
PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '')
@ -85,6 +89,7 @@ known_coins = {
'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
'namecoin': ('0.18.0', '', ('JeremyRand',)),
'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
'wownero': (WOWNERO_VERSION, WOWNERO_VERSION_TAG, ('wowario',)),
'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('fuzzbawls',)),
'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)),
'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)),
@ -103,6 +108,7 @@ expected_key_ids = {
'laanwj': ('1E4AED62986CD25D',),
'JeremyRand': ('2DBE339E29F6294C',),
'binaryfate': ('F0AF4D462A0BDF92',),
'wowario': ('793504B449C69220',),
'davidburkett38': ('3620E9D387E55666',),
'fuzzbawls': ('C1ABA64407731FD9',),
'pasta': ('52527BEDABE87984',),
@ -157,6 +163,17 @@ XMR_RPC_USER = os.getenv('XMR_RPC_USER', '')
XMR_RPC_PWD = os.getenv('XMR_RPC_PWD', '')
DEFAULT_XMR_RESTORE_HEIGHT = int(os.getenv('DEFAULT_XMR_RESTORE_HEIGHT', 2245107))
WOW_RPC_HOST = os.getenv('WOW_RPC_HOST', '127.0.0.1')
BASE_WOW_RPC_PORT = int(os.getenv('BASE_WOW_RPC_PORT', 34598))
BASE_WOW_ZMQ_PORT = int(os.getenv('BASE_WOW_ZMQ_PORT', 34698))
BASE_WOW_WALLET_PORT = int(os.getenv('BASE_WOW_WALLET_PORT', 34798))
WOW_WALLET_RPC_HOST = os.getenv('WOW_WALLET_RPC_HOST', '127.0.0.1')
WOW_WALLET_RPC_USER = os.getenv('WOW_WALLET_RPC_USER', 'wow_wallet_user')
WOW_WALLET_RPC_PWD = os.getenv('WOW_WALLET_RPC_PWD', 'wow_wallet_pwd')
WOW_RPC_USER = os.getenv('WOW_RPC_USER', '')
WOW_RPC_PWD = os.getenv('WOW_RPC_PWD', '')
DEFAULT_WOW_RESTORE_HEIGHT = int(os.getenv('DEFAULT_WOW_RESTORE_HEIGHT', 450000))
LTC_RPC_HOST = os.getenv('LTC_RPC_HOST', '127.0.0.1')
LTC_RPC_PORT = int(os.getenv('LTC_RPC_PORT', 19895))
LTC_ONION_PORT = int(os.getenv('LTC_ONION_PORT', 9333))
@ -226,12 +243,28 @@ monerod_proxy_config = [
'hide-my-port=1', # Don't share the p2p port
'p2p-bind-ip=127.0.0.1', # Don't broadcast ip
'in-peers=0', # Changes "error" in log to "incoming connections disabled"
'out-peers=24',
f'tx-proxy=tor,{TOR_PROXY_HOST}:{TOR_PROXY_PORT},disable_noise,16' # Outgoing tx relay to onion
]
monero_wallet_rpc_proxy_config = [
'daemon-ssl-allow-any-cert=1',
# 'daemon-ssl-allow-any-cert=1', moved to startup flag
]
wownerod_proxy_config = [
f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}',
'proxy-allow-dns-leaks=0',
'no-igd=1', # Disable UPnP port mapping
'hide-my-port=1', # Don't share the p2p port
'p2p-bind-ip=127.0.0.1', # Don't broadcast ip
'in-peers=0', # Changes "error" in log to "incoming connections disabled"
'out-peers=24',
f'tx-proxy=tor,{TOR_PROXY_HOST}:{TOR_PROXY_PORT},disable_noise,16' # Outgoing tx relay to onion
]
wownero_wallet_rpc_proxy_config = [
# 'daemon-ssl-allow-any-cert=1', moved to startup flag
]
default_socket = socket.socket
default_socket_timeout = socket.getdefaulttimeout()
@ -483,9 +516,9 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
logger.info('extractCore %s v%s%s', coin, version, version_tag)
extract_core_overwrite = extra_opts.get('extract_core_overwrite', True)
if coin in ('monero', 'firo'):
if coin == 'monero':
bins = ['monerod', 'monero-wallet-rpc']
if coin in ('monero', 'firo', 'wownero'):
if coin in ('monero', 'wownero'):
bins = [coin + 'd', coin + '-wallet-rpc']
elif coin == 'firo':
bins = [coin + 'd', coin + '-cli', coin + '-tx']
else:
@ -624,6 +657,30 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
assert_path = os.path.join(bin_dir, assert_filename)
if not os.path.exists(assert_path):
downloadFile(assert_url, assert_path)
elif coin == 'wownero':
use_file_ext = 'tar.bz2' if FILE_EXT == 'tar.gz' else FILE_EXT
release_filename = '{}-{}-{}.{}'.format(coin, version, BIN_ARCH, use_file_ext)
if os_name == 'osx':
os_name = 'mac'
architecture = 'x64'
release_url = 'https://git.wownero.com/attachments/280753b0-3af0-4a78-a248-8b925e8f4593'
if 'aarch64' in BIN_ARCH:
architecture = 'armv8'
release_url = 'https://git.wownero.com/attachments/0869ffe3-eeff-4240-a185-168ca80fa1e3'
elif 'arm' in BIN_ARCH:
architecture = 'armv7' # 32bit doesn't work
release_url = 'https://git.wownero.com/attachments/ff0c4886-3865-4670-9bc6-63dd60ded0e3'
release_path = os.path.join(bin_dir, release_filename)
if not os.path.exists(release_path):
downloadFile(release_url, release_path)
assert_filename = 'wownero-{}-hashes.txt'.format(version)
assert_url = 'https://git.wownero.com/wownero/wownero.org-website/raw/commit/{}/hashes.txt'.format(WOW_SITE_COMMIT)
assert_path = os.path.join(bin_dir, assert_filename)
if not os.path.exists(assert_path):
downloadFile(assert_url, assert_path)
elif coin == 'decred':
arch_name = BIN_ARCH
@ -642,6 +699,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
release_filename = '{}-{}-{}.{}'.format(coin, version, arch_name, FILE_EXT)
release_page_url = 'https://github.com/decred/decred-binaries/releases/download/v{}'.format(version)
release_url = release_page_url + '/' + 'decred-{}-v{}.{}'.format(arch_name, version, FILE_EXT)
release_path = os.path.join(bin_dir, release_filename)
if not os.path.exists(release_path):
downloadFile(release_url, release_path)
@ -789,13 +847,15 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
pubkeyurls.append('https://raw.githubusercontent.com/dashpay/dash/master/contrib/gitian-keys/pasta.pgp')
if coin == 'monero':
pubkeyurls.append('https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc')
if coin == 'wownero':
pubkeyurls.append('https://git.wownero.com/wownero/wownero/raw/branch/master/utils/gpg_keys/wowario.asc')
if coin == 'firo':
pubkeyurls.append('https://firo.org/reuben.asc')
if ADD_PUBKEY_URL != '':
pubkeyurls.append(ADD_PUBKEY_URL + '/' + pubkey_filename)
if coin in ('monero', 'firo'):
if coin in ('monero', 'wownero', 'firo'):
with open(assert_path, 'rb') as fp:
verified = gpg.verify_file(fp)
@ -862,7 +922,7 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
if not os.path.exists(data_dir):
os.makedirs(data_dir)
if coin == 'monero':
if coin in ('wownero', 'monero'):
core_conf_path = os.path.join(data_dir, coin + 'd.conf')
if os.path.exists(core_conf_path):
exitWithError('{} exists'.format(core_conf_path))
@ -886,18 +946,28 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
fp.write('zmq-rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
fp.write('prune-blockchain=1\n')
if tor_control_password is not None:
for opt_line in monerod_proxy_config:
fp.write(opt_line + '\n')
if coin == 'monero':
if XMR_RPC_USER != '':
fp.write(f'rpc-login={XMR_RPC_USER}:{XMR_RPC_PWD}\n')
if tor_control_password is not None:
for opt_line in monerod_proxy_config:
fp.write(opt_line + '\n')
if XMR_RPC_USER != '':
fp.write(f'rpc-login={XMR_RPC_USER}:{XMR_RPC_PWD}\n')
if coin == 'wownero':
if WOW_RPC_USER != '':
fp.write(f'rpc-login={WOW_RPC_USER}:{WOW_RPC_PWD}\n')
if tor_control_password is not None:
for opt_line in wownerod_proxy_config:
fp.write(opt_line + '\n')
if coin in ('wownero', 'monero'):
wallets_dir = core_settings.get('walletsdir', data_dir)
if not os.path.exists(wallets_dir):
os.makedirs(wallets_dir)
wallet_conf_path = os.path.join(wallets_dir, coin + '_wallet.conf')
wallet_conf_path = os.path.join(wallets_dir, coin + '-wallet-rpc.conf')
if coin == 'monero':
wallet_conf_path = os.path.join(wallets_dir, 'monero_wallet.conf')
if os.path.exists(wallet_conf_path):
exitWithError('{} exists'.format(wallet_conf_path))
with open(wallet_conf_path, 'w') as fp:
@ -911,9 +981,11 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
fp.write(f'wallet-dir={config_datadir}\n')
fp.write('log-file={}\n'.format(os.path.join(config_datadir, 'wallet.log')))
fp.write('shared-ringdb-dir={}\n'.format(os.path.join(config_datadir, 'shared-ringdb')))
fp.write('rpc-login={}:{}\n'.format(core_settings['walletrpcuser'], core_settings['walletrpcpassword']))
if coin == 'monero':
fp.write('shared-ringdb-dir={}\n'.format(os.path.join(config_datadir, 'shared-ringdb')))
elif coin == 'wownero':
fp.write('wow-shared-ringdb-dir={}\n'.format(os.path.join(config_datadir, 'shared-ringdb')))
if chain == 'regtest':
fp.write('allow-mismatched-daemon-version=1\n')
@ -1087,12 +1159,15 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False, e
coin_settings = settings['chainclients'][coin]
data_dir = coin_settings['datadir']
if coin == 'monero':
if coin in ('monero', 'wownero'):
core_conf_path = os.path.join(data_dir, coin + 'd.conf')
if not os.path.exists(core_conf_path):
exitWithError('{} does not exist'.format(core_conf_path))
wallets_dir = coin_settings.get('walletsdir', data_dir)
wallet_conf_path = os.path.join(wallets_dir, coin + '_wallet.conf')
wallet_conf_path = os.path.join(wallets_dir, coin + '-wallet-rpc.conf')
if coin == 'monero':
wallet_conf_path = os.path.join(wallets_dir, 'monero_wallet.conf')
if not os.path.exists(wallet_conf_path):
exitWithError('{} does not exist'.format(wallet_conf_path))
@ -1105,16 +1180,27 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False, e
# Disable tor first
for line in fp_in:
skip_line: bool = False
for opt_line in monerod_proxy_config:
setting: str = opt_line[0: opt_line.find('=') + 1]
if line.startswith(setting):
skip_line = True
break
if coin == 'monero':
for opt_line in monerod_proxy_config:
setting: str = opt_line[0: opt_line.find('=') + 1]
if line.startswith(setting):
skip_line = True
break
if coin == 'wownero':
for opt_line in wownerod_proxy_config:
setting: str = opt_line[0: opt_line.find('=') + 1]
if line.startswith(setting):
skip_line = True
break
if not skip_line:
fp.write(line)
if enable:
for opt_line in monerod_proxy_config:
fp.write(opt_line + '\n')
if coin == 'monero':
for opt_line in monerod_proxy_config:
fp.write(opt_line + '\n')
if coin == 'wownero':
for opt_line in wownerod_proxy_config:
fp.write(opt_line + '\n')
with open(wallet_conf_path, 'w') as fp:
with open(wallet_conf_path + '.last') as fp_in:
@ -1210,6 +1296,7 @@ def printHelp():
print('--htmlhost= Interface to host html server on, default:127.0.0.1.')
print('--wshost= Interface to host websocket server on, disable by setting to "none", default\'s to --htmlhost.')
print('--xmrrestoreheight=n Block height to restore Monero wallet from, default:{}.'.format(DEFAULT_XMR_RESTORE_HEIGHT))
print('--wowrestoreheight=n Block height to restore Wownero wallet from, default:{}.'.format(DEFAULT_WOW_RESTORE_HEIGHT))
print('--trustremotenode Set trusted-daemon for XMR, defaults to auto: true when daemon rpchost value is a private ip address else false')
print('--noextractover Prevent extracting cores if files exist. Speeds up tests')
print('--usetorproxy Use TOR proxy during setup. Note that some download links may be inaccessible over TOR.')
@ -1305,7 +1392,11 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
if c == Coins.XMR:
if coin_settings['manage_wallet_daemon']:
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
filename = coin_name + '-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename))
elif c == Coins.WOW:
if coin_settings['manage_wallet_daemon']:
filename = coin_name + '-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename))
elif c == Coins.DCR:
pass
@ -1470,6 +1561,7 @@ def main():
coins_changed = False
htmlhost = '127.0.0.1'
xmr_restore_height = DEFAULT_XMR_RESTORE_HEIGHT
wow_restore_height = DEFAULT_WOW_RESTORE_HEIGHT
prepare_bin_only = False
no_cores = False
enable_tor = False
@ -1585,6 +1677,9 @@ def main():
if name == 'xmrrestoreheight':
xmr_restore_height = int(s[1])
continue
if name == 'wowrestoreheight':
wow_restore_height = int(s[1])
continue
if name == 'keysdirpath':
extra_opts['keysdirpath'] = os.path.expanduser(s[1].strip('"'))
continue
@ -1825,6 +1920,28 @@ def main():
'core_version_group': 18,
'chain_lookups': 'local',
'startup_tries': 40,
},
'wownero': {
'connection_type': 'rpc' if 'wownero' in with_coins else 'none',
'manage_daemon': True if ('wownero' in with_coins and WOW_RPC_HOST == '127.0.0.1') else False,
'manage_wallet_daemon': True if ('wownero' in with_coins and WOW_WALLET_RPC_HOST == '127.0.0.1') else False,
'rpcport': BASE_WOW_RPC_PORT + port_offset,
'zmqport': BASE_WOW_ZMQ_PORT + port_offset,
'walletrpcport': BASE_WOW_WALLET_PORT + port_offset,
'rpchost': WOW_RPC_HOST,
'trusted_daemon': extra_opts.get('trust_remote_node', 'auto'),
'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,
'blocks_confirmed': 2,
'rpctimeout': 60,
'walletrpctimeout': 120,
'walletrpctimeoutlong': 300,
'core_type_group': 'xmr',
}
}
@ -1840,6 +1957,9 @@ def main():
if XMR_RPC_USER != '':
chainclients['monero']['rpcuser'] = XMR_RPC_USER
chainclients['monero']['rpcpassword'] = XMR_RPC_PWD
if WOW_RPC_USER != '':
chainclients['wownero']['rpcuser'] = WOW_RPC_USER
chainclients['wownero']['rpcpassword'] = WOW_RPC_PWD
if PIVX_RPC_USER != '':
chainclients['pivx']['rpcuser'] = PIVX_RPC_USER
chainclients['pivx']['rpcpassword'] = PIVX_RPC_PWD
@ -1854,6 +1974,7 @@ def main():
chainclients['nav']['rpcpassword'] = NAV_RPC_PWD
chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])
chainclients['wownero']['walletsdir'] = os.getenv('WOW_WALLETS_DIR', chainclients['wownero']['datadir'])
if initwalletsonly:
logger.info('Initialising wallets')

View file

@ -98,11 +98,11 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
daemon_path = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
datadir_path = os.path.expanduser(node_dir)
args = [daemon_bin, '--non-interactive', '--config-file=' + os.path.join(datadir_path, 'monerod.conf')] + opts
logging.info('Starting node {} --data-dir={}'.format(daemon_bin, node_dir))
args = [daemon_path, '--non-interactive', '--config-file=' + os.path.join(datadir_path, daemon_bin + '.conf')] + opts
logging.info('Starting node {} --data-dir={}'.format(daemon_path, node_dir))
# return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
file_stdout = open(os.path.join(datadir_path, 'core_stdout.log'), 'w')
@ -129,7 +129,7 @@ def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
args += opts
if needs_rewrite:
logging.info('Rewriting monero_wallet.conf')
logging.info('Rewriting wallet config')
shutil.copyfile(config_path, config_path + '.last')
with open(config_path + '.last') as fp_from, open(config_path, 'w') as fp_to:
for line in fp_from:
@ -208,10 +208,10 @@ def runClient(fp, data_dir, chain, start_only_coins):
except Exception as e:
logger.warning('Not starting unknown coin: {}'.format(c))
continue
if c == 'monero':
if c in ('monero', 'wownero'):
if v['manage_daemon'] is True:
swap_client.log.info(f'Starting {display_name} daemon')
filename = 'monerod' + ('.exe' if os.name == 'nt' else '')
filename = c + 'd' + ('.exe' if os.name == 'nt' else '')
daemons.append(startXmrDaemon(v['datadir'], v['bindir'], filename))
pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid))
@ -226,7 +226,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
proxy_host, proxy_port = swap_client.getXMRWalletProxy(coin_id, v['rpchost'])
if proxy_host:
proxy_log_str = ' through proxy'
opts += ['--proxy', f'{proxy_host}:{proxy_port}', ]
opts += ['--proxy', f'{proxy_host}:{proxy_port}', '--daemon-ssl-allow-any-cert', ]
swap_client.log.info('daemon-address: {} ({}){}'.format(daemon_addr, 'trusted' if trusted_daemon else 'untrusted', proxy_log_str))
@ -237,7 +237,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
opts.append(daemon_rpcuser + ':' + daemon_rpcpass)
opts.append('--trusted-daemon' if trusted_daemon else '--untrusted-daemon')
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
filename = c + '-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], filename, opts))
pid = daemons[-1].handle.pid
swap_client.log.info('Started {} {}'.format(filename, pid))

View file

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

View file

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

View file

@ -8,6 +8,8 @@
- ${DATA_PATH}/swapclient:/data/swapclient
- ${DATA_PATH}/monero_daemon:/data/monero_daemon
- ${DATA_PATH}/monero_wallet:/data/monero_wallet
- ${DATA_PATH}/wownero_daemon:/data/wownero_daemon
- ${DATA_PATH}/wownero_wallet:/data/wownero_wallet
- ${DATA_PATH}/particl:/data/particl
- ${DATA_PATH}/bitcoin:/data/bitcoin
- ${DATA_PATH}/litecoin:/data/litecoin
@ -45,6 +47,16 @@
- XMR_WALLET_RPC_USER
- XMR_WALLET_RPC_PWD
- DEFAULT_XMR_RESTORE_HEIGHT
- WOW_DATA_DIR
- WOW_RPC_HOST
- BASE_WOW_RPC_PORT
- BASE_WOW_ZMQ_PORT
- WOW_WALLETS_DIR
- WOW_WALLET_RPC_HOST
- BASE_WOW_WALLET_PORT
- WOW_WALLET_RPC_USER
- WOW_WALLET_RPC_PWD
- DEFAULT_WOW_RESTORE_HEIGHT
- PIVX_DATA_DIR
- PIVX_RPC_HOST
- PIVX_RPC_PORT

View file

@ -44,6 +44,16 @@ BASE_XMR_WALLET_PORT=29998
XMR_WALLET_RPC_USER=xmr_wallet_user
XMR_WALLET_RPC_PWD=xmr_wallet_pwd
WOW_DATA_DIR=/data/wownero_daemon
WOW_RPC_HOST=wownero_daemon
BASE_WOW_RPC_PORT=34598
WOW_WALLETS_DIR=/data/wownero_wallet
WOW_WALLET_RPC_HOST=wownero_wallet
BASE_WOW_WALLET_PORT=34798
WOW_WALLET_RPC_USER=wow_wallet_user
WOW_WALLET_RPC_PWD=wow_wallet_pwd
PIVX_DATA_DIR=/data/pivx
PIVX_RPC_HOST=pivx_core
PIVX_RPC_PORT=51473

View file

@ -57,12 +57,12 @@ def main():
if coin_name == 'particl':
# Nothing to do
continue
if coin_name == 'monero':
with open(os.path.join(fragments_dir, '1_monero-wallet.yml'), 'rb') as fp_in:
if coin_name in ('monero', 'wownero'):
with open(os.path.join(fragments_dir, '1_{coin_name}-wallet.yml'), 'rb') as fp_in:
for line in fp_in:
fp.write(line)
fpp.write(line)
with open(os.path.join(fragments_dir, '8_monero-daemon.yml'), 'rb') as fp_in:
with open(os.path.join(fragments_dir, '8_{coin_name}-daemon.yml'), 'rb') as fp_in:
for line in fp_in:
fp.write(line)
continue

View file

@ -0,0 +1,24 @@
FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=wownero --withoutcoins=particl
FROM debian:bullseye-slim
COPY --from=install_stage /coin_bin .
ENV WOWNERO_DATA /data
RUN groupadd -r wownero && useradd -r -m -g wownero wownero \
&& apt-get update \
&& apt-get install -qq --no-install-recommends gosu \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p "$WOWNERO_DATA" \
&& chown -R wownero:wownero "$WOWNERO_DATA" \
&& ln -sfn "$WOWNERO_DATA" /home/wownero/.wownero \
&& chown -h wownero:wownero /home/wownero/.wownero
VOLUME $WOWNERO_DATA
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/wownero/wownerod", "--non-interactive", "--config-file=/home/wownero/.wownero/wownerod.conf", "--confirm-external-bind"]

View file

@ -0,0 +1,11 @@
#!/bin/bash
set -e
if [[ "$1" == "wownerod" ]]; then
mkdir -p "$WOWNERO_DATA"
chown -h wownero:wownero /home/wownero/.wownero
exec gosu wownero "$@"
else
exec "$@"
fi

View file

@ -0,0 +1,16 @@
FROM i_wownero_daemon
ENV WOWNERO_DATA /data
RUN groupadd -r wownero_wallet && useradd -r -m -g wownero_wallet wownero_wallet \
&& apt-get update \
&& apt-get install -qq --no-install-recommends gosu \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p "$WOWNERO_DATA" \
&& chown -R wownero_wallet:wownero_wallet "$WOWNERO_DATA"
VOLUME $WOWNERO_DATA
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/wownero/wownero-wallet-rpc", "--non-interactive", "--config-file=/data/wownero-wallet-rpc.conf", "--confirm-external-bind"]

View file

@ -0,0 +1,11 @@
#!/bin/bash
set -e
if [[ "$1" == "wownero-wallet-rpc" ]]; then
mkdir -p "$WOWNERO_DATA"
chown -h wownero_wallet:wownero_wallet /data
exec gosu wownero_wallet "$@"
else
exec "$@"
fi

View file

@ -29,6 +29,7 @@ setuptools.setup(
"particl",
"bitcoin",
"monero",
"wownero",
],
install_requires=[
"wheel",

View file

@ -11,6 +11,7 @@ passenv =
BITCOIN_BINDIR
LITECOIN_BINDIR
XMR_BINDIR
WOW_BINDIR
TEST_PREPARE_PATH
TEST_RELOAD_PATH
deps =