Compare commits
No commits in common. "master" and "2024-05-20_merge" have entirely different histories.
master
...
2024-05-20
@ -21,7 +21,7 @@ test_task:
|
||||
- XMR_BINDIR: ${BIN_DIR}/monero
|
||||
setup_script:
|
||||
- apt-get update
|
||||
- apt-get install -y wget python3-pip gnupg unzip automake libtool pkg-config
|
||||
- apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config
|
||||
- pip install tox pytest
|
||||
- python3 setup.py install
|
||||
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.2.zip
|
||||
|
60
.travis.yml
Normal file
60
.travis.yml
Normal file
@ -0,0 +1,60 @@
|
||||
dist: bionic
|
||||
os: linux
|
||||
language: python
|
||||
python: '3.7'
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
env:
|
||||
global:
|
||||
- TEST_DIR=${HOME}/test_basicswap2
|
||||
- TEST_RELOAD_PATH=~/test_basicswap1
|
||||
- BIN_DIR=~/cached_bin
|
||||
- PARTICL_BINDIR=${BIN_DIR}/particl
|
||||
- BITCOIN_BINDIR=${BIN_DIR}/bitcoin
|
||||
- LITECOIN_BINDIR=${BIN_DIR}/litecoin
|
||||
- XMR_BINDIR=${BIN_DIR}/monero
|
||||
cache:
|
||||
directories:
|
||||
- "$BIN_DIR"
|
||||
before_install:
|
||||
- sudo apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config
|
||||
install:
|
||||
- travis_retry pip install tox pytest
|
||||
before_script:
|
||||
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.2.zip
|
||||
- unzip -d coincurve-anonswap coincurve-anonswap.zip
|
||||
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
|
||||
- cd coincurve-anonswap
|
||||
- python3 setup.py install --force
|
||||
script:
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- python3 setup.py install
|
||||
- basicswap-prepare --bindir=${BIN_DIR} --preparebinonly --withcoins=particl,bitcoin,litecoin,monero
|
||||
- export DATADIRS="${TEST_DIR}"
|
||||
- mkdir -p "${DATADIRS}/bin"
|
||||
- cp -r ${BIN_DIR} "${DATADIRS}/bin"
|
||||
- mkdir -p "${TEST_RELOAD_PATH}/bin"
|
||||
- cp -r ${BIN_DIR} "${TEST_RELOAD_PATH}/bin"
|
||||
- # tox
|
||||
- pytest tests/basicswap/test_xmr.py
|
||||
- pytest tests/basicswap/test_xmr_reload.py
|
||||
- pytest tests/basicswap/test_xmr_bids_offline.py
|
||||
after_success:
|
||||
- echo "End test"
|
||||
jobs:
|
||||
include:
|
||||
- stage: lint
|
||||
env:
|
||||
cache: false
|
||||
install:
|
||||
- travis_retry pip install flake8==3.7.0
|
||||
- travis_retry pip install codespell==1.15.0
|
||||
before_script:
|
||||
script:
|
||||
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py
|
||||
- codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static
|
||||
after_success:
|
||||
- echo "End lint"
|
||||
- stage: test
|
||||
env:
|
@ -7,6 +7,14 @@ ENV LANG=C.UTF-8 \
|
||||
RUN apt-get update; \
|
||||
apt-get install -y wget python3-pip gnupg unzip make g++ autoconf automake libtool pkg-config gosu tzdata;
|
||||
|
||||
# Must install protoc directly as latest package is only on 3.12
|
||||
RUN wget -O protobuf_src.tar.gz https://github.com/protocolbuffers/protobuf/releases/download/v21.1/protobuf-python-4.21.1.tar.gz && \
|
||||
tar xvf protobuf_src.tar.gz && \
|
||||
cd protobuf-3.21.1 && \
|
||||
./configure --prefix=/usr && \
|
||||
make -j$(nproc) install && \
|
||||
ldconfig
|
||||
|
||||
ARG COINCURVE_VERSION=v0.2
|
||||
RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_$COINCURVE_VERSION.zip && \
|
||||
unzip coincurve-anonswap.zip && \
|
||||
@ -20,6 +28,7 @@ RUN pip3 install -r requirements.txt
|
||||
|
||||
COPY . basicswap-master
|
||||
RUN cd basicswap-master; \
|
||||
protoc -I=basicswap --python_out=basicswap basicswap/messages.proto; \
|
||||
pip3 install .;
|
||||
|
||||
RUN useradd -ms /bin/bash swap_user && \
|
||||
|
58
README.md
58
README.md
@ -15,31 +15,27 @@ Table of Contents
|
||||
|
||||
## About
|
||||
|
||||
**BasicSwap** is the world’s most secure and decentralized DEX. It facilitates cross-chain atomic swaps by enabling peers to interact directly with each other within a free and open environment without central points of failure.
|
||||
The BasicSwap DEX is a privacy-first and decentralized exchange which features cross-chain atomic swaps and a distributed order book.
|
||||
|
||||
This DEX is fully non-custodial and features a decentralized order book, letting you create or accept swap offers without any fees, counterparties, or the need for accounts.
|
||||
[BasicSwap](https://academy.particl.io/en/latest/glossary.html#term-BasicSwap) is a cross-chain and privacy-centric DEX (decentralized exchange) that lets you trade cryptocurrencies with no third party involvement. Its distributed order book lets you make or take orders at no cost and trade within a free and open environment without central points of failure.
|
||||
|
||||
Built as a low-friction, highly secure solution to the frequent losses of funds on centralized exchanges (e.g., FTX, BitFinex, MtGox), **BasicSwap** aims to provide more reliable and secure cryptocurrency trading conditions for everyone.
|
||||
This DEX protocol was built in direct response to the increasingly invasive demands and data mining practices of today’s cryptocurrency exchanges. It strives to bring more decentralized and more private cryptocurrency trading conditions for all.
|
||||
|
||||
**BasicSwap** is currently in active development by the community. While it already offers some of the essential trading features you'd expect from an exchange, more features and quality-of-life improvements are being worked on with the goal to provide a smoother user experience.
|
||||
BasicSwap is still in beta. This means that, while it already offers most of the vital trading features you’d expect to see on centralized exchanges, it is still in heavy development, and many more features will come about in the near future.
|
||||
|
||||
Check out our [roadmap](https://basicswapdex.com/roadmap) to get a better idea of what we've got planned for it!
|
||||
|
||||
## Features
|
||||
|
||||
* **True cross-chain support** — Swap cryptocurrencies that live on entirely different blockchain environments, like Bitcoin and Monero.
|
||||
* **Decentralized order book** — Make or take swap offers on a completely decentralized order book system.
|
||||
* **No third-party or middleman** — Trade crypto with no intermediaries, completely eliminating central points of failure.
|
||||
* **Distributed order book** — Make or take limit orders on a completely distributed order book system.
|
||||
* **No third-party or middleman** — Trade crypto with no intermediaries whatsoever.
|
||||
* **No trading fees** — Only pay the typical cryptocurrency network fee.
|
||||
* **Superior financial privacy** — Protect your financial information from unauthorized access with BasicSwap’s privacy-conscious technology.
|
||||
* **Full Monero support** — Swap Monero with a variety of other cryptocurrencies like Bitcoin or Particl. No wrapped assets or layer-2 involved.
|
||||
* **Privacy from the ground up** — Every component of BasicSwap is built with a privacy-first commitment.
|
||||
* **Full Monero support** — Swap Monero with a variety of other cryptocurrencies like Bitcoin or Particl. No wrapped assets or trickery involved.
|
||||
* **User-friendly interface** — Enjoy all these features within a user-friendly and intuitive interface that handles all the complicated parts for you.
|
||||
|
||||
## Under the Hood
|
||||
|
||||
**BasicSwap** can be best understood as the decentralized version of the SWIFT messaging network; providing a decentralized messaging protocol that allows for peers to connect directly with each other with the purpose of executing atomic swaps without central points of failure and using official core wallets (Bitcoin Core, Litecoin Core, etc).
|
||||
|
||||
**BasicSwap** does not process, initiate, or execute swaps; it merely enables peers to communicate with each other and exchange the required information to simplify the process of using atomic swaps on the respective blockchains of the coins being swapped.
|
||||
|
||||
In essence, **BasicSwap** operates merely as a decentralized messaging protocol supplemented by a user-friendly interface.
|
||||
BasicSwap is still in beta. This means that, while it already offers most of the vital trading features you’d expect to see on centralized exchanges, it is still in heavy development, and many more features will come about in the near future.
|
||||
|
||||
## Available Assets
|
||||
|
||||
@ -88,18 +84,6 @@ BasicSwap is compatible with the following digital assets.
|
||||
<td>PIVX
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Decred
|
||||
</td>
|
||||
<td>DCR
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Wownero
|
||||
</td>
|
||||
<td>WOW
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Particl
|
||||
</td>
|
||||
@ -108,31 +92,39 @@ BasicSwap is compatible with the following digital assets.
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
If you’d like to add a cryptocurrency to BasicSwap, refer to how other cryptocurrencies have been integrated to the DEX by following [this link](https://academy.particl.io/en/latest/basicswap-guides/basicswapguides_apply.html).
|
||||
We plan on adding many other cryptocurrencies moving forward, including ETH and its ERC-20 tokens. However, due to the true cross-chain nature of the BasicSwap DEX protocol, each integration has to be done on a case-by-case basis.
|
||||
|
||||
If you’d like to add a cryptocurrency to BasicSwap, either [apply for a listing using our listing application form](https://forms.gle/9DsHoHTJVqSiMNHW9), or try coding the integration yourself by referencing how other cryptocurrencies have been added. Follow [this link](https://academy.particl.io/en/latest/basicswap-guides/basicswapguides_apply.html) for more information on how to integrate a coin yourself.
|
||||
|
||||
# Participate
|
||||
|
||||
### Chats
|
||||
|
||||
* **For support** Join the community on [#basicswap:matrix.org](https://matrix.to/#/#basicswap:matrix.org) using a Matrix client.
|
||||
* **For developers** The chat [#particl-dev:matrix.org](https://matrix.to/#/#particl-dev:matrix.org) using a Matrix client.
|
||||
* **For community** The community chat [https://discord.me/particl](https://discord.me/particl) [![Discord](https://img.shields.io/discord/391967609660112925)](https://discord.me/particl).
|
||||
|
||||
[![Twitter Follow](https://img.shields.io/twitter/follow/BasicSwapDEX?label=follow%20us&style=social)](http://twitter.com/BasicSwapDEX)
|
||||
[![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/particl?style=social)](http://reddit.com/r/particl)
|
||||
|
||||
### Documentation, installation
|
||||
|
||||
Follow the guides on [Particl Academy](https://academy.particl.io) for tutorials and guides on how BasicSwap works.
|
||||
For non-developers curious to explore a new world of commerce, binaries can be downloaded and installed. It is the easiest way to get started. Following the guides on [Particl Academy](https://academy.particl.io), a reference book in straightforward language, is recommended.
|
||||
|
||||
* [Download BasicSwapDEX](https://github.com/basicswap/basicswap/tree/master/doc)
|
||||
* [Download BasicSwapDEX](https://github.com/tecnovert/basicswap/tree/master/doc)
|
||||
|
||||
#### Community chat support
|
||||
|
||||
* [Matrix](https://matrix.to/#/#basicswap:matrix.org)
|
||||
* [Discord](https://discord.me/particl) navigate to the #support channel
|
||||
|
||||
* [Telegram](https://t.me/particlhelp)
|
||||
|
||||
* [Matrix](https://matrix.to/#/#particlhelp:matrix.org)
|
||||
|
||||
# Tutorials
|
||||
|
||||
You can find a wide variety of tutorials and step-by-step guides about BasicSwap on the [Particl Academy](https://academy.particl.io) or on Particl’s Youtube channel.
|
||||
|
||||
If you encounter an issue or try to accomplish something not mentioned in any of the tutorials included in the links above, please join the community chat support channel; you’ll be sure to find help and support from current contributors there!
|
||||
If you encounter an issue or try to accomplish something not mentioned in any of the tutorials included in the links above, please join the community chat support channels; you’ll be sure to find help and support from our awesome community and open-source team there!
|
||||
|
||||
# License
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
name = "basicswap"
|
||||
|
||||
__version__ = "0.13.4"
|
||||
__version__ = "0.13.0"
|
||||
|
@ -46,7 +46,7 @@ class BaseApp:
|
||||
self.settings = settings
|
||||
self.coin_clients = {}
|
||||
self.coin_interfaces = {}
|
||||
self.mxDB = threading.Lock()
|
||||
self.mxDB = threading.RLock()
|
||||
self.debug = self.settings.get('debug', False)
|
||||
self.delay_event = threading.Event()
|
||||
self.chainstate_delay_event = threading.Event()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -202,8 +202,6 @@ class DebugTypes(IntEnum):
|
||||
SEND_LOCKED_XMR = auto()
|
||||
B_LOCK_TX_MISSED_SEND = auto()
|
||||
DUPLICATE_ACTIONS = auto()
|
||||
DONT_CONFIRM_PTX = auto()
|
||||
OFFER_LOCK_2_VALUE_INC = auto()
|
||||
|
||||
|
||||
class NotificationTypes(IntEnum):
|
||||
@ -475,7 +473,7 @@ def getOfferProofOfFundsHash(offer_msg, offer_addr):
|
||||
# TODO: Hash must not include proof_of_funds sig if it exists in offer_msg
|
||||
h = hashlib.sha256()
|
||||
h.update(offer_addr.encode('utf-8'))
|
||||
offer_bytes = offer_msg.to_bytes()
|
||||
offer_bytes = offer_msg.SerializeToString()
|
||||
h.update(offer_bytes)
|
||||
return h.digest()
|
||||
|
||||
|
@ -1,35 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2019-2024 tecnovert
|
||||
# Copyright (c) 2019-2023 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import threading
|
||||
|
||||
from enum import IntEnum
|
||||
from .util import (
|
||||
COIN,
|
||||
make_int,
|
||||
format_amount,
|
||||
TemporaryError,
|
||||
)
|
||||
|
||||
XMR_COIN = 10 ** 12
|
||||
WOW_COIN = 10 ** 11
|
||||
|
||||
|
||||
class Coins(IntEnum):
|
||||
PART = 1
|
||||
BTC = 2
|
||||
LTC = 3
|
||||
DCR = 4
|
||||
# DCR = 4
|
||||
NMC = 5
|
||||
XMR = 6
|
||||
PART_BLIND = 7
|
||||
PART_ANON = 8
|
||||
WOW = 9
|
||||
# ZANO = 9
|
||||
# NDAU = 10
|
||||
PIVX = 11
|
||||
DASH = 12
|
||||
FIRO = 13
|
||||
NAV = 14
|
||||
LTC_MWEB = 15
|
||||
# ZANO = 16
|
||||
|
||||
|
||||
chainparams = {
|
||||
@ -152,41 +155,6 @@ chainparams = {
|
||||
'max_amount': 100000 * COIN,
|
||||
}
|
||||
},
|
||||
Coins.DCR: {
|
||||
'name': 'decred',
|
||||
'ticker': 'DCR',
|
||||
'message_magic': 'Decred Signed Message:\n',
|
||||
'blocks_target': 60 * 5,
|
||||
'decimal_places': 8,
|
||||
'mainnet': {
|
||||
'rpcport': 9109,
|
||||
'pubkey_address': 0x073f,
|
||||
'script_address': 0x071a,
|
||||
'key_prefix': 0x22de,
|
||||
'bip44': 42,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
},
|
||||
'testnet': {
|
||||
'rpcport': 19109,
|
||||
'pubkey_address': 0x0f21,
|
||||
'script_address': 0x0efc,
|
||||
'key_prefix': 0x230e,
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
'name': 'testnet3',
|
||||
},
|
||||
'regtest': { # simnet
|
||||
'rpcport': 18656,
|
||||
'pubkey_address': 0x0e91,
|
||||
'script_address': 0x0e6c,
|
||||
'key_prefix': 0x2307,
|
||||
'bip44': 1,
|
||||
'min_amount': 1000,
|
||||
'max_amount': 100000 * COIN,
|
||||
}
|
||||
},
|
||||
Coins.NMC: {
|
||||
'name': 'namecoin',
|
||||
'ticker': 'NMC',
|
||||
@ -249,33 +217,6 @@ 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',
|
||||
@ -446,3 +387,89 @@ def getCoinIdFromTicker(ticker):
|
||||
return ticker_map[ticker.lower()]
|
||||
except Exception:
|
||||
raise ValueError('Unknown coin')
|
||||
|
||||
|
||||
class CoinInterface:
|
||||
def __init__(self, network):
|
||||
self.setDefaults()
|
||||
self._network = network
|
||||
self._mx_wallet = threading.Lock()
|
||||
|
||||
def setDefaults(self):
|
||||
self._unknown_wallet_seed = True
|
||||
self._restore_height = None
|
||||
|
||||
def make_int(self, amount_in: int, r: int = 0) -> int:
|
||||
return make_int(amount_in, self.exp(), r=r)
|
||||
|
||||
def format_amount(self, amount_in, conv_int=False, r=0):
|
||||
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
|
||||
return format_amount(amount_int, self.exp())
|
||||
|
||||
def coin_name(self) -> str:
|
||||
coin_chainparams = chainparams[self.coin_type()]
|
||||
if coin_chainparams.get('use_ticker_as_name', False):
|
||||
return coin_chainparams['ticker']
|
||||
return coin_chainparams['name'].capitalize()
|
||||
|
||||
def ticker(self) -> str:
|
||||
ticker = chainparams[self.coin_type()]['ticker']
|
||||
if self._network == 'testnet':
|
||||
ticker = 't' + ticker
|
||||
elif self._network == 'regtest':
|
||||
ticker = 'rt' + ticker
|
||||
return ticker
|
||||
|
||||
def getExchangeTicker(self, exchange_name: str) -> str:
|
||||
return chainparams[self.coin_type()]['ticker']
|
||||
|
||||
def getExchangeName(self, exchange_name: str) -> str:
|
||||
return chainparams[self.coin_type()]['name']
|
||||
|
||||
def ticker_mainnet(self) -> str:
|
||||
ticker = chainparams[self.coin_type()]['ticker']
|
||||
return ticker
|
||||
|
||||
def min_amount(self) -> int:
|
||||
return chainparams[self.coin_type()][self._network]['min_amount']
|
||||
|
||||
def max_amount(self) -> int:
|
||||
return chainparams[self.coin_type()][self._network]['max_amount']
|
||||
|
||||
def setWalletSeedWarning(self, value: bool) -> None:
|
||||
self._unknown_wallet_seed = value
|
||||
|
||||
def setWalletRestoreHeight(self, value: int) -> None:
|
||||
self._restore_height = value
|
||||
|
||||
def knownWalletSeed(self) -> bool:
|
||||
return not self._unknown_wallet_seed
|
||||
|
||||
def chainparams(self):
|
||||
return chainparams[self.coin_type()]
|
||||
|
||||
def chainparams_network(self):
|
||||
return chainparams[self.coin_type()][self._network]
|
||||
|
||||
def has_segwit(self) -> bool:
|
||||
return chainparams[self.coin_type()].get('has_segwit', True)
|
||||
|
||||
def is_transient_error(self, ex) -> bool:
|
||||
if isinstance(ex, TemporaryError):
|
||||
return True
|
||||
str_error: str = str(ex).lower()
|
||||
if 'not enough unlocked money' in str_error:
|
||||
return True
|
||||
if 'no unlocked balance' in str_error:
|
||||
return True
|
||||
if 'transaction was rejected by daemon' in str_error:
|
||||
return True
|
||||
if 'invalid unlocked_balance' in str_error:
|
||||
return True
|
||||
if 'daemon is busy' in str_error:
|
||||
return True
|
||||
if 'timed out' in str_error:
|
||||
return True
|
||||
if 'request-sent' in str_error:
|
||||
return True
|
||||
return False
|
||||
|
@ -7,10 +7,10 @@
|
||||
import os
|
||||
|
||||
CONFIG_FILENAME = 'basicswap.json'
|
||||
BASICSWAP_DATADIR = os.getenv('BASICSWAP_DATADIR', os.path.join('~', '.basicswap'))
|
||||
BASICSWAP_DATADIR = os.getenv('BASICSWAP_DATADIR', '~/.basicswap')
|
||||
DEFAULT_ALLOW_CORS = False
|
||||
TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap'))
|
||||
DEFAULT_TEST_BINDIR = os.path.expanduser(os.getenv('DEFAULT_TEST_BINDIR', os.path.join('~', '.basicswap', 'bin')))
|
||||
DEFAULT_TEST_BINDIR = os.path.expanduser(os.getenv('DEFAULT_TEST_BINDIR', '~/.basicswap/bin'))
|
||||
|
||||
bin_suffix = ('.exe' if os.name == 'nt' else '')
|
||||
PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'particl')))
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1,533 +0,0 @@
|
||||
|
||||
|
||||
intro = """
|
||||
blake.py
|
||||
version 5, 2-Apr-2014
|
||||
|
||||
BLAKE is a SHA3 round-3 finalist designed and submitted by
|
||||
Jean-Philippe Aumasson et al.
|
||||
|
||||
At the core of BLAKE is a ChaCha-like mixer, very similar
|
||||
to that found in the stream cipher, ChaCha8. Besides being
|
||||
a very good mixer, ChaCha is fast.
|
||||
|
||||
References:
|
||||
http://www.131002.net/blake/
|
||||
http://csrc.nist.gov/groups/ST/hash/sha-3/index.html
|
||||
http://en.wikipedia.org/wiki/BLAKE_(hash_function)
|
||||
|
||||
This implementation assumes all data is in increments of
|
||||
whole bytes. (The formal definition of BLAKE allows for
|
||||
hashing individual bits.) Note too that this implementation
|
||||
does include the round-3 tweaks where the number of rounds
|
||||
was increased to 14/16 from 10/14.
|
||||
|
||||
This version can be imported into both Python2 (2.6 and 2.7)
|
||||
and Python3 programs. Python 2.5 requires an older version
|
||||
of blake.py (version 4).
|
||||
|
||||
Here are some comparative times for different versions of
|
||||
Python:
|
||||
|
||||
64-bit:
|
||||
2.6 6.284s
|
||||
2.7 6.343s
|
||||
3.2 7.620s
|
||||
pypy (2.7) 2.080s
|
||||
|
||||
32-bit:
|
||||
2.5 (32) 15.389s (with psyco)
|
||||
2.7-32 13.645s
|
||||
3.2-32 12.574s
|
||||
|
||||
One test on a 2.0GHz Core 2 Duo of 10,000 iterations of
|
||||
BLAKE-256 on a short message produced a time of 5.7 seconds.
|
||||
Not bad, but if raw speed is what you want, look to the
|
||||
the C version. It is 40x faster and did the same thing
|
||||
in 0.13 seconds.
|
||||
|
||||
Copyright (c) 2009-2012 by Larry Bugbee, Kent, WA
|
||||
ALL RIGHTS RESERVED.
|
||||
|
||||
blake.py IS EXPERIMENTAL SOFTWARE FOR EDUCATIONAL
|
||||
PURPOSES ONLY. IT IS MADE AVAILABLE "AS-IS" WITHOUT
|
||||
WARRANTY OR GUARANTEE OF ANY KIND. USE SIGNIFIES
|
||||
ACCEPTANCE OF ALL RISK.
|
||||
|
||||
To make your learning and experimentation less cumbersome,
|
||||
blake.py is free for any use.
|
||||
|
||||
Enjoy,
|
||||
|
||||
Larry Bugbee
|
||||
March 2011
|
||||
rev May 2011 - fixed Python version check (tx JP)
|
||||
rev Apr 2012 - fixed an out-of-order bit set in final()
|
||||
- moved self-test to a separate test pgm
|
||||
- this now works with Python2 and Python3
|
||||
rev Apr 2014 - added test and conversion of string input
|
||||
to byte string in update() (tx Soham)
|
||||
- added hexdigest() method.
|
||||
- now support state 3 so only one call to
|
||||
final() per instantiation is allowed. all
|
||||
subsequent calls to final(), digest() or
|
||||
hexdigest() simply return the stored value.
|
||||
|
||||
"""
|
||||
|
||||
import struct
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
#---------------------------------------------------------------
|
||||
|
||||
class BLAKE(object):
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# initial values, constants and padding
|
||||
|
||||
# IVx for BLAKE-x
|
||||
|
||||
IV64 = [
|
||||
0x6A09E667F3BCC908, 0xBB67AE8584CAA73B,
|
||||
0x3C6EF372FE94F82B, 0xA54FF53A5F1D36F1,
|
||||
0x510E527FADE682D1, 0x9B05688C2B3E6C1F,
|
||||
0x1F83D9ABFB41BD6B, 0x5BE0CD19137E2179,
|
||||
]
|
||||
|
||||
IV48 = [
|
||||
0xCBBB9D5DC1059ED8, 0x629A292A367CD507,
|
||||
0x9159015A3070DD17, 0x152FECD8F70E5939,
|
||||
0x67332667FFC00B31, 0x8EB44A8768581511,
|
||||
0xDB0C2E0D64F98FA7, 0x47B5481DBEFA4FA4,
|
||||
]
|
||||
|
||||
# note: the values here are the same as the high-order
|
||||
# half-words of IV64
|
||||
IV32 = [
|
||||
0x6A09E667, 0xBB67AE85,
|
||||
0x3C6EF372, 0xA54FF53A,
|
||||
0x510E527F, 0x9B05688C,
|
||||
0x1F83D9AB, 0x5BE0CD19,
|
||||
]
|
||||
|
||||
# note: the values here are the same as the low-order
|
||||
# half-words of IV48
|
||||
IV28 = [
|
||||
0xC1059ED8, 0x367CD507,
|
||||
0x3070DD17, 0xF70E5939,
|
||||
0xFFC00B31, 0x68581511,
|
||||
0x64F98FA7, 0xBEFA4FA4,
|
||||
]
|
||||
|
||||
# constants for BLAKE-64 and BLAKE-48
|
||||
C64 = [
|
||||
0x243F6A8885A308D3, 0x13198A2E03707344,
|
||||
0xA4093822299F31D0, 0x082EFA98EC4E6C89,
|
||||
0x452821E638D01377, 0xBE5466CF34E90C6C,
|
||||
0xC0AC29B7C97C50DD, 0x3F84D5B5B5470917,
|
||||
0x9216D5D98979FB1B, 0xD1310BA698DFB5AC,
|
||||
0x2FFD72DBD01ADFB7, 0xB8E1AFED6A267E96,
|
||||
0xBA7C9045F12C7F99, 0x24A19947B3916CF7,
|
||||
0x0801F2E2858EFC16, 0x636920D871574E69,
|
||||
]
|
||||
|
||||
# constants for BLAKE-32 and BLAKE-28
|
||||
# note: concatenate and the values are the same as the values
|
||||
# for the 1st half of C64
|
||||
C32 = [
|
||||
0x243F6A88, 0x85A308D3,
|
||||
0x13198A2E, 0x03707344,
|
||||
0xA4093822, 0x299F31D0,
|
||||
0x082EFA98, 0xEC4E6C89,
|
||||
0x452821E6, 0x38D01377,
|
||||
0xBE5466CF, 0x34E90C6C,
|
||||
0xC0AC29B7, 0xC97C50DD,
|
||||
0x3F84D5B5, 0xB5470917,
|
||||
]
|
||||
|
||||
# the 10 permutations of:0,...15}
|
||||
SIGMA = [
|
||||
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15],
|
||||
[14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3],
|
||||
[11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4],
|
||||
[ 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8],
|
||||
[ 9, 0, 5, 7, 2, 4,10,15,14, 1,11,12, 6, 8, 3,13],
|
||||
[ 2,12, 6,10, 0,11, 8, 3, 4,13, 7, 5,15,14, 1, 9],
|
||||
[12, 5, 1,15,14,13, 4,10, 0, 7, 6, 3, 9, 2, 8,11],
|
||||
[13,11, 7,14,12, 1, 3, 9, 5, 0,15, 4, 8, 6, 2,10],
|
||||
[ 6,15,14, 9,11, 3, 0, 8,12, 2,13, 7, 1, 4,10, 5],
|
||||
[10, 2, 8, 4, 7, 6, 1, 5,15,11, 9,14, 3,12,13, 0],
|
||||
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15],
|
||||
[14,10, 4, 8, 9,15,13, 6, 1,12, 0, 2,11, 7, 5, 3],
|
||||
[11, 8,12, 0, 5, 2,15,13,10,14, 3, 6, 7, 1, 9, 4],
|
||||
[ 7, 9, 3, 1,13,12,11,14, 2, 6, 5,10, 4, 0,15, 8],
|
||||
[ 9, 0, 5, 7, 2, 4,10,15,14, 1,11,12, 6, 8, 3,13],
|
||||
[ 2,12, 6,10, 0,11, 8, 3, 4,13, 7, 5,15,14, 1, 9],
|
||||
[12, 5, 1,15,14,13, 4,10, 0, 7, 6, 3, 9, 2, 8,11],
|
||||
[13,11, 7,14,12, 1, 3, 9, 5, 0,15, 4, 8, 6, 2,10],
|
||||
[ 6,15,14, 9,11, 3, 0, 8,12, 2,13, 7, 1, 4,10, 5],
|
||||
[10, 2, 8, 4, 7, 6, 1, 5,15,11, 9,14, 3,12,13, 0],
|
||||
]
|
||||
|
||||
MASK32BITS = 0xFFFFFFFF
|
||||
MASK64BITS = 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def __init__(self, hashbitlen):
|
||||
"""
|
||||
load the hashSate structure (copy hashbitlen...)
|
||||
hashbitlen: length of the hash output
|
||||
"""
|
||||
if hashbitlen not in [224, 256, 384, 512]:
|
||||
raise Exception('hash length not 224, 256, 384 or 512')
|
||||
|
||||
self.hashbitlen = hashbitlen
|
||||
self.h = [0]*8 # current chain value (initialized to the IV)
|
||||
self.t = 0 # number of *BITS* hashed so far
|
||||
self.cache = b'' # cached leftover data not yet compressed
|
||||
self.salt = [0]*4 # salt (null by default)
|
||||
self.state = 1 # set to 2 by update and 3 by final
|
||||
self.nullt = 0 # Boolean value for special case \ell_i=0
|
||||
|
||||
# The algorithm is the same for both the 32- and 64- versions
|
||||
# of BLAKE. The difference is in word size (4 vs 8 bytes),
|
||||
# blocksize (64 vs 128 bytes), number of rounds (14 vs 16)
|
||||
# and a few very specific constants.
|
||||
if (hashbitlen == 224) or (hashbitlen == 256):
|
||||
# setup for 32-bit words and 64-bit block
|
||||
self.byte2int = self._fourByte2int
|
||||
self.int2byte = self._int2fourByte
|
||||
self.MASK = self.MASK32BITS
|
||||
self.WORDBYTES = 4
|
||||
self.WORDBITS = 32
|
||||
self.BLKBYTES = 64
|
||||
self.BLKBITS = 512
|
||||
self.ROUNDS = 14 # was 10 before round 3
|
||||
self.cxx = self.C32
|
||||
self.rot1 = 16 # num bits to shift in G
|
||||
self.rot2 = 12 # num bits to shift in G
|
||||
self.rot3 = 8 # num bits to shift in G
|
||||
self.rot4 = 7 # num bits to shift in G
|
||||
self.mul = 0 # for 32-bit words, 32<<self.mul where self.mul = 0
|
||||
|
||||
# 224- and 256-bit versions (32-bit words)
|
||||
if hashbitlen == 224:
|
||||
self.h = self.IV28[:]
|
||||
else:
|
||||
self.h = self.IV32[:]
|
||||
|
||||
elif (hashbitlen == 384) or (hashbitlen == 512):
|
||||
# setup for 64-bit words and 128-bit block
|
||||
self.byte2int = self._eightByte2int
|
||||
self.int2byte = self._int2eightByte
|
||||
self.MASK = self.MASK64BITS
|
||||
self.WORDBYTES = 8
|
||||
self.WORDBITS = 64
|
||||
self.BLKBYTES = 128
|
||||
self.BLKBITS = 1024
|
||||
self.ROUNDS = 16 # was 14 before round 3
|
||||
self.cxx = self.C64
|
||||
self.rot1 = 32 # num bits to shift in G
|
||||
self.rot2 = 25 # num bits to shift in G
|
||||
self.rot3 = 16 # num bits to shift in G
|
||||
self.rot4 = 11 # num bits to shift in G
|
||||
self.mul = 1 # for 64-bit words, 32<<self.mul where self.mul = 1
|
||||
|
||||
# 384- and 512-bit versions (64-bit words)
|
||||
if hashbitlen == 384:
|
||||
self.h = self.IV48[:]
|
||||
else:
|
||||
self.h = self.IV64[:]
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def _compress(self, block):
|
||||
byte2int = self.byte2int
|
||||
mul = self.mul # de-reference these for ...speed? ;-)
|
||||
cxx = self.cxx
|
||||
rot1 = self.rot1
|
||||
rot2 = self.rot2
|
||||
rot3 = self.rot3
|
||||
rot4 = self.rot4
|
||||
MASK = self.MASK
|
||||
WORDBITS = self.WORDBITS
|
||||
SIGMA = self.SIGMA
|
||||
|
||||
# get message (<<2 is the same as *4 but faster)
|
||||
m = [byte2int(block[i<<2<<mul:(i<<2<<mul)+(4<<mul)]) for i in range(16)]
|
||||
|
||||
# initialization
|
||||
v = [0]*16
|
||||
v[ 0: 8] = [self.h[i] for i in range(8)]
|
||||
v[ 8:16] = [self.cxx[i] for i in range(8)]
|
||||
v[ 8:12] = [v[8+i] ^ self.salt[i] for i in range(4)]
|
||||
if self.nullt == 0: # (i>>1 is the same as i/2 but faster)
|
||||
v[12] = v[12] ^ (self.t & MASK)
|
||||
v[13] = v[13] ^ (self.t & MASK)
|
||||
v[14] = v[14] ^ (self.t >> self.WORDBITS)
|
||||
v[15] = v[15] ^ (self.t >> self.WORDBITS)
|
||||
|
||||
# - - - - - - - - - - - - - - - - -
|
||||
# ready? let's ChaCha!!!
|
||||
|
||||
def G(a, b, c, d, i):
|
||||
va = v[a] # it's faster to deref and reref later
|
||||
vb = v[b]
|
||||
vc = v[c]
|
||||
vd = v[d]
|
||||
|
||||
sri = SIGMA[round][i]
|
||||
sri1 = SIGMA[round][i+1]
|
||||
|
||||
va = ((va + vb) + (m[sri] ^ cxx[sri1]) ) & MASK
|
||||
x = vd ^ va
|
||||
vd = (x >> rot1) | ((x << (WORDBITS-rot1)) & MASK)
|
||||
vc = (vc + vd) & MASK
|
||||
x = vb ^ vc
|
||||
vb = (x >> rot2) | ((x << (WORDBITS-rot2)) & MASK)
|
||||
|
||||
va = ((va + vb) + (m[sri1] ^ cxx[sri]) ) & MASK
|
||||
x = vd ^ va
|
||||
vd = (x >> rot3) | ((x << (WORDBITS-rot3)) & MASK)
|
||||
vc = (vc + vd) & MASK
|
||||
x = vb ^ vc
|
||||
vb = (x >> rot4) | ((x << (WORDBITS-rot4)) & MASK)
|
||||
|
||||
v[a] = va
|
||||
v[b] = vb
|
||||
v[c] = vc
|
||||
v[d] = vd
|
||||
|
||||
for round in range(self.ROUNDS):
|
||||
# column step
|
||||
G( 0, 4, 8,12, 0)
|
||||
G( 1, 5, 9,13, 2)
|
||||
G( 2, 6,10,14, 4)
|
||||
G( 3, 7,11,15, 6)
|
||||
# diagonal step
|
||||
G( 0, 5,10,15, 8)
|
||||
G( 1, 6,11,12,10)
|
||||
G( 2, 7, 8,13,12)
|
||||
G( 3, 4, 9,14,14)
|
||||
|
||||
# - - - - - - - - - - - - - - - - -
|
||||
|
||||
# save current hash value (use i&0x3 to get 0,1,2,3,0,1,2,3)
|
||||
self.h = [self.h[i]^v[i]^v[i+8]^self.salt[i&0x3]
|
||||
for i in range(8)]
|
||||
# print 'self.h', [num2hex(h) for h in self.h]
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def addsalt(self, salt):
|
||||
""" adds a salt to the hash function (OPTIONAL)
|
||||
should be called AFTER Init, and BEFORE update
|
||||
salt: a bytestring, length determined by hashbitlen.
|
||||
if not of sufficient length, the bytestring
|
||||
will be assumed to be a big endian number and
|
||||
prefixed with an appropriate number of null
|
||||
bytes, and if too large, only the low order
|
||||
bytes will be used.
|
||||
|
||||
if hashbitlen=224 or 256, then salt will be 16 bytes
|
||||
if hashbitlen=384 or 512, then salt will be 32 bytes
|
||||
"""
|
||||
# fail if addsalt() was not called at the right time
|
||||
if self.state != 1:
|
||||
raise Exception('addsalt() not called after init() and before update()')
|
||||
# salt size is to be 4x word size
|
||||
saltsize = self.WORDBYTES * 4
|
||||
# if too short, prefix with null bytes. if too long,
|
||||
# truncate high order bytes
|
||||
if len(salt) < saltsize:
|
||||
salt = (chr(0)*(saltsize-len(salt)) + salt)
|
||||
else:
|
||||
salt = salt[-saltsize:]
|
||||
# prep the salt array
|
||||
self.salt[0] = self.byte2int(salt[ : 4<<self.mul])
|
||||
self.salt[1] = self.byte2int(salt[ 4<<self.mul: 8<<self.mul])
|
||||
self.salt[2] = self.byte2int(salt[ 8<<self.mul:12<<self.mul])
|
||||
self.salt[3] = self.byte2int(salt[12<<self.mul: ])
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def update(self, data):
|
||||
""" update the state with new data, storing excess data
|
||||
as necessary. may be called multiple times and if a
|
||||
call sends less than a full block in size, the leftover
|
||||
is cached and will be consumed in the next call
|
||||
data: data to be hashed (bytestring)
|
||||
"""
|
||||
self.state = 2
|
||||
|
||||
BLKBYTES = self.BLKBYTES # de-referenced for improved readability
|
||||
BLKBITS = self.BLKBITS
|
||||
|
||||
datalen = len(data)
|
||||
if not datalen: return
|
||||
|
||||
if type(data) == type(u''):
|
||||
|
||||
# use either of the next two lines for a proper
|
||||
# response under both Python2 and Python3
|
||||
data = data.encode('UTF-8') # converts to byte string
|
||||
#data = bytearray(data, 'utf-8') # use if want mutable
|
||||
|
||||
# This next line works for Py3 but fails under
|
||||
# Py2 because the Py2 version of bytes() will
|
||||
# accept only *one* argument. Arrrrgh!!!
|
||||
#data = bytes(data, 'utf-8') # converts to immutable byte
|
||||
# string but... under p7
|
||||
# bytes() wants only 1 arg
|
||||
# ...a dummy, 2nd argument like encoding=None
|
||||
# that does nothing would at least allow
|
||||
# compatibility between Python2 and Python3.
|
||||
|
||||
left = len(self.cache)
|
||||
fill = BLKBYTES - left
|
||||
|
||||
# if any cached data and any added new data will fill a
|
||||
# full block, fill and compress
|
||||
if left and datalen >= fill:
|
||||
self.cache = self.cache + data[:fill]
|
||||
self.t += BLKBITS # update counter
|
||||
self._compress(self.cache)
|
||||
self.cache = b''
|
||||
data = data[fill:]
|
||||
datalen -= fill
|
||||
|
||||
# compress new data until not enough for a full block
|
||||
while datalen >= BLKBYTES:
|
||||
self.t += BLKBITS # update counter
|
||||
self._compress(data[:BLKBYTES])
|
||||
data = data[BLKBYTES:]
|
||||
datalen -= BLKBYTES
|
||||
|
||||
# cache all leftover bytes until next call to update()
|
||||
if datalen > 0:
|
||||
self.cache = self.cache + data[:datalen]
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def final(self, data=''):
|
||||
""" finalize the hash -- pad and hash remaining data
|
||||
returns hashval, the digest
|
||||
"""
|
||||
if self.state == 3:
|
||||
# we have already finalized so simply return the
|
||||
# previously calculated/stored hash value
|
||||
return self.hash
|
||||
|
||||
if data:
|
||||
self.update(data)
|
||||
|
||||
ZZ = b'\x00'
|
||||
ZO = b'\x01'
|
||||
OZ = b'\x80'
|
||||
OO = b'\x81'
|
||||
PADDING = OZ + ZZ*128 # pre-formatted padding data
|
||||
|
||||
# copy nb. bits hash in total as a 64-bit BE word
|
||||
# copy nb. bits hash in total as a 128-bit BE word
|
||||
tt = self.t + (len(self.cache) << 3)
|
||||
if self.BLKBYTES == 64:
|
||||
msglen = self._int2eightByte(tt)
|
||||
else:
|
||||
low = tt & self.MASK
|
||||
high = tt >> self.WORDBITS
|
||||
msglen = self._int2eightByte(high) + self._int2eightByte(low)
|
||||
|
||||
# size of block without the words at the end that count
|
||||
# the number of bits, 55 or 111.
|
||||
# Note: (((self.WORDBITS/8)*2)+1) equals ((self.WORDBITS>>2)+1)
|
||||
sizewithout = self.BLKBYTES - ((self.WORDBITS>>2)+1)
|
||||
|
||||
if len(self.cache) == sizewithout:
|
||||
# special case of one padding byte
|
||||
self.t -= 8
|
||||
if self.hashbitlen in [224, 384]:
|
||||
self.update(OZ)
|
||||
else:
|
||||
self.update(OO)
|
||||
else:
|
||||
if len(self.cache) < sizewithout:
|
||||
# enough space to fill the block
|
||||
# use t=0 if no remaining data
|
||||
if len(self.cache) == 0:
|
||||
self.nullt=1
|
||||
self.t -= (sizewithout - len(self.cache)) << 3
|
||||
self.update(PADDING[:sizewithout - len(self.cache)])
|
||||
else:
|
||||
# NOT enough space, need 2 compressions
|
||||
# ...add marker, pad with nulls and compress
|
||||
self.t -= (self.BLKBYTES - len(self.cache)) << 3
|
||||
self.update(PADDING[:self.BLKBYTES - len(self.cache)])
|
||||
# ...now pad w/nulls leaving space for marker & bit count
|
||||
self.t -= (sizewithout+1) << 3
|
||||
self.update(PADDING[1:sizewithout+1]) # pad with zeroes
|
||||
|
||||
self.nullt = 1 # raise flag to set t=0 at the next _compress
|
||||
|
||||
# append a marker byte
|
||||
if self.hashbitlen in [224, 384]:
|
||||
self.update(ZZ)
|
||||
else:
|
||||
self.update(ZO)
|
||||
self.t -= 8
|
||||
|
||||
# append the number of bits (long long)
|
||||
self.t -= self.BLKBYTES
|
||||
self.update(msglen)
|
||||
|
||||
hashval = []
|
||||
if self.BLKBYTES == 64:
|
||||
for h in self.h:
|
||||
hashval.append(self._int2fourByte(h))
|
||||
else:
|
||||
for h in self.h:
|
||||
hashval.append(self._int2eightByte(h))
|
||||
|
||||
self.hash = b''.join(hashval)[:self.hashbitlen >> 3]
|
||||
self.state = 3
|
||||
|
||||
return self.hash
|
||||
|
||||
digest = final # may use digest() as a synonym for final()
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
def hexdigest(self, data=''):
|
||||
return hexlify(self.final(data)).decode('UTF-8')
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# utility functions
|
||||
|
||||
def _fourByte2int(self, bytestr): # see also long2byt() below
|
||||
""" convert a 4-byte string to an int (long) """
|
||||
return struct.unpack('!L', bytestr)[0]
|
||||
|
||||
def _eightByte2int(self, bytestr):
|
||||
""" convert a 8-byte string to an int (long long) """
|
||||
return struct.unpack('!Q', bytestr)[0]
|
||||
|
||||
def _int2fourByte(self, x): # see also long2byt() below
|
||||
""" convert a number to a 4-byte string, high order
|
||||
truncation possible (in Python x could be a BIGNUM)
|
||||
"""
|
||||
return struct.pack('!L', x)
|
||||
|
||||
def _int2eightByte(self, x):
|
||||
""" convert a number to a 8-byte string, high order
|
||||
truncation possible (in Python x could be a BIGNUM)
|
||||
"""
|
||||
return struct.pack('!Q', x)
|
||||
|
||||
|
||||
#---------------------------------------------------------------
|
||||
#---------------------------------------------------------------
|
||||
#---------------------------------------------------------------
|
||||
|
||||
|
||||
def blake_hash(data):
|
||||
return BLAKE(256).digest(data)
|
@ -1,37 +0,0 @@
|
||||
from blake256 import blake_hash
|
||||
|
||||
testVectors = [
|
||||
["716f6e863f744b9ac22c97ec7b76ea5f5908bc5b2f67c61510bfc4751384ea7a", ""],
|
||||
["43234ff894a9c0590d0246cfc574eb781a80958b01d7a2fa1ac73c673ba5e311", "a"],
|
||||
["658c6d9019a1deddbcb3640a066dfd23471553a307ab941fd3e677ba887be329", "ab"],
|
||||
["1833a9fa7cf4086bd5fda73da32e5a1d75b4c3f89d5c436369f9d78bb2da5c28", "abc"],
|
||||
["35282468f3b93c5aaca6408582fced36e578f67671ed0741c332d68ac72d7aa2", "abcd"],
|
||||
["9278d633efce801c6aa62987d7483d50e3c918caed7d46679551eed91fba8904", "abcde"],
|
||||
["7a17ee5e289845adcafaf6ca1b05c4a281b232a71c7083f66c19ba1d1169a8d4", "abcdef"],
|
||||
["ee8c7f94ff805cb2e644643010ea43b0222056420917ec70c3da764175193f8f", "abcdefg"],
|
||||
["7b37c0876d29c5add7800a1823795a82b809fc12f799ff6a4b5e58d52c42b17e", "abcdefgh"],
|
||||
["bdc514bea74ffbb9c3aa6470b08ceb80a88e313ad65e4a01457bbffd0acc86de", "abcdefghi"],
|
||||
["12e3afb9739df8d727e93d853faeafc374cc55aedc937e5a1e66f5843b1d4c2e", "abcdefghij"],
|
||||
["22297d373b751f581944bb26315133f6fda2f0bf60f65db773900f61f81b7e79", "Discard medicine more than two years old."],
|
||||
["4d48d137bc9cf6d21415b805bf33f59320337d85c673998260e03a02a0d760cd", "He who has a shady past knows that nice guys finish last."],
|
||||
["beba299e10f93e17d45663a6dc4b8c9349e4f5b9bac0d7832389c40a1b401e5c", "I wouldn't marry him with a ten foot pole."],
|
||||
["42e082ae7f967781c6cd4e0ceeaeeb19fb2955adbdbaf8c7ec4613ac130071b3", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"],
|
||||
["207d06b205bfb359df91b48b6fd8aa6e4798b712d1cc5e91a254da9cef8684a3", "The days of the digital watch are numbered. -Tom Stoppard"],
|
||||
["d56eab6927e371e2148b0788779aaf565d30567af2af822b6be3b90db9767a70", "Nepal premier won't resign."],
|
||||
["01020709ca7fd10dc7756ce767d508d7206167d300b7a7ed76838a8547a7898c", "For every action there is an equal and opposite government program."],
|
||||
["5569a6cc6535a66da221d8f6ad25008f28752d0343f3f1d757f1ecc9b1c61536", "His money is twice tainted: 'taint yours and 'taint mine."],
|
||||
["8ff699b5ac7687c82600e89d0ff6cfa87e7179759184386971feb76fbae9975f", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"],
|
||||
["f4b3a7c85a418b15ce330fd41ae0254b036ad48dd98aa37f0506a995ba9c6029", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"],
|
||||
["1ed94bab64fe560ef0983165fcb067e9a8a971c1db8e6fb151ff9a7c7fe877e3", "size: a.out: bad magic"],
|
||||
["ff15b54992eedf9889f7b4bbb16692881aa01ed10dfc860fdb04785d8185cd3c", "The major problem is with sendmail. -Mark Horton"],
|
||||
["8a0a7c417a47deec0b6474d8c247da142d2e315113a2817af3de8f45690d8652", "Give me a rock, paper and scissors and I will move the world. CCFestoon"],
|
||||
["310d263fdab056a930324cdea5f46f9ea70219c1a74b01009994484113222a62", "If the enemy is within range, then so are you."],
|
||||
["1aaa0903aa4cf872fe494c322a6e535698ea2140e15f26fb6088287aedceb6ba", "It's well we cannot hear the screams/That we create in others' dreams."],
|
||||
["2eb81bcaa9e9185a7587a1b26299dcfb30f2a58a7f29adb584b969725457ad4f", "You remind me of a TV show, but that's all right: I watch it anyway."],
|
||||
["c27b1683ef76e274680ab5492e592997b0d9d5ac5a5f4651b6036f64215256af", "C is as portable as Stonehedge!!"],
|
||||
["3995cce8f32b174c22ffac916124bd095c80205d9d5f1bb08a155ac24b40d6cb", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"],
|
||||
["496f7063f8bd479bf54e9d87e9ba53e277839ac7fdaecc5105f2879b58ee562f", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"],
|
||||
["2e0eff918940b01eea9539a02212f33ee84f77fab201f4287aa6167e4a1ed043", "How can you write a big system without C++? -Paul Glick"]]
|
||||
|
||||
for vectorSet in testVectors:
|
||||
assert vectorSet[0] == blake_hash(vectorSet[1]).encode('hex')
|
@ -1,3 +0,0 @@
|
||||
from .mnemonic import Mnemonic
|
||||
|
||||
__all__ = ["Mnemonic"]
|
@ -1,298 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2013 Pavol Rusnak
|
||||
# Copyright (c) 2017 mruddy
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
# this software and associated documentation files (the "Software"), to deal in
|
||||
# the Software without restriction, including without limitation the rights to
|
||||
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
# of the Software, and to permit persons to whom the Software is furnished to do
|
||||
# so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import itertools
|
||||
import os
|
||||
import secrets
|
||||
import typing as t
|
||||
import unicodedata
|
||||
|
||||
PBKDF2_ROUNDS = 2048
|
||||
|
||||
|
||||
class ConfigurationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# Refactored code segments from <https://github.com/keis/base58>
|
||||
def b58encode(v: bytes) -> str:
|
||||
alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
|
||||
p, acc = 1, 0
|
||||
for c in reversed(v):
|
||||
acc += p * c
|
||||
p = p << 8
|
||||
|
||||
string = ""
|
||||
while acc:
|
||||
acc, idx = divmod(acc, 58)
|
||||
string = alphabet[idx : idx + 1] + string
|
||||
return string
|
||||
|
||||
|
||||
class Mnemonic(object):
|
||||
def __init__(self, language: str = "english", wordlist: list[str] | None = None):
|
||||
self.radix = 2048
|
||||
self.language = language
|
||||
|
||||
if wordlist is None:
|
||||
d = os.path.join(os.path.dirname(__file__), f"wordlist/{language}.txt")
|
||||
if os.path.exists(d) and os.path.isfile(d):
|
||||
with open(d, "r", encoding="utf-8") as f:
|
||||
wordlist = [w.strip() for w in f.readlines()]
|
||||
else:
|
||||
raise ConfigurationError("Language not detected")
|
||||
|
||||
if len(wordlist) != self.radix:
|
||||
raise ConfigurationError(f"Wordlist must contain {self.radix} words.")
|
||||
|
||||
self.wordlist = wordlist
|
||||
# Japanese must be joined by ideographic space
|
||||
self.delimiter = "\u3000" if language == "japanese" else " "
|
||||
|
||||
@classmethod
|
||||
def list_languages(cls) -> list[str]:
|
||||
return [
|
||||
f.split(".")[0]
|
||||
for f in os.listdir(os.path.join(os.path.dirname(__file__), "wordlist"))
|
||||
if f.endswith(".txt")
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def normalize_string(txt: t.AnyStr) -> str:
|
||||
if isinstance(txt, bytes):
|
||||
utxt = txt.decode("utf8")
|
||||
elif isinstance(txt, str):
|
||||
utxt = txt
|
||||
else:
|
||||
raise TypeError("String value expected")
|
||||
|
||||
return unicodedata.normalize("NFKD", utxt)
|
||||
|
||||
@classmethod
|
||||
def detect_language(cls, code: str) -> str:
|
||||
"""Scan the Mnemonic until the language becomes unambiguous, including as abbreviation prefixes.
|
||||
|
||||
Unfortunately, there are valid words that are ambiguous between languages, which are complete words
|
||||
in one language and are prefixes in another:
|
||||
|
||||
english: abandon ... about
|
||||
french: abandon ... aboutir
|
||||
|
||||
If prefixes remain ambiguous, require exactly one language where word(s) match exactly.
|
||||
"""
|
||||
code = cls.normalize_string(code)
|
||||
possible = set(cls(lang) for lang in cls.list_languages())
|
||||
words = set(code.split())
|
||||
for word in words:
|
||||
# possible languages have candidate(s) starting with the word/prefix
|
||||
possible = set(
|
||||
p for p in possible if any(c.startswith(word) for c in p.wordlist)
|
||||
)
|
||||
if not possible:
|
||||
raise ConfigurationError(f"Language unrecognized for {word!r}")
|
||||
if len(possible) == 1:
|
||||
return possible.pop().language
|
||||
# Multiple languages match: A prefix in many, but an exact match in one determines language.
|
||||
complete = set()
|
||||
for word in words:
|
||||
exact = set(p for p in possible if word in p.wordlist)
|
||||
if len(exact) == 1:
|
||||
complete.update(exact)
|
||||
if len(complete) == 1:
|
||||
return complete.pop().language
|
||||
raise ConfigurationError(
|
||||
f"Language ambiguous between {', '.join(p.language for p in possible)}"
|
||||
)
|
||||
|
||||
def generate(self, strength: int = 128) -> str:
|
||||
"""
|
||||
Create a new mnemonic using a random generated number as entropy.
|
||||
|
||||
As defined in BIP39, the entropy must be a multiple of 32 bits, and its size must be between 128 and 256 bits.
|
||||
Therefore the possible values for `strength` are 128, 160, 192, 224 and 256.
|
||||
|
||||
If not provided, the default entropy length will be set to 128 bits.
|
||||
|
||||
The return is a list of words that encodes the generated entropy.
|
||||
|
||||
:param strength: Number of bytes used as entropy
|
||||
:type strength: int
|
||||
:return: A randomly generated mnemonic
|
||||
:rtype: str
|
||||
"""
|
||||
if strength not in [128, 160, 192, 224, 256]:
|
||||
raise ValueError(
|
||||
"Invalid strength value. Allowed values are [128, 160, 192, 224, 256]."
|
||||
)
|
||||
return self.to_mnemonic(secrets.token_bytes(strength // 8))
|
||||
|
||||
# Adapted from <http://tinyurl.com/oxmn476>
|
||||
def to_entropy(self, words: list[str] | str) -> bytearray:
|
||||
if not isinstance(words, list):
|
||||
words = words.split(" ")
|
||||
if len(words) not in [12, 15, 18, 21, 24]:
|
||||
raise ValueError(
|
||||
"Number of words must be one of the following: [12, 15, 18, 21, 24], but it is not (%d)."
|
||||
% len(words)
|
||||
)
|
||||
# Look up all the words in the list and construct the
|
||||
# concatenation of the original entropy and the checksum.
|
||||
concatLenBits = len(words) * 11
|
||||
concatBits = [False] * concatLenBits
|
||||
wordindex = 0
|
||||
for word in words:
|
||||
# Find the words index in the wordlist
|
||||
ndx = self.wordlist.index(self.normalize_string(word))
|
||||
if ndx < 0:
|
||||
raise LookupError('Unable to find "%s" in word list.' % word)
|
||||
# Set the next 11 bits to the value of the index.
|
||||
for ii in range(11):
|
||||
concatBits[(wordindex * 11) + ii] = (ndx & (1 << (10 - ii))) != 0
|
||||
wordindex += 1
|
||||
checksumLengthBits = concatLenBits // 33
|
||||
entropyLengthBits = concatLenBits - checksumLengthBits
|
||||
# Extract original entropy as bytes.
|
||||
entropy = bytearray(entropyLengthBits // 8)
|
||||
for ii in range(len(entropy)):
|
||||
for jj in range(8):
|
||||
if concatBits[(ii * 8) + jj]:
|
||||
entropy[ii] |= 1 << (7 - jj)
|
||||
# Take the digest of the entropy.
|
||||
hashBytes = hashlib.sha256(entropy).digest()
|
||||
hashBits = list(
|
||||
itertools.chain.from_iterable(
|
||||
[c & (1 << (7 - i)) != 0 for i in range(8)] for c in hashBytes
|
||||
)
|
||||
)
|
||||
# Check all the checksum bits.
|
||||
for i in range(checksumLengthBits):
|
||||
if concatBits[entropyLengthBits + i] != hashBits[i]:
|
||||
raise ValueError("Failed checksum.")
|
||||
return entropy
|
||||
|
||||
def to_mnemonic(self, data: bytes) -> str:
|
||||
if len(data) not in [16, 20, 24, 28, 32]:
|
||||
raise ValueError(
|
||||
f"Data length should be one of the following: [16, 20, 24, 28, 32], but it is not {len(data)}."
|
||||
)
|
||||
h = hashlib.sha256(data).hexdigest()
|
||||
b = (
|
||||
bin(int.from_bytes(data, byteorder="big"))[2:].zfill(len(data) * 8)
|
||||
+ bin(int(h, 16))[2:].zfill(256)[: len(data) * 8 // 32]
|
||||
)
|
||||
result = []
|
||||
for i in range(len(b) // 11):
|
||||
idx = int(b[i * 11 : (i + 1) * 11], 2)
|
||||
result.append(self.wordlist[idx])
|
||||
return self.delimiter.join(result)
|
||||
|
||||
def check(self, mnemonic: str) -> bool:
|
||||
mnemonic_list = self.normalize_string(mnemonic).split(" ")
|
||||
# list of valid mnemonic lengths
|
||||
if len(mnemonic_list) not in [12, 15, 18, 21, 24]:
|
||||
return False
|
||||
try:
|
||||
idx = map(
|
||||
lambda x: bin(self.wordlist.index(x))[2:].zfill(11), mnemonic_list
|
||||
)
|
||||
b = "".join(idx)
|
||||
except ValueError:
|
||||
return False
|
||||
l = len(b) # noqa: E741
|
||||
d = b[: l // 33 * 32]
|
||||
h = b[-l // 33 :]
|
||||
nd = int(d, 2).to_bytes(l // 33 * 4, byteorder="big")
|
||||
nh = bin(int(hashlib.sha256(nd).hexdigest(), 16))[2:].zfill(256)[: l // 33]
|
||||
return h == nh
|
||||
|
||||
def expand_word(self, prefix: str) -> str:
|
||||
if prefix in self.wordlist:
|
||||
return prefix
|
||||
else:
|
||||
matches = [word for word in self.wordlist if word.startswith(prefix)]
|
||||
if len(matches) == 1: # matched exactly one word in the wordlist
|
||||
return matches[0]
|
||||
else:
|
||||
# exact match not found.
|
||||
# this is not a validation routine, just return the input
|
||||
return prefix
|
||||
|
||||
def expand(self, mnemonic: str) -> str:
|
||||
return " ".join(map(self.expand_word, mnemonic.split(" ")))
|
||||
|
||||
@classmethod
|
||||
def to_seed(cls, mnemonic: str, passphrase: str = "") -> bytes:
|
||||
mnemonic = cls.normalize_string(mnemonic)
|
||||
passphrase = cls.normalize_string(passphrase)
|
||||
passphrase = "mnemonic" + passphrase
|
||||
mnemonic_bytes = mnemonic.encode("utf-8")
|
||||
passphrase_bytes = passphrase.encode("utf-8")
|
||||
stretched = hashlib.pbkdf2_hmac(
|
||||
"sha512", mnemonic_bytes, passphrase_bytes, PBKDF2_ROUNDS
|
||||
)
|
||||
return stretched[:64]
|
||||
|
||||
@staticmethod
|
||||
def to_hd_master_key(seed: bytes, testnet: bool = False) -> str:
|
||||
if len(seed) != 64:
|
||||
raise ValueError("Provided seed should have length of 64")
|
||||
|
||||
# Compute HMAC-SHA512 of seed
|
||||
seed = hmac.new(b"Bitcoin seed", seed, digestmod=hashlib.sha512).digest()
|
||||
|
||||
# Serialization format can be found at: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format
|
||||
xprv = b"\x04\x88\xad\xe4" # Version for private mainnet
|
||||
if testnet:
|
||||
xprv = b"\x04\x35\x83\x94" # Version for private testnet
|
||||
xprv += b"\x00" * 9 # Depth, parent fingerprint, and child number
|
||||
xprv += seed[32:] # Chain code
|
||||
xprv += b"\x00" + seed[:32] # Master key
|
||||
|
||||
# Double hash using SHA256
|
||||
hashed_xprv = hashlib.sha256(xprv).digest()
|
||||
hashed_xprv = hashlib.sha256(hashed_xprv).digest()
|
||||
|
||||
# Append 4 bytes of checksum
|
||||
xprv += hashed_xprv[:4]
|
||||
|
||||
# Return base58
|
||||
return b58encode(xprv)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
hex_data = sys.argv[1]
|
||||
else:
|
||||
hex_data = sys.stdin.readline().strip()
|
||||
data = bytes.fromhex(hex_data)
|
||||
m = Mnemonic("english")
|
||||
print(m.to_mnemonic(data))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1 +0,0 @@
|
||||
# Marker file for PEP 561.
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,7 @@ from enum import IntEnum, auto
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
|
||||
CURRENT_DB_VERSION = 24
|
||||
CURRENT_DB_VERSION = 23
|
||||
CURRENT_DB_DATA_VERSION = 4
|
||||
Base = declarative_base()
|
||||
|
||||
@ -127,7 +127,6 @@ class Bid(Base):
|
||||
amount_to = sa.Column(sa.BigInteger) # amount * offer.rate
|
||||
|
||||
pkhash_buyer = sa.Column(sa.LargeBinary)
|
||||
pkhash_buyer_to = sa.Column(sa.LargeBinary) # Used for the ptx if coin pubkey hashes differ
|
||||
amount = sa.Column(sa.BigInteger)
|
||||
rate = sa.Column(sa.BigInteger)
|
||||
|
||||
@ -192,11 +191,6 @@ class Bid(Base):
|
||||
else:
|
||||
self.states += pack_state(new_state, now)
|
||||
|
||||
def getLockTXBVout(self):
|
||||
if self.xmr_b_lock_tx:
|
||||
return self.xmr_b_lock_tx.vout
|
||||
return None
|
||||
|
||||
|
||||
class SwapTx(Base):
|
||||
__tablename__ = 'transactions'
|
||||
@ -528,14 +522,3 @@ class MessageLink(Base):
|
||||
msg_type = sa.Column(sa.Integer)
|
||||
msg_sequence = sa.Column(sa.Integer)
|
||||
msg_id = sa.Column(sa.LargeBinary)
|
||||
|
||||
|
||||
class CheckedBlock(Base):
|
||||
__tablename__ = 'checkedblocks'
|
||||
|
||||
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
||||
created_at = sa.Column(sa.BigInteger)
|
||||
coin_type = sa.Column(sa.Integer)
|
||||
block_height = sa.Column(sa.Integer)
|
||||
block_hash = sa.Column(sa.LargeBinary)
|
||||
block_time = sa.Column(sa.BigInteger)
|
||||
|
@ -93,7 +93,7 @@ def upgradeDatabaseData(self, data_version):
|
||||
created_at=now))
|
||||
|
||||
self.db_data_version = CURRENT_DB_DATA_VERSION
|
||||
self.setIntKV('db_data_version', self.db_data_version, session)
|
||||
self.setIntKVInSession('db_data_version', self.db_data_version, session)
|
||||
session.commit()
|
||||
self.log.info('Upgraded database records to version {}'.format(self.db_data_version))
|
||||
finally:
|
||||
@ -300,21 +300,9 @@ def upgradeDatabase(self, db_version):
|
||||
elif current_version == 22:
|
||||
db_version += 1
|
||||
session.execute('ALTER TABLE offers ADD COLUMN amount_to INTEGER')
|
||||
elif current_version == 23:
|
||||
db_version += 1
|
||||
session.execute('''
|
||||
CREATE TABLE checkedblocks (
|
||||
record_id INTEGER NOT NULL,
|
||||
created_at BIGINT,
|
||||
coin_type INTEGER,
|
||||
block_height INTEGER,
|
||||
block_hash BLOB,
|
||||
block_time INTEGER,
|
||||
PRIMARY KEY (record_id))''')
|
||||
session.execute('ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB')
|
||||
if current_version != db_version:
|
||||
self.db_version = db_version
|
||||
self.setIntKV('db_version', db_version, session)
|
||||
self.setIntKVInSession('db_version', db_version, session)
|
||||
session.commit()
|
||||
session.close()
|
||||
session.remove()
|
||||
|
@ -52,7 +52,5 @@ def remove_expired_data(self, time_offset: int = 0):
|
||||
if num_offers > 0 or num_bids > 0:
|
||||
self.log.info('Removed data for {} expired offer{} and {} bid{}.'.format(num_offers, 's' if num_offers != 1 else '', num_bids, 's' if num_bids != 1 else ''))
|
||||
|
||||
session.execute('DELETE FROM checkedblocks WHERE created_at <= :expired_at', {'expired_at': now - time_offset})
|
||||
|
||||
finally:
|
||||
self.closeSession(session)
|
||||
|
@ -98,8 +98,6 @@ def parse_cmd(cmd: str, type_map: str):
|
||||
type_ind = type_map[i]
|
||||
if type_ind == 'i':
|
||||
typed_params.append(int(param))
|
||||
elif type_ind == 'f':
|
||||
typed_params.append(float(param))
|
||||
elif type_ind == 'b':
|
||||
typed_params.append(toBool(param))
|
||||
elif type_ind == 'j':
|
||||
@ -267,8 +265,6 @@ class HttpHandler(BaseHTTPRequestHandler):
|
||||
summary = swap_client.getSummary()
|
||||
|
||||
result = None
|
||||
cmd = ''
|
||||
coin_type_selected = -1
|
||||
coin_type = -1
|
||||
coin_id = -1
|
||||
call_type = 'cli'
|
||||
@ -281,84 +277,71 @@ class HttpHandler(BaseHTTPRequestHandler):
|
||||
call_type = get_data_entry_or(form_data, 'call_type', 'cli')
|
||||
type_map = get_data_entry_or(form_data, 'type_map', '')
|
||||
try:
|
||||
coin_type_selected = get_data_entry(form_data, 'coin_type')
|
||||
coin_type_split = coin_type_selected.split(',')
|
||||
coin_type = Coins(int(coin_type_split[0]))
|
||||
coin_variant = int(coin_type_split[1])
|
||||
coin_id = int(get_data_entry(form_data, 'coin_type'))
|
||||
if coin_id in (-2, -3, -4):
|
||||
coin_type = Coins(Coins.XMR)
|
||||
elif coin_id in (-5,):
|
||||
coin_type = Coins(Coins.LTC)
|
||||
else:
|
||||
coin_type = Coins(coin_id)
|
||||
except Exception:
|
||||
raise ValueError('Unknown Coin Type')
|
||||
|
||||
if coin_type in (Coins.DCR,):
|
||||
call_type = 'http'
|
||||
|
||||
try:
|
||||
cmd = get_data_entry(form_data, 'cmd')
|
||||
except Exception:
|
||||
raise ValueError('Invalid command')
|
||||
if coin_type in (Coins.XMR, Coins.WOW):
|
||||
if coin_type == Coins.XMR:
|
||||
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_variant == 2:
|
||||
if coin_id == -4:
|
||||
rv = ci.rpc_wallet(method, params)
|
||||
elif coin_variant == 0:
|
||||
elif coin_id == -3:
|
||||
rv = ci.rpc(method, params)
|
||||
elif coin_variant == 1:
|
||||
elif coin_id == -2:
|
||||
if params == []:
|
||||
params = None
|
||||
rv = ci.rpc2(method, params)
|
||||
else:
|
||||
raise ValueError('Unknown RPC variant')
|
||||
raise ValueError('Unknown XMR RPC variant')
|
||||
result = json.dumps(rv, indent=4)
|
||||
else:
|
||||
if call_type == 'http':
|
||||
ci = swap_client.ci(coin_type)
|
||||
method, params = parse_cmd(cmd, type_map)
|
||||
if coin_variant == 1:
|
||||
rv = ci.rpc_wallet(method, params)
|
||||
elif coin_variant == 2:
|
||||
rv = ci.rpc_wallet_mweb(method, params)
|
||||
if coin_id == -5:
|
||||
rv = swap_client.ci(coin_type).rpc_wallet_mweb(method, params)
|
||||
else:
|
||||
if coin_type in (Coins.DCR, ):
|
||||
rv = ci.rpc(method, params)
|
||||
else:
|
||||
rv = ci.rpc_wallet(method, params)
|
||||
rv = swap_client.ci(coin_type).rpc_wallet(method, params)
|
||||
if not isinstance(rv, str):
|
||||
rv = json.dumps(rv, indent=4)
|
||||
result = cmd + '\n' + rv
|
||||
else:
|
||||
result = cmd + '\n' + swap_client.callcoincli(coin_type, cmd)
|
||||
except Exception as ex:
|
||||
result = cmd + '\n' + str(ex)
|
||||
result = str(ex)
|
||||
if self.server.swap_client.debug is True:
|
||||
self.server.swap_client.log.error(traceback.format_exc())
|
||||
|
||||
template = env.get_template('rpc.html')
|
||||
|
||||
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, Coins.WOW)]
|
||||
coins = listAvailableCoins(swap_client, with_variants=False)
|
||||
with_xmr: bool = any(c[0] == Coins.XMR for c in coins)
|
||||
coins = [c for c in coins if c[0] != Coins.XMR]
|
||||
|
||||
if any(c[0] == Coins.DCR for c in coin_available):
|
||||
coins.append((str(int(Coins.DCR)) + ',1', 'Decred Wallet'))
|
||||
if any(c[0] == Coins.LTC for c in coin_available):
|
||||
coins.append((str(int(Coins.LTC)) + ',2', 'Litecoin MWEB Wallet'))
|
||||
if any(c[0] == Coins.LTC for c in coins):
|
||||
coins.append((-5, 'Litecoin MWEB Wallet'))
|
||||
if with_xmr:
|
||||
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'))
|
||||
coins.append((-2, 'Monero'))
|
||||
coins.append((-3, 'Monero JSON'))
|
||||
coins.append((-4, 'Monero Wallet'))
|
||||
|
||||
return self.render_template(template, {
|
||||
'messages': messages,
|
||||
'err_messages': err_messages,
|
||||
'coins': coins,
|
||||
'coin_type': coin_type_selected,
|
||||
'coin_type': coin_id,
|
||||
'call_type': call_type,
|
||||
'result': result,
|
||||
'messages': messages,
|
||||
|
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2023 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
class Curves(IntEnum):
|
||||
secp256k1 = 1
|
||||
ed25519 = 2
|
@ -1,238 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import threading
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from basicswap.chainparams import (
|
||||
chainparams,
|
||||
)
|
||||
from basicswap.util import (
|
||||
ensure,
|
||||
i2b, b2i,
|
||||
make_int,
|
||||
format_amount,
|
||||
TemporaryError,
|
||||
)
|
||||
from basicswap.util.crypto import (
|
||||
hash160,
|
||||
)
|
||||
from basicswap.util.ecc import (
|
||||
ep,
|
||||
getSecretInt,
|
||||
)
|
||||
from coincurve.dleag import (
|
||||
verify_secp256k1_point
|
||||
)
|
||||
from coincurve.keys import (
|
||||
PublicKey,
|
||||
)
|
||||
|
||||
|
||||
class Curves(IntEnum):
|
||||
secp256k1 = 1
|
||||
ed25519 = 2
|
||||
|
||||
|
||||
class CoinInterface:
|
||||
@staticmethod
|
||||
def watch_blocks_for_scripts() -> bool:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def compareFeeRates(a, b) -> bool:
|
||||
return abs(a - b) < 20
|
||||
|
||||
def __init__(self, network):
|
||||
self.setDefaults()
|
||||
self._network = network
|
||||
self._mx_wallet = threading.Lock()
|
||||
|
||||
def setDefaults(self):
|
||||
self._unknown_wallet_seed = True
|
||||
self._restore_height = None
|
||||
|
||||
def make_int(self, amount_in: int, r: int = 0) -> int:
|
||||
return make_int(amount_in, self.exp(), r=r)
|
||||
|
||||
def format_amount(self, amount_in, conv_int=False, r=0):
|
||||
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
|
||||
return format_amount(amount_int, self.exp())
|
||||
|
||||
def coin_name(self) -> str:
|
||||
coin_chainparams = chainparams[self.coin_type()]
|
||||
if coin_chainparams.get('use_ticker_as_name', False):
|
||||
return coin_chainparams['ticker']
|
||||
return coin_chainparams['name'].capitalize()
|
||||
|
||||
def ticker(self) -> str:
|
||||
ticker = chainparams[self.coin_type()]['ticker']
|
||||
if self._network == 'testnet':
|
||||
ticker = 't' + ticker
|
||||
elif self._network == 'regtest':
|
||||
ticker = 'rt' + ticker
|
||||
return ticker
|
||||
|
||||
def getExchangeTicker(self, exchange_name: str) -> str:
|
||||
return chainparams[self.coin_type()]['ticker']
|
||||
|
||||
def getExchangeName(self, exchange_name: str) -> str:
|
||||
return chainparams[self.coin_type()]['name']
|
||||
|
||||
def ticker_mainnet(self) -> str:
|
||||
ticker = chainparams[self.coin_type()]['ticker']
|
||||
return ticker
|
||||
|
||||
def min_amount(self) -> int:
|
||||
return chainparams[self.coin_type()][self._network]['min_amount']
|
||||
|
||||
def max_amount(self) -> int:
|
||||
return chainparams[self.coin_type()][self._network]['max_amount']
|
||||
|
||||
def setWalletSeedWarning(self, value: bool) -> None:
|
||||
self._unknown_wallet_seed = value
|
||||
|
||||
def setWalletRestoreHeight(self, value: int) -> None:
|
||||
self._restore_height = value
|
||||
|
||||
def knownWalletSeed(self) -> bool:
|
||||
return not self._unknown_wallet_seed
|
||||
|
||||
def chainparams(self):
|
||||
return chainparams[self.coin_type()]
|
||||
|
||||
def chainparams_network(self):
|
||||
return chainparams[self.coin_type()][self._network]
|
||||
|
||||
def has_segwit(self) -> bool:
|
||||
return chainparams[self.coin_type()].get('has_segwit', True)
|
||||
|
||||
def use_p2shp2wsh(self) -> bool:
|
||||
# p2sh-p2wsh
|
||||
return False
|
||||
|
||||
def is_transient_error(self, ex) -> bool:
|
||||
if isinstance(ex, TemporaryError):
|
||||
return True
|
||||
str_error: str = str(ex).lower()
|
||||
if 'not enough unlocked money' in str_error:
|
||||
return True
|
||||
if 'no unlocked balance' in str_error:
|
||||
return True
|
||||
if 'transaction was rejected by daemon' in str_error:
|
||||
return True
|
||||
if 'invalid unlocked_balance' in str_error:
|
||||
return True
|
||||
if 'daemon is busy' in str_error:
|
||||
return True
|
||||
if 'timed out' in str_error:
|
||||
return True
|
||||
if 'request-sent' in str_error:
|
||||
return True
|
||||
return False
|
||||
|
||||
def setConfTarget(self, new_conf_target: int) -> None:
|
||||
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
|
||||
self._conf_target = new_conf_target
|
||||
|
||||
def walletRestoreHeight(self) -> int:
|
||||
return self._restore_height
|
||||
|
||||
def get_connection_type(self):
|
||||
return self._connection_type
|
||||
|
||||
def using_segwit(self) -> bool:
|
||||
# Using btc native segwit
|
||||
return self._use_segwit
|
||||
|
||||
def use_tx_vsize(self) -> bool:
|
||||
return self._use_segwit
|
||||
|
||||
def getLockTxSwapOutputValue(self, bid, xmr_swap) -> int:
|
||||
return bid.amount
|
||||
|
||||
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap) -> int:
|
||||
return xmr_swap.a_swap_refund_value
|
||||
|
||||
def getLockRefundTxSwapOutput(self, xmr_swap) -> int:
|
||||
# Only one prevout exists
|
||||
return 0
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
class AdaptorSigInterface():
|
||||
def getScriptLockTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
b'',
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
b'',
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes((1,)),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
bytes(72),
|
||||
b'',
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
|
||||
class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
||||
@staticmethod
|
||||
def curve_type():
|
||||
return Curves.secp256k1
|
||||
|
||||
def getNewSecretKey(self) -> bytes:
|
||||
return i2b(getSecretInt())
|
||||
|
||||
def getPubkey(self, privkey: bytes) -> bytes:
|
||||
return PublicKey.from_secret(privkey).format()
|
||||
|
||||
def pkh(self, pubkey: bytes) -> bytes:
|
||||
return hash160(pubkey)
|
||||
|
||||
def verifyKey(self, k: bytes) -> bool:
|
||||
i = b2i(k)
|
||||
return (i < ep.o and i > 0)
|
||||
|
||||
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
|
||||
return verify_secp256k1_point(pubkey_bytes)
|
||||
|
||||
def isValidAddressHash(self, address_hash: bytes) -> bool:
|
||||
hash_len = len(address_hash)
|
||||
if hash_len == 20:
|
||||
return True
|
||||
|
||||
def isValidPubkey(self, pubkey: bytes) -> bool:
|
||||
try:
|
||||
self.verifyPubkey(pubkey)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool:
|
||||
pubkey = PublicKey(pubkey)
|
||||
return pubkey.verify(sig, signed_hash, hasher=None)
|
||||
|
||||
def sumKeys(self, ka: bytes, kb: bytes) -> bytes:
|
||||
# TODO: Add to coincurve
|
||||
return i2b((b2i(ka) + b2i(kb)) % ep.o)
|
||||
|
||||
def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes:
|
||||
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
|
@ -5,31 +5,25 @@
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import json
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from basicswap.basicswap_util import (
|
||||
getVoutByAddress,
|
||||
getVoutByScriptPubKey,
|
||||
)
|
||||
from basicswap.contrib.test_framework import (
|
||||
segwit_addr,
|
||||
)
|
||||
from basicswap.interface.base import (
|
||||
Secp256k1Interface,
|
||||
)
|
||||
from basicswap.contrib.test_framework import segwit_addr
|
||||
|
||||
from basicswap.interface import (
|
||||
Curves)
|
||||
from basicswap.util import (
|
||||
ensure,
|
||||
b2h, i2b, b2i, i2h,
|
||||
)
|
||||
make_int,
|
||||
b2h, i2b, b2i, i2h)
|
||||
from basicswap.util.ecc import (
|
||||
ep,
|
||||
pointToCPK, CPKToPoint,
|
||||
)
|
||||
getSecretInt)
|
||||
from basicswap.util.script import (
|
||||
decodeScriptNum,
|
||||
getCompactSizeLen,
|
||||
@ -43,20 +37,16 @@ from basicswap.util.address import (
|
||||
decodeAddress,
|
||||
pubkeyToAddress,
|
||||
)
|
||||
from basicswap.util.crypto import (
|
||||
hash160,
|
||||
sha256,
|
||||
)
|
||||
from coincurve.keys import (
|
||||
PrivateKey,
|
||||
PublicKey,
|
||||
)
|
||||
PublicKey)
|
||||
from coincurve.dleag import (
|
||||
verify_secp256k1_point)
|
||||
from coincurve.ecdsaotves import (
|
||||
ecdsaotves_enc_sign,
|
||||
ecdsaotves_enc_verify,
|
||||
ecdsaotves_dec_sig,
|
||||
ecdsaotves_rec_enc_key
|
||||
)
|
||||
ecdsaotves_rec_enc_key)
|
||||
|
||||
from basicswap.contrib.test_framework.messages import (
|
||||
COIN,
|
||||
@ -65,7 +55,8 @@ from basicswap.contrib.test_framework.messages import (
|
||||
CTxIn,
|
||||
CTxInWitness,
|
||||
CTxOut,
|
||||
)
|
||||
uint256_from_str)
|
||||
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript, CScriptOp,
|
||||
OP_IF, OP_ELSE, OP_ENDIF,
|
||||
@ -77,12 +68,12 @@ from basicswap.contrib.test_framework.script import (
|
||||
OP_HASH160, OP_EQUAL,
|
||||
SIGHASH_ALL,
|
||||
SegwitV0SignatureHash,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
TxLockTypes
|
||||
)
|
||||
hash160)
|
||||
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.basicswap_util import (
|
||||
TxLockTypes)
|
||||
|
||||
from basicswap.chainparams import CoinInterface, Coins
|
||||
from basicswap.rpc import make_rpc_func, openrpc
|
||||
|
||||
|
||||
@ -118,58 +109,10 @@ def find_vout_for_address_from_txobj(tx_obj, addr: str) -> int:
|
||||
raise RuntimeError("Vout not found for address: txid={}, addr={}".format(tx_obj['txid'], addr))
|
||||
|
||||
|
||||
def extractScriptLockScriptValues(script_bytes: bytes) -> (bytes, bytes):
|
||||
script_len = len(script_bytes)
|
||||
ensure(script_len == 71, 'Bad script length')
|
||||
o = 0
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == 33)
|
||||
o += 2
|
||||
pk1 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk2 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG)
|
||||
|
||||
return pk1, pk2
|
||||
|
||||
|
||||
def extractScriptLockRefundScriptValues(script_bytes: bytes):
|
||||
script_len = len(script_bytes)
|
||||
ensure(script_len > 73, 'Bad script length')
|
||||
ensure_op(script_bytes[0] == OP_IF)
|
||||
ensure_op(script_bytes[1] == OP_2)
|
||||
ensure_op(script_bytes[2] == 33)
|
||||
pk1 = script_bytes[3: 3 + 33]
|
||||
ensure_op(script_bytes[36] == 33)
|
||||
pk2 = script_bytes[37: 37 + 33]
|
||||
ensure_op(script_bytes[70] == OP_2)
|
||||
ensure_op(script_bytes[71] == OP_CHECKMULTISIG)
|
||||
ensure_op(script_bytes[72] == OP_ELSE)
|
||||
o = 73
|
||||
csv_val, nb = decodeScriptNum(script_bytes, o)
|
||||
o += nb
|
||||
|
||||
ensure(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long
|
||||
ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == OP_DROP)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk3 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == OP_CHECKSIG)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == OP_ENDIF)
|
||||
|
||||
return pk1, pk2, csv_val, pk3
|
||||
|
||||
|
||||
class BTCInterface(Secp256k1Interface):
|
||||
class BTCInterface(CoinInterface):
|
||||
@staticmethod
|
||||
def curve_type():
|
||||
return Curves.secp256k1
|
||||
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
@ -206,6 +149,10 @@ class BTCInterface(Secp256k1Interface):
|
||||
rv += output.nValue
|
||||
return rv
|
||||
|
||||
@staticmethod
|
||||
def compareFeeRates(a, b) -> bool:
|
||||
return abs(a - b) < 20
|
||||
|
||||
@staticmethod
|
||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||
return 147
|
||||
@ -220,7 +167,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
|
||||
@staticmethod
|
||||
def getExpectedSequence(lockType: int, lockVal: int) -> int:
|
||||
ensure(lockVal >= 1, 'Bad lockVal')
|
||||
assert (lockVal >= 1), 'Bad lockVal'
|
||||
if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
|
||||
return lockVal
|
||||
if lockType == TxLockTypes.SEQUENCE_LOCK_TIME:
|
||||
@ -259,23 +206,6 @@ class BTCInterface(Secp256k1Interface):
|
||||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||
self._expect_seedid_hex = None
|
||||
|
||||
def open_rpc(self, wallet=None):
|
||||
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
||||
|
||||
def json_request(self, rpc_conn, method, params):
|
||||
try:
|
||||
v = rpc_conn.json_request(method, params)
|
||||
r = json.loads(v.decode('utf-8'))
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
raise ValueError('RPC Server Error ' + str(ex))
|
||||
if 'error' in r and r['error'] is not None:
|
||||
raise ValueError('RPC error ' + str(r['error']))
|
||||
return r['result']
|
||||
|
||||
def close_rpc(self, rpc_conn):
|
||||
rpc_conn.close()
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
wallets = self.rpc('listwallets')
|
||||
|
||||
@ -293,6 +223,40 @@ class BTCInterface(Secp256k1Interface):
|
||||
|
||||
return len(wallets)
|
||||
|
||||
def using_segwit(self) -> bool:
|
||||
# Using btc native segwit
|
||||
return self._use_segwit
|
||||
|
||||
def use_p2shp2wsh(self) -> bool:
|
||||
# p2sh-p2wsh
|
||||
return False
|
||||
|
||||
def get_connection_type(self):
|
||||
return self._connection_type
|
||||
|
||||
def open_rpc(self, wallet=None):
|
||||
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
||||
|
||||
def json_request(self, rpc_conn, method, params):
|
||||
try:
|
||||
v = rpc_conn.json_request(method, params)
|
||||
r = json.loads(v.decode('utf-8'))
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
raise ValueError('RPC Server Error ' + str(ex))
|
||||
|
||||
if 'error' in r and r['error'] is not None:
|
||||
raise ValueError('RPC error ' + str(r['error']))
|
||||
|
||||
return r['result']
|
||||
|
||||
def close_rpc(self, rpc_conn):
|
||||
rpc_conn.close()
|
||||
|
||||
def setConfTarget(self, new_conf_target: int) -> None:
|
||||
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
|
||||
self._conf_target = new_conf_target
|
||||
|
||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||
self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
|
||||
|
||||
@ -321,7 +285,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
|
||||
max_tries = 5000
|
||||
for i in range(max_tries):
|
||||
prev_block_header = self.rpc('getblockheader', [last_block_header['previousblockhash']])
|
||||
prev_block_header = self.rpc('getblock', [last_block_header['previousblockhash']])
|
||||
if prev_block_header['time'] <= time:
|
||||
return last_block_header if block_after else prev_block_header
|
||||
|
||||
@ -339,6 +303,9 @@ class BTCInterface(Secp256k1Interface):
|
||||
rv['locked_utxos'] = len(self.rpc_wallet('listlockunspent'))
|
||||
return rv
|
||||
|
||||
def walletRestoreHeight(self) -> int:
|
||||
return self._restore_height
|
||||
|
||||
def getWalletRestoreHeight(self) -> int:
|
||||
start_time = self.rpc_wallet('getwalletinfo')['keypoololdest']
|
||||
|
||||
@ -386,6 +353,18 @@ class BTCInterface(Secp256k1Interface):
|
||||
self._log.debug('validateaddress failed: {}'.format(address))
|
||||
return False
|
||||
|
||||
def isValidAddressHash(self, address_hash: bytes) -> bool:
|
||||
hash_len = len(address_hash)
|
||||
if hash_len == 20:
|
||||
return True
|
||||
|
||||
def isValidPubkey(self, pubkey: bytes) -> bool:
|
||||
try:
|
||||
self.verifyPubkey(pubkey)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||
if not or_watch_only:
|
||||
@ -443,11 +422,11 @@ class BTCInterface(Secp256k1Interface):
|
||||
return segwit_addr.encode(bech32_prefix, version, pkh)
|
||||
|
||||
def pkh_to_address(self, pkh: bytes) -> str:
|
||||
# pkh is ripemd160(sha256(pk))
|
||||
# pkh is hash160(pk)
|
||||
assert (len(pkh) == 20)
|
||||
prefix = self.chainparams_network()['pubkey_address']
|
||||
data = bytes((prefix,)) + pkh
|
||||
checksum = sha256(sha256(data))
|
||||
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
|
||||
return b58encode(data + checksum[0:4])
|
||||
|
||||
def sh_to_address(self, sh: bytes) -> str:
|
||||
@ -473,6 +452,12 @@ class BTCInterface(Secp256k1Interface):
|
||||
assert (len(pk) == 33)
|
||||
return self.pkh_to_address(hash160(pk))
|
||||
|
||||
def getNewSecretKey(self) -> bytes:
|
||||
return i2b(getSecretInt())
|
||||
|
||||
def getPubkey(self, privkey):
|
||||
return PublicKey.from_secret(privkey).format()
|
||||
|
||||
def getAddressHashFromKey(self, key: bytes) -> bytes:
|
||||
pk = self.getPubkey(key)
|
||||
return hash160(pk)
|
||||
@ -480,6 +465,13 @@ class BTCInterface(Secp256k1Interface):
|
||||
def getSeedHash(self, seed) -> bytes:
|
||||
return self.getAddressHashFromKey(seed)[::-1]
|
||||
|
||||
def verifyKey(self, k: bytes) -> bool:
|
||||
i = b2i(k)
|
||||
return (i < ep.o and i > 0)
|
||||
|
||||
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
|
||||
return verify_secp256k1_point(pubkey_bytes)
|
||||
|
||||
def encodeKey(self, key_bytes: bytes) -> str:
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
return toWIF(wif_prefix, key_bytes)
|
||||
@ -499,6 +491,13 @@ class BTCInterface(Secp256k1Interface):
|
||||
def decodeKey(self, k: str) -> bytes:
|
||||
return decodeWif(k)
|
||||
|
||||
def sumKeys(self, ka, kb):
|
||||
# TODO: Add to coincurve
|
||||
return i2b((b2i(ka) + b2i(kb)) % ep.o)
|
||||
|
||||
def sumPubkeys(self, Ka, Kb):
|
||||
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
|
||||
|
||||
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
|
||||
# p2wpkh
|
||||
return CScript([OP_0, pkh])
|
||||
@ -509,6 +508,24 @@ class BTCInterface(Secp256k1Interface):
|
||||
tx.deserialize(BytesIO(tx_bytes))
|
||||
return tx
|
||||
|
||||
def extractScriptLockScriptValues(self, script_bytes: bytes):
|
||||
script_len = len(script_bytes)
|
||||
ensure(script_len == 71, 'Bad script length')
|
||||
o = 0
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == 33)
|
||||
o += 2
|
||||
pk1 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk2 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG)
|
||||
|
||||
return pk1, pk2
|
||||
|
||||
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
@ -518,6 +535,37 @@ class BTCInterface(Secp256k1Interface):
|
||||
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
|
||||
return self.fundTx(tx_bytes, feerate)
|
||||
|
||||
def extractScriptLockRefundScriptValues(self, script_bytes: bytes):
|
||||
script_len = len(script_bytes)
|
||||
ensure(script_len > 73, 'Bad script length')
|
||||
ensure_op(script_bytes[0] == OP_IF)
|
||||
ensure_op(script_bytes[1] == OP_2)
|
||||
ensure_op(script_bytes[2] == 33)
|
||||
pk1 = script_bytes[3: 3 + 33]
|
||||
ensure_op(script_bytes[36] == 33)
|
||||
pk2 = script_bytes[37: 37 + 33]
|
||||
ensure_op(script_bytes[70] == OP_2)
|
||||
ensure_op(script_bytes[71] == OP_CHECKMULTISIG)
|
||||
ensure_op(script_bytes[72] == OP_ELSE)
|
||||
o = 73
|
||||
csv_val, nb = decodeScriptNum(script_bytes, o)
|
||||
o += nb
|
||||
|
||||
ensure(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long
|
||||
ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == OP_DROP)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk3 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == OP_CHECKSIG)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == OP_ENDIF)
|
||||
|
||||
return pk1, pk2, csv_val, pk3
|
||||
|
||||
def genScriptLockRefundTxScript(self, Kal, Kaf, csv_val) -> CScript:
|
||||
|
||||
Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal)
|
||||
@ -609,7 +657,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||
|
||||
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
|
||||
tx_lock_refund.rehash()
|
||||
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||
@ -696,7 +744,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
ensure(locked_coin == swap_value, 'Bad locked value')
|
||||
|
||||
# Check script
|
||||
A, B = extractScriptLockScriptValues(script_out)
|
||||
A, B = self.extractScriptLockScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script pubkey')
|
||||
ensure(B == Kaf, 'Bad script pubkey')
|
||||
|
||||
@ -709,7 +757,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
for pi in tx.vin:
|
||||
ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True])
|
||||
prevout = ptx['vout'][pi.prevout.n]
|
||||
inputs_value += self.make_int(prevout['value'])
|
||||
inputs_value += make_int(prevout['value'])
|
||||
|
||||
prevout_type = prevout['scriptPubKey']['type']
|
||||
if prevout_type == 'witness_v0_keyhash':
|
||||
@ -764,7 +812,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
locked_coin = tx.vout[locked_n].nValue
|
||||
|
||||
# Check script and values
|
||||
A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
|
||||
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script pubkey')
|
||||
ensure(B == Kaf, 'Bad script pubkey')
|
||||
ensure(csv_val == csv_val_expect, 'Bad script csv value')
|
||||
@ -876,30 +924,27 @@ class BTCInterface(Secp256k1Interface):
|
||||
|
||||
return True
|
||||
|
||||
def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
|
||||
def signTx(self, key_bytes, tx_bytes, input_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||
|
||||
eck = PrivateKey(key_bytes)
|
||||
return eck.sign(sig_hash, hasher=None) + bytes((SIGHASH_ALL,))
|
||||
|
||||
def signTxOtVES(self, key_sign: bytes, pubkey_encrypt: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
|
||||
def signTxOtVES(self, key_sign, pubkey_encrypt, tx_bytes, input_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||
|
||||
return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash)
|
||||
|
||||
def verifyTxOtVES(self, tx_bytes: bytes, ct: bytes, Ks: bytes, Ke: bytes, input_n: int, prevout_script: bytes, prevout_value):
|
||||
def verifyTxOtVES(self, tx_bytes, ct, Ks, Ke, input_n, prevout_script, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||
return ecdsaotves_enc_verify(Ks, Ke, sig_hash, ct)
|
||||
|
||||
def decryptOtVES(self, k: bytes, esig: bytes) -> bytes:
|
||||
def decryptOtVES(self, k, esig):
|
||||
return ecdsaotves_dec_sig(k, esig) + bytes((SIGHASH_ALL,))
|
||||
|
||||
def recoverEncKey(self, esig, sig, K):
|
||||
return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type
|
||||
|
||||
def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||
@ -907,7 +952,11 @@ class BTCInterface(Secp256k1Interface):
|
||||
pubkey = PublicKey(K)
|
||||
return pubkey.verify(sig[: -1], sig_hash, hasher=None) # Pop the hashtype byte
|
||||
|
||||
def fundTx(self, tx: bytes, feerate) -> bytes:
|
||||
def verifySig(self, pubkey, signed_hash, sig):
|
||||
pubkey = PublicKey(pubkey)
|
||||
return pubkey.verify(sig, signed_hash, hasher=None)
|
||||
|
||||
def fundTx(self, tx, feerate):
|
||||
feerate_str = self.format_amount(feerate)
|
||||
# TODO: unlock unspents if bid cancelled
|
||||
options = {
|
||||
@ -917,7 +966,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), options])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def listInputs(self, tx_bytes: bytes):
|
||||
def listInputs(self, tx_bytes):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
all_locked = self.rpc_wallet('listlockunspent')
|
||||
@ -969,20 +1018,20 @@ class BTCInterface(Secp256k1Interface):
|
||||
return hash160(K)
|
||||
|
||||
def getScriptDest(self, script):
|
||||
return CScript([OP_0, sha256(script)])
|
||||
return CScript([OP_0, hashlib.sha256(script).digest()])
|
||||
|
||||
def getScriptScriptSig(self, script: bytes) -> bytes:
|
||||
return bytes()
|
||||
|
||||
def getP2SHP2WSHDest(self, script):
|
||||
script_hash = sha256(script)
|
||||
script_hash = hashlib.sha256(script).digest()
|
||||
assert len(script_hash) == 32
|
||||
p2wsh_hash = hash160(CScript([OP_0, script_hash]))
|
||||
assert len(p2wsh_hash) == 20
|
||||
return CScript([OP_HASH160, p2wsh_hash, OP_EQUAL])
|
||||
|
||||
def getP2SHP2WSHScriptSig(self, script):
|
||||
script_hash = sha256(script)
|
||||
script_hash = hashlib.sha256(script).digest()
|
||||
assert len(script_hash) == 32
|
||||
return CScript([CScript([OP_0, script_hash, ]), ])
|
||||
|
||||
@ -1001,7 +1050,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
|
||||
def getWalletTransaction(self, txid: bytes):
|
||||
try:
|
||||
return bytes.fromhex(self.rpc_wallet('gettransaction', [txid.hex()]))
|
||||
return bytes.fromhex(self.rpc('gettransaction', [txid.hex()]))
|
||||
except Exception as ex:
|
||||
# TODO: filter errors
|
||||
return None
|
||||
@ -1023,11 +1072,11 @@ class BTCInterface(Secp256k1Interface):
|
||||
tx.wit.vtxinwit.clear()
|
||||
return tx.serialize()
|
||||
|
||||
def extractLeaderSig(self, tx_bytes: bytes) -> bytes:
|
||||
def extractLeaderSig(self, tx_bytes) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
return tx.wit.vtxinwit[0].scriptWitness.stack[1]
|
||||
|
||||
def extractFollowerSig(self, tx_bytes: bytes) -> bytes:
|
||||
def extractFollowerSig(self, tx_bytes) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
return tx.wit.vtxinwit[0].scriptWitness.stack[2]
|
||||
|
||||
@ -1050,6 +1099,9 @@ class BTCInterface(Secp256k1Interface):
|
||||
|
||||
return bytes.fromhex(self.publishTx(b_lock_tx))
|
||||
|
||||
def recoverEncKey(self, esig, sig, K):
|
||||
return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type
|
||||
|
||||
def getTxVSize(self, tx, add_bytes: int = 0, add_witness_bytes: int = 0) -> int:
|
||||
wsf = self.witnessScaleFactor()
|
||||
len_full = len(tx.serialize_with_witness()) + add_bytes + add_witness_bytes
|
||||
@ -1079,10 +1131,10 @@ class BTCInterface(Secp256k1Interface):
|
||||
witness_bytes = 109
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
pay_fee = round(fee_rate * vsize / 1000)
|
||||
self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.')
|
||||
self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
||||
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||
wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
@ -1097,7 +1149,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
tx.nVersion = self.txVersion()
|
||||
|
||||
script_lock = self.getScriptForPubkeyHash(Kbs)
|
||||
chain_b_lock_txid_int = b2i(chain_b_lock_txid)
|
||||
chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
|
||||
|
||||
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n),
|
||||
nSequence=0,
|
||||
@ -1119,11 +1171,11 @@ class BTCInterface(Secp256k1Interface):
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||
return addr_info['iswatchonly']
|
||||
|
||||
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
||||
def getSCLockScriptAddress(self, lock_script):
|
||||
lock_tx_dest = self.getScriptDest(lock_script)
|
||||
return self.encodeScriptDest(lock_tx_dest)
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||
# Add watchonly address and rescan if required
|
||||
|
||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||
@ -1192,30 +1244,30 @@ class BTCInterface(Secp256k1Interface):
|
||||
'vout': utxo['vout']})
|
||||
return rv, chain_height
|
||||
|
||||
def withdrawCoin(self, value: float, addr_to: str, subfee: bool):
|
||||
def withdrawCoin(self, value, addr_to, subfee):
|
||||
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
||||
return self.rpc_wallet('sendtoaddress', params)
|
||||
|
||||
def signCompact(self, k, message: str) -> bytes:
|
||||
message_hash = sha256(bytes(message, 'utf-8'))
|
||||
def signCompact(self, k, message):
|
||||
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
|
||||
|
||||
privkey = PrivateKey(k)
|
||||
return privkey.sign_recoverable(message_hash, hasher=None)[:64]
|
||||
|
||||
def signRecoverable(self, k, message: str) -> bytes:
|
||||
message_hash = sha256(bytes(message, 'utf-8'))
|
||||
def signRecoverable(self, k, message):
|
||||
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
|
||||
|
||||
privkey = PrivateKey(k)
|
||||
return privkey.sign_recoverable(message_hash, hasher=None)
|
||||
|
||||
def verifyCompactSig(self, K, message: str, sig) -> None:
|
||||
message_hash = sha256(bytes(message, 'utf-8'))
|
||||
def verifyCompactSig(self, K, message, sig):
|
||||
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
|
||||
pubkey = PublicKey(K)
|
||||
rv = pubkey.verify_compact(sig, message_hash, hasher=None)
|
||||
assert (rv is True)
|
||||
|
||||
def verifySigAndRecover(self, sig, message: str) -> bytes:
|
||||
message_hash = sha256(bytes(message, 'utf-8'))
|
||||
def verifySigAndRecover(self, sig, message):
|
||||
message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest()
|
||||
pubkey = PublicKey.from_signature_and_message(sig, message_hash, hasher=None)
|
||||
return pubkey.format()
|
||||
|
||||
@ -1224,7 +1276,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
message_magic = self.chainparams()['message_magic']
|
||||
|
||||
message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8')
|
||||
message_hash = sha256(sha256(message_bytes))
|
||||
message_hash = hashlib.sha256(hashlib.sha256(message_bytes).digest()).digest()
|
||||
signature_bytes = base64.b64decode(signature)
|
||||
rec_id = (signature_bytes[0] - 27) & 3
|
||||
signature_bytes = signature_bytes[1:] + bytes((rec_id,))
|
||||
@ -1242,6 +1294,40 @@ class BTCInterface(Secp256k1Interface):
|
||||
def showLockTransfers(self, kbv, Kbs, restore_height):
|
||||
raise ValueError('Unimplemented')
|
||||
|
||||
def getLockTxSwapOutputValue(self, bid, xmr_swap):
|
||||
return bid.amount
|
||||
|
||||
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
|
||||
return xmr_swap.a_swap_refund_value
|
||||
|
||||
def getLockRefundTxSwapOutput(self, xmr_swap):
|
||||
# Only one prevout exists
|
||||
return 0
|
||||
|
||||
def getScriptLockTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
b'',
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
b'',
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes((1,)),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
bytes(72),
|
||||
b'',
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getWitnessStackSerialisedLength(self, witness_stack):
|
||||
length = getCompactSizeLen(len(witness_stack))
|
||||
for e in witness_stack:
|
||||
@ -1344,12 +1430,6 @@ class BTCInterface(Secp256k1Interface):
|
||||
prove_utxos = [] # TODO: Send specific utxos
|
||||
return (sign_for_addr, signature, prove_utxos)
|
||||
|
||||
def encodeProofUtxos(self, proof_utxos):
|
||||
packed_utxos = bytes()
|
||||
for utxo in proof_utxos:
|
||||
packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big')
|
||||
return packed_utxos
|
||||
|
||||
def decodeProofUtxos(self, msg_utxos):
|
||||
proof_utxos = []
|
||||
if len(msg_utxos) > 0:
|
||||
@ -1379,7 +1459,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
return True
|
||||
return False
|
||||
|
||||
def isWalletEncryptedLocked(self) -> (bool, bool):
|
||||
def isWalletEncryptedLocked(self):
|
||||
wallet_info = self.rpc_wallet('getwalletinfo')
|
||||
encrypted = 'unlocked_until' in wallet_info
|
||||
locked = encrypted and wallet_info['unlocked_until'] <= 0
|
||||
@ -1422,7 +1502,7 @@ class BTCInterface(Secp256k1Interface):
|
||||
return CScript([OP_HASH160, script_hash, OP_EQUAL])
|
||||
|
||||
def get_p2wsh_script_pubkey(self, script: bytearray) -> bytearray:
|
||||
return CScript([OP_0, sha256(script)])
|
||||
return CScript([OP_0, hashlib.sha256(script).digest()])
|
||||
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
# Only works for wallet txns
|
||||
@ -1435,10 +1515,10 @@ class BTCInterface(Secp256k1Interface):
|
||||
return {'txid': txid_hex, 'amount': 0, 'height': rv['blockheight']}
|
||||
return None
|
||||
|
||||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str:
|
||||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int) -> str:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'])))
|
||||
pkh = self.decodeAddress(output_addr)
|
||||
script = self.getScriptForPubkeyHash(pkh)
|
||||
@ -1446,11 +1526,11 @@ class BTCInterface(Secp256k1Interface):
|
||||
tx.rehash()
|
||||
return tx.serialize().hex()
|
||||
|
||||
def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int, txn_script: bytes = None) -> str:
|
||||
def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int) -> str:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.nLockTime = locktime
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']), nSequence=sequence,))
|
||||
pkh = self.decodeAddress(output_addr)
|
||||
script = self.getScriptForPubkeyHash(pkh)
|
||||
@ -1470,30 +1550,6 @@ class BTCInterface(Secp256k1Interface):
|
||||
tx_vsize += 323 if redeem else 287
|
||||
return tx_vsize
|
||||
|
||||
def find_prevout_info(self, txn_hex: str, txn_script: bytes):
|
||||
txjs = self.rpc('decoderawtransaction', [txn_hex])
|
||||
|
||||
if self.using_segwit():
|
||||
p2wsh = self.getScriptDest(txn_script)
|
||||
n = getVoutByScriptPubKey(txjs, p2wsh.hex())
|
||||
else:
|
||||
addr_to = self.encode_p2sh(txn_script)
|
||||
n = getVoutByAddress(txjs, addr_to)
|
||||
|
||||
return {
|
||||
'txid': txjs['txid'],
|
||||
'vout': n,
|
||||
'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'],
|
||||
'redeemScript': txn_script.hex(),
|
||||
'amount': txjs['vout'][n]['value']
|
||||
}
|
||||
|
||||
def isTxExistsError(self, err_str: str) -> bool:
|
||||
return 'Transaction already in block chain' in err_str
|
||||
|
||||
def isTxNonFinalError(self, err_str: str) -> bool:
|
||||
return 'non-BIP68-final' in err_str or 'non-final' in err_str
|
||||
|
||||
|
||||
def testBTCInterface():
|
||||
print('TODO: testBTCInterface')
|
||||
|
@ -8,7 +8,7 @@
|
||||
from .btc import BTCInterface
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.util.address import decodeAddress
|
||||
from basicswap.contrib.mnemonic import Mnemonic
|
||||
from mnemonic import Mnemonic
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript,
|
||||
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
|
||||
@ -25,11 +25,11 @@ class DASHInterface(BTCInterface):
|
||||
self._wallet_passphrase = ''
|
||||
self._have_checked_seed = False
|
||||
|
||||
def entropyToMnemonic(self, key: bytes) -> str:
|
||||
def seedToMnemonic(self, key: bytes) -> str:
|
||||
return Mnemonic('english').to_mnemonic(key)
|
||||
|
||||
def initialiseWallet(self, key: bytes):
|
||||
words = self.entropyToMnemonic(key)
|
||||
words = self.seedToMnemonic(key)
|
||||
|
||||
mnemonic_passphrase = ''
|
||||
self.rpc_wallet('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase])
|
||||
@ -66,7 +66,7 @@ class DASHInterface(BTCInterface):
|
||||
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}.')
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
|
@ -1,4 +0,0 @@
|
||||
|
||||
from .dcr import DCRInterface
|
||||
|
||||
__all__ = ['DCRInterface',]
|
File diff suppressed because it is too large
Load Diff
@ -1,204 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import copy
|
||||
from enum import IntEnum
|
||||
from basicswap.util.crypto import blake256
|
||||
from basicswap.util.integer import decode_compactsize, encode_compactsize
|
||||
|
||||
|
||||
class TxSerializeType(IntEnum):
|
||||
Full = 0
|
||||
NoWitness = 1
|
||||
OnlyWitness = 2
|
||||
|
||||
|
||||
class SigHashType(IntEnum):
|
||||
SigHashAll = 0x1
|
||||
SigHashNone = 0x2
|
||||
SigHashSingle = 0x3
|
||||
SigHashAnyOneCanPay = 0x80
|
||||
|
||||
SigHashMask = 0x1f
|
||||
|
||||
|
||||
class SignatureType(IntEnum):
|
||||
STEcdsaSecp256k1 = 0
|
||||
STEd25519 = 1
|
||||
STSchnorrSecp256k1 = 2
|
||||
|
||||
|
||||
class COutPoint:
|
||||
__slots__ = ('hash', 'n', 'tree')
|
||||
|
||||
def __init__(self, hash=0, n=0, tree=0):
|
||||
self.hash = hash
|
||||
self.n = n
|
||||
self.tree = tree
|
||||
|
||||
def get_hash(self) -> bytes:
|
||||
return self.hash.to_bytes(32, 'big')
|
||||
|
||||
|
||||
class CTxIn:
|
||||
__slots__ = ('prevout', 'sequence',
|
||||
'value_in', 'block_height', 'block_index', 'signature_script') # Witness
|
||||
|
||||
def __init__(self, prevout=COutPoint(), sequence=0):
|
||||
self.prevout = prevout
|
||||
self.sequence = sequence
|
||||
self.value_in = -1
|
||||
self.block_height = 0
|
||||
self.block_index = 0xffffffff
|
||||
self.signature_script = bytes()
|
||||
|
||||
|
||||
class CTxOut:
|
||||
__slots__ = ('value', 'version', 'script_pubkey')
|
||||
|
||||
def __init__(self, value=0, script_pubkey=bytes()):
|
||||
self.value = value
|
||||
self.version = 0
|
||||
self.script_pubkey = script_pubkey
|
||||
|
||||
|
||||
class CTransaction:
|
||||
__slots__ = ('hash', 'version', 'vin', 'vout', 'locktime', 'expiry')
|
||||
|
||||
def __init__(self, tx=None):
|
||||
if tx is None:
|
||||
self.version = 1
|
||||
self.vin = []
|
||||
self.vout = []
|
||||
self.locktime = 0
|
||||
self.expiry = 0
|
||||
else:
|
||||
self.version = tx.version
|
||||
self.vin = copy.deepcopy(tx.vin)
|
||||
self.vout = copy.deepcopy(tx.vout)
|
||||
self.locktime = tx.locktime
|
||||
self.expiry = tx.expiry
|
||||
|
||||
def deserialize(self, data: bytes) -> None:
|
||||
|
||||
version = int.from_bytes(data[:4], 'little')
|
||||
self.version = version & 0xffff
|
||||
ser_type: int = version >> 16
|
||||
o = 4
|
||||
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness:
|
||||
num_txin, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
|
||||
for i in range(num_txin):
|
||||
txi = CTxIn()
|
||||
txi.prevout = COutPoint()
|
||||
txi.prevout.hash = int.from_bytes(data[o:o + 32], 'little')
|
||||
o += 32
|
||||
txi.prevout.n = int.from_bytes(data[o:o + 4], 'little')
|
||||
o += 4
|
||||
txi.prevout.tree = data[o]
|
||||
o += 1
|
||||
txi.sequence = int.from_bytes(data[o:o + 4], 'little')
|
||||
o += 4
|
||||
self.vin.append(txi)
|
||||
|
||||
num_txout, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
|
||||
for i in range(num_txout):
|
||||
txo = CTxOut()
|
||||
txo.value = int.from_bytes(data[o:o + 8], 'little')
|
||||
o += 8
|
||||
txo.version = int.from_bytes(data[o:o + 2], 'little')
|
||||
o += 2
|
||||
script_bytes, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
txo.script_pubkey = data[o:o + script_bytes]
|
||||
o += script_bytes
|
||||
self.vout.append(txo)
|
||||
|
||||
self.locktime = int.from_bytes(data[o:o + 4], 'little')
|
||||
o += 4
|
||||
self.expiry = int.from_bytes(data[o:o + 4], 'little')
|
||||
o += 4
|
||||
|
||||
if ser_type == TxSerializeType.NoWitness:
|
||||
return
|
||||
|
||||
num_wit_scripts, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
|
||||
if ser_type == TxSerializeType.OnlyWitness:
|
||||
self.vin = [CTxIn() for _ in range(num_wit_scripts)]
|
||||
else:
|
||||
if num_wit_scripts != len(self.vin):
|
||||
raise ValueError('non equal witness and prefix txin quantities')
|
||||
|
||||
for i in range(num_wit_scripts):
|
||||
txi = self.vin[i]
|
||||
txi.value_in = int.from_bytes(data[o:o + 8], 'little')
|
||||
o += 8
|
||||
txi.block_height = int.from_bytes(data[o:o + 4], 'little')
|
||||
o += 4
|
||||
txi.block_index = int.from_bytes(data[o:o + 4], 'little')
|
||||
o += 4
|
||||
script_bytes, nb = decode_compactsize(data, o)
|
||||
o += nb
|
||||
txi.signature_script = data[o:o + script_bytes]
|
||||
o += script_bytes
|
||||
|
||||
def serialize(self, ser_type=TxSerializeType.Full) -> bytes:
|
||||
data = bytes()
|
||||
version = (self.version & 0xffff) | (ser_type << 16)
|
||||
data += version.to_bytes(4, 'little')
|
||||
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness:
|
||||
data += encode_compactsize(len(self.vin))
|
||||
for txi in self.vin:
|
||||
data += txi.prevout.hash.to_bytes(32, 'little')
|
||||
data += txi.prevout.n.to_bytes(4, 'little')
|
||||
data += txi.prevout.tree.to_bytes(1, 'little')
|
||||
data += txi.sequence.to_bytes(4, 'little')
|
||||
|
||||
data += encode_compactsize(len(self.vout))
|
||||
for txo in self.vout:
|
||||
data += txo.value.to_bytes(8, 'little')
|
||||
data += txo.version.to_bytes(2, 'little')
|
||||
data += encode_compactsize(len(txo.script_pubkey))
|
||||
data += txo.script_pubkey
|
||||
|
||||
data += self.locktime.to_bytes(4, 'little')
|
||||
data += self.expiry.to_bytes(4, 'little')
|
||||
|
||||
if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness:
|
||||
data += encode_compactsize(len(self.vin))
|
||||
for txi in self.vin:
|
||||
tc_value_in = txi.value_in & 0xffffffffffffffff # Convert negative values
|
||||
data += tc_value_in.to_bytes(8, 'little')
|
||||
data += txi.block_height.to_bytes(4, 'little')
|
||||
data += txi.block_index.to_bytes(4, 'little')
|
||||
data += encode_compactsize(len(txi.signature_script))
|
||||
data += txi.signature_script
|
||||
|
||||
return data
|
||||
|
||||
def TxHash(self) -> bytes:
|
||||
return blake256(self.serialize(TxSerializeType.NoWitness))[::-1]
|
||||
|
||||
def TxHashWitness(self) -> bytes:
|
||||
raise ValueError('todo')
|
||||
|
||||
def TxHashFull(self) -> bytes:
|
||||
raise ValueError('todo')
|
||||
|
||||
|
||||
def findOutput(tx, script_pk: bytes):
|
||||
for i in range(len(tx.vout)):
|
||||
if tx.vout[i].script_pubkey == script_pk:
|
||||
return i
|
||||
return None
|
@ -1,47 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 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 traceback
|
||||
from basicswap.rpc import Jsonrpc
|
||||
|
||||
|
||||
def callrpc(rpc_port, auth, method, params=[], host='127.0.0.1'):
|
||||
try:
|
||||
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
|
||||
x = Jsonrpc(url)
|
||||
x.__handler = None
|
||||
v = x.json_request(method, params)
|
||||
x.close()
|
||||
r = json.loads(v.decode('utf-8'))
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
raise ValueError('RPC server error ' + str(ex) + ', method: ' + method)
|
||||
|
||||
if 'error' in r and r['error'] is not None:
|
||||
raise ValueError('RPC error ' + str(r['error']))
|
||||
|
||||
return r['result']
|
||||
|
||||
|
||||
def openrpc(rpc_port, auth, host='127.0.0.1'):
|
||||
try:
|
||||
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
|
||||
return Jsonrpc(url)
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
raise ValueError('RPC error ' + str(ex))
|
||||
|
||||
|
||||
def make_rpc_func(port, auth, host='127.0.0.1'):
|
||||
port = port
|
||||
auth = auth
|
||||
host = host
|
||||
|
||||
def rpc_func(method, params=None):
|
||||
nonlocal port, auth, host
|
||||
return callrpc(port, auth, method, params, host)
|
||||
return rpc_func
|
@ -1,50 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
|
||||
OP_0 = 0x00
|
||||
OP_DATA_1 = 0x01
|
||||
OP_1NEGATE = 0x4f
|
||||
OP_1 = 0x51
|
||||
OP_IF = 0x63
|
||||
OP_ELSE = 0x67
|
||||
OP_ENDIF = 0x68
|
||||
OP_DROP = 0x75
|
||||
OP_DUP = 0x76
|
||||
OP_EQUAL = 0x87
|
||||
OP_EQUALVERIFY = 0x88
|
||||
OP_PUSHDATA1 = 0x4c
|
||||
OP_PUSHDATA2 = 0x4d
|
||||
OP_PUSHDATA4 = 0x4e
|
||||
OP_HASH160 = 0xa9
|
||||
OP_CHECKSIG = 0xac
|
||||
OP_CHECKMULTISIG = 0xae
|
||||
OP_CHECKSEQUENCEVERIFY = 0xb2
|
||||
|
||||
|
||||
def push_script_data(data_array: bytearray, data: bytes) -> None:
|
||||
len_data: int = len(data)
|
||||
|
||||
if len_data == 0 or (len_data == 1 and data[0] == 0):
|
||||
data_array += bytes((OP_0,))
|
||||
return
|
||||
if len_data == 1 and data[0] <= 16:
|
||||
data_array += bytes((OP_1 - 1 + data[0],))
|
||||
return
|
||||
if len_data == 1 and data[0] == 0x81:
|
||||
data_array += bytes((OP_1NEGATE,))
|
||||
return
|
||||
|
||||
if len_data < OP_PUSHDATA1:
|
||||
data_array += len_data.to_bytes(1, 'little')
|
||||
elif len_data <= 0xff:
|
||||
data_array += bytes((OP_PUSHDATA1, len_data))
|
||||
elif len_data <= 0xffff:
|
||||
data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, 'little')
|
||||
else:
|
||||
data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little')
|
||||
|
||||
data_array += data
|
@ -1,66 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import os
|
||||
import select
|
||||
import subprocess
|
||||
|
||||
|
||||
def createDCRWallet(args, hex_seed, logging, delay_event):
|
||||
logging.info('Creating DCR wallet')
|
||||
|
||||
(pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read
|
||||
|
||||
if os.name == 'nt':
|
||||
str_args = ' '.join(args)
|
||||
p = subprocess.Popen(str_args, shell=True, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w)
|
||||
else:
|
||||
p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w)
|
||||
|
||||
def readOutput():
|
||||
buf = os.read(pipe_r, 1024).decode('utf-8')
|
||||
response = None
|
||||
if 'Opened wallet' in buf:
|
||||
pass
|
||||
elif 'Use the existing configured private passphrase' in buf:
|
||||
response = b'y\n'
|
||||
elif 'Do you want to add an additional layer of encryption' in buf:
|
||||
response = b'n\n'
|
||||
elif 'Do you have an existing wallet seed' in buf:
|
||||
response = b'y\n'
|
||||
elif 'Enter existing wallet seed' in buf:
|
||||
response = (hex_seed + '\n').encode('utf-8')
|
||||
elif 'Seed input successful' in buf:
|
||||
pass
|
||||
elif 'Upgrading database from version' in buf:
|
||||
pass
|
||||
elif 'Ticket commitments db upgrade done' in buf:
|
||||
pass
|
||||
elif 'The wallet has been created successfully' in buf:
|
||||
pass
|
||||
else:
|
||||
raise ValueError(f'Unexpected output: {buf}')
|
||||
if response is not None:
|
||||
p.stdin.write(response)
|
||||
p.stdin.flush()
|
||||
|
||||
try:
|
||||
while p.poll() is None:
|
||||
if os.name == 'nt':
|
||||
readOutput()
|
||||
delay_event.wait(0.1)
|
||||
continue
|
||||
while len(select.select([pipe_r], [], [], 0)[0]) == 1:
|
||||
readOutput()
|
||||
delay_event.wait(0.1)
|
||||
except Exception as e:
|
||||
logging.error(f'dcrwallet --create failed: {e}')
|
||||
finally:
|
||||
if p.poll() is None:
|
||||
p.terminate()
|
||||
os.close(pipe_r)
|
||||
os.close(pipe_w)
|
||||
p.stdin.close()
|
@ -5,8 +5,8 @@
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import hashlib
|
||||
import random
|
||||
import hashlib
|
||||
|
||||
from .btc import BTCInterface, find_vout_for_address_from_txobj
|
||||
from basicswap.util import (
|
||||
@ -42,6 +42,9 @@ class FIROInterface(BTCInterface):
|
||||
# No multiwallet support
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def getExchangeName(self, exchange_name):
|
||||
return 'zcoin'
|
||||
|
||||
@ -49,9 +52,6 @@ class FIROInterface(BTCInterface):
|
||||
# load with -hdseed= parameter
|
||||
pass
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def getNewAddress(self, use_segwit, label='swap_receive'):
|
||||
return self.rpc('getnewaddress', [label])
|
||||
# addr_plain = self.rpc('getnewaddress', [label])
|
||||
@ -76,7 +76,7 @@ class FIROInterface(BTCInterface):
|
||||
return addr_info['ismine']
|
||||
return addr_info['ismine'] or addr_info['iswatchonly']
|
||||
|
||||
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
||||
def getSCLockScriptAddress(self, lock_script):
|
||||
lock_tx_dest = self.getScriptDest(lock_script)
|
||||
address = self.encodeScriptDest(lock_tx_dest)
|
||||
|
||||
@ -87,7 +87,7 @@ class FIROInterface(BTCInterface):
|
||||
|
||||
return address
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||
# Add watchonly address and rescan if required
|
||||
|
||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||
@ -201,7 +201,7 @@ class FIROInterface(BTCInterface):
|
||||
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}.')
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||
@ -337,7 +337,7 @@ class FIROInterface(BTCInterface):
|
||||
return
|
||||
current_height -= 1
|
||||
|
||||
def getBlockWithTxns(self, block_hash: str):
|
||||
def getBlockWithTxns(self, block_hash):
|
||||
# TODO: Bypass decoderawtransaction and getblockheader
|
||||
block = self.rpc('getblock', [block_hash, False])
|
||||
block_header = self.rpc('getblockheader', [block_hash])
|
||||
@ -355,11 +355,9 @@ class FIROInterface(BTCInterface):
|
||||
|
||||
block_rv = {
|
||||
'hash': block_hash,
|
||||
'previousblockhash': block_header['previousblockhash'],
|
||||
'tx': tx_rv,
|
||||
'confirmations': block_header['confirmations'],
|
||||
'height': block_header['height'],
|
||||
'time': block_header['time'],
|
||||
'version': block_header['version'],
|
||||
'merkleroot': block_header['merkleroot'],
|
||||
}
|
||||
|
@ -13,15 +13,9 @@ from coincurve.keys import (
|
||||
PublicKey,
|
||||
PrivateKey,
|
||||
)
|
||||
from basicswap.interface.btc import (
|
||||
BTCInterface,
|
||||
extractScriptLockRefundScriptValues,
|
||||
findOutput,
|
||||
find_vout_for_address_from_txobj,
|
||||
)
|
||||
from .btc import BTCInterface, find_vout_for_address_from_txobj, findOutput
|
||||
from basicswap.rpc import make_rpc_func
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.contrib.mnemonic import Mnemonic
|
||||
from basicswap.interface.contrib.nav_test_framework.mininode import (
|
||||
CTxIn,
|
||||
CTxOut,
|
||||
@ -30,6 +24,7 @@ from basicswap.interface.contrib.nav_test_framework.mininode import (
|
||||
CTransaction,
|
||||
CTxInWitness,
|
||||
FromHex,
|
||||
uint256_from_str,
|
||||
)
|
||||
from basicswap.util.crypto import hash160
|
||||
from basicswap.util.address import (
|
||||
@ -38,7 +33,7 @@ from basicswap.util.address import (
|
||||
encodeAddress,
|
||||
)
|
||||
from basicswap.util import (
|
||||
b2i, i2b, i2h,
|
||||
i2b, i2h,
|
||||
ensure,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
@ -53,6 +48,7 @@ from basicswap.interface.contrib.nav_test_framework.script import (
|
||||
SIGHASH_ALL,
|
||||
SegwitVersion1SignatureHash,
|
||||
)
|
||||
from mnemonic import Mnemonic
|
||||
|
||||
|
||||
class NAVInterface(BTCInterface):
|
||||
@ -73,20 +69,20 @@ class NAVInterface(BTCInterface):
|
||||
# No multiwallet support
|
||||
self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def use_p2shp2wsh(self) -> bool:
|
||||
# p2sh-p2wsh
|
||||
return True
|
||||
|
||||
def entropyToMnemonic(self, key: bytes) -> None:
|
||||
def seedToMnemonic(self, key):
|
||||
return Mnemonic('english').to_mnemonic(key)
|
||||
|
||||
def initialiseWallet(self, key):
|
||||
# Load with -importmnemonic= parameter
|
||||
# load with -importmnemonic= parameter
|
||||
pass
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
return 1
|
||||
|
||||
def getWalletSeedID(self):
|
||||
return self.rpc('getwalletinfo')['hdmasterkeyid']
|
||||
|
||||
@ -309,7 +305,7 @@ class NAVInterface(BTCInterface):
|
||||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
|
||||
scriptSig=self.getScriptScriptSig(txn_script)))
|
||||
@ -323,7 +319,7 @@ class NAVInterface(BTCInterface):
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.nLockTime = locktime
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
|
||||
nSequence=sequence,
|
||||
scriptSig=self.getScriptScriptSig(txn_script)))
|
||||
@ -419,7 +415,7 @@ class NAVInterface(BTCInterface):
|
||||
return
|
||||
current_height -= 1
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
|
||||
# Add watchonly address and rescan if required
|
||||
|
||||
if not self.isAddressMine(dest_address, or_watch_only=True):
|
||||
@ -483,18 +479,16 @@ class NAVInterface(BTCInterface):
|
||||
|
||||
block_rv = {
|
||||
'hash': block_hash,
|
||||
'previousblockhash': block_header['previousblockhash'],
|
||||
'tx': tx_rv,
|
||||
'confirmations': block_header['confirmations'],
|
||||
'height': block_header['height'],
|
||||
'time': block_header['time'],
|
||||
'version': block_header['version'],
|
||||
'merkleroot': block_header['merkleroot'],
|
||||
}
|
||||
|
||||
return block_rv
|
||||
|
||||
def getScriptScriptSig(self, script: bytes) -> bytes:
|
||||
def getScriptScriptSig(self, script: bytes) -> bytearray:
|
||||
return self.getP2SHP2WSHScriptSig(script)
|
||||
|
||||
def getScriptDest(self, script):
|
||||
@ -516,7 +510,7 @@ class NAVInterface(BTCInterface):
|
||||
tx.vout.append(self.txoType()(output_amount, script_pk))
|
||||
return tx.serialize()
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
||||
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||
wtx = self.rpc('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
@ -530,7 +524,7 @@ class NAVInterface(BTCInterface):
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
|
||||
chain_b_lock_txid_int = b2i(chain_b_lock_txid)
|
||||
chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
|
||||
|
||||
script_sig = self.getInputScriptForPubkeyHash(self.getPubkeyHash(Kbs))
|
||||
|
||||
@ -682,7 +676,7 @@ class NAVInterface(BTCInterface):
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||
|
||||
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
|
||||
tx_lock_refund.rehash()
|
||||
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||
|
@ -7,6 +7,9 @@
|
||||
|
||||
from .btc import BTCInterface
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.util import (
|
||||
make_int,
|
||||
)
|
||||
|
||||
|
||||
class NMCInterface(BTCInterface):
|
||||
@ -14,7 +17,7 @@ class NMCInterface(BTCInterface):
|
||||
def coin_type():
|
||||
return Coins.NMC
|
||||
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1):
|
||||
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
|
||||
self._log.debug('[rm] scantxoutset start') # scantxoutset is slow
|
||||
ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
|
||||
self._log.debug('[rm] scantxoutset end')
|
||||
@ -23,7 +26,7 @@ class NMCInterface(BTCInterface):
|
||||
if txid and o['txid'] != txid.hex():
|
||||
continue
|
||||
# Verify amount
|
||||
if self.make_int(o['amount']) != int(bid_amount):
|
||||
if make_int(o['amount']) != int(bid_amount):
|
||||
self._log.warning('Found output to lock tx address of incorrect value: %s, %s', str(o['amount']), o['txid'])
|
||||
continue
|
||||
|
||||
|
@ -14,10 +14,11 @@ from basicswap.contrib.test_framework.messages import (
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript,
|
||||
OP_0,
|
||||
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG,
|
||||
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
|
||||
)
|
||||
from basicswap.util import (
|
||||
ensure,
|
||||
make_int,
|
||||
TemporaryError,
|
||||
)
|
||||
from basicswap.util.script import (
|
||||
@ -26,15 +27,10 @@ from basicswap.util.script import (
|
||||
getWitnessElementLen,
|
||||
)
|
||||
from basicswap.util.address import (
|
||||
encodeStealthAddress,
|
||||
)
|
||||
from basicswap.interface.btc import (
|
||||
BTCInterface,
|
||||
extractScriptLockScriptValues,
|
||||
extractScriptLockRefundScriptValues,
|
||||
)
|
||||
|
||||
toWIF,
|
||||
encodeStealthAddress)
|
||||
from basicswap.chainparams import Coins, chainparams
|
||||
from .btc import BTCInterface
|
||||
|
||||
|
||||
class BalanceTypes(IntEnum):
|
||||
@ -78,9 +74,6 @@ class PARTInterface(BTCInterface):
|
||||
super().__init__(coin_settings, network, swap_client)
|
||||
self.setAnonTxRingSize(int(coin_settings.get('anon_tx_ring_size', 12)))
|
||||
|
||||
def use_tx_vsize(self) -> bool:
|
||||
return True
|
||||
|
||||
def setAnonTxRingSize(self, value):
|
||||
ensure(value >= 3 and value < 33, 'Invalid anon_tx_ring_size value')
|
||||
self._anon_tx_ring_size = value
|
||||
@ -100,7 +93,7 @@ class PARTInterface(BTCInterface):
|
||||
index_info = self.rpc('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo')
|
||||
return index_info['spentindex']
|
||||
|
||||
def initialiseWallet(self, key: bytes) -> None:
|
||||
def initialiseWallet(self, key):
|
||||
raise ValueError('TODO')
|
||||
|
||||
def withdrawCoin(self, value, addr_to, subfee):
|
||||
@ -352,21 +345,21 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
ensure(lock_output_n is not None, 'Output not found in tx')
|
||||
|
||||
# Check value
|
||||
locked_txo_value = self.make_int(blinded_info['amount'])
|
||||
locked_txo_value = make_int(blinded_info['amount'])
|
||||
ensure(locked_txo_value == swap_value, 'Bad locked value')
|
||||
|
||||
# Check script
|
||||
lock_txo_scriptpk = bytes.fromhex(lock_tx_obj['vout'][lock_output_n]['scriptPubKey']['hex'])
|
||||
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
|
||||
ensure(lock_txo_scriptpk == script_pk, 'Bad output script')
|
||||
A, B = extractScriptLockScriptValues(script_out)
|
||||
A, B = self.extractScriptLockScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script leader pubkey')
|
||||
ensure(B == Kaf, 'Bad script follower pubkey')
|
||||
|
||||
# TODO: Check that inputs are unspent, rangeproofs and commitments sum
|
||||
# Verify fee rate
|
||||
vsize = lock_tx_obj['vsize']
|
||||
fee_paid = self.make_int(lock_tx_obj['vout'][0]['ct_fee'])
|
||||
fee_paid = make_int(lock_tx_obj['vout'][0]['ct_fee'])
|
||||
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
|
||||
@ -401,13 +394,13 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
lock_refund_output_n, blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce)
|
||||
ensure(lock_refund_output_n is not None, 'Output not found in tx')
|
||||
|
||||
lock_refund_txo_value = self.make_int(blinded_info['amount'])
|
||||
lock_refund_txo_value = make_int(blinded_info['amount'])
|
||||
|
||||
# Check script
|
||||
lock_refund_txo_scriptpk = bytes.fromhex(lock_refund_tx_obj['vout'][lock_refund_output_n]['scriptPubKey']['hex'])
|
||||
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
|
||||
ensure(lock_refund_txo_scriptpk == script_pk, 'Bad output script')
|
||||
A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
|
||||
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script pubkey')
|
||||
ensure(B == Kaf, 'Bad script pubkey')
|
||||
ensure(csv_val == csv_val_expect, 'Bad script csv value')
|
||||
@ -422,7 +415,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||
|
||||
# Check value
|
||||
fee_paid = self.make_int(lock_refund_tx_obj['vout'][0]['ct_fee'])
|
||||
fee_paid = make_int(lock_refund_tx_obj['vout'][0]['ct_fee'])
|
||||
ensure(swap_value - lock_refund_txo_value == fee_paid, 'Bad output value')
|
||||
|
||||
# Check fee rate
|
||||
@ -470,7 +463,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script)
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
vsize = self.getTxVSize(self.loadTx(tx_bytes), add_witness_bytes=witness_bytes)
|
||||
fee_paid = self.make_int(lock_refund_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
fee_paid = make_int(lock_refund_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
fee_rate_paid = fee_paid * 1000 // vsize
|
||||
ensure(self.compareFeeRates(fee_rate_paid, feerate), 'Bad fee rate, expected: {}'.format(feerate))
|
||||
|
||||
@ -534,7 +527,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options])
|
||||
lock_spend_tx_hex = rv['hex']
|
||||
lock_spend_tx_obj = self.rpc('decoderawtransaction', [lock_spend_tx_hex])
|
||||
pay_fee = self.make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
|
||||
# lock_spend_tx_hex does not include the dummy witness stack
|
||||
witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack)
|
||||
@ -606,8 +599,8 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
ensure(rv['inputs_valid'] is True, 'Invalid inputs')
|
||||
|
||||
# Check amount
|
||||
fee_paid = self.make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
amount_difference = self.make_int(input_blinded_info['amount']) - self.make_int(output_blinded_info['amount'])
|
||||
fee_paid = make_int(lock_spend_tx_obj['vout'][0]['ct_fee'])
|
||||
amount_difference = make_int(input_blinded_info['amount']) - make_int(output_blinded_info['amount'])
|
||||
ensure(fee_paid == amount_difference, 'Invalid output amount')
|
||||
|
||||
# Check fee
|
||||
@ -637,7 +630,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
|
||||
output_pubkey_hex = addr_info['pubkey']
|
||||
|
||||
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
|
||||
# Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance
|
||||
|
||||
@ -695,7 +688,8 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
else:
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
wif_scan_key = self.encodeKey(kbv)
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
@ -709,7 +703,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
assert (tx['outputs'][0]['stealth_address'] == sx_addr) # Should not be possible
|
||||
ensure(tx['outputs'][0]['type'] == 'blind', 'Output is not anon')
|
||||
|
||||
if self.make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||
height = 0
|
||||
if tx['confirmations'] > 0:
|
||||
chain_height = self.rpc('getblockcount')
|
||||
@ -720,14 +714,15 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
return -1
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['ismine']:
|
||||
wif_scan_key = self.encodeKey(kbv)
|
||||
wif_spend_key = self.encodeKey(kbs)
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
@ -746,7 +741,7 @@ class PARTInterfaceBlind(PARTInterface):
|
||||
raise ValueError('Too many spendable outputs')
|
||||
|
||||
utxo = utxos[0]
|
||||
utxo_sats = self.make_int(utxo['amount'])
|
||||
utxo_sats = make_int(utxo['amount'])
|
||||
|
||||
if spend_actual_balance and utxo_sats != cb_swap_value:
|
||||
self._log.warning('Spending actual balance {}, not swap value {}.'.format(utxo_sats, cb_swap_value))
|
||||
@ -831,7 +826,8 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
else:
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['iswatchonly']:
|
||||
wif_scan_key = self.encodeKey(kbv)
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()])
|
||||
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
@ -845,7 +841,7 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
assert (tx['outputs'][0]['stealth_address'] == sx_addr) # Should not be possible
|
||||
ensure(tx['outputs'][0]['type'] == 'anon', 'Output is not anon')
|
||||
|
||||
if self.make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||
if make_int(tx['outputs'][0]['amount']) == cb_swap_value:
|
||||
height = 0
|
||||
if tx['confirmations'] > 0:
|
||||
chain_height = self.rpc('getblockcount')
|
||||
@ -856,14 +852,15 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
return -1
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [sx_addr])
|
||||
if not addr_info['ismine']:
|
||||
wif_scan_key = self.encodeKey(kbv)
|
||||
wif_spend_key = self.encodeKey(kbs)
|
||||
wif_prefix = self.chainparams_network()['key_prefix']
|
||||
wif_scan_key = toWIF(wif_prefix, kbv)
|
||||
wif_spend_key = toWIF(wif_prefix, kbs)
|
||||
self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key])
|
||||
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
|
||||
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
|
||||
@ -877,7 +874,7 @@ class PARTInterfaceAnon(PARTInterface):
|
||||
raise ValueError('Too many spendable outputs')
|
||||
|
||||
utxo = autxos[0]
|
||||
utxo_sats = self.make_int(utxo['amount'])
|
||||
utxo_sats = make_int(utxo['amount'])
|
||||
|
||||
if spend_actual_balance and utxo_sats != cb_swap_value:
|
||||
self._log.warning('Spending actual balance {}, not swap value {}.'.format(utxo_sats, cb_swap_value))
|
||||
|
@ -75,11 +75,9 @@ class PIVXInterface(BTCInterface):
|
||||
|
||||
block_rv = {
|
||||
'hash': block_hash,
|
||||
'previousblockhash': block_header['previousblockhash'],
|
||||
'tx': tx_rv,
|
||||
'confirmations': block_header['confirmations'],
|
||||
'height': block_header['height'],
|
||||
'time': block_header['time'],
|
||||
'version': block_header['version'],
|
||||
'merkleroot': block_header['merkleroot'],
|
||||
}
|
||||
@ -107,7 +105,7 @@ class PIVXInterface(BTCInterface):
|
||||
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}.')
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||
|
@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from basicswap.chainparams import WOW_COIN, Coins
|
||||
from .xmr import XMRInterface
|
||||
|
||||
|
||||
class WOWInterface(XMRInterface):
|
||||
|
||||
@staticmethod
|
||||
def coin_type():
|
||||
return Coins.WOW
|
||||
|
||||
@staticmethod
|
||||
def ticker_str() -> int:
|
||||
return Coins.WOW.name
|
||||
|
||||
@staticmethod
|
||||
def COIN():
|
||||
return WOW_COIN
|
||||
|
||||
@staticmethod
|
||||
def exp() -> int:
|
||||
return 11
|
@ -24,21 +24,20 @@ from coincurve.dleag import (
|
||||
verify_ed25519_point,
|
||||
)
|
||||
|
||||
from basicswap.interface.base import (
|
||||
Curves,
|
||||
)
|
||||
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_xmr import (
|
||||
make_xmr_rpc_func,
|
||||
make_xmr_rpc2_func)
|
||||
from basicswap.chainparams import XMR_COIN, Coins
|
||||
from basicswap.interface.base import CoinInterface
|
||||
from basicswap.chainparams import XMR_COIN, CoinInterface, Coins
|
||||
|
||||
|
||||
class XMRInterface(CoinInterface):
|
||||
@ -50,10 +49,6 @@ class XMRInterface(CoinInterface):
|
||||
def coin_type():
|
||||
return Coins.XMR
|
||||
|
||||
@staticmethod
|
||||
def ticker_str() -> int:
|
||||
return Coins.XMR.name
|
||||
|
||||
@staticmethod
|
||||
def COIN():
|
||||
return XMR_COIN
|
||||
@ -133,6 +128,9 @@ class XMRInterface(CoinInterface):
|
||||
self.rpc2 = make_xmr_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_xmr_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
|
||||
@ -158,7 +156,7 @@ class XMRInterface(CoinInterface):
|
||||
pass
|
||||
self.rpc_wallet('open_wallet', params)
|
||||
|
||||
def initialiseWallet(self, key_view: bytes, key_spend: bytes, restore_height=None) -> None:
|
||||
def initialiseWallet(self, key_view, key_spend, restore_height=None):
|
||||
with self._mx_wallet:
|
||||
try:
|
||||
self.openWallet(self._wallet_filename)
|
||||
@ -214,7 +212,7 @@ class XMRInterface(CoinInterface):
|
||||
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(f'{self.ticker_str()} get_block_count failed with: {e}')
|
||||
self._log.warning('XMR get_block_count failed with: %s', str(e))
|
||||
rv['verificationprogress'] = 0.0
|
||||
|
||||
return rv
|
||||
@ -242,6 +240,9 @@ class XMRInterface(CoinInterface):
|
||||
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)
|
||||
@ -395,7 +396,7 @@ class XMRInterface(CoinInterface):
|
||||
|
||||
try:
|
||||
current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height']
|
||||
self._log.info(f'findTxnByHash {self.ticker_str()} current_height {current_height}\nhash: {txid}')
|
||||
self._log.info('findTxnByHash XMR 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
|
||||
@ -410,7 +411,7 @@ class XMRInterface(CoinInterface):
|
||||
|
||||
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, lock_tx_vout=None) -> bytes:
|
||||
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.
|
||||
@ -479,9 +480,9 @@ class XMRInterface(CoinInterface):
|
||||
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('{} {} sweep_all.'.format(self.ticker_str(), 'estimate fee' if estimate_fee else 'withdraw'))
|
||||
self._log.debug('{} balance: {}'.format(self.ticker_str(), balance['balance']))
|
||||
params = {'address': addr_to, 'do_not_relay': estimate_fee, 'subaddr_indices_all': True}
|
||||
self._log.info('XMR {} sweep_all.'.format('estimate fee' if estimate_fee else 'withdraw'))
|
||||
self._log.debug('XMR 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)
|
||||
@ -489,7 +490,7 @@ class XMRInterface(CoinInterface):
|
||||
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 = self.make_int(value)
|
||||
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
|
||||
|
@ -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 in (Coins.XMR, Coins.WOW):
|
||||
if coin_type == Coins.XMR:
|
||||
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 in (Coins.XMR, Coins.WOW):
|
||||
elif coin_type == Coins.XMR:
|
||||
txid_hex = swap_client.withdrawCoin(coin_type, value, address, sweepall)
|
||||
else:
|
||||
txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee)
|
||||
@ -228,7 +228,6 @@ def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes:
|
||||
'amount_from': ci_from.format_amount(o.amount_from),
|
||||
'amount_to': ci_to.format_amount((o.amount_from * o.rate) // ci_from.COIN()),
|
||||
'rate': ci_to.format_amount(o.rate),
|
||||
'min_bid_amount': ci_from.format_amount(o.min_bid_amount),
|
||||
}
|
||||
if with_extra_info:
|
||||
offer_data['amount_negotiable'] = o.amount_negotiable
|
||||
@ -429,31 +428,27 @@ def js_smsgaddresses(self, url_split, post_string, is_json) -> bytes:
|
||||
swap_client = self.server.swap_client
|
||||
swap_client.checkSystemStatus()
|
||||
post_data = {} if post_string == '' else getFormData(post_string, is_json)
|
||||
|
||||
if len(url_split) > 3:
|
||||
mode: str = url_split[3]
|
||||
if mode == 'new':
|
||||
if url_split[3] == 'new':
|
||||
addressnote = get_data_entry_or(post_data, 'addressnote', '')
|
||||
new_addr, pubkey = swap_client.newSMSGAddress(addressnote=addressnote)
|
||||
return bytes(json.dumps({'new_address': new_addr, 'pubkey': pubkey}), 'UTF-8')
|
||||
if mode == 'add':
|
||||
if url_split[3] == 'add':
|
||||
addressnote = get_data_entry_or(post_data, 'addressnote', '')
|
||||
pubkey_hex = get_data_entry(post_data, 'addresspubkey')
|
||||
added_address = swap_client.addSMSGAddress(pubkey_hex, addressnote)
|
||||
return bytes(json.dumps({'added_address': added_address, 'pubkey': pubkey_hex}), 'UTF-8')
|
||||
elif mode == 'edit':
|
||||
elif url_split[3] == 'edit':
|
||||
address = get_data_entry(post_data, 'address')
|
||||
activeind = int(get_data_entry(post_data, 'active_ind'))
|
||||
addressnote = get_data_entry_or(post_data, 'addressnote', '')
|
||||
new_addr = swap_client.editSMSGAddress(address, activeind, addressnote)
|
||||
return bytes(json.dumps({'edited_address': address}), 'UTF-8')
|
||||
elif mode == 'disableall':
|
||||
rv = swap_client.disableAllSMSGAddresses()
|
||||
return bytes(json.dumps(rv), 'UTF-8')
|
||||
|
||||
filters = {
|
||||
'exclude_inactive': post_data.get('exclude_inactive', True),
|
||||
}
|
||||
|
||||
return bytes(json.dumps(swap_client.listAllSMSGAddresses(filters)), 'UTF-8')
|
||||
|
||||
|
||||
@ -685,7 +680,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 in (Coins.XMR, Coins.WOW):
|
||||
if coin == Coins.XMR:
|
||||
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)
|
||||
@ -693,7 +688,7 @@ def js_getcoinseed(self, url_split, post_string, is_json) -> bytes:
|
||||
|
||||
seed_key = swap_client.getWalletKey(coin, 1)
|
||||
if coin == Coins.DASH:
|
||||
return bytes(json.dumps({'coin': ci.ticker(), 'seed': seed_key.hex(), 'mnemonic': ci.entropyToMnemonic(seed_key)}), 'UTF-8')
|
||||
return bytes(json.dumps({'coin': ci.ticker(), 'seed': seed_key.hex(), 'mnemonic': ci.seedToMnemonic(seed_key)}), 'UTF-8')
|
||||
seed_id = ci.getSeedHash(seed_key)
|
||||
return bytes(json.dumps({'coin': ci.ticker(), 'seed': seed_key.hex(), 'seed_id': seed_id.hex()}), 'UTF-8')
|
||||
|
||||
|
156
basicswap/messages.proto
Normal file
156
basicswap/messages.proto
Normal file
@ -0,0 +1,156 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package basicswap;
|
||||
|
||||
/* Step 1, seller -> network */
|
||||
message OfferMessage {
|
||||
uint32 protocol_version = 1;
|
||||
uint32 coin_from = 2;
|
||||
uint32 coin_to = 3;
|
||||
uint64 amount_from = 4;
|
||||
uint64 amount_to = 5;
|
||||
uint64 min_bid_amount = 6;
|
||||
uint64 time_valid = 7;
|
||||
enum LockType {
|
||||
NOT_SET = 0;
|
||||
SEQUENCE_LOCK_BLOCKS = 1;
|
||||
SEQUENCE_LOCK_TIME = 2;
|
||||
ABS_LOCK_BLOCKS = 3;
|
||||
ABS_LOCK_TIME = 4;
|
||||
}
|
||||
LockType lock_type = 8;
|
||||
uint32 lock_value = 9;
|
||||
uint32 swap_type = 10;
|
||||
|
||||
/* optional */
|
||||
string proof_address = 11;
|
||||
string proof_signature = 12;
|
||||
bytes pkhash_seller = 13;
|
||||
bytes secret_hash = 14;
|
||||
|
||||
uint64 fee_rate_from = 15;
|
||||
uint64 fee_rate_to = 16;
|
||||
|
||||
bool amount_negotiable = 17;
|
||||
bool rate_negotiable = 18;
|
||||
|
||||
bytes proof_utxos = 19; /* 32 byte txid 2 byte vout, repeated */
|
||||
}
|
||||
|
||||
/* Step 2, buyer -> seller */
|
||||
message BidMessage {
|
||||
uint32 protocol_version = 1;
|
||||
bytes offer_msg_id = 2;
|
||||
uint64 time_valid = 3; /* seconds bid is valid for */
|
||||
uint64 amount = 4; /* amount of amount_from bid is for */
|
||||
uint64 amount_to = 5;
|
||||
bytes pkhash_buyer = 6; /* buyer's address to receive amount_from */
|
||||
string proof_address = 7;
|
||||
string proof_signature = 8;
|
||||
|
||||
bytes proof_utxos = 9; /* 32 byte txid 2 byte vout, repeated */
|
||||
}
|
||||
|
||||
/* For tests */
|
||||
message BidMessage_test {
|
||||
uint32 protocol_version = 1;
|
||||
bytes offer_msg_id = 2;
|
||||
uint64 time_valid = 3;
|
||||
uint64 amount = 4;
|
||||
uint64 rate = 5;
|
||||
}
|
||||
|
||||
/* Step 3, seller -> buyer */
|
||||
message BidAcceptMessage {
|
||||
bytes bid_msg_id = 1;
|
||||
bytes initiate_txid = 2;
|
||||
bytes contract_script = 3;
|
||||
}
|
||||
|
||||
message OfferRevokeMessage {
|
||||
bytes offer_msg_id = 1;
|
||||
bytes signature = 2;
|
||||
}
|
||||
|
||||
message BidRejectMessage {
|
||||
bytes bid_msg_id = 1;
|
||||
uint32 reject_code = 2;
|
||||
}
|
||||
|
||||
message XmrBidMessage {
|
||||
/* MSG1L, F -> L */
|
||||
uint32 protocol_version = 1;
|
||||
bytes offer_msg_id = 2;
|
||||
uint64 time_valid = 3; /* seconds bid is valid for */
|
||||
uint64 amount = 4; /* amount of amount_from bid is for */
|
||||
uint64 amount_to = 5;
|
||||
|
||||
bytes pkaf = 6;
|
||||
|
||||
bytes kbvf = 7;
|
||||
bytes kbsf_dleag = 8;
|
||||
|
||||
bytes dest_af = 9;
|
||||
}
|
||||
|
||||
message XmrSplitMessage {
|
||||
bytes msg_id = 1;
|
||||
uint32 msg_type = 2; /* 1 XmrBid, 2 XmrBidAccept */
|
||||
uint32 sequence = 3;
|
||||
bytes dleag = 4;
|
||||
}
|
||||
|
||||
message XmrBidAcceptMessage {
|
||||
bytes bid_msg_id = 1;
|
||||
|
||||
bytes pkal = 2;
|
||||
bytes kbvl = 3;
|
||||
bytes kbsl_dleag = 4;
|
||||
|
||||
/* MSG2F */
|
||||
bytes a_lock_tx = 5;
|
||||
bytes a_lock_tx_script = 6;
|
||||
bytes a_lock_refund_tx = 7;
|
||||
bytes a_lock_refund_tx_script = 8;
|
||||
bytes a_lock_refund_spend_tx = 9;
|
||||
bytes al_lock_refund_tx_sig = 10;
|
||||
}
|
||||
|
||||
message XmrBidLockTxSigsMessage {
|
||||
/* MSG3L */
|
||||
bytes bid_msg_id = 1;
|
||||
bytes af_lock_refund_spend_tx_esig = 2;
|
||||
bytes af_lock_refund_tx_sig = 3;
|
||||
}
|
||||
|
||||
message XmrBidLockSpendTxMessage {
|
||||
/* MSG4F */
|
||||
bytes bid_msg_id = 1;
|
||||
bytes a_lock_spend_tx = 2;
|
||||
bytes kal_sig = 3;
|
||||
}
|
||||
|
||||
message XmrBidLockReleaseMessage {
|
||||
/* MSG5F */
|
||||
bytes bid_msg_id = 1;
|
||||
bytes al_lock_spend_tx_esig = 2;
|
||||
}
|
||||
|
||||
message ADSBidIntentMessage {
|
||||
/* L -> F Sent from bidder, construct a reverse bid */
|
||||
uint32 protocol_version = 1;
|
||||
bytes offer_msg_id = 2;
|
||||
uint64 time_valid = 3; /* seconds bid is valid for */
|
||||
uint64 amount_from = 4; /* amount of offer.coin_from bid is for */
|
||||
uint64 amount_to = 5; /* amount of offer.coin_to bid is for, equivalent to bid.amount */
|
||||
}
|
||||
|
||||
message ADSBidIntentAcceptMessage {
|
||||
/* F -> L Sent from offerer, construct a reverse bid */
|
||||
bytes bid_msg_id = 1;
|
||||
|
||||
bytes pkaf = 2;
|
||||
bytes kbvf = 3;
|
||||
bytes kbsf_dleag = 4;
|
||||
bytes dest_af = 5;
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
|
||||
'''
|
||||
syntax = "proto3";
|
||||
|
||||
0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum
|
||||
1 I64 fixed64, sfixed64, double
|
||||
2 LEN string, bytes, embedded messages, packed repeated fields
|
||||
5 I32 fixed32, sfixed32, float
|
||||
|
||||
Don't encode fields of default values.
|
||||
When decoding initialise all fields not set from data.
|
||||
|
||||
protobuf ParseFromString would reset the whole object, from_bytes won't.
|
||||
'''
|
||||
|
||||
from basicswap.util.integer import encode_varint, decode_varint
|
||||
|
||||
|
||||
class NonProtobufClass():
|
||||
def __init__(self, init_all: bool = True, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
found_field: bool = False
|
||||
for field_num, v in self._map.items():
|
||||
field_name, wire_type, field_type = v
|
||||
if field_name == key:
|
||||
setattr(self, field_name, value)
|
||||
found_field = True
|
||||
break
|
||||
if found_field is False:
|
||||
raise ValueError(f'got an unexpected keyword argument \'{key}\'')
|
||||
|
||||
if init_all:
|
||||
self.init_fields()
|
||||
|
||||
def init_fields(self) -> None:
|
||||
# Set default values for missing fields
|
||||
for field_num, v in self._map.items():
|
||||
field_name, wire_type, field_type = v
|
||||
if hasattr(self, field_name):
|
||||
continue
|
||||
if wire_type == 0:
|
||||
setattr(self, field_name, 0)
|
||||
elif wire_type == 2:
|
||||
if field_type == 1:
|
||||
setattr(self, field_name, str())
|
||||
else:
|
||||
setattr(self, field_name, bytes())
|
||||
else:
|
||||
raise ValueError(f'Unknown wire_type {wire_type}')
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
rv = bytes()
|
||||
|
||||
for field_num, v in self._map.items():
|
||||
field_name, wire_type, field_type = v
|
||||
if not hasattr(self, field_name):
|
||||
continue
|
||||
field_value = getattr(self, field_name)
|
||||
tag = (field_num << 3) | wire_type
|
||||
if wire_type == 0:
|
||||
if field_value == 0:
|
||||
continue
|
||||
rv += encode_varint(tag)
|
||||
rv += encode_varint(field_value)
|
||||
elif wire_type == 2:
|
||||
if len(field_value) == 0:
|
||||
continue
|
||||
rv += encode_varint(tag)
|
||||
if isinstance(field_value, str):
|
||||
field_value = field_value.encode('utf-8')
|
||||
rv += encode_varint(len(field_value))
|
||||
rv += field_value
|
||||
else:
|
||||
raise ValueError(f'Unknown wire_type {wire_type}')
|
||||
return rv
|
||||
|
||||
def from_bytes(self, b: bytes, init_all: bool = True) -> None:
|
||||
max_len: int = len(b)
|
||||
o: int = 0
|
||||
while o < max_len:
|
||||
tag, lv = decode_varint(b, o)
|
||||
o += lv
|
||||
wire_type = tag & 7
|
||||
field_num = tag >> 3
|
||||
|
||||
field_name, wire_type_expect, field_type = self._map[field_num]
|
||||
if wire_type != wire_type_expect:
|
||||
raise ValueError(f'Unexpected wire_type {wire_type} for field {field_num}')
|
||||
|
||||
if wire_type == 0:
|
||||
field_value, lv = decode_varint(b, o)
|
||||
o += lv
|
||||
elif wire_type == 2:
|
||||
field_len, lv = decode_varint(b, o)
|
||||
o += lv
|
||||
field_value = b[o: o + field_len]
|
||||
o += field_len
|
||||
if field_type == 1:
|
||||
field_value = field_value.decode('utf-8')
|
||||
else:
|
||||
raise ValueError(f'Unknown wire_type {wire_type}')
|
||||
|
||||
setattr(self, field_name, field_value)
|
||||
|
||||
if init_all:
|
||||
self.init_fields()
|
||||
|
||||
|
||||
class OfferMessage(NonProtobufClass):
|
||||
_map = {
|
||||
1: ('protocol_version', 0, 0),
|
||||
2: ('coin_from', 0, 0),
|
||||
3: ('coin_to', 0, 0),
|
||||
4: ('amount_from', 0, 0),
|
||||
5: ('amount_to', 0, 0),
|
||||
6: ('min_bid_amount', 0, 0),
|
||||
7: ('time_valid', 0, 0),
|
||||
8: ('lock_type', 0, 0),
|
||||
9: ('lock_value', 0, 0),
|
||||
10: ('swap_type', 0, 0),
|
||||
11: ('proof_address', 2, 1),
|
||||
12: ('proof_signature', 2, 1),
|
||||
13: ('pkhash_seller', 2, 0),
|
||||
14: ('secret_hash', 2, 0),
|
||||
15: ('fee_rate_from', 0, 0),
|
||||
16: ('fee_rate_to', 0, 0),
|
||||
17: ('amount_negotiable', 0, 2),
|
||||
18: ('rate_negotiable', 0, 2),
|
||||
19: ('proof_utxos', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class BidMessage(NonProtobufClass):
|
||||
_map = {
|
||||
1: ('protocol_version', 0, 0),
|
||||
2: ('offer_msg_id', 2, 0),
|
||||
3: ('time_valid', 0, 0),
|
||||
4: ('amount', 0, 0),
|
||||
5: ('amount_to', 0, 0),
|
||||
6: ('pkhash_buyer', 2, 0),
|
||||
7: ('proof_address', 2, 1),
|
||||
8: ('proof_signature', 2, 1),
|
||||
9: ('proof_utxos', 2, 0),
|
||||
10: ('pkhash_buyer_to', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class BidAcceptMessage(NonProtobufClass):
|
||||
# Step 3, seller -> buyer
|
||||
_map = {
|
||||
1: ('bid_msg_id', 2, 0),
|
||||
2: ('initiate_txid', 2, 0),
|
||||
3: ('contract_script', 2, 0),
|
||||
4: ('pkhash_seller', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class OfferRevokeMessage(NonProtobufClass):
|
||||
_map = {
|
||||
1: ('offer_msg_id', 2, 0),
|
||||
2: ('signature', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class BidRejectMessage(NonProtobufClass):
|
||||
_map = {
|
||||
1: ('bid_msg_id', 2, 0),
|
||||
2: ('reject_code', 0, 0),
|
||||
}
|
||||
|
||||
|
||||
class XmrBidMessage(NonProtobufClass):
|
||||
# MSG1L, F -> L
|
||||
_map = {
|
||||
1: ('protocol_version', 0, 0),
|
||||
2: ('offer_msg_id', 2, 0),
|
||||
3: ('time_valid', 0, 0),
|
||||
4: ('amount', 0, 0),
|
||||
5: ('amount_to', 0, 0),
|
||||
6: ('pkaf', 2, 0),
|
||||
7: ('kbvf', 2, 0),
|
||||
8: ('kbsf_dleag', 2, 0),
|
||||
9: ('dest_af', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class XmrSplitMessage(NonProtobufClass):
|
||||
_map = {
|
||||
1: ('msg_id', 2, 0),
|
||||
2: ('msg_type', 0, 0),
|
||||
3: ('sequence', 0, 0),
|
||||
4: ('dleag', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class XmrBidAcceptMessage(NonProtobufClass):
|
||||
_map = {
|
||||
1: ('bid_msg_id', 2, 0),
|
||||
2: ('pkal', 2, 0),
|
||||
3: ('kbvl', 2, 0),
|
||||
4: ('kbsl_dleag', 2, 0),
|
||||
|
||||
# MSG2F
|
||||
5: ('a_lock_tx', 2, 0),
|
||||
6: ('a_lock_tx_script', 2, 0),
|
||||
7: ('a_lock_refund_tx', 2, 0),
|
||||
8: ('a_lock_refund_tx_script', 2, 0),
|
||||
9: ('a_lock_refund_spend_tx', 2, 0),
|
||||
10: ('al_lock_refund_tx_sig', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class XmrBidLockTxSigsMessage(NonProtobufClass):
|
||||
# MSG3L
|
||||
_map = {
|
||||
1: ('bid_msg_id', 2, 0),
|
||||
2: ('af_lock_refund_spend_tx_esig', 2, 0),
|
||||
3: ('af_lock_refund_tx_sig', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class XmrBidLockSpendTxMessage(NonProtobufClass):
|
||||
# MSG4F
|
||||
_map = {
|
||||
1: ('bid_msg_id', 2, 0),
|
||||
2: ('a_lock_spend_tx', 2, 0),
|
||||
3: ('kal_sig', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class XmrBidLockReleaseMessage(NonProtobufClass):
|
||||
# MSG5F
|
||||
_map = {
|
||||
1: ('bid_msg_id', 2, 0),
|
||||
2: ('al_lock_spend_tx_esig', 2, 0),
|
||||
}
|
||||
|
||||
|
||||
class ADSBidIntentMessage(NonProtobufClass):
|
||||
# L -> F Sent from bidder, construct a reverse bid
|
||||
_map = {
|
||||
1: ('protocol_version', 0, 0),
|
||||
2: ('offer_msg_id', 2, 0),
|
||||
3: ('time_valid', 0, 0),
|
||||
4: ('amount_from', 0, 0),
|
||||
5: ('amount_to', 0, 0),
|
||||
}
|
||||
|
||||
|
||||
class ADSBidIntentAcceptMessage(NonProtobufClass):
|
||||
# F -> L Sent from offerer, construct a reverse bid
|
||||
_map = {
|
||||
1: ('bid_msg_id', 2, 0),
|
||||
2: ('pkaf', 2, 0),
|
||||
3: ('kbvf', 2, 0),
|
||||
4: ('kbsf_dleag', 2, 0),
|
||||
5: ('dest_af', 2, 0),
|
||||
}
|
54
basicswap/messages_pb2.py
Normal file
54
basicswap/messages_pb2.py
Normal file
@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: messages.proto
|
||||
# Protobuf Python Version: 4.25.3
|
||||
"""Generated protocol buffer code."""
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import descriptor_pool as _descriptor_pool
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf.internal import builder as _builder
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xc0\x04\n\x0cOfferMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x11\n\tcoin_from\x18\x02 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x03 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x06 \x01(\x04\x12\x12\n\ntime_valid\x18\x07 \x01(\x04\x12\x33\n\tlock_type\x18\x08 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\t \x01(\r\x12\x11\n\tswap_type\x18\n \x01(\r\x12\x15\n\rproof_address\x18\x0b \x01(\t\x12\x17\n\x0fproof_signature\x18\x0c \x01(\t\x12\x15\n\rpkhash_seller\x18\r \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\x0e \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0f \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x10 \x01(\x04\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\x12\x13\n\x0bproof_utxos\x18\x13 \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\xce\x01\n\nBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x06 \x01(\x0c\x12\x15\n\rproof_address\x18\x07 \x01(\t\x12\x17\n\x0fproof_signature\x18\x08 \x01(\t\x12\x13\n\x0bproof_utxos\x18\t \x01(\x0c\"s\n\x0f\x42idMessage_test\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x0c\n\x04rate\x18\x05 \x01(\x04\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\";\n\x10\x42idRejectMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x13\n\x0breject_code\x18\x02 \x01(\r\"\xb7\x01\n\rXmrBidMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\x12\x0c\n\x04pkaf\x18\x06 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x07 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x08 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\t \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x80\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkal\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x03 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x04 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x05 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x07 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\x08 \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\t \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\n \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"X\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x0f\n\x07kal_sig\x18\x03 \x01(\x0c\"M\n\x18XmrBidLockReleaseMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x02 \x01(\x0c\"\x81\x01\n\x13\x41\x44SBidIntentMessage\x12\x18\n\x10protocol_version\x18\x01 \x01(\r\x12\x14\n\x0coffer_msg_id\x18\x02 \x01(\x0c\x12\x12\n\ntime_valid\x18\x03 \x01(\x04\x12\x13\n\x0b\x61mount_from\x18\x04 \x01(\x04\x12\x11\n\tamount_to\x18\x05 \x01(\x04\"p\n\x19\x41\x44SBidIntentAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x0c\n\x04pkaf\x18\x02 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x03 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x04 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x05 \x01(\x0c\x62\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'messages_pb2', _globals)
|
||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||
DESCRIPTOR._options = None
|
||||
_globals['_OFFERMESSAGE']._serialized_start=30
|
||||
_globals['_OFFERMESSAGE']._serialized_end=606
|
||||
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_start=493
|
||||
_globals['_OFFERMESSAGE_LOCKTYPE']._serialized_end=606
|
||||
_globals['_BIDMESSAGE']._serialized_start=609
|
||||
_globals['_BIDMESSAGE']._serialized_end=815
|
||||
_globals['_BIDMESSAGE_TEST']._serialized_start=817
|
||||
_globals['_BIDMESSAGE_TEST']._serialized_end=932
|
||||
_globals['_BIDACCEPTMESSAGE']._serialized_start=934
|
||||
_globals['_BIDACCEPTMESSAGE']._serialized_end=1020
|
||||
_globals['_OFFERREVOKEMESSAGE']._serialized_start=1022
|
||||
_globals['_OFFERREVOKEMESSAGE']._serialized_end=1083
|
||||
_globals['_BIDREJECTMESSAGE']._serialized_start=1085
|
||||
_globals['_BIDREJECTMESSAGE']._serialized_end=1144
|
||||
_globals['_XMRBIDMESSAGE']._serialized_start=1147
|
||||
_globals['_XMRBIDMESSAGE']._serialized_end=1330
|
||||
_globals['_XMRSPLITMESSAGE']._serialized_start=1332
|
||||
_globals['_XMRSPLITMESSAGE']._serialized_end=1416
|
||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_start=1419
|
||||
_globals['_XMRBIDACCEPTMESSAGE']._serialized_end=1675
|
||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_start=1677
|
||||
_globals['_XMRBIDLOCKTXSIGSMESSAGE']._serialized_end=1791
|
||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_start=1793
|
||||
_globals['_XMRBIDLOCKSPENDTXMESSAGE']._serialized_end=1881
|
||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_start=1883
|
||||
_globals['_XMRBIDLOCKRELEASEMESSAGE']._serialized_end=1960
|
||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_start=1963
|
||||
_globals['_ADSBIDINTENTMESSAGE']._serialized_end=2092
|
||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_start=2094
|
||||
_globals['_ADSBIDINTENTACCEPTMESSAGE']._serialized_end=2206
|
||||
# @@protoc_insertion_point(module_scope)
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020-2024 tecnovert
|
||||
# Copyright (c) 2020-2023 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@ -10,15 +10,12 @@ from basicswap.db import (
|
||||
from basicswap.util import (
|
||||
SerialiseNum,
|
||||
)
|
||||
from basicswap.util.script import (
|
||||
decodeScriptNum,
|
||||
)
|
||||
from basicswap.script import (
|
||||
OpCodes,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
EventLogTypes,
|
||||
SwapTypes,
|
||||
EventLogTypes,
|
||||
)
|
||||
from . import ProtocolInterface
|
||||
|
||||
@ -26,13 +23,13 @@ INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
|
||||
ABS_LOCK_TIME_LEEWAY = 10 * 60
|
||||
|
||||
|
||||
def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256) -> bytearray:
|
||||
def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY) -> bytearray:
|
||||
script = bytearray([
|
||||
OpCodes.OP_IF,
|
||||
OpCodes.OP_SIZE,
|
||||
0x01, 0x20, # 32
|
||||
OpCodes.OP_EQUALVERIFY,
|
||||
op_hash,
|
||||
OpCodes.OP_SHA256,
|
||||
0x20]) \
|
||||
+ secret_hash \
|
||||
+ bytearray([
|
||||
@ -57,46 +54,6 @@ def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pk
|
||||
return script
|
||||
|
||||
|
||||
def verifyContractScript(script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256):
|
||||
if script[0] != OpCodes.OP_IF or \
|
||||
script[1] != OpCodes.OP_SIZE or \
|
||||
script[2] != 0x01 or script[3] != 0x20 or \
|
||||
script[4] != OpCodes.OP_EQUALVERIFY or \
|
||||
script[5] != op_hash or \
|
||||
script[6] != 0x20:
|
||||
return False, None, None, None, None
|
||||
o = 7
|
||||
script_hash = script[o: o + 32]
|
||||
o += 32
|
||||
if script[o] != OpCodes.OP_EQUALVERIFY or \
|
||||
script[o + 1] != OpCodes.OP_DUP or \
|
||||
script[o + 2] != OpCodes.OP_HASH160 or \
|
||||
script[o + 3] != 0x14:
|
||||
return False, script_hash, None, None, None
|
||||
o += 4
|
||||
pkh_redeem = script[o: o + 20]
|
||||
o += 20
|
||||
if script[o] != OpCodes.OP_ELSE:
|
||||
return False, script_hash, pkh_redeem, None, None
|
||||
o += 1
|
||||
lock_val, nb = decodeScriptNum(script, o)
|
||||
o += nb
|
||||
if script[o] != op_lock or \
|
||||
script[o + 1] != OpCodes.OP_DROP or \
|
||||
script[o + 2] != OpCodes.OP_DUP or \
|
||||
script[o + 3] != OpCodes.OP_HASH160 or \
|
||||
script[o + 4] != 0x14:
|
||||
return False, script_hash, pkh_redeem, lock_val, None
|
||||
o += 5
|
||||
pkh_refund = script[o: o + 20]
|
||||
o += 20
|
||||
if script[o] != OpCodes.OP_ENDIF or \
|
||||
script[o + 1] != OpCodes.OP_EQUALVERIFY or \
|
||||
script[o + 2] != OpCodes.OP_CHECKSIG:
|
||||
return False, script_hash, pkh_redeem, lock_val, pkh_refund
|
||||
return True, script_hash, pkh_redeem, lock_val, pkh_refund
|
||||
|
||||
|
||||
def extractScriptSecretHash(script):
|
||||
return script[7:39]
|
||||
|
||||
@ -105,7 +62,7 @@ def redeemITx(self, bid_id: bytes, session):
|
||||
bid, offer = self.getBidAndOffer(bid_id, session)
|
||||
ci_from = self.ci(offer.coin_from)
|
||||
|
||||
txn = self.createRedeemTxn(ci_from.coin_type(), bid, for_txn_type='initiate', session=session)
|
||||
txn = self.createRedeemTxn(ci_from.coin_type(), bid, for_txn_type='initiate')
|
||||
txid = ci_from.publishTx(bytes.fromhex(txn))
|
||||
|
||||
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
|
||||
|
@ -1,13 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020-2024 tecnovert
|
||||
# Copyright (c) 2020-2023 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from sqlalchemy.orm import scoped_session
|
||||
|
||||
from basicswap.util import (
|
||||
ensure,
|
||||
)
|
||||
from basicswap.interface.base import Curves
|
||||
from basicswap.interface import Curves
|
||||
from basicswap.chainparams import (
|
||||
Coins,
|
||||
)
|
||||
@ -19,17 +21,13 @@ from basicswap.basicswap_util import (
|
||||
from . import ProtocolInterface
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript, CScriptOp,
|
||||
OP_CHECKMULTISIG
|
||||
)
|
||||
OP_CHECKMULTISIG)
|
||||
|
||||
|
||||
def addLockRefundSigs(self, xmr_swap, ci):
|
||||
self.log.debug('Setting lock refund tx sigs')
|
||||
|
||||
witness_stack = []
|
||||
if ci.coin_type() not in (Coins.DCR, ):
|
||||
witness_stack += [b'', ]
|
||||
witness_stack += [
|
||||
witness_stack = [
|
||||
b'',
|
||||
xmr_swap.al_lock_refund_tx_sig,
|
||||
xmr_swap.af_lock_refund_tx_sig,
|
||||
xmr_swap.a_lock_tx_script,
|
||||
@ -43,7 +41,7 @@ def addLockRefundSigs(self, xmr_swap, ci):
|
||||
def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key):
|
||||
self.log.info('Manually recovering %s', bid_id.hex())
|
||||
# Manually recover txn if other key is known
|
||||
session = self.openSession()
|
||||
session = scoped_session(self.session_factory)
|
||||
try:
|
||||
bid, xmr_swap = self.getXmrBidFromSession(session, bid_id)
|
||||
ensure(bid, 'Bid not found: {}.'.format(bid_id.hex()))
|
||||
@ -76,15 +74,15 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key):
|
||||
address_to = self.getCachedStealthAddressForCoin(offer.coin_to)
|
||||
|
||||
amount = bid.amount_to
|
||||
lock_tx_vout = bid.getLockTXBVout()
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, amount, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True, lock_tx_vout=lock_tx_vout)
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True)
|
||||
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, txid.hex(), session)
|
||||
session.commit()
|
||||
|
||||
return txid
|
||||
finally:
|
||||
self.closeSession(session, commit=False)
|
||||
session.close()
|
||||
session.remove()
|
||||
|
||||
|
||||
def getChainBSplitKey(swap_client, bid, xmr_swap, offer):
|
||||
|
@ -1,13 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2020-2024 tecnovert
|
||||
# Copyright (c) 2020-2023 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import shlex
|
||||
import urllib
|
||||
import logging
|
||||
import traceback
|
||||
import subprocess
|
||||
from xmlrpc.client import (
|
||||
@ -18,6 +20,18 @@ from xmlrpc.client import (
|
||||
from .util import jsonDecimal
|
||||
|
||||
|
||||
def waitForRPC(rpc_func, expect_wallet=True, max_tries=7):
|
||||
for i in range(max_tries + 1):
|
||||
try:
|
||||
rpc_func('getwalletinfo' if expect_wallet else 'getblockchaininfo')
|
||||
return
|
||||
except Exception as ex:
|
||||
if i < max_tries:
|
||||
logging.warning('Can\'t connect to RPC: %s. Retrying in %d second/s.', str(ex), (i + 1))
|
||||
time.sleep(i + 1)
|
||||
raise ValueError('waitForRPC failed')
|
||||
|
||||
|
||||
class Jsonrpc():
|
||||
# __getattr__ complicates extending ServerProxy
|
||||
def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
||||
|
@ -26,5 +26,3 @@ class OpCodes(IntEnum):
|
||||
OP_CHECKSIG = 0xac,
|
||||
OP_CHECKLOCKTIMEVERIFY = 0xb1,
|
||||
OP_CHECKSEQUENCEVERIFY = 0xb2,
|
||||
|
||||
OP_SHA256_DECRED = 0xc0,
|
||||
|
@ -5,12 +5,17 @@
|
||||
<div class="flex flex-wrap items-center -m-2">
|
||||
<div class="w-full md:w-1/2 p-2">
|
||||
<ul class="flex flex-wrap items-center gap-x-3 mb-2">
|
||||
<li>{{ breadcrumb_line_svg | safe }}</li>
|
||||
<li>
|
||||
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/">
|
||||
<p>Home</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>{{ breadcrumb_line_svg | safe }}</li>
|
||||
<li>
|
||||
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/wallets">
|
||||
<p>Home</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>{{ breadcrumb_line_svg | safe }}</li>
|
||||
<li>
|
||||
<a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/changepassword">Change Password</a>
|
||||
@ -32,8 +37,8 @@
|
||||
<img class="absolute h-64 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="">
|
||||
<div class="relative z-20 flex flex-wrap items-center -m-3">
|
||||
<div class="w-full md:w-1/2 p-3">
|
||||
<h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">Change/Set your Password</h2>
|
||||
<p class="font-normal text-coolGray-200 dark:text-white">Change or Set your BasicSwap / Wallets password.</p>
|
||||
<h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">Change Password</h2>
|
||||
<p class="font-normal text-coolGray-200 dark:text-white">Update your BasicSwap / Wallets password.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,13 +27,13 @@
|
||||
<p class="text-sm text-coolGray-400 font-medium">GUI: v3.0.0</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
|
||||
<p class="mr-2 text-sm font-bold dark:text-white text-gray-90 ">Made with </p>
|
||||
{{ love_svg | safe }}
|
||||
</div>
|
||||
<span class="ml-2 text-sm font-bold dark:text-white text-gray-90 ">by TV and CRZ</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/2">
|
||||
<div class="flex flex-wrap md:justify-end -mx-5">
|
||||
<div class="px-5">
|
||||
<a class="inline-block text-coolGray-300 hover:text-coolGray-400" href="https://github.com/basicswap/basicswap" target="_blank">
|
||||
<a class="inline-block text-coolGray-300 hover:text-coolGray-400" href="https://github.com/tecnovert/basicswap" target="_blank">
|
||||
{{ github_svg | safe }}
|
||||
</a>
|
||||
</div>
|
||||
@ -43,67 +43,69 @@
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
var toggleImages = function() {
|
||||
var html = document.querySelector('html');
|
||||
var darkImages = document.querySelectorAll('.dark-image');
|
||||
var lightImages = document.querySelectorAll('.light-image');
|
||||
(function() {
|
||||
var toggleImages = function() {
|
||||
var html = document.querySelector('html');
|
||||
var darkImages = document.querySelectorAll('.dark-image');
|
||||
var lightImages = document.querySelectorAll('.light-image');
|
||||
|
||||
if (html && html.classList.contains('dark')) {
|
||||
toggleImageDisplay(darkImages, 'block');
|
||||
toggleImageDisplay(lightImages, 'none');
|
||||
} else {
|
||||
toggleImageDisplay(darkImages, 'none');
|
||||
toggleImageDisplay(lightImages, 'block');
|
||||
if (html && html.classList.contains('dark')) {
|
||||
toggleImageDisplay(darkImages, 'block');
|
||||
toggleImageDisplay(lightImages, 'none');
|
||||
} else {
|
||||
toggleImageDisplay(darkImages, 'none');
|
||||
toggleImageDisplay(lightImages, 'block');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var toggleImageDisplay = function(images, display) {
|
||||
images.forEach(function(img) {
|
||||
img.style.display = display;
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var themeToggle = document.getElementById('theme-toggle');
|
||||
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener('click', function() {
|
||||
toggleImages();
|
||||
var toggleImageDisplay = function(images, display) {
|
||||
images.forEach(function(img) {
|
||||
img.style.display = display;
|
||||
});
|
||||
}
|
||||
|
||||
toggleImages();
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var themeToggle = document.getElementById('theme-toggle');
|
||||
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener('click', function() {
|
||||
toggleImages();
|
||||
});
|
||||
}
|
||||
|
||||
toggleImages();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
var themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
|
||||
var themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
|
||||
var themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
|
||||
var themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
|
||||
|
||||
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
themeToggleLightIcon.classList.remove('hidden');
|
||||
} else {
|
||||
themeToggleDarkIcon.classList.remove('hidden');
|
||||
}
|
||||
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
themeToggleLightIcon.classList.remove('hidden');
|
||||
} else {
|
||||
themeToggleDarkIcon.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function setTheme(theme) {
|
||||
if (theme === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('color-theme', 'light');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
}
|
||||
}
|
||||
function setTheme(theme) {
|
||||
if (theme === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('color-theme', 'light');
|
||||
} else {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('theme-toggle').addEventListener('click', () => {
|
||||
if (localStorage.getItem('color-theme') === 'dark') {
|
||||
setTheme('light');
|
||||
} else {
|
||||
setTheme('dark');
|
||||
}
|
||||
themeToggleDarkIcon.classList.toggle('hidden');
|
||||
themeToggleLightIcon.classList.toggle('hidden');
|
||||
toggleImages();
|
||||
});
|
||||
document.getElementById('theme-toggle').addEventListener('click', () => {
|
||||
if (localStorage.getItem('color-theme') === 'dark') {
|
||||
setTheme('light');
|
||||
} else {
|
||||
setTheme('dark');
|
||||
}
|
||||
themeToggleDarkIcon.classList.toggle('hidden');
|
||||
themeToggleLightIcon.classList.toggle('hidden');
|
||||
toggleImages();
|
||||
});
|
||||
|
||||
</script>
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
{% from 'style.html' import change_password_svg, notifications_network_offer_svg, notifications_bid_accepted_svg, notifications_unknow_event_svg, notifications_new_bid_on_offer_svg, notifications_close_svg, swap_in_progress_mobile_svg, wallet_svg, page_back_svg, order_book_svg, new_offer_svg, settings_svg, asettings_svg, cog_svg, rpc_svg, debug_svg, explorer_svg, tor_svg, smsg_svg, outputs_svg, automation_svg, shutdown_svg, notifications_svg, debug_nerd_svg, wallet_locked_svg, mobile_menu_svg, wallet_unlocked_svg, tor_purple_svg, sun_svg, moon_svg, swap_in_progress_svg, swap_in_progress_green_svg, available_bids_svg, your_offers_svg, bids_received_svg, bids_sent_svg, header_arrow_down_svg, love_svg %}
|
||||
{% from 'style.html' import notifications_network_offer_svg, notifications_bid_accepted_svg, notifications_unknow_event_svg, notifications_new_bid_on_offer_svg, notifications_close_svg, swap_in_progress_mobile_svg, wallet_svg, page_back_svg, order_book_svg, new_offer_svg, settings_svg, asettings_svg, cog_svg, rpc_svg, debug_svg, explorer_svg, tor_svg, smsg_svg, outputs_svg, automation_svg, shutdown_svg, notifications_svg, debug_nerd_svg, wallet_locked_svg, mobile_menu_svg, wallet_unlocked_svg, tor_purple_svg, sun_svg, moon_svg, swap_in_progress_svg, swap_in_progress_green_svg, available_bids_svg, your_offers_svg, bids_received_svg, bids_sent_svg, header_arrow_down_svg, love_svg %}
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@ -12,7 +12,6 @@
|
||||
<link type="text/css" media="all" href="/static/css/style.css" rel="stylesheet">
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script src="/static/js/libs/flowbite.js"></script>
|
||||
|
||||
<script>
|
||||
const isDarkMode =
|
||||
localStorage.getItem('color-theme') === 'dark' ||
|
||||
@ -20,7 +19,7 @@
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
|
||||
if (!localStorage.getItem('color-theme')) {
|
||||
localStorage.setItem('color-theme', 'dark');
|
||||
localStorage.setItem('color-theme', isDarkMode ? 'dark' : 'light');
|
||||
}
|
||||
|
||||
document.documentElement.classList.toggle('dark', isDarkMode);
|
||||
@ -129,10 +128,6 @@
|
||||
<li>
|
||||
<a href="/settings" class="flex items-center block py-4 px-4 hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white"> <span class="sr-only">Settings</span>
|
||||
{{ cog_svg | safe }} Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/changepassword" class="flex items-center block py-4 px-4 hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white"> <span class="sr-only">Change/Set Password</span>
|
||||
{{ change_password_svg | safe }} Change/Set Password</a>
|
||||
</li>
|
||||
{% if debug_mode == true %}
|
||||
<li>
|
||||
@ -265,10 +260,10 @@
|
||||
<!-- dev mode icons on/off -->
|
||||
<ul class="xl:flex">
|
||||
<li>
|
||||
<div data-tooltip-target="tooltip-DEV" class="ml-5 flex items-center text-gray-50 hover:text-gray-100 text-sm">
|
||||
<div data-tooltip-target="tooltip-debug" class="ml-5 flex items-center text-gray-50 hover:text-gray-100 text-sm">
|
||||
{{ debug_nerd_svg | safe }}
|
||||
</div>
|
||||
<div id="tooltip-DEV" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
|
||||
<span data-tooltip-target="tooltip-DEV" ></span> </div>
|
||||
<div id="tooltip-debug" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
|
||||
<p><b>Debug mode:</b> Active</p>
|
||||
{% if debug_ui_mode == true %}
|
||||
<p><b>Debug UI mode:</b> Active</p>
|
||||
@ -283,7 +278,8 @@
|
||||
<ul class="xl:flex"><li>
|
||||
{% if locked == true %}
|
||||
<div data-tooltip-target="tooltip-locked-wallets" class="ml-5 flex items-center text-gray-50 hover:text-gray-100 text-sm">
|
||||
{{ wallet_locked_svg | safe }}</div>
|
||||
{{ wallet_locked_svg | safe }}
|
||||
<span data-tooltip-target="tooltip-locked-wallets" ></span> </div>
|
||||
<div id="tooltip-locked-wallets" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
|
||||
<p><b>Wallets:</b> Locked </p>
|
||||
</div>
|
||||
@ -291,7 +287,8 @@
|
||||
<a href='/lock'>
|
||||
<div data-tooltip-target="tooltip-unlocked-wallets" class="ml-5 flex items-center text-gray-50 hover:text-gray-100 text-sm">
|
||||
{{ wallet_unlocked_svg | safe }}
|
||||
</div>
|
||||
<span data-tooltip-target="tooltip-unlocked-wallets" ></span> </div>
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
<div id="tooltip-unlocked-wallets" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
|
||||
<p><b>Wallets:</b> Unlocked </p>
|
||||
</div>
|
||||
@ -306,7 +303,7 @@
|
||||
<a href="/tor">
|
||||
<div data-tooltip-target="tooltip-tor" class="flex items-center text-gray-50 hover:text-gray-100 text-sm">
|
||||
{{ tor_purple_svg | safe }}
|
||||
</a></div>
|
||||
</a> <span data-tooltip-target="tooltip-tor"></span></div>
|
||||
<div id="tooltip-tor" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip"><b>Tor mode:</b> Active
|
||||
{% if tor_established == true %}
|
||||
<br><b>Tor:</b> Connected
|
||||
@ -319,6 +316,7 @@
|
||||
<button data-tooltip-target="tooltip-darkmode" id="theme-toggle" type="button" class="text-gray-500 dark:text-gray-400 focus:outline-none rounded-lg text-sm ml-5">
|
||||
{{ sun_svg | safe }}
|
||||
{{ moon_svg | safe }}
|
||||
<span data-tooltip-target="tooltip-darkmode"></span>
|
||||
<div id="tooltip-darkmode" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">Dark mode</div>
|
||||
</button>
|
||||
</div>
|
||||
@ -455,11 +453,6 @@
|
||||
{{ settings_svg | safe }}
|
||||
</span><span>Settings</span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="flex items-center pl-3 py-3 pr-2 text-gray-50 hover:bg-gray-900 rounded" href="/changepassword"> <span class="inline-block mr-3">
|
||||
{{ change_password_svg | safe }}
|
||||
</span><span>Change/Set Password</span></a>
|
||||
</li>
|
||||
{% if debug_mode == true %}
|
||||
<li>
|
||||
<a class="flex items-center pl-3 py-3 pr-4 text-gray-50 hover:bg-gray-900 rounded" href="/rpc"> <span class="inline-block mr-3">
|
||||
|
@ -10,11 +10,14 @@
|
||||
<div class="w-auto p-1">
|
||||
{{ circular_info_messages_svg | safe }}
|
||||
</div>
|
||||
|
||||
|
||||
<ul class="ml-4 mt-1">
|
||||
<li class="font-semibold text-sm text-green-500 error_msg"><span class="bold">INFO:</span></li>
|
||||
<li class="font-medium text-sm text-green-500 infomsg">{{ m[1] }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="w-auto p-2">
|
||||
<button type="button" class="ms-auto bg-green-50 text-green-500 rounded-lg focus:ring-0 focus:ring-green-400 p-1.5 hover:bg-green-200 inline-flex items-center justify-center h-8 w-8 focus:outline-none dark:bg-gray-800 dark:text-green-400 dark:hover:bg-gray-700" data-dismiss-target="#messages_{{ m[0] }}" aria-label="Close"><span class="sr-only">Close</span>
|
||||
|
@ -6,8 +6,7 @@
|
||||
<div class="md:w-1/2 pl-4">
|
||||
<span class="inline-block py-1 px-3 mb-4 text-xs leading-5 bg-blue-500 text-white font-medium rounded-full shadow-sm">(BSX) BasicSwap v{{ version }} - (GUI) v.3.0.0</span>
|
||||
<h3 class="mb-6 text-4xl md:text-5xl leading-tight text-coolGray-900 font-bold tracking-tighter dark:text-white">Welcome to BasicSwap DEX</h3>
|
||||
<p class="mb-12 text-lg md:text-xl text-coolGray-500 dark:text-gray-300 font-medium">The World's Most Secure and Decentralized DEX, Safely swap cryptocurrencies without central points of failure.
|
||||
It’s free, completely trustless, and highly secure.</p>
|
||||
<p class="mb-12 text-lg md:text-xl text-coolGray-500 dark:text-gray-300 font-medium">Swap cryptocurrencies in <span class="underline">total privacy</span> with no middlemen, fees, <br> or restrictions. </p>
|
||||
<div class="flex flex-wrap mb-10 text-center md:text-left">
|
||||
<div class="w-full md:w-auto mb-6 md:mb-0 md:pr-6">
|
||||
<a href="/wallets">
|
||||
|
@ -1,5 +1,5 @@
|
||||
{% include 'header.html' %}
|
||||
{% from 'style.html' import breadcrumb_line_svg, input_arrow_down_svg, circular_arrows_svg, confirm_green_svg, green_cross_close_svg, circular_info_messages_svg %}
|
||||
{% from 'style.html' import breadcrumb_line_svg, input_arrow_down_svg, circular_arrows_svg, confirm_green_svg, green_cross_close_svg %}
|
||||
|
||||
<div class="container mx-auto">
|
||||
<section class="p-5 mt-5">
|
||||
@ -57,21 +57,19 @@
|
||||
{% if sent_bid_id %}
|
||||
<section class="py-4" id="messages_send_bid_id" role="alert">
|
||||
<div class="container px-4 mx-auto">
|
||||
<div class="p-6 text-green-800 rounded-lg bg-green-50 border border-green-500 dark:bg-gray-500 dark:text-green-400 rounded-md">
|
||||
<div class="p-6 bg-green-100 border border-green-200 rounded-md">
|
||||
<div class="flex flex-wrap justify-between items-center -m-2">
|
||||
<div class="flex-1 p-2">
|
||||
<div class="flex flex-wrap -m-1">
|
||||
<div class="w-auto p-1">
|
||||
{{ circular_info_messages_svg | safe }}
|
||||
{{ confirm_green_svg | safe }}
|
||||
</div>
|
||||
<ul class="ml-4 mt-1">
|
||||
<li class="font-semibold text-sm text-green-500 error_msg"><span class="bold">INFO:</span></li>
|
||||
<li class="font-medium text-sm text-green-500 infomsg">Sent Bid {{ sent_bid_id }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex-1 p-1">
|
||||
<h3 class="infomsg font-medium text-sm text-green-900">Sent Bid {{ sent_bid_id }}</h3></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-auto p-2">
|
||||
<button type="button" class="ms-auto bg-green-50 text-green-500 rounded-lg focus:ring-0 focus:ring-green-400 p-1.5 hover:bg-green-200 inline-flex items-center justify-center h-8 w-8 focus:outline-none dark:bg-gray-800 dark:text-green-400 dark:hover:bg-gray-700" data-dismiss-target="#messages_send_bid_id" aria-label="Close"><span class="sr-only">Close</span>
|
||||
<button type="button" class="ml-auto bg-green-100 text-green-500 rounded-lg focus:ring-0 focus:ring-green-400 p-1.5 hover:bg-green-200 inline-flex h-8 w-8 focus:outline-none" data-dismiss-target="#messages_send_bid_id" aria-label="Close"><span class="sr-only">Close</span>
|
||||
{{ green_cross_close_svg | safe }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -149,7 +149,7 @@
|
||||
<div class="custom-select">
|
||||
<div class="relative">
|
||||
{{ input_down_arrow_offer_svg | safe }}
|
||||
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_from" name="coin_from" disabled>
|
||||
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_from" name="coin_from" onchange="set_rate('coin_from');" disabled>
|
||||
<option value="-1">Select coin you send</option>
|
||||
{% for c in coins_from %}
|
||||
<option{% if data.coin_from==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png">{{ c[1] }}
|
||||
@ -164,7 +164,7 @@
|
||||
<div class="w-full md:w-1/2 p-3">
|
||||
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Send</p>
|
||||
<div class="relative">
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" readonly>
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" onchange="set_rate('amt_from');" readonly>
|
||||
</div>
|
||||
</div>
|
||||
{% if data.swap_style == 'xmr' %}
|
||||
@ -220,7 +220,7 @@
|
||||
<div class="custom-select">
|
||||
<div class="relative">
|
||||
{{ input_down_arrow_offer_svg | safe }}
|
||||
<select class="cursor-not-allowed disabled-select select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_to" name="coin_to" disabled>
|
||||
<select class="cursor-not-allowed disabled-select select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_to" name="coin_to" onchange="set_rate('coin_to');" disabled>
|
||||
<option value="-1"></option>
|
||||
{% for c in coins %}
|
||||
<option{% if data.coin_to==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png">{{ c[1] }}</option>
|
||||
@ -235,7 +235,7 @@
|
||||
<div class="w-full md:w-1/2 p-3">
|
||||
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Get</p>
|
||||
<div class="relative">
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" readonly>
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" onchange="set_rate('amt_to');" readonly>
|
||||
</div>
|
||||
</div>
|
||||
{% if data.swap_style == 'xmr' and coin_to != '6' %}
|
||||
@ -301,7 +301,7 @@
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
|
||||
{{ select_rate_svg | safe }}
|
||||
</div>
|
||||
<input class="cursor-not-allowed disabled-input pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" readonly>
|
||||
<input class="cursor-not-allowed disabled-input pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" onchange="set_rate('rate');" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -233,7 +233,7 @@
|
||||
<input class="pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" onchange="set_rate('rate');">
|
||||
</div>
|
||||
<div class="text-sm mt-2">
|
||||
<a href="" id="get_rate_inferred_button" class="mt-2 dark:text-white bold text-coolGray-800">Get Rate Inferred:</a><span class="dark:text-white" id="rate_inferred_display"></span>
|
||||
<a href="" id="get_rate_inferred_button" class="mt-2 dark:text-white bold text-coolGray-800">Get Rate Inferred:<span id="rate_inferred_display"></span></a>
|
||||
</div>
|
||||
<div class="flex form-check form-check-inline mt-5">
|
||||
<div class="flex items-center h-5"> <input class="form-check-input hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="rate_lock" name="rate_lock" value="rl" checked=checked> </div>
|
||||
@ -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 */,'9' /* WOW */, '8' /* PART_ANON */, '7' /* PART_BLIND */, '13' /* FIRO */];
|
||||
const adaptor_sig_only_coins = ['6' /* XMR */, '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)) {
|
||||
|
@ -157,7 +157,7 @@
|
||||
<div class="custom-select">
|
||||
<div class="relative">
|
||||
{{ input_down_arrow_offer_svg | safe }}
|
||||
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_from" name="coin_from" disabled>
|
||||
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_from" name="coin_from" onchange="set_rate('coin_from');" disabled>
|
||||
<option value="-1">Select coin you send</option>
|
||||
{% for c in coins_from %}
|
||||
<option{% if data.coin_from==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png">{{ c[1] }}</option>
|
||||
@ -171,7 +171,7 @@
|
||||
<div class="w-full md:w-1/2 p-3">
|
||||
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Send</p>
|
||||
<div class="relative">
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input appearance-none pr-10 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" readonly>
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input appearance-none pr-10 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_from" name="amt_from" value="{{ data.amt_from }}" onchange="set_rate('amt_from');" readonly>
|
||||
</div>
|
||||
</div>
|
||||
{% if data.swap_style == 'xmr' %}
|
||||
@ -217,7 +217,7 @@
|
||||
<div class="custom-select">
|
||||
<div class="relative">
|
||||
{{ input_down_arrow_offer_svg | safe }}
|
||||
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_to" name="coin_to" disabled>
|
||||
<select class="select cursor-not-allowed disabled-select hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" id="coin_to" name="coin_to" onchange="set_rate('coin_to');" disabled>
|
||||
<option value="-1"></option>
|
||||
{% for c in coins %}
|
||||
<option{% if data.coin_to==c[0] %} selected{% endif %} value="{{ c[0] }}" data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}-20.png">{{ c[1] }}</option>
|
||||
@ -230,7 +230,7 @@
|
||||
<div class="w-full md:w-1/2 p-3">
|
||||
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Amount You Get</p>
|
||||
<div class="relative">
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" readonly>
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> </div> <input class="cursor-not-allowed disabled-input hover:border-blue-500 pr-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-5 focus:ring-0" type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" onchange="set_rate('amt_to');" readonly>
|
||||
</div>
|
||||
</div>
|
||||
{% if data.swap_style == 'xmr' and coin_to != '6' %}
|
||||
@ -286,7 +286,7 @@
|
||||
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
|
||||
{{ select_rate_svg | safe }}
|
||||
</div>
|
||||
<input class="cursor-not-allowed disabled-input pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" readonly>
|
||||
<input class="cursor-not-allowed disabled-input pl-10 hover:border-blue-500 pl-10 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="rate" name="rate" value="{{ data.rate }}" onchange="set_rate('rate');" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -65,7 +65,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="py-4 flex flex-wrap justify-center overflow-hidden container-to-blur">
|
||||
<section class="py-4 flex overflow-hidden container-to-blur">
|
||||
|
||||
{% if 'BTC' in enabled_chart_coins %}
|
||||
<div class="container px-4 mx-auto">
|
||||
@ -74,13 +74,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="btc-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white active-container" id="btc-active">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Bitcoin.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Bitcoin">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
<img src="/static/images/coins/Bitcoin.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Bitcoin">
|
||||
<p class="ml-2 text-black text-sm dark:text-white">
|
||||
Bitcoin (BTC)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-left monospace text-gray-700 dark:text-gray-100" id="btc-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="btc-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="btc-price-usd-value"></span>
|
||||
</p>
|
||||
@ -102,13 +102,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="xmr-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white price-container">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Monero.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Monero">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
<img src="/static/images/coins/Monero.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Monero">
|
||||
<p class="ml-2 text-black text-sm dark:text-white">
|
||||
Monero (XMR)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-left monospace text-gray-700 dark:text-gray-100" id="xmr-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="xmr-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="xmr-price-usd-value"></span>
|
||||
</p>
|
||||
@ -135,13 +135,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="part-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Particl.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Particl">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
<img src="/static/images/coins/Particl.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Particl">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
Particl (PART)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-leftt monospace text-gray-700 dark:text-gray-100" id="part-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="part-price-usd">
|
||||
<span class="text-sm">
|
||||
$ <span id="part-price-usd-value"></span>
|
||||
</span>
|
||||
@ -169,13 +169,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="ltc-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Litecoin.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Litecoin">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
<img src="/static/images/coins/Litecoin.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Litecoin">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
Litecoin (LTC)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-leftt monospace text-gray-700 dark:text-gray-100" id="ltc-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="ltc-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="ltc-price-usd-value"></span>
|
||||
</span>
|
||||
@ -203,13 +203,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="firo-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Firo.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Firo">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
Firo
|
||||
<img src="/static/images/coins/Firo.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Firo">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
Firo (FIRO)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-leftt monospace text-gray-700 dark:text-gray-100" id="firo-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="firo-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="firo-price-usd-value"></span>
|
||||
</span>
|
||||
@ -237,13 +237,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="pivx-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/PIVX.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="PIVX">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
PIVX
|
||||
<img src="/static/images/coins/PIVX.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="PIVX">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
PIVX (PIVX)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-left monospace text-gray-700 dark:text-gray-100" id="pivx-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="pivx-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="pivx-price-usd-value"></span>
|
||||
</span>
|
||||
@ -271,13 +271,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="dash-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Dash.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Dash">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
Dash
|
||||
<img src="/static/images/coins/Dash.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Dash">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
Dash (DASH)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-leftt monospace text-gray-700 dark:text-gray-100" id="dash-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="dash-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="dash-price-usd-value"></span>
|
||||
</span>
|
||||
@ -305,13 +305,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="eth-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Ethereum.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Ethereum">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
<img src="/static/images/coins/Ethereum.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Ethereum">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
Ethereum (ETH)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-lettt monospace text-gray-700 dark:text-gray-100" id="eth-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="eth-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="eth-price-usd-value"></span>
|
||||
</span>
|
||||
@ -339,13 +339,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="doge-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Doge.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Dogecoin">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
<img src="/static/images/coins/Doge.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Dogecoin">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
Dogecoin (DOGE)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-left monospace text-gray-700 dark:text-gray-100" id="doge-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="doge-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="doge-price-usd-value"></span>
|
||||
</span>
|
||||
@ -373,13 +373,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="dcr-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Decred.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Decred">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
<img src="/static/images/coins/Decred.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Decred">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
Decred (DCR)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-left monospace text-gray-700 dark:text-gray-100" id="dcr-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="dcr-price-usd">
|
||||
<span class="text-sm">
|
||||
<span id="dcr-price-usd-value" style="min-width: 80px;"></span>
|
||||
</span>
|
||||
@ -407,13 +407,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="zano-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Zano.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="Zano">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
Zano
|
||||
<img src="/static/images/coins/Zano.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="Zano">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
Zano (ZANO)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-leftt monospace text-gray-700 dark:text-gray-100" id="zano-price-usd">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="zano-price-usd-value">
|
||||
<span class="text-sm">
|
||||
<span id="zano-price-usd-value" style="min-width: 80px;"></span>
|
||||
</span>
|
||||
@ -428,7 +428,7 @@
|
||||
<div id="zano-volume-24h">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center text-xs text-gray-600 dark:text-gray-300 mt-2">
|
||||
<div class="flex items-center text-xs text-gray-600 dark:text-gray-300 mt-2 hidden">
|
||||
<span class="bold mr-2">BTC:</span>
|
||||
<span id="zano-price-btc">
|
||||
</span>
|
||||
@ -441,13 +441,13 @@
|
||||
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="wow-container">
|
||||
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white">
|
||||
<div class="flex items-center">
|
||||
<img src="/static/images/coins/Wownero.png" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="WOWNERO">
|
||||
<p class="ml-1 text-black text-sm dark:text-white">
|
||||
<img src="/static/images/coins/Wownero.png" class="rounded-xl" style="width: 35px; height: 35px; object-fit: contain;" alt="WOWNERO">
|
||||
<p class="ml-2 text-black text-md dark:text-white">
|
||||
Wownero (WOW)
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start">
|
||||
<p class="my-2 text-xl font-bold text-left monospace text-gray-700 dark:text-gray-100" id="wow-price-usd-value">
|
||||
<p class="my-2 text-xl font-bold text-left text-gray-700 dark:text-gray-100" id="wow-price-usd-value">
|
||||
<span class="text-sm">
|
||||
<span id="wow-price-usd-value" style="min-width: 80px;"></span>
|
||||
</span>
|
||||
@ -486,20 +486,22 @@ window.addEventListener('load', function() {
|
||||
if (container != null) {
|
||||
container.addEventListener('click', () => {
|
||||
setActiveContainer(container_id);
|
||||
updateChart(coin, coinGeckoApiKey);
|
||||
updateChart(coin);
|
||||
});
|
||||
}
|
||||
if (coin === 'WOW') {
|
||||
fetchWowneroData(coinGeckoApiKey);
|
||||
fetchWowneroData();
|
||||
} else if (coin === 'ZANO') {
|
||||
fetchZanoData(coinGeckoApiKey);
|
||||
} else {
|
||||
fetchCryptoCompareData(coin, api_key);
|
||||
}
|
||||
});
|
||||
updateChart('BTC', coinGeckoApiKey);
|
||||
updateChart('BTC');
|
||||
});
|
||||
|
||||
function fetchWowneroData(coinGeckoApiKey) {
|
||||
fetch(`https://api.coingecko.com/api/v3/coins/wownero/market_chart?vs_currency=usd&days=2&api_key={{coingecko_api_key}}`)
|
||||
function fetchZanoData(coinGeckoApiKey) {
|
||||
fetch(`https://api.coingecko.com/api/v3/coins/zano/market_chart?vs_currency=usd&days=30&interval=daily&api_key=${coinGeckoApiKey}`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error fetching data. Status: ${response.status}`);
|
||||
@ -507,41 +509,57 @@ function fetchWowneroData(coinGeckoApiKey) {
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
displayWowneroData(data);
|
||||
// TODO bad responses block all others from displaying properly
|
||||
// })
|
||||
// .catch(error => {
|
||||
// console.error('Fetching Wownero data:', error);
|
||||
// displayErrorMessage('Unable to fetch data for Wownero. Please try again later.');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function fetchCryptoCompareData(coin, api_key) {
|
||||
fetch(`https://min-api.cryptocompare.com/data/pricemultifull?fsyms=${coin}&tsyms=USD,BTC&api_key={{chart_api_key}}`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error fetching data. Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
displayCoinData(coin, data);
|
||||
displayZanoData(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Fetching ${coin} data:`, error);
|
||||
displayErrorMessage(`Unable to fetch data. Please verify your API key or try again later.`);
|
||||
console.error('Fetching Zano data:', error);
|
||||
displayErrorMessage('Unable to fetch data for Zano. Please try again later.');
|
||||
});
|
||||
}
|
||||
|
||||
function fetchCryptoCompareData(coin, api_key) {
|
||||
fetch(`https://min-api.cryptocompare.com/data/pricemultifull?fsyms=${coin}&tsyms=USD,BTC&api_key=${api_key}`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error fetching data. Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
displayCoinData(coin, data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Fetching ${coin} data:`, error);
|
||||
displayErrorMessage(`Unable to fetch data. Please verify your API key or try again later.`);
|
||||
});
|
||||
}
|
||||
|
||||
function fetchWowneroData(coinGeckoApiKey) {
|
||||
fetch(`https://api.coingecko.com/api/v3/coins/wownero/market_chart?vs_currency=usd&days=1&api_key=${coinGeckoApiKey}`)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error fetching data. Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
displayWowneroData(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fetching Wownero data:', error);
|
||||
displayErrorMessage('Unable to fetch data for Wownero. Please try again later.');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function displayWowneroData(data) {
|
||||
const prices = data.prices;
|
||||
|
||||
const latestPriceUSD = prices[prices.length - 1][1];
|
||||
const priceChange24h = prices[prices.length - 1][1] / prices[prices.length - 24][1] - 1;
|
||||
const priceChange24h = data.market_caps[data.market_caps.length - 1][1] / data.market_caps[data.market_caps.length - 2][1] - 1;
|
||||
const volume24h = data.total_volumes[data.total_volumes.length - 1][1];
|
||||
|
||||
document.getElementById('wow-price-usd-value').textContent = '$ ' + latestPriceUSD.toFixed(4);
|
||||
document.getElementById('wow-price-usd-value').textContent = latestPriceUSD.toFixed(2) + ' $';
|
||||
document.getElementById('wow-price-change-container').textContent = (priceChange24h * 100).toFixed(2) + '%';
|
||||
document.getElementById('wow-volume-24h').textContent = volume24h.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' USD';
|
||||
|
||||
@ -557,35 +575,49 @@ function fetchCryptoCompareData(coin, api_key) {
|
||||
const priceBTC = latestPriceUSD / latestPriceBTC;
|
||||
|
||||
document.getElementById('wow-price-btc').textContent = priceBTC.toFixed(8) + ' BTC';
|
||||
}
|
||||
|
||||
function displayZanoData(data) {
|
||||
const prices = data.prices;
|
||||
|
||||
const latestPriceUSD = prices[prices.length - 1][1];
|
||||
const priceChange24h = data.market_caps[data.market_caps.length - 1][1] / data.market_caps[data.market_caps.length - 2][1] - 1;
|
||||
const volume24h = data.total_volumes[data.total_volumes.length - 1][1];
|
||||
|
||||
document.getElementById('zano-price-usd-value').textContent = latestPriceUSD.toFixed(2) + ' $';
|
||||
document.getElementById('zano-price-change-container').textContent = (priceChange24h * 100).toFixed(2) + '%';
|
||||
document.getElementById('zano-volume-24h').textContent = volume24h.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' USD';
|
||||
|
||||
const priceChangeContainer = document.getElementById('zano-price-change-container');
|
||||
if (priceChange24h >= 0) {
|
||||
priceChangeContainer.innerHTML = positivePriceChangeHTML(priceChange24h * 100);
|
||||
} else {
|
||||
priceChangeContainer.innerHTML = negativePriceChangeHTML(priceChange24h * 100);
|
||||
}
|
||||
|
||||
const latestPriceBTC = parseFloat(data.prices[data.prices.length - 1][1]);
|
||||
// Todo fix value USD -> BTC
|
||||
const priceBTC = latestPriceUSD / latestPriceBTC;
|
||||
|
||||
document.getElementById('zano-price-btc').textContent = priceBTC.toFixed(8) + ' BTC';
|
||||
}
|
||||
|
||||
function displayCoinData(coin, data) {
|
||||
const priceUSD = data.RAW[coin].USD.PRICE;
|
||||
const priceBTC = data.RAW[coin].BTC.PRICE;
|
||||
const priceChange1d = data.RAW[coin].USD.CHANGEPCT24HOUR;
|
||||
const priceChange1h = data.RAW[coin].USD.CHANGEPCTHOUR;
|
||||
const volume24h = data.RAW[coin].USD.TOTALVOLUME24HTO;
|
||||
const c = coin
|
||||
|
||||
if (c === 'BTC') {
|
||||
document.querySelector(`#${c.toLowerCase()}-price-usd`).textContent = '$ ' + priceUSD.toFixed(1);
|
||||
} else if (c === 'ZANO' || c === 'FIRO') {
|
||||
document.querySelector(`#${c.toLowerCase()}-price-usd`).textContent = '$ ' + priceUSD.toFixed(3);
|
||||
} else if (c === 'DOGE' || c === 'PIVX' || c === 'PART') {
|
||||
document.querySelector(`#${c.toLowerCase()}-price-usd`).textContent = '$ ' + priceUSD.toFixed(4);
|
||||
} else {
|
||||
document.querySelector(`#${c.toLowerCase()}-price-usd`).textContent = '$ ' + priceUSD.toFixed(2);
|
||||
document.querySelector(`#${coin.toLowerCase()}-price-usd`).textContent = priceUSD.toFixed(2) + ' $';
|
||||
if (coin !== 'BTC') {
|
||||
document.querySelector(`#${coin.toLowerCase()}-price-btc`).textContent = priceBTC.toFixed(8) + ' BTC';
|
||||
}
|
||||
|
||||
if (c !== 'BTC') {
|
||||
document.querySelector(`#${c.toLowerCase()}-price-btc`).textContent = priceBTC.toFixed(8) + ' BTC';
|
||||
}
|
||||
document.querySelector(`#${c.toLowerCase()}-price-change-container`).textContent = priceChange1d.toFixed(2) + '%';
|
||||
document.querySelector(`#${c.toLowerCase()}-volume-24h`).textContent = volume24h.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' USD';
|
||||
const priceChangeContainer = document.querySelector(`#${c.toLowerCase()}-price-change-container`);
|
||||
if (priceChange1d >= 0) {
|
||||
priceChangeContainer.innerHTML = positivePriceChangeHTML(priceChange1d);
|
||||
document.querySelector(`#${coin.toLowerCase()}-price-change-container`).textContent = priceChange1h.toFixed(2) + '%';
|
||||
document.querySelector(`#${coin.toLowerCase()}-volume-24h`).textContent = volume24h.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' USD';
|
||||
const priceChangeContainer = document.querySelector(`#${coin.toLowerCase()}-price-change-container`);
|
||||
if (priceChange1h >= 0) {
|
||||
priceChangeContainer.innerHTML = positivePriceChangeHTML(priceChange1h);
|
||||
} else {
|
||||
priceChangeContainer.innerHTML = negativePriceChangeHTML(priceChange1d);
|
||||
priceChangeContainer.innerHTML = negativePriceChangeHTML(priceChange1h);
|
||||
}
|
||||
}
|
||||
|
||||
@ -635,8 +667,6 @@ function negativePriceChangeHTML(value) {
|
||||
}
|
||||
|
||||
function setActiveContainer(containerId) {
|
||||
const api_key = '{{chart_api_key}}';
|
||||
const coinGeckoApiKey = '{{coingecko_api_key}}';
|
||||
const containerIds = ['btc-container', 'xmr-container', 'part-container', 'pivx-container', 'firo-container', 'dash-container', 'ltc-container', 'doge-container', 'eth-container', 'dcr-container', 'zano-container', 'wow-container'];
|
||||
const activeClass = 'active-container';
|
||||
containerIds.forEach(id => {
|
||||
@ -661,10 +691,13 @@ const coinOptions = {
|
||||
}
|
||||
};
|
||||
|
||||
function updateChart(coinSymbol, coinGeckoApiKey) {
|
||||
function updateChart(coinSymbol) {
|
||||
coin = coinSymbol;
|
||||
const api_key = '{{chart_api_key}}';
|
||||
const coinGeckoApiKey = '{{coingecko_api_key}}';
|
||||
|
||||
if (coinSymbol === 'WOW') {
|
||||
fetch(`https://api.coingecko.com/api/v3/coins/wownero/market_chart?vs_currency=usd&days=30&interval=daily&api_key={{coingecko_api_key}}`)
|
||||
fetch(`https://api.coingecko.com/api/v3/coins/wownero/market_chart?vs_currency=usd&days=30&interval=daily&api_key=${coinGeckoApiKey}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const chartData = {
|
||||
@ -683,8 +716,28 @@ function updateChart(coinSymbol, coinGeckoApiKey) {
|
||||
chart.update();
|
||||
})
|
||||
.catch(error => console.error('Error updating chart for Wownero:', error));
|
||||
} else if (coinSymbol === 'ZANO') {
|
||||
fetch(`https://api.coingecko.com/api/v3/coins/zano/market_chart?vs_currency=usd&days=30&interval=daily&api_key=${coinGeckoApiKey}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const chartData = {
|
||||
labels: data.prices.map(entry => formatDate(new Date(entry[0]))),
|
||||
datasets: [{
|
||||
label: 'Zano Price (USD)',
|
||||
data: data.prices.map(entry => entry[1]),
|
||||
borderColor: 'rgba(77, 132, 240, 1)',
|
||||
backgroundColor: 'rgba(77, 132, 240, 0.1)',
|
||||
fill: true
|
||||
}]
|
||||
};
|
||||
|
||||
chart.data = chartData;
|
||||
chart.options.scales.y.title.text = 'Price (USD) - Zano 30 DAYS';
|
||||
chart.update();
|
||||
})
|
||||
.catch(error => console.error('Error updating chart for Zano:', error));
|
||||
} else {
|
||||
fetch(`https://min-api.cryptocompare.com/data/v2/histoday?fsym=${coinSymbol}&tsym=USD&limit=30&api_key={{chart_api_key}}`)
|
||||
fetch(`https://min-api.cryptocompare.com/data/v2/histoday?fsym=${coinSymbol}&tsym=USD&limit=30&api_key=${api_key}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Check if data is undefined
|
||||
@ -836,9 +889,9 @@ const chart = new Chart(ctx, {
|
||||
<div class="flex items-center justify-center pb-4 dark:text-white">
|
||||
<div class="rounded-b-md">
|
||||
<div class="w-full md:w-0/12">
|
||||
<div class="container flex flex-wrap justify-center">
|
||||
<div class="flex flex-wrap justify-center -m-1.5">
|
||||
|
||||
<div class="md:w-auto p-1.5 hover-container">
|
||||
<div class="w-full md:w-auto p-1.5 hover-container">
|
||||
<div class="flex">
|
||||
<button id="coin_to_button" class="bg-gray-50 text-gray-900 appearance-none w-10 dark:bg-gray-500 dark:text-white border-l border-t border-b border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-l-lg flex items-center" disabled>
|
||||
</button>
|
||||
@ -851,6 +904,8 @@ const chart = new Chart(ctx, {
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
@ -858,6 +913,8 @@ const chart = new Chart(ctx, {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-auto p-1.5 hover-container">
|
||||
<div class="flex">
|
||||
<button id="coin_from_button" class="bg-gray-50 text-gray-900 appearance-none w-10 dark:bg-gray-500 dark:text-white border-l border-t border-b border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-l-lg flex items-center" disabled>
|
||||
</button>
|
||||
<div class="relative">
|
||||
@ -872,7 +929,7 @@ const chart = new Chart(ctx, {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-auto mt-3">
|
||||
<div class="flex items-center">
|
||||
<div class="w-full md:w-auto p-1.5">
|
||||
<p class="text-sm font-heading bold">Sort By:</p>
|
||||
</div>
|
||||
@ -1261,11 +1318,10 @@ const coinNameToSymbol = {
|
||||
'Dash': 'DASH',
|
||||
'PIVX': 'PIVX',
|
||||
'Decred': 'DCR',
|
||||
'Zano': 'ZANO'
|
||||
'Zano': 'Zano'
|
||||
};
|
||||
|
||||
const exchangeRateCache = {};
|
||||
const coinGeckoApiKey = '{{coingecko_api_key}}';
|
||||
|
||||
function updateUsdValue(cryptoCell, coinFullName, isRate = false) {
|
||||
const coinSymbol = coinNameToSymbol[coinFullName];
|
||||
@ -1276,52 +1332,68 @@ function updateUsdValue(cryptoCell, coinFullName, isRate = false) {
|
||||
|
||||
const cryptoValue = parseFloat(cryptoCell.textContent);
|
||||
const usdCell = cryptoCell.nextElementSibling;
|
||||
if (!usdCell) {
|
||||
console.error("USD cell does not exist.");
|
||||
return;
|
||||
if (usdCell) {
|
||||
// Check if the exchange rate is in the cache and is not expired
|
||||
if (exchangeRateCache[coinSymbol] && !isCacheExpired(coinSymbol)) {
|
||||
console.log(`Using cached exchange rate for ${coinSymbol}`);
|
||||
const exchangeRate = exchangeRateCache[coinSymbol].rate;
|
||||
const cryptoValue = parseFloat(cryptoCell.textContent);
|
||||
const usdValue = cryptoValue * exchangeRate;
|
||||
usdCell.textContent = `${usdValue.toFixed(2)} USD`;
|
||||
|
||||
updateProfitLoss(cryptoCell.closest('tr'));
|
||||
updateProfitValue(cryptoCell.closest('tr'));
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (exchangeRateCache[coinSymbol] && !isCacheExpired(coinSymbol)) {
|
||||
console.log(`Using cached exchange rate for ${coinSymbol}`);
|
||||
const exchangeRate = exchangeRateCache[coinSymbol].rate;
|
||||
const usdValue = cryptoValue * exchangeRate;
|
||||
usdCell.textContent = `${usdValue.toFixed(2)} USD`;
|
||||
if (isRate) {
|
||||
const rateCell = usdCell.nextElementSibling;
|
||||
if (rateCell) {
|
||||
const usdValue = rateCell.previousElementSibling.textContent * cryptoValue;
|
||||
usdCell.textContent = `${usdValue.toFixed(2)} USD`;
|
||||
} else {
|
||||
console.error("Rate cell does not exist.");
|
||||
}
|
||||
} else {
|
||||
const apiUrl = `https://min-api.cryptocompare.com/data/price?fsym=${coinSymbol}&tsyms=USD`;
|
||||
|
||||
updateProfitLoss(cryptoCell.closest('tr'));
|
||||
updateProfitValue(cryptoCell.closest('tr'));
|
||||
return;
|
||||
if (exchangeRateCache[coinSymbol] && !isCacheExpired(coinSymbol)) {
|
||||
// console.log(`Using cached exchange rate for ${coinSymbol}`);
|
||||
const exchangeRate = exchangeRateCache[coinSymbol].rate;
|
||||
const usdValue = cryptoValue * exchangeRate;
|
||||
usdCell.textContent = `${usdValue.toFixed(2)} USD`;
|
||||
|
||||
updateProfitLoss(cryptoCell.closest('tr'));
|
||||
updateProfitValue(cryptoCell.closest('tr'));
|
||||
} else {
|
||||
// console.log(`Fetching exchange rate from API for ${coinSymbol}`);
|
||||
fetch(apiUrl)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const exchangeRate = data.USD;
|
||||
if (!isNaN(exchangeRate)) {
|
||||
// console.log(`Received exchange rate from API for ${coinSymbol}`);
|
||||
const usdValue = cryptoValue * exchangeRate;
|
||||
usdCell.textContent = `${usdValue.toFixed(2)} USD`;
|
||||
|
||||
exchangeRateCache[coinSymbol] = {
|
||||
rate: exchangeRate,
|
||||
timestamp: Date.now(),
|
||||
ttl: 300000 // 5 minutes
|
||||
};
|
||||
|
||||
updateProfitLoss(cryptoCell.closest('tr'));
|
||||
updateProfitValue(cryptoCell.closest('tr'));
|
||||
} else {
|
||||
console.error('Invalid exchange rate. Response:', data);
|
||||
usdCell.textContent = 'Invalid exchange rate';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const apiUrl = coinSymbol === 'WOW'
|
||||
? `https://api.coingecko.com/api/v3/simple/price?ids=wownero&vs_currencies=usd&api_key=${{coingecko_api_key}}`
|
||||
: `https://min-api.cryptocompare.com/data/price?fsym=${coinSymbol}&tsyms=USD`;
|
||||
|
||||
fetch(apiUrl)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const exchangeRate = coinSymbol === 'WOW' ? data.wownero.usd : data.USD;
|
||||
if (!isNaN(exchangeRate)) {
|
||||
console.log(`Received exchange rate from API for ${coinSymbol}`);
|
||||
const usdValue = cryptoValue * exchangeRate;
|
||||
usdCell.textContent = `${usdValue.toFixed(2)} USD`;
|
||||
|
||||
exchangeRateCache[coinSymbol] = {
|
||||
rate: exchangeRate,
|
||||
timestamp: Date.now(),
|
||||
ttl: 300000 // 5 minutes
|
||||
};
|
||||
|
||||
updateProfitLoss(cryptoCell.closest('tr'));
|
||||
updateProfitValue(cryptoCell.closest('tr'));
|
||||
} else {
|
||||
console.error('Invalid exchange rate. Response:', data);
|
||||
usdCell.textContent = 'Invalid exchange rate';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`Fetching ${coinSymbol} data:`, error);
|
||||
usdCell.textContent = 'Error fetching data';
|
||||
});
|
||||
}
|
||||
|
||||
function isCacheExpired(coinSymbol) {
|
||||
@ -1336,17 +1408,15 @@ function updateProfitLoss(row) {
|
||||
|
||||
if (!isNaN(sellingUSD) && !isNaN(buyingUSD)) {
|
||||
const profitLossPercentage = ((sellingUSD - buyingUSD) / buyingUSD) * 100;
|
||||
profitLossCell.textContent = `${profitLossPercentage.toFixed(2)}%`;
|
||||
|
||||
if (profitLossPercentage > 0) {
|
||||
profitLossCell.textContent = `-${profitLossPercentage.toFixed(2)}%`; // Change from "+" to "-"
|
||||
profitLossCell.classList.add('text-green-500'); // Profit (negative)
|
||||
profitLossCell.classList.add('text-green-500'); // Profit (positive)
|
||||
profitLossCell.classList.remove('text-red-500');
|
||||
} else if (profitLossPercentage < 0) {
|
||||
profitLossCell.textContent = `+${Math.abs(profitLossPercentage).toFixed(2)}%`; // Change from "-" to "+"
|
||||
profitLossCell.classList.add('text-red-500'); // Loss (positive)
|
||||
profitLossCell.classList.add('text-red-500'); // Loss (negative)
|
||||
profitLossCell.classList.remove('text-green-500');
|
||||
} else {
|
||||
profitLossCell.textContent = `${profitLossPercentage.toFixed(2)}%`;
|
||||
profitLossCell.classList.add('text-yellow-500'); // No profit or loss (zero)
|
||||
profitLossCell.classList.remove('text-green-500', 'text-red-500');
|
||||
}
|
||||
@ -1356,7 +1426,6 @@ function updateProfitLoss(row) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function updateProfitValue(row) {
|
||||
const sellingUSD = parseFloat(row.querySelector('.usd-value').textContent);
|
||||
const profitValueCell = row.querySelector('.profit-value');
|
||||
@ -1391,43 +1460,51 @@ function sortTable(columnIndex) {
|
||||
const sortIcon = document.getElementById(`sort-icon-${columnIndex}`);
|
||||
let sortOrder = sortIcon.textContent === '↓' ? 1 : -1;
|
||||
|
||||
sortIcon.textContent = sortOrder === 1 ? '↑' : '↓';
|
||||
if (sortOrder === 1) {
|
||||
sortIcon.textContent = '↑';
|
||||
} else {
|
||||
sortIcon.textContent = '↓';
|
||||
}
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const aValue = a.cells[columnIndex].textContent.trim();
|
||||
const bValue = b.cells[columnIndex].textContent.trim();
|
||||
|
||||
return aValue < bValue ? -1 * sortOrder : aValue > bValue ? 1 * sortOrder : 0;
|
||||
if (aValue < bValue) return -1 * sortOrder;
|
||||
if (aValue > bValue) return 1 * sortOrder;
|
||||
return 0;
|
||||
});
|
||||
|
||||
rows.forEach(row => table.querySelector('tbody').appendChild(row));
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const coinToSelect = document.getElementById('coin_to');
|
||||
const coinFromSelect = document.getElementById('coin_from');
|
||||
const coinToButton = document.getElementById('coin_to_button');
|
||||
const coinFromButton = document.getElementById('coin_from_button');
|
||||
const coinToSelect = document.getElementById('coin_to');
|
||||
const coinFromSelect = document.getElementById('coin_from');
|
||||
const coinToButton = document.getElementById('coin_to_button');
|
||||
const coinFromButton = document.getElementById('coin_from_button');
|
||||
|
||||
function updateSelectedImage(selectElement, buttonElement) {
|
||||
const selectedOption = selectElement.options[selectElement.selectedIndex];
|
||||
const imageURL = selectedOption.getAttribute('data-image');
|
||||
buttonElement.style.backgroundImage = imageURL ? `url('${imageURL}')` : 'none';
|
||||
function updateSelectedImage(selectElement, buttonElement) {
|
||||
const selectedOption = selectElement.options[selectElement.selectedIndex];
|
||||
const imageURL = selectedOption.getAttribute('data-image');
|
||||
if (imageURL) {
|
||||
buttonElement.style.backgroundImage = `url('${imageURL}')`;
|
||||
} else {
|
||||
buttonElement.style.backgroundImage = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
coinToSelect.addEventListener('change', function() {
|
||||
updateSelectedImage(coinToSelect, coinToButton);
|
||||
});
|
||||
|
||||
coinFromSelect.addEventListener('change', function() {
|
||||
updateSelectedImage(coinFromSelect, coinFromButton);
|
||||
});
|
||||
|
||||
// Initialize selected images on page load
|
||||
coinToSelect.addEventListener('change', function() {
|
||||
updateSelectedImage(coinToSelect, coinToButton);
|
||||
updateSelectedImage(coinFromSelect, coinFromButton);
|
||||
});
|
||||
});
|
||||
|
||||
coinFromSelect.addEventListener('change', function() {
|
||||
updateSelectedImage(coinFromSelect, coinFromButton);
|
||||
});
|
||||
|
||||
// Initialize selected images on page load
|
||||
updateSelectedImage(coinToSelect, coinToButton);
|
||||
updateSelectedImage(coinFromSelect, coinFromButton);
|
||||
});
|
||||
</script>
|
||||
{% include 'footer.html' %}
|
||||
</body>
|
||||
|
@ -63,7 +63,7 @@
|
||||
<td class="py-3 px-6">
|
||||
<div class="relative">
|
||||
{{ input_arrow_down_svg| safe }}
|
||||
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="coin_type" id="coin_type" onchange="set_coin();">
|
||||
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="coin_type">
|
||||
<option value="-1" {% if coin_type==-1 %} selected{% endif %}>Select Coin</option>
|
||||
{% for c in coins %}
|
||||
<option value="{{ c[0] }}" {% if coin_type==c[0] %} selected{% endif %}>{{ c[1] }}</option>
|
||||
@ -72,21 +72,21 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-6">
|
||||
<input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="cmd" id="cmd" oninput="set_method();">
|
||||
<input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="cmd">
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
<td class="py-3 px-6">
|
||||
<div class="relative">
|
||||
{{ input_arrow_down_svg| safe }}
|
||||
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="call_type" id="call_type" >
|
||||
<select class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" name="call_type">
|
||||
<option value="cli" {% if call_type=="cli" %} selected{% endif %}>CLI</option>
|
||||
<option value="http" {% if call_type=="http" %} selected{% endif %}>HTTP</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-6">
|
||||
<input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="type_map" id="type_map" title="Convert inputs when using http. Example: 'sifbj' 1st parameter is a string, 2nd is converted to an int, 3rd to float then boolean and json object or array">
|
||||
<input class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="type_map" title="Convert inputs when using http. Example: 'sibj' 1st parameter is a string, 2nd is converted to an int then boolean and json object or array">
|
||||
</td>
|
||||
</table>
|
||||
</div>
|
||||
@ -176,31 +176,4 @@
|
||||
</div>
|
||||
{% include 'footer.html' %}
|
||||
</body>
|
||||
<script>
|
||||
function set_method() {
|
||||
const coin_type = document.getElementById('coin_type').value.split(',')[0];
|
||||
if (coin_type == 4 || coin_type == -6) {
|
||||
const cmd = document.getElementById('cmd');
|
||||
const type_map = document.getElementById('type_map');
|
||||
let method = cmd.value.split(' ')[0];
|
||||
if (method == 'sendtoaddress') {
|
||||
type_map.value = 'sf';
|
||||
}
|
||||
}
|
||||
}
|
||||
function set_coin() {
|
||||
const coin_type = document.getElementById('coin_type').value.split(',')[0];
|
||||
let call_type = document.getElementById('call_type');
|
||||
if (coin_type == '4' || coin_type == '6') {
|
||||
call_type.disabled = true;
|
||||
call_type.value = 'http';
|
||||
} else {
|
||||
call_type.disabled = false;
|
||||
}
|
||||
set_method();
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
set_coin();
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
|
@ -105,7 +105,7 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if c.manage_daemon is defined %}
|
||||
{% if c.name in ('wownero', 'monero') %}
|
||||
{% if c.name == '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 in ('wownero', 'monero') %}
|
||||
{% if c.name == '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">
|
||||
|
@ -694,12 +694,4 @@
|
||||
</g>
|
||||
</svg>
|
||||
' %}
|
||||
{% set change_password_svg = '
|
||||
<svg class="text-gray-500 w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 24 24">
|
||||
<g fill="#6b7280">
|
||||
<path d="M19,10H5a3,3,0,0,0-3,3v8a3,3,0,0,0,3,3H19a3,3,0,0,0,3-3V13A3,3,0,0,0,19,10Zm-7,9a2,2,0,1,1,2-2A2,2,0,0,1,12,19Z" fill="#6b7280"></path>
|
||||
<path data-color="color-2" d="M18,8H16V6a3.958,3.958,0,0,0-3.911-4h-.042A3.978,3.978,0,0,0,8,5.911V8H6V5.9A5.961,5.961,0,0,1,11.949,0h.061A5.979,5.979,0,0,1,18,6.01Z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
' %}
|
||||
{% set select_box_class = 'hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0' %}
|
||||
|
@ -1,4 +1,3 @@
|
||||
{% from 'style.html' import circular_info_messages_svg, green_cross_close_svg, red_cross_close_svg, circular_error_messages_svg %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@ -39,15 +38,18 @@
|
||||
{% for m in messages %}
|
||||
<section class="py-4" id="messages_{{ m[0] }}" role="alert">
|
||||
<div class="container px-4 mx-auto">
|
||||
<div class="p-6 text-green-800 rounded-lg bg-green-50 border border-green-500 dark:bg-gray-500 dark:text-green-400 rounded-md">
|
||||
<div class="p-6 bg-green-100 border border-green-200 rounded-md">
|
||||
<div class="flex flex-wrap justify-between items-center -m-2">
|
||||
<div class="flex-1 p-2">
|
||||
<div class="flex flex-wrap -m-1">
|
||||
<div class="w-auto p-1"> {{ circular_info_messages_svg | safe }} </div>
|
||||
<ul class="ml-4 mt-1">
|
||||
<li class="font-semibold text-sm text-green-500 error_msg text-left"><span class="bold">ALERT:</span></li>
|
||||
<li class="font-medium text-sm text-green-500 infomsg">This will unlock the system for all users!</li>
|
||||
</ul>
|
||||
<div class="w-auto p-1">
|
||||
<svg class="relative top-0.5" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.4732 4.80667C12.4112 4.74418 12.3375 4.69458 12.2563 4.66074C12.175 4.62689 12.0879 4.60947 11.9999 4.60947C11.9119 4.60947 11.8247 4.62689 11.7435 4.66074C11.6623 4.69458 11.5885 4.74418 11.5266 4.80667L6.55989 9.78L4.47322 7.68667C4.40887 7.62451 4.33291 7.57563 4.24967 7.54283C4.16644 7.51003 4.07755 7.49394 3.9881 7.49549C3.89865 7.49703 3.81037 7.51619 3.72832 7.55185C3.64627 7.58751 3.57204 7.63898 3.50989 7.70333C3.44773 7.76768 3.39885 7.84364 3.36605 7.92688C3.33324 8.01011 3.31716 8.099 3.31871 8.18845C3.32025 8.2779 3.3394 8.36618 3.37507 8.44823C3.41073 8.53028 3.4622 8.60451 3.52655 8.66667L6.08655 11.2267C6.14853 11.2892 6.22226 11.3387 6.3035 11.3726C6.38474 11.4064 6.47188 11.4239 6.55989 11.4239C6.64789 11.4239 6.73503 11.4064 6.81627 11.3726C6.89751 11.3387 6.97124 11.2892 7.03322 11.2267L12.4732 5.78667C12.5409 5.72424 12.5949 5.64847 12.6318 5.56414C12.6688 5.4798 12.6878 5.38873 12.6878 5.29667C12.6878 5.2046 12.6688 5.11353 12.6318 5.02919C12.5949 4.94486 12.5409 4.86909 12.4732 4.80667Z" fill="#2AD168"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 p-1">
|
||||
<h3 class="font-medium text-sm text-green-900 text-left">{{ m[1] }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -58,15 +60,18 @@
|
||||
{% for m in err_messages %}
|
||||
<section class="py-4" id="err_messages_{{ m[0] }}" role="alert">
|
||||
<div class="container px-4 mx-auto">
|
||||
<div class="p-6 text-green-800 rounded-lg bg-red-50 border border-red-400 dark:bg-gray-500 dark:text-red-400 rounded-md">
|
||||
<div class="p-6 bg-red-100 border border-red-200 rounded-md">
|
||||
<div class="flex flex-wrap justify-between items-center -m-2">
|
||||
<div class="flex-1 p-2">
|
||||
<div class="flex flex-wrap -m-1">
|
||||
<div class="w-auto p-1"> {{ circular_error_messages_svg | safe }} </div>
|
||||
<ul class="ml-4 mt-1">
|
||||
<li class="font-semibold text-sm text-red-500 error_msg text-left"><span class="bold">ERROR:</span></li>
|
||||
<li class="font-medium text-sm text-red-500 error_msg">{{ m[1] }}</li>
|
||||
</ul>
|
||||
<div class="w-auto p-1">
|
||||
<svg class="relative top-0.5" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.4733 5.52667C10.4114 5.46419 10.3376 5.41459 10.2564 5.38075C10.1751 5.3469 10.088 5.32947 9.99999 5.32947C9.91198 5.32947 9.82485 5.3469 9.74361 5.38075C9.66237 5.41459 9.58863 5.46419 9.52666 5.52667L7.99999 7.06001L6.47333 5.52667C6.34779 5.40114 6.17753 5.33061 5.99999 5.33061C5.82246 5.33061 5.65219 5.40114 5.52666 5.52667C5.40112 5.65221 5.3306 5.82247 5.3306 6.00001C5.3306 6.17754 5.40112 6.3478 5.52666 6.47334L7.05999 8.00001L5.52666 9.52667C5.46417 9.58865 5.41458 9.66238 5.38073 9.74362C5.34689 9.82486 5.32946 9.912 5.32946 10C5.32946 10.088 5.34689 10.1752 5.38073 10.2564C5.41458 10.3376 5.46417 10.4114 5.52666 10.4733C5.58863 10.5358 5.66237 10.5854 5.74361 10.6193C5.82485 10.6531 5.91198 10.6705 5.99999 10.6705C6.088 10.6705 6.17514 10.6531 6.25638 10.6193C6.33762 10.5854 6.41135 10.5358 6.47333 10.4733L7.99999 8.94001L9.52666 10.4733C9.58863 10.5358 9.66237 10.5854 9.74361 10.6193C9.82485 10.6531 9.91198 10.6705 9.99999 10.6705C10.088 10.6705 10.1751 10.6531 10.2564 10.6193C10.3376 10.5854 10.4114 10.5358 10.4733 10.4733C10.5358 10.4114 10.5854 10.3376 10.6193 10.2564C10.6531 10.1752 10.6705 10.088 10.6705 10C10.6705 9.912 10.6531 9.82486 10.6193 9.74362C10.5854 9.66238 10.5358 9.58865 10.4733 9.52667L8.93999 8.00001L10.4733 6.47334C10.5358 6.41137 10.5854 6.33763 10.6193 6.25639C10.6531 6.17515 10.6705 6.08802 10.6705 6.00001C10.6705 5.912 10.6531 5.82486 10.6193 5.74362C10.5854 5.66238 10.5358 5.58865 10.4733 5.52667ZM12.7133 3.28667C12.0983 2.64994 11.3627 2.14206 10.5494 1.79266C9.736 1.44327 8.8612 1.25936 7.976 1.25167C7.0908 1.24398 6.21294 1.41266 5.39363 1.74786C4.57432 2.08307 3.82998 2.57809 3.20403 3.20404C2.57807 3.82999 2.08305 4.57434 1.74785 5.39365C1.41264 6.21296 1.24396 7.09082 1.25166 7.97602C1.25935 8.86121 1.44326 9.73601 1.79265 10.5494C2.14204 11.3627 2.64992 12.0984 3.28666 12.7133C3.90164 13.3501 4.63727 13.858 5.45063 14.2074C6.26399 14.5567 7.13879 14.7407 8.02398 14.7483C8.90918 14.756 9.78704 14.5874 10.6064 14.2522C11.4257 13.9169 12.17 13.4219 12.796 12.796C13.4219 12.17 13.9169 11.4257 14.2521 10.6064C14.5873 9.78706 14.756 8.90919 14.7483 8.024C14.7406 7.1388 14.5567 6.264 14.2073 5.45064C13.8579 4.63728 13.3501 3.90165 12.7133 3.28667ZM11.7733 11.7733C10.9014 12.6463 9.75368 13.1899 8.52585 13.3115C7.29802 13.4332 6.066 13.1254 5.03967 12.4405C4.01335 11.7557 3.25623 10.7361 2.89731 9.55566C2.53838 8.37518 2.59986 7.10677 3.07127 5.96653C3.54267 4.82629 4.39484 3.88477 5.48259 3.30238C6.57033 2.71999 7.82635 2.53276 9.03666 2.77259C10.247 3.01242 11.3367 3.66447 12.1202 4.61765C12.9036 5.57083 13.3324 6.76617 13.3333 8.00001C13.3357 8.70087 13.1991 9.39524 12.9313 10.0429C12.6635 10.6906 12.2699 11.2788 11.7733 11.7733Z" fill="#EF5944"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 p-1">
|
||||
<h3 class="text-left font-medium text-sm text-red-900 error_msg">Error: {{ m[1] }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -98,7 +103,7 @@
|
||||
<a class="inline-block text-xs font-medium text-blue-500 hover:text-blue-600 hover:underline" href="https://academy.particl.io/en/latest/faq/get_support.html" target="_blank" contenteditable="false">Help / Tutorials</a>
|
||||
</p>
|
||||
<p class="text-center">
|
||||
<span class="text-xs font-medium text-coolGray-500 dark:text-gray-500" contenteditable="false">{{ title }}</span>
|
||||
<span class="text-xs font-medium text-coolGray-500 dark:text-gray-500" contenteditable="false">{{ title }} - GUI 3.0.0</span>
|
||||
</p>
|
||||
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||
</form>
|
||||
|
@ -202,11 +202,9 @@
|
||||
<div class="container mt-5 mx-auto">
|
||||
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||
<div class="px-6">
|
||||
{% if w.cid != '4' %} {# DCR #}
|
||||
<div class="flex flex-wrap justify-end">
|
||||
<div class="w-full md:w-auto p-1.5"> <input class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none cursor-pointer" type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"> </div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -266,8 +264,8 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{# / PART #}
|
||||
{% if w.cid in '6, 9' %}
|
||||
{# XMR | WOW #}
|
||||
{% if w.cid == '6' %}
|
||||
{# XMR #}
|
||||
<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 +410,8 @@
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% if w.cid in '6, 9' %}
|
||||
{# XMR | WOW #}
|
||||
{% if w.cid == '6' %}
|
||||
{# XMR #}
|
||||
<script>
|
||||
// Monero Sub
|
||||
var moneroSubAddress = "{{ w.deposit_address }}";
|
||||
@ -564,145 +562,124 @@
|
||||
<td class="py-3 px-6">
|
||||
<div class="flex"> <input placeholder="{{ w.ticker }} Amount" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="amount" name="amt_{{ w.cid }}" value="{{ w.wd_value }}">
|
||||
<div class="ml-2 flex">
|
||||
{% if w.cid == '1' %}
|
||||
{# PART #}
|
||||
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">25%</button>
|
||||
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">50%</button>
|
||||
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">100%</button>
|
||||
<script>
|
||||
function setAmount(percent, balance, cid, blindBalance, anonBalance) {
|
||||
var amountInput = document.getElementById('amount');
|
||||
var typeSelect = document.getElementById('withdraw_type');
|
||||
var selectedType = typeSelect.value;
|
||||
var floatBalance;
|
||||
var calculatedAmount;
|
||||
{% if w.cid == '1' %}
|
||||
{# PART #}
|
||||
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', '{{ w.blind_balance }}', '{{ w.anon_balance }}')">25%</button> <button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', '{{ w.blind_balance }}', '{{ w.anon_balance }}')">50%</button> <button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', '{{ w.blind_balance }}', '{{ w.anon_balance }}')">100%</button>
|
||||
<script>
|
||||
function setAmount(percent, balance, blindBalance, anonBalance) {
|
||||
var amountInput = document.getElementById('amount');
|
||||
var typeSelect = document.getElementById('withdraw_type');
|
||||
var selectedType = typeSelect.value;
|
||||
var floatBalance;
|
||||
var calculatedAmount;
|
||||
|
||||
switch(selectedType) {
|
||||
case 'plain':
|
||||
floatBalance = parseFloat(balance);
|
||||
break;
|
||||
case 'blind':
|
||||
floatBalance = parseFloat(blindBalance);
|
||||
break;
|
||||
case 'anon':
|
||||
floatBalance = parseFloat(anonBalance);
|
||||
break;
|
||||
default:
|
||||
floatBalance = parseFloat(balance);
|
||||
break;
|
||||
}
|
||||
calculatedAmount = floatBalance * percent;
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
switch(selectedType) {
|
||||
case 'plain':
|
||||
floatBalance = parseFloat(balance);
|
||||
calculatedAmount = floatBalance * percent;
|
||||
break;
|
||||
case 'blind':
|
||||
floatBalance = parseFloat(blindBalance);
|
||||
calculatedAmount = floatBalance * percent;
|
||||
break;
|
||||
case 'anon':
|
||||
floatBalance = parseFloat(anonBalance);
|
||||
calculatedAmount = floatBalance * percent;
|
||||
break;
|
||||
default:
|
||||
floatBalance = parseFloat(balance);
|
||||
calculatedAmount = floatBalance * percent;
|
||||
break;
|
||||
}
|
||||
|
||||
var subfeeCheckbox = document.querySelector(`[name="subfee_${cid}"]`);
|
||||
if (subfeeCheckbox) {
|
||||
subfeeCheckbox.checked = (percent === 1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{# / PART #}
|
||||
{% elif w.cid == '3' %}
|
||||
{# LTC #}
|
||||
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">25%</button>
|
||||
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">50%</button>
|
||||
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">100%</button>
|
||||
<script>
|
||||
function setAmount(percent, balance, cid, mwebBalance) {
|
||||
var amountInput = document.getElementById('amount');
|
||||
var typeSelect = document.getElementById('withdraw_type');
|
||||
var selectedType = typeSelect.value;
|
||||
var floatBalance;
|
||||
var calculatedAmount;
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
}
|
||||
</script>
|
||||
{# / PART #}
|
||||
{% elif w.cid == '3' %}
|
||||
{# LTC #}
|
||||
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', '{{ w.mweb_balance }}', 'mweb')">25%</button> <button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', '{{ w.mweb_balance }}', 'mweb')">50%</button> <button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', '{{ w.mweb_balance }}', 'mweb')">100%</button>
|
||||
<script>
|
||||
function setAmount(percent, balance, mwebBalance, selectedType) {
|
||||
var amountInput = document.getElementById('amount');
|
||||
var typeSelect = document.getElementById('withdraw_type');
|
||||
var selectedType = typeSelect.value;
|
||||
var floatBalance;
|
||||
var calculatedAmount;
|
||||
|
||||
switch(selectedType) {
|
||||
case 'plain':
|
||||
floatBalance = parseFloat(balance);
|
||||
break;
|
||||
case 'mweb':
|
||||
floatBalance = parseFloat(mwebBalance);
|
||||
break;
|
||||
default:
|
||||
floatBalance = parseFloat(balance);
|
||||
break;
|
||||
}
|
||||
calculatedAmount = floatBalance * percent;
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
switch(selectedType) {
|
||||
case 'plain':
|
||||
floatBalance = parseFloat(balance);
|
||||
calculatedAmount = floatBalance * percent;
|
||||
break;
|
||||
case 'mweb':
|
||||
floatBalance = parseFloat(mwebBalance);
|
||||
calculatedAmount = floatBalance * percent;
|
||||
break;
|
||||
default:
|
||||
floatBalance = parseFloat(balance);
|
||||
calculatedAmount = floatBalance * percent;
|
||||
break;
|
||||
}
|
||||
|
||||
var subfeeCheckbox = document.querySelector(`[name="subfee_${cid}"]`);
|
||||
if (subfeeCheckbox) {
|
||||
subfeeCheckbox.checked = (percent === 1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{# / LTC #}
|
||||
{% else %}
|
||||
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }})">25%</button>
|
||||
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }})">50%</button>
|
||||
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }})">100%</button>
|
||||
<script>
|
||||
function setAmount(percent, balance, cid) {
|
||||
var amountInput = document.getElementById('amount');
|
||||
var floatBalance = parseFloat(balance);
|
||||
var calculatedAmount = floatBalance * percent;
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
}
|
||||
</script>
|
||||
{# / LTC #}
|
||||
{% else %}
|
||||
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}')">25%</button> <button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}')">50%</button> <button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', '{{ w.cid }}')">100%</button>
|
||||
<script>
|
||||
function setAmount(percent, balance, cid) {
|
||||
var amountInput = document.getElementById('amount');
|
||||
var floatBalance;
|
||||
var calculatedAmount;
|
||||
|
||||
const specialCids = [6, 9];
|
||||
floatBalance = parseFloat(balance);
|
||||
calculatedAmount = floatBalance * percent;
|
||||
|
||||
console.log("CID:", cid);
|
||||
console.log("Percent:", percent);
|
||||
console.log("Balance:", balance);
|
||||
console.log("Calculated Amount:", calculatedAmount);
|
||||
if (cid === '6' && percent === 1) {
|
||||
amountInput.setAttribute('data-hidden', 'true');
|
||||
amountInput.placeholder = 'Sweep All';
|
||||
amountInput.value = '';
|
||||
amountInput.disabled = true;
|
||||
} else if (amountInput.getAttribute('data-hidden') === 'true' && percent !== 1) {
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
amountInput.setAttribute('data-hidden', 'false');
|
||||
amountInput.placeholder = '';
|
||||
amountInput.disabled = false;
|
||||
} else {
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
amountInput.placeholder = '';
|
||||
amountInput.disabled = false;
|
||||
}
|
||||
|
||||
if (specialCids.includes(parseInt(cid)) && percent === 1) {
|
||||
amountInput.setAttribute('data-hidden', 'true');
|
||||
amountInput.placeholder = 'Sweep All';
|
||||
amountInput.value = '';
|
||||
amountInput.disabled = true;
|
||||
console.log("Sweep All activated for special CID:", cid);
|
||||
} else {
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
amountInput.setAttribute('data-hidden', 'false');
|
||||
amountInput.placeholder = '';
|
||||
amountInput.disabled = false;
|
||||
}
|
||||
if (cid === '6' && percent === 1) {
|
||||
var sweepAllCheckbox = document.getElementById('sweepall');
|
||||
if (sweepAllCheckbox) {
|
||||
sweepAllCheckbox.checked = true;
|
||||
}
|
||||
} else {
|
||||
var sweepAllCheckbox = document.getElementById('sweepall');
|
||||
if (sweepAllCheckbox) {
|
||||
sweepAllCheckbox.checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sweepAllCheckbox = document.getElementById('sweepall');
|
||||
if (sweepAllCheckbox) {
|
||||
if (specialCids.includes(parseInt(cid)) && percent === 1) {
|
||||
sweepAllCheckbox.checked = true;
|
||||
console.log("Sweep All checkbox checked");
|
||||
} else {
|
||||
sweepAllCheckbox.checked = false;
|
||||
console.log("Sweep All checkbox unchecked");
|
||||
}
|
||||
}
|
||||
|
||||
let subfeeCheckbox = document.querySelector(`[name="subfee_${cid}"]`);
|
||||
if (subfeeCheckbox) {
|
||||
subfeeCheckbox.checked = (percent === 1);
|
||||
console.log("Subfee checkbox status for CID", cid, ":", subfeeCheckbox.checked);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
{% if w.cid in '6, 9' %} {# XMR | WOW #}
|
||||
<td class="py-3 px-6 bold">Sweep All:</td>
|
||||
<td class="py-3 px-6">
|
||||
<input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="sweepall" name="sweepall_{{ w.cid }}" {% if w.wd_sweepall==true %} checked=checked{% endif %}>
|
||||
</td>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
</script>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
{% if w.cid == '6' %} {# XMR #}
|
||||
<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>
|
||||
{% endif %}
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
{% if w.cid == '1' %}
|
||||
{# PART #}
|
||||
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
|
||||
@ -774,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 not in '6, 9' %}
|
||||
{# !XMR | WOW #}
|
||||
{% if w.cid != '6' %}
|
||||
{# !XMR #}
|
||||
{% if w.show_utxo_groups %}
|
||||
{% else %}
|
||||
<div class="w-full md:w-auto p-1.5"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="showutxogroups" name="showutxogroups" value="Show UTXO Groups"> {{ utxo_groups_svg | safe }} Show UTXO Groups </button> </div>
|
||||
{% endif %} {% endif %}
|
||||
{% if w.cid in '6, 9' %}
|
||||
{# XMR | WOW #}
|
||||
{% if w.cid == '6' %}
|
||||
{# 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="estfee_{{ w.cid }}" value="Estimate Fee">Estimate {{ w.ticker }} Fee </button> </div>
|
||||
{% endif %}
|
||||
{# / XMR | WOW #} <div class="w-full md:w-auto p-1.5 ml-2"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="withdraw_{{ w.cid }}" value="Withdraw" onclick="return confirmWithdrawal();">{{ withdraw_svg | safe }} Withdraw {{ w.ticker }} </div>
|
||||
{# / 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -793,8 +770,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% if w.cid not in '6, 9' %}
|
||||
{# !XMR | WOW #}
|
||||
{% if w.cid != '6' %}
|
||||
{# !XMR #}
|
||||
{% if w.show_utxo_groups %}
|
||||
<section class="p-6">
|
||||
<div class="flex items-center">
|
||||
@ -865,46 +842,30 @@
|
||||
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||
</form>
|
||||
<script>
|
||||
const coinNameToSymbol = {
|
||||
'Bitcoin': 'BTC',
|
||||
'Particl': 'PART',
|
||||
'Particl Blind': 'PART',
|
||||
'Particl Anon': 'PART',
|
||||
'Monero': 'XMR',
|
||||
'Wownero': 'WOW',
|
||||
'Litecoin': 'LTC',
|
||||
'Firo': 'FIRO',
|
||||
'Dash': 'DASH',
|
||||
'PIVX': 'PIVX',
|
||||
'Decred': 'DCR',
|
||||
'Zano': 'ZANO',
|
||||
};
|
||||
const coinNameToSymbol = {
|
||||
'Bitcoin': 'BTC',
|
||||
'Particl': 'PART',
|
||||
'Particl Blind': 'PART',
|
||||
'Particl Anon': 'PART',
|
||||
'Monero': 'XMR',
|
||||
'Litecoin': 'LTC',
|
||||
'Firo': 'FIRO',
|
||||
'Dash': 'DASH',
|
||||
'PIVX': 'PIVX',
|
||||
'DECRED': 'DCR',
|
||||
'WOWNERO': 'WOW'
|
||||
};
|
||||
|
||||
const getUsdValue = (cryptoValue, coinSymbol) => {
|
||||
if (coinSymbol === 'WOW') {
|
||||
return fetch(`https://api.coingecko.com/api/v3/simple/price?ids=wownero&vs_currencies=usd`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const exchangeRate = data.wownero.usd;
|
||||
if (!isNaN(exchangeRate)) {
|
||||
return cryptoValue * exchangeRate;
|
||||
} else {
|
||||
throw new Error(`Invalid exchange rate for ${coinSymbol}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return fetch(`https://min-api.cryptocompare.com/data/price?fsym=${coinSymbol}&tsyms=USD`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const exchangeRate = data.USD;
|
||||
if (!isNaN(exchangeRate)) {
|
||||
return cryptoValue * exchangeRate;
|
||||
} else {
|
||||
throw new Error(`Invalid exchange rate for ${coinSymbol}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
const getUsdValue = (cryptoValue, coinSymbol) => fetch(`https://min-api.cryptocompare.com/data/price?fsym=${coinSymbol}&tsyms=USD`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const exchangeRate = data.USD;
|
||||
if (!isNaN(exchangeRate)) {
|
||||
return cryptoValue * exchangeRate;
|
||||
} else {
|
||||
throw new Error(`Invalid exchange rate for ${coinSymbol}`);
|
||||
}
|
||||
});
|
||||
|
||||
const updateUsdValue = async (cryptoCell, coinFullName, usdValueSpan) => {
|
||||
const coinSymbol = coinNameToSymbol[coinFullName] || '';
|
||||
@ -1001,7 +962,9 @@ const getUsdValue = (cryptoValue, coinSymbol) => {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
{% include 'footer.html' %}
|
||||
<script>
|
||||
function confirmReseed() {
|
||||
return confirm("Are you sure?\nBackup your wallet before and after.\nWon't detect used keys.\nShould only be used for new wallets.");
|
||||
}
|
||||
@ -1014,6 +977,5 @@ const getUsdValue = (cryptoValue, coinSymbol) => {
|
||||
return confirm("Are you sure?");
|
||||
}
|
||||
</script>
|
||||
{% include 'footer.html' %}
|
||||
</body>
|
||||
</html>
|
@ -35,7 +35,7 @@
|
||||
<div id="total-btc-value" class="text-sm text-white mt-2"></div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
|
||||
<a class="rounded-full mr-5 flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="refresh" href="/changepassword">{{ lock_svg | safe }}<span>Change/Set Password</span></a>
|
||||
<a class="rounded-full mr-5 flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="refresh" href="/changepassword">{{ lock_svg | safe }}<span>Change Password</span></a>
|
||||
<a class="rounded-full flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="refresh" href="/wallets">{{ circular_arrows_svg | safe }}<span>Refresh</span></a>
|
||||
</div>
|
||||
</div>
|
||||
@ -213,47 +213,27 @@ 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',
|
||||
};
|
||||
|
||||
const getUsdValue = (cryptoValue, coinSymbol) => {
|
||||
let apiUrl;
|
||||
|
||||
if (coinSymbol === 'WOW') {
|
||||
apiUrl = `https://api.coingecko.com/api/v3/simple/price?ids=wownero&vs_currencies=usd`;
|
||||
} else {
|
||||
apiUrl = `https://min-api.cryptocompare.com/data/price?fsym=${coinSymbol}&tsyms=USD`;
|
||||
}
|
||||
|
||||
return fetch(apiUrl)
|
||||
return fetch(`https://min-api.cryptocompare.com/data/price?fsym=${coinSymbol}&tsyms=USD`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (coinSymbol === 'WOW') {
|
||||
const exchangeRate = data.wownero.usd;
|
||||
if (!isNaN(exchangeRate)) {
|
||||
return {
|
||||
usdValue: cryptoValue * exchangeRate,
|
||||
btcValue: cryptoValue / exchangeRate
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Invalid exchange rate for ${coinSymbol}`);
|
||||
}
|
||||
const exchangeRate = data.USD;
|
||||
if (!isNaN(exchangeRate)) {
|
||||
return {
|
||||
usdValue: cryptoValue * exchangeRate,
|
||||
btcValue: cryptoValue / exchangeRate
|
||||
};
|
||||
} else {
|
||||
const exchangeRate = data.USD;
|
||||
if (!isNaN(exchangeRate)) {
|
||||
return {
|
||||
usdValue: cryptoValue * exchangeRate,
|
||||
btcValue: cryptoValue / exchangeRate
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Invalid exchange rate for ${coinSymbol}`);
|
||||
}
|
||||
throw new Error(`Invalid exchange rate for ${coinSymbol}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -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 not in (Coins.XMR, Coins.WOW):
|
||||
if coin_from != Coins.XMR:
|
||||
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 not in (Coins.XMR, Coins.WOW):
|
||||
if coin_to != Coins.XMR:
|
||||
page_data['fee_to_conf'] = ci_to._conf_target # Set default value
|
||||
parsed_data['coin_to'] = coin_to
|
||||
except Exception:
|
||||
@ -115,7 +115,7 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
||||
errors.append('Amount From')
|
||||
|
||||
try:
|
||||
if have_data_entry(form_data, 'amt_bid_min') is False:
|
||||
if 'amt_bid_min' not in page_data:
|
||||
if options.get('add_min_bid_amt', False) is True:
|
||||
parsed_data['amt_bid_min'] = ci_from.chainparams_network()['min_amount']
|
||||
else:
|
||||
@ -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.WOW, Coins.PART_ANON):
|
||||
elif parsed_data['coin_to'] in (Coins.XMR, 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 in (Coins.XMR, Coins.WOW):
|
||||
if ci_to == Coins.XMR:
|
||||
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.WOW, Coins.PART_ANON):
|
||||
elif parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON):
|
||||
swap_type = SwapTypes.XMR_SWAP
|
||||
|
||||
if swap_type == SwapTypes.XMR_SWAP:
|
||||
@ -472,8 +472,8 @@ def page_newoffer(self, url_split, post_string):
|
||||
'err_messages': err_messages,
|
||||
'coins_from': coins_from,
|
||||
'coins': coins_to,
|
||||
'addrs': swap_client.listSMSGAddresses('offer_send_from'),
|
||||
'addrs_to': swap_client.listSMSGAddresses('offer_send_to'),
|
||||
'addrs': swap_client.listSmsgAddresses('offer_send_from'),
|
||||
'addrs_to': swap_client.listSmsgAddresses('offer_send_to'),
|
||||
'data': page_data,
|
||||
'automation_strategies': automation_strategies,
|
||||
'summary': summary,
|
||||
@ -602,7 +602,7 @@ def page_offer(self, url_split, post_string):
|
||||
'created_at': offer.created_at,
|
||||
'expired_at': offer.expire_at,
|
||||
'sent': offer.was_sent,
|
||||
'was_revoked': True if offer.active_ind == 2 else False,
|
||||
'was_revoked': 'True' if offer.active_ind == 2 else 'False',
|
||||
'show_bid_form': show_bid_form,
|
||||
'show_edit_form': show_edit_form,
|
||||
'amount_negotiable': offer.amount_negotiable,
|
||||
@ -677,7 +677,7 @@ def page_offer(self, url_split, post_string):
|
||||
'err_messages': err_messages,
|
||||
'data': data,
|
||||
'bids': formatted_bids,
|
||||
'addrs': None if show_bid_form is None else swap_client.listSMSGAddresses('bid'),
|
||||
'addrs': None if show_bid_form is None else swap_client.listSmsgAddresses('bid'),
|
||||
'summary': summary,
|
||||
})
|
||||
|
||||
|
@ -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 in ('monero', 'wownero'):
|
||||
if name == 'monero':
|
||||
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 in ('monero', 'wownero'):
|
||||
if name == 'monero':
|
||||
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')
|
||||
|
@ -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, Coins.WOW):
|
||||
if estimate_fee and coin_id not in (Coins.XMR, ):
|
||||
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 in (Coins.XMR, Coins.WOW):
|
||||
elif coin_id == Coins.XMR:
|
||||
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 in (Coins.XMR, Coins.WOW):
|
||||
if k == Coins.XMR:
|
||||
wallet_data['main_address'] = w.get('main_address', 'Refresh necessary')
|
||||
elif k == Coins.LTC:
|
||||
wallet_data['mweb_address'] = w.get('mweb_address', 'Refresh necessary')
|
||||
|
@ -1,11 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022-2024 tecnovert
|
||||
# Copyright (c) 2022-2023 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import hashlib
|
||||
from basicswap.contrib.segwit_addr import bech32_decode, convertbits, bech32_encode
|
||||
from basicswap.util.crypto import ripemd160, sha256
|
||||
from basicswap.util.crypto import ripemd160
|
||||
|
||||
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
|
||||
@ -67,7 +68,7 @@ def encodeStealthAddress(prefix_byte: int, scan_pubkey: bytes, spend_pubkey: byt
|
||||
data += bytes((0x00,)) # num prefix bits
|
||||
|
||||
b = bytes((prefix_byte,)) + data
|
||||
b += sha256(sha256(b))[:4]
|
||||
b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
|
||||
return b58encode(b)
|
||||
|
||||
|
||||
@ -82,15 +83,16 @@ def toWIF(prefix_byte: int, b: bytes, compressed: bool = True) -> str:
|
||||
b = bytes((prefix_byte,)) + b
|
||||
if compressed:
|
||||
b += bytes((0x01,))
|
||||
b += sha256(sha256(b))[:4]
|
||||
b += hashlib.sha256(hashlib.sha256(b).digest()).digest()[:4]
|
||||
return b58encode(b)
|
||||
|
||||
|
||||
def getKeyID(key_data: bytes) -> bytes:
|
||||
return ripemd160(sha256(key_data))
|
||||
sha256_hash = hashlib.sha256(key_data).digest()
|
||||
return ripemd160(sha256_hash)
|
||||
|
||||
|
||||
def bech32Decode(hrp: str, addr: str) -> bytes:
|
||||
def bech32Decode(hrp, addr):
|
||||
hrpgot, data = bech32_decode(addr)
|
||||
if hrpgot != hrp:
|
||||
return None
|
||||
@ -100,26 +102,25 @@ def bech32Decode(hrp: str, addr: str) -> bytes:
|
||||
return bytes(decoded)
|
||||
|
||||
|
||||
def bech32Encode(hrp: str, data: bytes) -> str:
|
||||
def bech32Encode(hrp, data):
|
||||
ret = bech32_encode(hrp, convertbits(data, 8, 5))
|
||||
if bech32Decode(hrp, ret) is None:
|
||||
return None
|
||||
return ret
|
||||
|
||||
|
||||
def decodeAddress(address: str) -> bytes:
|
||||
addr_data = b58decode(address)
|
||||
if addr_data is None:
|
||||
return None
|
||||
prefixed_data = addr_data[:-4]
|
||||
checksum = addr_data[-4:]
|
||||
if sha256(sha256(prefixed_data))[:4] != checksum:
|
||||
raise ValueError('Checksum mismatch')
|
||||
return prefixed_data
|
||||
def decodeAddress(address_str: str):
|
||||
b58_addr = b58decode(address_str)
|
||||
if b58_addr is not None:
|
||||
address = b58_addr[:-4]
|
||||
checksum = b58_addr[-4:]
|
||||
assert (hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4] == checksum), 'Checksum mismatch'
|
||||
return b58_addr[:-4]
|
||||
return None
|
||||
|
||||
|
||||
def encodeAddress(address: bytes) -> str:
|
||||
checksum = sha256(sha256(address))
|
||||
checksum = hashlib.sha256(hashlib.sha256(address).digest()).digest()
|
||||
return b58encode(address + checksum[0:4])
|
||||
|
||||
|
||||
|
@ -1,41 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2022-2024 tecnovert
|
||||
# Copyright (c) 2022-2023 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from basicswap.contrib.blake256.blake256 import blake_hash
|
||||
|
||||
from Crypto.Hash import HMAC, RIPEMD160, SHA256, SHA512 # pycryptodome
|
||||
from Crypto.Hash import RIPEMD160, SHA256 # pycryptodome
|
||||
|
||||
|
||||
def sha256(data: bytes) -> bytes:
|
||||
def sha256(data):
|
||||
h = SHA256.new()
|
||||
h.update(data)
|
||||
return h.digest()
|
||||
|
||||
|
||||
def sha512(data: bytes) -> bytes:
|
||||
h = SHA512.new()
|
||||
h.update(data)
|
||||
return h.digest()
|
||||
|
||||
|
||||
def ripemd160(data: bytes) -> bytes:
|
||||
def ripemd160(data):
|
||||
h = RIPEMD160.new()
|
||||
h.update(data)
|
||||
return h.digest()
|
||||
|
||||
|
||||
def blake256(data: bytes) -> bytes:
|
||||
return blake_hash(data)
|
||||
|
||||
|
||||
def hash160(data: bytes) -> bytes:
|
||||
return ripemd160(sha256(data))
|
||||
|
||||
|
||||
def hmac_sha512(secret: bytes, data: bytes) -> bytes:
|
||||
h = HMAC.new(secret, digestmod=SHA512)
|
||||
h.update(data)
|
||||
return h.digest()
|
||||
def hash160(s):
|
||||
return ripemd160(sha256(s))
|
||||
|
@ -1,116 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024 tecnovert
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
from .crypto import blake256, hash160, hmac_sha512, ripemd160
|
||||
|
||||
from coincurve.keys import (
|
||||
PrivateKey,
|
||||
PublicKey)
|
||||
|
||||
|
||||
def BIP32Hash(chaincode: bytes, child_no: int, key_data_type: int, keydata: bytes):
|
||||
return hmac_sha512(chaincode, key_data_type.to_bytes(1, 'big') + keydata + child_no.to_bytes(4, 'big'))
|
||||
|
||||
|
||||
def hash160_dcr(data: bytes) -> bytes:
|
||||
return ripemd160(blake256(data))
|
||||
|
||||
|
||||
class ExtKeyPair():
|
||||
__slots__ = ('_depth', '_fingerprint', '_child_no', '_chaincode', '_key', '_pubkey', 'hash_func')
|
||||
|
||||
def __init__(self, coin_type=1):
|
||||
if coin_type == 4:
|
||||
self.hash_func = hash160_dcr
|
||||
else:
|
||||
self.hash_func = hash160
|
||||
|
||||
def set_seed(self, seed: bytes) -> None:
|
||||
hashout: bytes = hmac_sha512(b'Bitcoin seed', seed)
|
||||
self._key = hashout[:32]
|
||||
self._pubkey = None
|
||||
self._chaincode = hashout[32:]
|
||||
self._depth = 0
|
||||
self._child_no = 0
|
||||
self._fingerprint = b'\0' * 4
|
||||
|
||||
def has_key(self) -> bool:
|
||||
return False if self._key is None else True
|
||||
|
||||
def neuter(self) -> None:
|
||||
if self._key is None:
|
||||
raise ValueError('Already neutered')
|
||||
self._pubkey = PublicKey.from_secret(self._key).format()
|
||||
self._key = None
|
||||
|
||||
def derive(self, child_no: int):
|
||||
out = ExtKeyPair()
|
||||
out._depth = self._depth + 1
|
||||
out._child_no = child_no
|
||||
|
||||
if (child_no >> 31) == 0:
|
||||
if self._key:
|
||||
K = PublicKey.from_secret(self._key)
|
||||
k_encoded = K.format()
|
||||
else:
|
||||
K = PublicKey(self._pubkey)
|
||||
k_encoded = self._pubkey
|
||||
out._fingerprint = self.hash_func(k_encoded)[:4]
|
||||
new_hash = BIP32Hash(self._chaincode, child_no, k_encoded[0], k_encoded[1:])
|
||||
out._chaincode = new_hash[32:]
|
||||
|
||||
if self._key:
|
||||
k = PrivateKey(self._key)
|
||||
k.add(new_hash[:32], update=True)
|
||||
out._key = k.secret
|
||||
out._pubkey = None
|
||||
else:
|
||||
K.add(new_hash[:32], update=True)
|
||||
out._key = None
|
||||
out._pubkey = K.format()
|
||||
else:
|
||||
k = PrivateKey(self._key)
|
||||
out._fingerprint = self.hash_func(self._pubkey if self._pubkey else PublicKey.from_secret(self._key).format())[:4]
|
||||
new_hash = BIP32Hash(self._chaincode, child_no, 0, self._key)
|
||||
out._chaincode = new_hash[32:]
|
||||
k.add(new_hash[:32], update=True)
|
||||
out._key = k.secret
|
||||
out._pubkey = None
|
||||
|
||||
out.hash_func = self.hash_func
|
||||
return out
|
||||
|
||||
def encode_v(self) -> bytes:
|
||||
return self._depth.to_bytes(1, 'big') + \
|
||||
self._fingerprint + \
|
||||
self._child_no.to_bytes(4, 'big') + \
|
||||
self._chaincode + \
|
||||
b'\x00' + \
|
||||
self._key
|
||||
|
||||
def encode_p(self) -> bytes:
|
||||
pubkey = PublicKey.from_secret(self._key).format() if self._pubkey is None else self._pubkey
|
||||
return self._depth.to_bytes(1, 'big') + \
|
||||
self._fingerprint + \
|
||||
self._child_no.to_bytes(4, 'big') + \
|
||||
self._chaincode + \
|
||||
pubkey
|
||||
|
||||
def decode(self, data: bytes) -> None:
|
||||
if len(data) != 74:
|
||||
raise ValueError('Unexpected extkey length')
|
||||
self._depth = data[0]
|
||||
self._fingerprint = data[1:5]
|
||||
self._child_no = int.from_bytes(data[5:9], 'big')
|
||||
self._chaincode = data[9:41]
|
||||
|
||||
if data[41] == 0:
|
||||
self._key = data[42:]
|
||||
self._pubkey = None
|
||||
else:
|
||||
self._key = None
|
||||
self._pubkey = data[41:]
|
@ -5,41 +5,13 @@
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
|
||||
def decode_compactsize(b: bytes, offset: int = 0) -> (int, int):
|
||||
i = b[offset]
|
||||
if i < 0xfd:
|
||||
return i, 1
|
||||
offset += 1
|
||||
if i == 0xfd:
|
||||
return int.from_bytes(b[offset: offset + 2], 'little'), 3
|
||||
if i == 0xfe:
|
||||
return int.from_bytes(b[offset: offset + 4], 'little'), 5
|
||||
# 0xff
|
||||
return int.from_bytes(b[offset: offset + 8], 'little'), 9
|
||||
|
||||
|
||||
def encode_compactsize(i: int) -> bytes:
|
||||
if i < 0xfd:
|
||||
return bytes((i,))
|
||||
if i <= 0xffff:
|
||||
return bytes((0xfd,)) + i.to_bytes(2, 'little')
|
||||
if i <= 0xffffffff:
|
||||
return bytes((0xfe,)) + i.to_bytes(4, 'little')
|
||||
return bytes((0xff,)) + i.to_bytes(8, 'little')
|
||||
|
||||
|
||||
def decode_varint(b: bytes, offset: int = 0) -> (int, int):
|
||||
i: int = 0
|
||||
num_bytes: int = 0
|
||||
while True:
|
||||
c = b[offset + num_bytes]
|
||||
i += (c & 0x7F) << (num_bytes * 7)
|
||||
num_bytes += 1
|
||||
if not c & 0x80:
|
||||
break
|
||||
if num_bytes > 8:
|
||||
raise ValueError('Too many bytes')
|
||||
return i, num_bytes
|
||||
def decode_varint(b: bytes) -> int:
|
||||
i = 0
|
||||
shift = 0
|
||||
for c in b:
|
||||
i += (c & 0x7F) << shift
|
||||
shift += 7
|
||||
return i
|
||||
|
||||
|
||||
def encode_varint(i: int) -> bytes:
|
||||
|
@ -5,40 +5,37 @@
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import contextlib
|
||||
import gnupg
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import mmap
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import mmap
|
||||
import stat
|
||||
import gnupg
|
||||
import socks
|
||||
import shutil
|
||||
import signal
|
||||
import socket
|
||||
import socks
|
||||
import stat
|
||||
import sys
|
||||
import hashlib
|
||||
import tarfile
|
||||
import threading
|
||||
import time
|
||||
import urllib.parse
|
||||
import zipfile
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import contextlib
|
||||
import urllib.parse
|
||||
from urllib.error import ContentTooShortError
|
||||
from urllib.parse import _splittype
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.parse import _splittype
|
||||
|
||||
import basicswap.config as cfg
|
||||
from basicswap import __version__
|
||||
from basicswap.base import getaddrinfo_tor
|
||||
from basicswap.basicswap import BasicSwap
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||
from basicswap import __version__
|
||||
from basicswap.ui.util import getCoinName
|
||||
from basicswap.util import toBool
|
||||
from basicswap.util.rfc2440 import rfc2440_hash_password
|
||||
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
||||
from bin.basicswap_run import startDaemon, startXmrWalletDaemon
|
||||
|
||||
PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0')
|
||||
@ -55,25 +52,18 @@ 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', '')
|
||||
|
||||
DASH_VERSION = os.getenv('DASH_VERSION', '20.0.2')
|
||||
DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '')
|
||||
|
||||
FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.13.3')
|
||||
FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.13.2')
|
||||
FIRO_VERSION_TAG = os.getenv('FIRO_VERSION_TAG', '')
|
||||
|
||||
NAV_VERSION = os.getenv('NAV_VERSION', '7.0.3')
|
||||
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', '')
|
||||
|
||||
GUIX_SSL_CERT_DIR = None
|
||||
|
||||
ADD_PUBKEY_URL = os.getenv('ADD_PUBKEY_URL', '')
|
||||
@ -85,12 +75,10 @@ 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',)),
|
||||
'litecoin': (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ('davidburkett38',)),
|
||||
'decred': (DCR_VERSION, DCR_VERSION_TAG, ('decred_release',)),
|
||||
'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',)),
|
||||
@ -99,7 +87,6 @@ known_coins = {
|
||||
|
||||
disabled_coins = [
|
||||
'navcoin',
|
||||
'namecoin', # Needs update
|
||||
]
|
||||
|
||||
expected_key_ids = {
|
||||
@ -108,13 +95,11 @@ expected_key_ids = {
|
||||
'laanwj': ('1E4AED62986CD25D',),
|
||||
'JeremyRand': ('2DBE339E29F6294C',),
|
||||
'binaryfate': ('F0AF4D462A0BDF92',),
|
||||
'wowario': ('793504B449C69220',),
|
||||
'davidburkett38': ('3620E9D387E55666',),
|
||||
'fuzzbawls': ('C1ABA64407731FD9',),
|
||||
'pasta': ('52527BEDABE87984',),
|
||||
'reuben': ('1290A1D0FA7EE109',),
|
||||
'nav_builder': ('2782262BF6E7FADB',),
|
||||
'decred_release': ('6D897EDF518A031D',),
|
||||
}
|
||||
|
||||
USE_PLATFORM = os.getenv('USE_PLATFORM', platform.system())
|
||||
@ -163,17 +148,6 @@ 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))
|
||||
@ -186,14 +160,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', '')
|
||||
|
||||
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')
|
||||
DCR_WALLET_RPC_PORT = int(os.getenv('DCR_WALLET_RPC_PORT', 9209))
|
||||
DCR_WALLET_PWD = os.getenv('DCR_WALLET_PWD', random.randbytes(random.randint(14, 18)).hex())
|
||||
DCR_RPC_USER = os.getenv('DCR_RPC_USER', 'user')
|
||||
DCR_RPC_PWD = os.getenv('DCR_RPC_PWD', random.randbytes(random.randint(14, 18)).hex())
|
||||
|
||||
NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1')
|
||||
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698))
|
||||
|
||||
@ -243,28 +209,12 @@ 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', moved to startup flag
|
||||
'daemon-ssl-allow-any-cert=1',
|
||||
]
|
||||
|
||||
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()
|
||||
@ -405,16 +355,16 @@ def setConnectionParameters(timeout: int = 5, allow_set_tor: bool = True):
|
||||
socket.setdefaulttimeout(timeout)
|
||||
|
||||
|
||||
def popConnectionParameters() -> None:
|
||||
def popConnectionParameters():
|
||||
if use_tor_proxy:
|
||||
socket.socket = default_socket
|
||||
socket.getaddrinfo = default_socket_getaddrinfo
|
||||
socket.setdefaulttimeout(default_socket_timeout)
|
||||
|
||||
|
||||
def downloadFile(url: str, path: str, timeout=5, resume_from=0) -> None:
|
||||
logger.info(f'Downloading file {url}')
|
||||
logger.info(f'To {path}')
|
||||
def downloadFile(url, path, timeout=5, resume_from=0):
|
||||
logger.info('Downloading file %s', url)
|
||||
logger.info('To %s', path)
|
||||
try:
|
||||
setConnectionParameters(timeout=timeout)
|
||||
urlretrieve(url, path, make_reporthook(resume_from), resume_from=resume_from)
|
||||
@ -435,12 +385,13 @@ def importPubkeyFromUrls(gpg, pubkeyurls):
|
||||
try:
|
||||
logger.info('Importing public key from url: ' + url)
|
||||
rv = gpg.import_keys(downloadBytes(url))
|
||||
for key in rv.fingerprints:
|
||||
gpg.trust_keys(key, 'TRUST_FULLY')
|
||||
break
|
||||
except Exception as e:
|
||||
logging.warning('Import from url failed: %s', str(e))
|
||||
|
||||
for key in rv.fingerprints:
|
||||
gpg.trust_keys(key, 'TRUST_FULLY')
|
||||
|
||||
|
||||
def testTorConnection():
|
||||
test_url = 'https://check.torproject.org/'
|
||||
@ -516,9 +467,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', 'wownero'):
|
||||
if coin in ('monero', 'wownero'):
|
||||
bins = [coin + 'd', coin + '-wallet-rpc']
|
||||
if coin in ('monero', 'firo'):
|
||||
if coin == 'monero':
|
||||
bins = ['monerod', 'monero-wallet-rpc']
|
||||
elif coin == 'firo':
|
||||
bins = [coin + 'd', coin + '-cli', coin + '-tx']
|
||||
else:
|
||||
@ -568,25 +519,11 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
|
||||
logging.warning('Unable to set file permissions: %s, for %s', str(e), out_path)
|
||||
return
|
||||
|
||||
bins = [coin + 'd', coin + '-cli', coin + '-tx']
|
||||
versions = version.split('.')
|
||||
dir_name = 'dashcore' if coin == 'dash' else coin
|
||||
if coin == 'decred':
|
||||
bins = ['dcrd', 'dcrwallet']
|
||||
else:
|
||||
bins = [coin + 'd', coin + '-cli', coin + '-tx']
|
||||
versions = version.split('.')
|
||||
if int(versions[0]) >= 22 or int(versions[1]) >= 19:
|
||||
bins.append(coin + '-wallet')
|
||||
|
||||
def get_archive_path(b):
|
||||
if coin == 'pivx':
|
||||
return '{}-{}/bin/{}'.format(dir_name, version, b)
|
||||
elif coin == 'particl' and '_nousb-' in release_path:
|
||||
return '{}-{}_nousb/bin/{}'.format(dir_name, version + version_tag, b)
|
||||
elif coin == 'decred':
|
||||
return '{}-{}-v{}/{}'.format(dir_name, extra_opts['arch_name'], version, b)
|
||||
else:
|
||||
return '{}-{}/bin/{}'.format(dir_name, version + version_tag, b)
|
||||
|
||||
if int(versions[0]) >= 22 or int(versions[1]) >= 19:
|
||||
bins.append(coin + '-wallet')
|
||||
if 'win32' in BIN_ARCH or 'win64' in BIN_ARCH:
|
||||
with zipfile.ZipFile(release_path) as fz:
|
||||
for b in bins:
|
||||
@ -594,7 +531,7 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
|
||||
out_path = os.path.join(bin_dir, b)
|
||||
if (not os.path.exists(out_path)) or extract_core_overwrite:
|
||||
with open(out_path, 'wb') as fout:
|
||||
fout.write(fz.read(get_archive_path(b)))
|
||||
fout.write(fz.read('{}-{}/bin/{}'.format(dir_name, version, b)))
|
||||
try:
|
||||
os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
|
||||
except Exception as e:
|
||||
@ -604,7 +541,15 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts=
|
||||
for b in bins:
|
||||
out_path = os.path.join(bin_dir, b)
|
||||
if not os.path.exists(out_path) or extract_core_overwrite:
|
||||
with open(out_path, 'wb') as fout, ft.extractfile(get_archive_path(b)) as fi:
|
||||
|
||||
if coin == 'pivx':
|
||||
filename = '{}-{}/bin/{}'.format(dir_name, version, b)
|
||||
elif coin == 'particl' and '_nousb-' in release_path:
|
||||
filename = '{}-{}_nousb/bin/{}'.format(dir_name, version + version_tag, b)
|
||||
else:
|
||||
filename = '{}-{}/bin/{}'.format(dir_name, version + version_tag, b)
|
||||
|
||||
with open(out_path, 'wb') as fout, ft.extractfile(filename) as fi:
|
||||
fout.write(fi.read())
|
||||
try:
|
||||
os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
|
||||
@ -657,70 +602,6 @@ 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'
|
||||
elif 'osx64' in BIN_ARCH:
|
||||
release_url = 'https://git.wownero.com/attachments/7e3fd17c-1bcd-442c-b82d-92a00cccffb8'
|
||||
elif 'win64' in BIN_ARCH:
|
||||
release_url = 'https://git.wownero.com/attachments/a1cf8611-1727-4b49-a8e5-1c66fe4f72a3'
|
||||
elif 'win32' in BIN_ARCH:
|
||||
release_url = 'https://git.wownero.com/attachments/007d606d-56e0-4c8a-92c1-d0974a781e80'
|
||||
|
||||
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
|
||||
if USE_PLATFORM == 'Darwin':
|
||||
arch_name = 'darwin-amd64'
|
||||
elif USE_PLATFORM == 'Windows':
|
||||
arch_name = 'windows-amd64'
|
||||
else:
|
||||
machine: str = platform.machine()
|
||||
if 'arm' in machine:
|
||||
arch_name = 'linux-arm'
|
||||
else:
|
||||
arch_name = 'linux-amd64'
|
||||
|
||||
extra_opts['arch_name'] = arch_name
|
||||
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)
|
||||
|
||||
assert_filename = 'decred-v{}-manifest.txt'.format(version)
|
||||
assert_url = release_page_url + '/' + assert_filename
|
||||
assert_path = os.path.join(bin_dir, assert_filename)
|
||||
if not os.path.exists(assert_path):
|
||||
downloadFile(assert_url, assert_path)
|
||||
|
||||
assert_sig_filename = assert_filename + '.asc'
|
||||
assert_sig_url = assert_url + '.asc'
|
||||
assert_sig_path = os.path.join(bin_dir, assert_sig_filename)
|
||||
if not os.path.exists(assert_sig_path):
|
||||
downloadFile(assert_sig_url, assert_sig_path)
|
||||
else:
|
||||
major_version = int(version.split('.')[0])
|
||||
|
||||
@ -841,27 +722,23 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
||||
|
||||
if coin in ('navcoin', ):
|
||||
pubkey_filename = '{}_builder.pgp'.format(coin)
|
||||
elif coin in ('decred', ):
|
||||
pubkey_filename = '{}_release.pgp'.format(coin)
|
||||
else:
|
||||
pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name)
|
||||
pubkeyurls = [
|
||||
'https://raw.githubusercontent.com/basicswap/basicswap/master/pgp/keys/' + pubkey_filename,
|
||||
'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp/keys/' + pubkey_filename,
|
||||
'https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/' + pubkey_filename,
|
||||
]
|
||||
if coin == 'dash':
|
||||
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', 'wownero', 'firo'):
|
||||
if coin in ('monero', 'firo'):
|
||||
with open(assert_path, 'rb') as fp:
|
||||
verified = gpg.verify_file(fp)
|
||||
|
||||
@ -901,15 +778,12 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
||||
|
||||
|
||||
def writeTorSettings(fp, coin, coin_settings, tor_control_password):
|
||||
onionport = coin_settings['onionport']
|
||||
'''
|
||||
TOR_PROXY_HOST must be an ip address.
|
||||
BTC versions >21 and Particl with lookuptorcontrolhost=any can accept hostnames, XMR and LTC cannot
|
||||
'''
|
||||
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
|
||||
if coin in ('decred',):
|
||||
return
|
||||
|
||||
onionport = coin_settings['onionport']
|
||||
fp.write(f'torpassword={tor_control_password}\n')
|
||||
fp.write(f'torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n')
|
||||
|
||||
@ -928,7 +802,7 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
||||
if not os.path.exists(data_dir):
|
||||
os.makedirs(data_dir)
|
||||
|
||||
if coin in ('wownero', 'monero'):
|
||||
if coin == '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))
|
||||
@ -952,28 +826,18 @@ 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 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 tor_control_password is not None:
|
||||
for opt_line in monerod_proxy_config:
|
||||
fp.write(opt_line + '\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 XMR_RPC_USER != '':
|
||||
fp.write(f'rpc-login={XMR_RPC_USER}:{XMR_RPC_PWD}\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-rpc.conf')
|
||||
if coin == 'monero':
|
||||
wallet_conf_path = os.path.join(wallets_dir, 'monero_wallet.conf')
|
||||
wallet_conf_path = os.path.join(wallets_dir, coin + '_wallet.conf')
|
||||
if os.path.exists(wallet_conf_path):
|
||||
exitWithError('{} exists'.format(wallet_conf_path))
|
||||
with open(wallet_conf_path, 'w') as fp:
|
||||
@ -987,11 +851,9 @@ 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')
|
||||
|
||||
@ -1001,43 +863,6 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
||||
fp.write(opt_line + '\n')
|
||||
return
|
||||
|
||||
if coin == 'decred':
|
||||
chainname = 'simnet' if chain == 'regtest' else chain
|
||||
core_conf_path = os.path.join(data_dir, 'dcrd.conf')
|
||||
if os.path.exists(core_conf_path):
|
||||
exitWithError('{} exists'.format(core_conf_path))
|
||||
with open(core_conf_path, 'w') as fp:
|
||||
if chain != 'mainnet':
|
||||
fp.write(chainname + '=1\n')
|
||||
fp.write('debuglevel=info\n')
|
||||
fp.write('notls=1\n')
|
||||
|
||||
fp.write('rpclisten={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
|
||||
|
||||
fp.write('rpcuser={}\n'.format(core_settings['rpcuser']))
|
||||
fp.write('rpcpass={}\n'.format(core_settings['rpcpassword']))
|
||||
|
||||
if tor_control_password is not None:
|
||||
writeTorSettings(fp, coin, core_settings, tor_control_password)
|
||||
|
||||
wallet_conf_path = os.path.join(data_dir, 'dcrwallet.conf')
|
||||
if os.path.exists(wallet_conf_path):
|
||||
exitWithError('{} exists'.format(wallet_conf_path))
|
||||
with open(wallet_conf_path, 'w') as fp:
|
||||
if chain != 'mainnet':
|
||||
fp.write(chainname + '=1\n')
|
||||
fp.write('debuglevel=info\n')
|
||||
fp.write('noservertls=1\n')
|
||||
fp.write('noclienttls=1\n')
|
||||
|
||||
fp.write('rpcconnect={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
|
||||
fp.write('rpclisten={}:{}\n'.format(core_settings['walletrpchost'], core_settings['walletrpcport']))
|
||||
|
||||
fp.write('username={}\n'.format(core_settings['rpcuser']))
|
||||
fp.write('password={}\n'.format(core_settings['rpcpassword']))
|
||||
|
||||
return
|
||||
|
||||
core_conf_path = os.path.join(data_dir, coin + '.conf')
|
||||
if os.path.exists(core_conf_path):
|
||||
exitWithError('{} exists'.format(core_conf_path))
|
||||
@ -1165,15 +990,12 @@ 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 in ('monero', 'wownero'):
|
||||
if coin == 'monero':
|
||||
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-rpc.conf')
|
||||
if coin == 'monero':
|
||||
wallet_conf_path = os.path.join(wallets_dir, 'monero_wallet.conf')
|
||||
wallet_conf_path = os.path.join(wallets_dir, coin + '_wallet.conf')
|
||||
if not os.path.exists(wallet_conf_path):
|
||||
exitWithError('{} does not exist'.format(wallet_conf_path))
|
||||
|
||||
@ -1186,27 +1008,16 @@ 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
|
||||
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
|
||||
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 not skip_line:
|
||||
fp.write(line)
|
||||
if enable:
|
||||
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')
|
||||
for opt_line in monerod_proxy_config:
|
||||
fp.write(opt_line + '\n')
|
||||
|
||||
with open(wallet_conf_path, 'w') as fp:
|
||||
with open(wallet_conf_path + '.last') as fp_in:
|
||||
@ -1228,11 +1039,7 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False, e
|
||||
coin_settings['trusted_daemon'] = extra_opts.get('trust_remote_node', 'auto')
|
||||
return
|
||||
|
||||
if coin == 'decred':
|
||||
config_path = os.path.join(data_dir, 'dcrd.conf')
|
||||
else:
|
||||
config_path = os.path.join(data_dir, coin + '.conf')
|
||||
|
||||
config_path = os.path.join(data_dir, coin + '.conf')
|
||||
if not os.path.exists(config_path):
|
||||
exitWithError('{} does not exist'.format(config_path))
|
||||
|
||||
@ -1244,12 +1051,9 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False, e
|
||||
default_onionport = PART_ONION_PORT
|
||||
elif coin == 'litecoin':
|
||||
default_onionport = LTC_ONION_PORT
|
||||
elif coin in ('decred',):
|
||||
pass
|
||||
else:
|
||||
exitWithError('Unknown default onion listening port for {}'.format(coin))
|
||||
if default_onionport > 0:
|
||||
coin_settings['onionport'] = default_onionport
|
||||
coin_settings['onionport'] = default_onionport
|
||||
|
||||
# Backup
|
||||
shutil.copyfile(config_path, config_path + '.last')
|
||||
@ -1302,7 +1106,6 @@ 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.')
|
||||
@ -1323,13 +1126,13 @@ def printHelp():
|
||||
|
||||
|
||||
def finalise_daemon(d):
|
||||
logging.info('Interrupting {}'.format(d.handle.pid))
|
||||
logging.info('Interrupting {}'.format(d.pid))
|
||||
try:
|
||||
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
||||
d.handle.wait(timeout=120)
|
||||
d.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
||||
d.wait(timeout=120)
|
||||
except Exception as e:
|
||||
logging.info(f'Error {e} for process {d.handle.pid}')
|
||||
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
|
||||
logging.info(f'Error {e} for process {d.pid}')
|
||||
for fp in (d.stdout, d.stderr, d.stdin):
|
||||
if fp:
|
||||
fp.close()
|
||||
|
||||
@ -1350,7 +1153,7 @@ def test_particl_encryption(data_dir, settings, chain, use_tor_proxy):
|
||||
if coin_settings['manage_daemon']:
|
||||
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
|
||||
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args))
|
||||
swap_client.setDaemonPID(c, daemons[-1].handle.pid)
|
||||
swap_client.setDaemonPID(c, daemons[-1].pid)
|
||||
swap_client.setCoinRunParams(c)
|
||||
swap_client.createCoinInterface(c)
|
||||
swap_client.waitForDaemonRPC(c, with_wallet=True)
|
||||
@ -1378,7 +1181,6 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||
swap_client = None
|
||||
daemons = []
|
||||
daemon_args = ['-noconnect', '-nodnsseed']
|
||||
generated_mnemonic: bool = False
|
||||
|
||||
coins_failed_to_initialise = []
|
||||
|
||||
@ -1388,7 +1190,7 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||
if not swap_client.use_tor_proxy:
|
||||
# Cannot set -bind or -whitebind together with -listen=0
|
||||
daemon_args.append('-nolisten')
|
||||
coins_to_create_wallets_for = (Coins.PART, Coins.BTC, Coins.LTC, Coins.DCR, Coins.DASH)
|
||||
coins_to_create_wallets_for = (Coins.PART, Coins.BTC, Coins.LTC, Coins.DASH)
|
||||
# Always start Particl, it must be running to initialise a wallet in addcoin mode
|
||||
# Particl must be loaded first as subsequent coins are initialised from the Particl mnemonic
|
||||
start_daemons = ['particl', ] + [c for c in with_coins if c != 'particl']
|
||||
@ -1398,14 +1200,8 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||
|
||||
if c == Coins.XMR:
|
||||
if coin_settings['manage_wallet_daemon']:
|
||||
filename = coin_name + '-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
||||
filename = 'monero-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
|
||||
else:
|
||||
if coin_settings['manage_daemon']:
|
||||
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
|
||||
@ -1415,26 +1211,11 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||
coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())]
|
||||
|
||||
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
|
||||
swap_client.setDaemonPID(c, daemons[-1].handle.pid)
|
||||
swap_client.setDaemonPID(c, daemons[-1].pid)
|
||||
swap_client.setCoinRunParams(c)
|
||||
swap_client.createCoinInterface(c)
|
||||
|
||||
if c in coins_to_create_wallets_for:
|
||||
if c == Coins.DCR:
|
||||
if coin_settings['manage_wallet_daemon'] is False:
|
||||
continue
|
||||
from basicswap.interface.dcr.util import createDCRWallet
|
||||
|
||||
dcr_password = coin_settings['wallet_pwd'] if WALLET_ENCRYPTION_PWD == '' else WALLET_ENCRYPTION_PWD
|
||||
extra_opts = ['--appdata="{}"'.format(coin_settings['datadir']),
|
||||
'--pass={}'.format(dcr_password),
|
||||
]
|
||||
|
||||
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
|
||||
args = [os.path.join(coin_settings['bindir'], filename), '--create'] + extra_opts
|
||||
hex_seed = swap_client.getWalletKey(Coins.DCR, 1).hex()
|
||||
createDCRWallet(args, hex_seed, logger, threading.Event())
|
||||
continue
|
||||
swap_client.waitForDaemonRPC(c, with_wallet=False)
|
||||
# Create wallet if it doesn't exist yet
|
||||
wallets = swap_client.callcoinrpc(c, 'listwallets')
|
||||
@ -1459,7 +1240,6 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||
logger.info('Loading Particl mnemonic')
|
||||
if particl_wallet_mnemonic is None:
|
||||
particl_wallet_mnemonic = swap_client.callcoinrpc(Coins.PART, 'mnemonic', ['new'])['mnemonic']
|
||||
generated_mnemonic = True
|
||||
swap_client.callcoinrpc(Coins.PART, 'extkeyimportmaster', [particl_wallet_mnemonic])
|
||||
# Particl wallet must be unlocked to call getWalletKey
|
||||
if WALLET_ENCRYPTION_PWD != '':
|
||||
@ -1469,9 +1249,7 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||
c = swap_client.getCoinIdFromName(coin_name)
|
||||
if c in (Coins.PART, ):
|
||||
continue
|
||||
if c not in (Coins.DCR, ):
|
||||
# initialiseWallet only sets main_wallet_seedid_
|
||||
swap_client.waitForDaemonRPC(c)
|
||||
swap_client.waitForDaemonRPC(c)
|
||||
try:
|
||||
swap_client.initialiseWallet(c, raise_errors=True)
|
||||
except Exception as e:
|
||||
@ -1491,17 +1269,14 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings,
|
||||
|
||||
print('')
|
||||
for pair in coins_failed_to_initialise:
|
||||
c, e = pair
|
||||
c, _ = pair
|
||||
if c in (Coins.PIVX, ):
|
||||
print(f'NOTE - Unable to initialise wallet for {getCoinName(c)}. To complete setup click \'Reseed Wallet\' from the ui page once chain is synced.')
|
||||
else:
|
||||
print(f'WARNING - Failed to initialise wallet for {getCoinName(c)}: {e}')
|
||||
|
||||
if 'decred' in with_coins and WALLET_ENCRYPTION_PWD != '':
|
||||
print('WARNING - dcrwallet requires the password to be entered at the first startup when encrypted.\nPlease use basicswap-run with --startonlycoin=decred and the WALLET_ENCRYPTION_PWD environment var set for the initial sync.')
|
||||
print(f'WARNING - Failed to initialise wallet for {getCoinName(c)}')
|
||||
|
||||
if particl_wallet_mnemonic is not None:
|
||||
if generated_mnemonic:
|
||||
if particl_wallet_mnemonic:
|
||||
# Print directly to stdout for tests
|
||||
print('IMPORTANT - Save your particl wallet recovery phrase:\n{}\n'.format(particl_wallet_mnemonic))
|
||||
|
||||
@ -1518,7 +1293,7 @@ def signal_handler(sig, frame):
|
||||
|
||||
|
||||
def check_btc_fastsync_data(base_dir, sync_file_path):
|
||||
github_pgp_url = 'https://raw.githubusercontent.com/basicswap/basicswap/master/pgp'
|
||||
github_pgp_url = 'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp'
|
||||
gitlab_pgp_url = 'https://gitlab.com/particl/basicswap/-/raw/master/pgp'
|
||||
asc_filename = BITCOIN_FASTSYNC_FILE + '.asc'
|
||||
asc_file_path = os.path.join(base_dir, asc_filename)
|
||||
@ -1567,7 +1342,6 @@ 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
|
||||
@ -1654,24 +1428,24 @@ def main():
|
||||
particl_wallet_mnemonic = s[1].strip('"')
|
||||
continue
|
||||
if name in ('withcoin', 'withcoins'):
|
||||
for coin in [s.strip().lower() for s in s[1].split(',')]:
|
||||
for coin in [s.lower() for s in s[1].split(',')]:
|
||||
ensure_coin_valid(coin)
|
||||
with_coins.add(coin)
|
||||
coins_changed = True
|
||||
continue
|
||||
if name in ('withoutcoin', 'withoutcoins'):
|
||||
for coin in [s.strip().lower() for s in s[1].split(',')]:
|
||||
for coin in [s.lower() for s in s[1].split(',')]:
|
||||
ensure_coin_valid(coin, test_disabled=False)
|
||||
with_coins.discard(coin)
|
||||
coins_changed = True
|
||||
continue
|
||||
if name == 'addcoin':
|
||||
add_coin = s[1].strip().lower()
|
||||
add_coin = s[1].lower()
|
||||
ensure_coin_valid(add_coin)
|
||||
with_coins = {add_coin, }
|
||||
continue
|
||||
if name == 'disablecoin':
|
||||
disable_coin = s[1].strip().lower()
|
||||
disable_coin = s[1].lower()
|
||||
ensure_coin_valid(disable_coin, test_disabled=False)
|
||||
continue
|
||||
if name == 'htmlhost':
|
||||
@ -1683,9 +1457,6 @@ 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
|
||||
@ -1717,7 +1488,7 @@ def main():
|
||||
if use_tor_proxy and extra_opts.get('no_tor_proxy', False):
|
||||
exitWithError('Can\'t use --usetorproxy and --notorproxy together')
|
||||
|
||||
# Automatically enable usetorproxy for certain commands if it's set in basicswap config
|
||||
# Automatically enable tor for certain commands if it's set in basicswap config
|
||||
if not (initwalletsonly or enable_tor or disable_tor or disable_coin) and \
|
||||
not use_tor_proxy and os.path.exists(config_path):
|
||||
settings = load_config(config_path)
|
||||
@ -1784,19 +1555,7 @@ def main():
|
||||
'override_feerate': 0.002,
|
||||
'conf_target': 2,
|
||||
'core_version_group': 21,
|
||||
},
|
||||
'bitcoin': {
|
||||
'connection_type': 'rpc' if 'bitcoin' in with_coins else 'none',
|
||||
'manage_daemon': True if ('bitcoin' in with_coins and BTC_RPC_HOST == '127.0.0.1') else False,
|
||||
'rpchost': BTC_RPC_HOST,
|
||||
'rpcport': BTC_RPC_PORT + port_offset,
|
||||
'onionport': BTC_ONION_PORT + port_offset,
|
||||
'datadir': os.getenv('BTC_DATA_DIR', os.path.join(data_dir, 'bitcoin')),
|
||||
'bindir': os.path.join(bin_dir, 'bitcoin'),
|
||||
'use_segwit': True,
|
||||
'blocks_confirmed': 1,
|
||||
'conf_target': 2,
|
||||
'core_version_group': 22,
|
||||
'chain_lookups': 'local',
|
||||
},
|
||||
'litecoin': {
|
||||
'connection_type': 'rpc' if 'litecoin' in with_coins else 'none',
|
||||
@ -1811,26 +1570,21 @@ def main():
|
||||
'conf_target': 2,
|
||||
'core_version_group': 21,
|
||||
'min_relay_fee': 0.00001,
|
||||
'chain_lookups': 'local',
|
||||
},
|
||||
'decred': {
|
||||
'connection_type': 'rpc' if 'decred' in with_coins else 'none',
|
||||
'manage_daemon': True if ('decred' in with_coins and DCR_RPC_HOST == '127.0.0.1') else False,
|
||||
'manage_wallet_daemon': True if ('decred' in with_coins and DCR_WALLET_RPC_HOST == '127.0.0.1') else False,
|
||||
'wallet_pwd': DCR_WALLET_PWD if WALLET_ENCRYPTION_PWD == '' else '',
|
||||
'rpchost': DCR_RPC_HOST,
|
||||
'rpcport': DCR_RPC_PORT + port_offset,
|
||||
'walletrpchost': DCR_WALLET_RPC_HOST,
|
||||
'walletrpcport': DCR_WALLET_RPC_PORT + port_offset,
|
||||
'rpcuser': DCR_RPC_USER,
|
||||
'rpcpassword': DCR_RPC_PWD,
|
||||
'datadir': os.getenv('DCR_DATA_DIR', os.path.join(data_dir, 'decred')),
|
||||
'bindir': os.path.join(bin_dir, 'decred'),
|
||||
'use_csv': True,
|
||||
'bitcoin': {
|
||||
'connection_type': 'rpc' if 'bitcoin' in with_coins else 'none',
|
||||
'manage_daemon': True if ('bitcoin' in with_coins and BTC_RPC_HOST == '127.0.0.1') else False,
|
||||
'rpchost': BTC_RPC_HOST,
|
||||
'rpcport': BTC_RPC_PORT + port_offset,
|
||||
'onionport': BTC_ONION_PORT + port_offset,
|
||||
'datadir': os.getenv('BTC_DATA_DIR', os.path.join(data_dir, 'bitcoin')),
|
||||
'bindir': os.path.join(bin_dir, 'bitcoin'),
|
||||
'use_segwit': True,
|
||||
'blocks_confirmed': 2,
|
||||
'blocks_confirmed': 1,
|
||||
'conf_target': 2,
|
||||
'core_type_group': 'dcr',
|
||||
'min_relay_fee': 0.00001,
|
||||
'core_version_group': 22,
|
||||
'chain_lookups': 'local',
|
||||
},
|
||||
'namecoin': {
|
||||
'connection_type': 'rpc' if 'namecoin' in with_coins else 'none',
|
||||
@ -1866,7 +1620,6 @@ def main():
|
||||
'rpctimeout': 60,
|
||||
'walletrpctimeout': 120,
|
||||
'walletrpctimeoutlong': 600,
|
||||
'core_type_group': 'xmr',
|
||||
},
|
||||
'pivx': {
|
||||
'connection_type': 'rpc' if 'pivx' in with_coins else 'none',
|
||||
@ -1881,6 +1634,7 @@ def main():
|
||||
'blocks_confirmed': 1,
|
||||
'conf_target': 2,
|
||||
'core_version_group': 17,
|
||||
'chain_lookups': 'local',
|
||||
},
|
||||
'dash': {
|
||||
'connection_type': 'rpc' if 'dash' in with_coins else 'none',
|
||||
@ -1895,6 +1649,7 @@ def main():
|
||||
'blocks_confirmed': 1,
|
||||
'conf_target': 2,
|
||||
'core_version_group': 18,
|
||||
'chain_lookups': 'local',
|
||||
},
|
||||
'firo': {
|
||||
'connection_type': 'rpc' if 'firo' in with_coins else 'none',
|
||||
@ -1910,6 +1665,7 @@ def main():
|
||||
'conf_target': 2,
|
||||
'core_version_group': 14,
|
||||
'min_relay_fee': 0.00001,
|
||||
'chain_lookups': 'local',
|
||||
},
|
||||
'navcoin': {
|
||||
'connection_type': 'rpc' if 'navcoin' in with_coins else 'none',
|
||||
@ -1926,28 +1682,6 @@ 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',
|
||||
}
|
||||
}
|
||||
|
||||
@ -1963,9 +1697,6 @@ 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
|
||||
@ -1980,7 +1711,6 @@ 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')
|
||||
@ -2054,8 +1784,6 @@ def main():
|
||||
if add_coin != '':
|
||||
logger.info('Adding coin: %s', add_coin)
|
||||
settings = load_config(config_path)
|
||||
if tor_control_password is None and settings.get('use_tor', False):
|
||||
extra_opts['tor_control_password'] = settings.get('tor_control_password', None)
|
||||
|
||||
if particl_wallet_mnemonic != 'none':
|
||||
# Ensure Particl wallet is unencrypted or correct password is supplied
|
||||
|
@ -18,7 +18,6 @@ import basicswap.config as cfg
|
||||
from basicswap import __version__
|
||||
from basicswap.ui.util import getCoinName
|
||||
from basicswap.basicswap import BasicSwap
|
||||
from basicswap.chainparams import chainparams
|
||||
from basicswap.http_server import HttpThread
|
||||
from basicswap.contrib.websocket_server import WebsocketServer
|
||||
|
||||
@ -29,21 +28,18 @@ if not len(logger.handlers):
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
swap_client = None
|
||||
|
||||
|
||||
class Daemon:
|
||||
__slots__ = ('handle', 'files')
|
||||
|
||||
def __init__(self, handle, files):
|
||||
self.handle = handle
|
||||
self.files = files
|
||||
|
||||
|
||||
def is_known_coin(coin_name: str) -> bool:
|
||||
for k, v in chainparams.items():
|
||||
if coin_name == v['name']:
|
||||
return True
|
||||
return False
|
||||
# TODO: deduplicate
|
||||
known_coins = [
|
||||
'particl',
|
||||
'litecoin',
|
||||
'bitcoin',
|
||||
'namecoin',
|
||||
'monero',
|
||||
'pivx',
|
||||
'dash',
|
||||
'firo',
|
||||
'navcoin',
|
||||
]
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
@ -53,7 +49,7 @@ def signal_handler(sig, frame):
|
||||
swap_client.stopRunning()
|
||||
|
||||
|
||||
def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
||||
def startDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
|
||||
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
||||
|
||||
datadir_path = os.path.expanduser(node_dir)
|
||||
@ -75,64 +71,41 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
||||
for line in config_to_add:
|
||||
fp.write(line + '\n')
|
||||
|
||||
args = [daemon_bin, ]
|
||||
add_datadir: bool = extra_config.get('add_datadir', True)
|
||||
if add_datadir:
|
||||
args.append('-datadir=' + datadir_path)
|
||||
args += opts
|
||||
logging.info('Starting node ' + daemon_bin + ' ' + (('-datadir=' + node_dir) if add_datadir else ''))
|
||||
|
||||
opened_files = []
|
||||
if extra_config.get('stdout_to_file', False):
|
||||
stdout_dest = open(os.path.join(datadir_path, extra_config.get('stdout_filename', 'core_stdout.log')), 'w')
|
||||
opened_files.append(stdout_dest)
|
||||
stderr_dest = stdout_dest
|
||||
else:
|
||||
stdout_dest = subprocess.PIPE
|
||||
stderr_dest = subprocess.PIPE
|
||||
|
||||
if extra_config.get('use_shell', False):
|
||||
str_args = ' '.join(args)
|
||||
return Daemon(subprocess.Popen(str_args, shell=True, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files)
|
||||
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files)
|
||||
args = [daemon_bin, '-datadir=' + datadir_path] + opts
|
||||
logging.info('Starting node ' + daemon_bin + ' ' + '-datadir=' + node_dir)
|
||||
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=datadir_path)
|
||||
|
||||
|
||||
def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
|
||||
daemon_path = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
||||
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
||||
|
||||
datadir_path = os.path.expanduser(node_dir)
|
||||
config_filename = 'wownerod.conf' if daemon_bin.startswith('wow') else 'monerod.conf'
|
||||
args = [daemon_path, '--non-interactive', '--config-file=' + os.path.join(datadir_path, config_filename)] + opts
|
||||
logging.info('Starting node {} --data-dir={}'.format(daemon_path, 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))
|
||||
|
||||
# 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')
|
||||
file_stderr = open(os.path.join(datadir_path, 'core_stderr.log'), 'w')
|
||||
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=file_stdout, stderr=file_stderr, cwd=datadir_path), [file_stdout, file_stderr])
|
||||
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=file_stdout, stderr=file_stderr, cwd=datadir_path)
|
||||
|
||||
|
||||
def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
|
||||
daemon_bin = os.path.expanduser(os.path.join(bin_dir, wallet_bin))
|
||||
args = [daemon_bin, '--non-interactive']
|
||||
|
||||
needs_rewrite: bool = False
|
||||
config_to_remove = ['daemon-address=', 'untrusted-daemon=', 'trusted-daemon=', 'proxy=']
|
||||
|
||||
data_dir = os.path.expanduser(node_dir)
|
||||
config_path = os.path.join(data_dir, 'monero_wallet.conf')
|
||||
args = [daemon_bin, '--non-interactive', '--config-file=' + config_path] + opts
|
||||
|
||||
wallet_config_filename = 'wownero-wallet-rpc.conf' if wallet_bin.startswith('wow') else 'monero_wallet.conf'
|
||||
config_path = os.path.join(data_dir, wallet_config_filename)
|
||||
if os.path.exists(config_path):
|
||||
args += ['--config-file=' + config_path]
|
||||
with open(config_path) as fp:
|
||||
for line in fp:
|
||||
if any(line.startswith(config_line) for config_line in config_to_remove):
|
||||
logging.warning('Found old config in monero_wallet.conf: {}'.format(line.strip()))
|
||||
needs_rewrite = True
|
||||
args += opts
|
||||
|
||||
# Remove old config
|
||||
needs_rewrite: bool = False
|
||||
config_to_remove = ['daemon-address=', 'untrusted-daemon=', 'trusted-daemon=', 'proxy=']
|
||||
with open(config_path) as fp:
|
||||
for line in fp:
|
||||
if any(line.startswith(config_line) for config_line in config_to_remove):
|
||||
logging.warning('Found old config in monero_wallet.conf: {}'.format(line.strip()))
|
||||
needs_rewrite = True
|
||||
if needs_rewrite:
|
||||
logging.info('Rewriting wallet config')
|
||||
logging.info('Rewriting monero_wallet.conf')
|
||||
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:
|
||||
@ -144,7 +117,7 @@ def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
|
||||
# TODO: return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=data_dir)
|
||||
wallet_stdout = open(os.path.join(data_dir, 'wallet_stdout.log'), 'w')
|
||||
wallet_stderr = open(os.path.join(data_dir, 'wallet_stderr.log'), 'w')
|
||||
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir), [wallet_stdout, wallet_stderr])
|
||||
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir)
|
||||
|
||||
|
||||
def ws_new_client(client, server):
|
||||
@ -175,11 +148,7 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
||||
pids_path = os.path.join(data_dir, '.pids')
|
||||
|
||||
if os.getenv('WALLET_ENCRYPTION_PWD', '') != '':
|
||||
if 'decred' in start_only_coins:
|
||||
# Workaround for dcrwallet requiring password for initial startup
|
||||
logger.warning('Allowing set WALLET_ENCRYPTION_PWD var with --startonlycoin=decred.')
|
||||
else:
|
||||
raise ValueError('Please unset the WALLET_ENCRYPTION_PWD environment variable.')
|
||||
raise ValueError('Please unset the WALLET_ENCRYPTION_PWD environment variable.')
|
||||
|
||||
if not os.path.exists(settings_path):
|
||||
raise ValueError('Settings file not found: ' + str(settings_path))
|
||||
@ -203,20 +172,21 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
||||
try:
|
||||
# Try start daemons
|
||||
for c, v in settings['chainclients'].items():
|
||||
|
||||
if len(start_only_coins) > 0 and c not in start_only_coins:
|
||||
continue
|
||||
try:
|
||||
coin_id = swap_client.getCoinIdFromName(c)
|
||||
display_name = getCoinName(coin_id)
|
||||
except Exception as e:
|
||||
logger.warning('Not starting unknown coin: {}'.format(c))
|
||||
continue
|
||||
if c in ('monero', 'wownero'):
|
||||
logger.warning('Error getting coin display name for {}: {}'.format(c, str(e)))
|
||||
display_name = 'Unknown'
|
||||
if c == 'monero':
|
||||
if v['manage_daemon'] is True:
|
||||
swap_client.log.info(f'Starting {display_name} daemon')
|
||||
filename = c + 'd' + ('.exe' if os.name == 'nt' else '')
|
||||
filename = 'monerod' + ('.exe' if os.name == 'nt' else '')
|
||||
daemons.append(startXmrDaemon(v['datadir'], v['bindir'], filename))
|
||||
pid = daemons[-1].handle.pid
|
||||
pid = daemons[-1].pid
|
||||
swap_client.log.info('Started {} {}'.format(filename, pid))
|
||||
|
||||
if v['manage_wallet_daemon'] is True:
|
||||
@ -229,7 +199,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}', '--daemon-ssl-allow-any-cert', ]
|
||||
opts += ['--proxy', f'{proxy_host}:{proxy_port}', ]
|
||||
|
||||
swap_client.log.info('daemon-address: {} ({}){}'.format(daemon_addr, 'trusted' if trusted_daemon else 'untrusted', proxy_log_str))
|
||||
|
||||
@ -240,49 +210,18 @@ 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 = c + '-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
||||
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
||||
daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], filename, opts))
|
||||
pid = daemons[-1].handle.pid
|
||||
pid = daemons[-1].pid
|
||||
swap_client.log.info('Started {} {}'.format(filename, pid))
|
||||
|
||||
continue # /monero
|
||||
|
||||
if c == 'decred':
|
||||
appdata = v['datadir']
|
||||
extra_opts = [f'--appdata="{appdata}"', ]
|
||||
use_shell: bool = True if os.name == 'nt' else False
|
||||
if v['manage_daemon'] is True:
|
||||
swap_client.log.info(f'Starting {display_name} daemon')
|
||||
filename = 'dcrd' + ('.exe' if os.name == 'nt' else '')
|
||||
|
||||
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrd_stdout.log', 'use_shell': use_shell}
|
||||
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config))
|
||||
pid = daemons[-1].handle.pid
|
||||
swap_client.log.info('Started {} {}'.format(filename, pid))
|
||||
|
||||
if v['manage_wallet_daemon'] is True:
|
||||
swap_client.log.info(f'Starting {display_name} wallet daemon')
|
||||
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
|
||||
|
||||
wallet_pwd = v['wallet_pwd']
|
||||
if wallet_pwd == '':
|
||||
# Only set when in startonlycoin mode
|
||||
wallet_pwd = os.getenv('WALLET_ENCRYPTION_PWD', '')
|
||||
if wallet_pwd != '':
|
||||
extra_opts.append(f'--pass="{wallet_pwd}"')
|
||||
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrwallet_stdout.log', 'use_shell': use_shell}
|
||||
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config))
|
||||
pid = daemons[-1].handle.pid
|
||||
swap_client.log.info('Started {} {}'.format(filename, pid))
|
||||
|
||||
continue # /decred
|
||||
|
||||
continue
|
||||
if v['manage_daemon'] is True:
|
||||
swap_client.log.info(f'Starting {display_name} daemon')
|
||||
|
||||
filename = c + 'd' + ('.exe' if os.name == 'nt' else '')
|
||||
daemons.append(startDaemon(v['datadir'], v['bindir'], filename))
|
||||
pid = daemons[-1].handle.pid
|
||||
pid = daemons[-1].pid
|
||||
pids.append((c, pid))
|
||||
swap_client.setDaemonPID(c, pid)
|
||||
swap_client.log.info('Started {} {}'.format(filename, pid))
|
||||
@ -342,18 +281,18 @@ def runClient(fp, data_dir, chain, start_only_coins):
|
||||
|
||||
closed_pids = []
|
||||
for d in daemons:
|
||||
swap_client.log.info('Interrupting {}'.format(d.handle.pid))
|
||||
swap_client.log.info('Interrupting {}'.format(d.pid))
|
||||
try:
|
||||
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
||||
d.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
||||
except Exception as e:
|
||||
swap_client.log.info('Interrupting %d, error %s', d.handle.pid, str(e))
|
||||
swap_client.log.info('Interrupting %d, error %s', d.pid, str(e))
|
||||
for d in daemons:
|
||||
try:
|
||||
d.handle.wait(timeout=120)
|
||||
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
|
||||
d.wait(timeout=120)
|
||||
for fp in (d.stdout, d.stderr, d.stdin):
|
||||
if fp:
|
||||
fp.close()
|
||||
closed_pids.append(d.handle.pid)
|
||||
closed_pids.append(d.pid)
|
||||
except Exception as ex:
|
||||
swap_client.log.error('Error: {}'.format(ex))
|
||||
|
||||
@ -420,16 +359,13 @@ def main():
|
||||
continue
|
||||
if name == 'startonlycoin':
|
||||
for coin in [s.lower() for s in s[1].split(',')]:
|
||||
if is_known_coin(coin) is False:
|
||||
if coin not in known_coins:
|
||||
raise ValueError(f'Unknown coin: {coin}')
|
||||
start_only_coins.add(coin)
|
||||
continue
|
||||
|
||||
logger.warning('Unknown argument %s', v)
|
||||
|
||||
if os.name == 'nt':
|
||||
logger.warning('Running on windows is discouraged and windows support may be discontinued in the future. Please consider using the WSL docker setup instead.')
|
||||
|
||||
if data_dir is None:
|
||||
data_dir = os.path.join(os.path.expanduser(cfg.BASICSWAP_DATADIR))
|
||||
logger.info('Using datadir: %s', data_dir)
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
## Source code
|
||||
|
||||
git clone https://github.com/basicswap/basicswap.git
|
||||
git clone https://github.com/tecnovert/basicswap.git
|
||||
|
||||
|
||||
## Run Using Docker
|
||||
@ -125,7 +125,7 @@ Install Git:
|
||||
|
||||
Download the BasicSwap code:
|
||||
|
||||
git clone https://github.com/basicswap/basicswap.git
|
||||
git clone https://github.com/tecnovert/basicswap.git
|
||||
cd basicswap/docker/
|
||||
|
||||
|
||||
@ -140,7 +140,7 @@ Continue from the [Run Using Docker](#run-using-docker) section.
|
||||
|
||||
### Ubuntu Setup:
|
||||
|
||||
apt-get install -y wget git python3-venv python3-pip gnupg unzip automake libtool pkg-config curl jq
|
||||
apt-get install -y wget git python3-venv python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config curl jq
|
||||
|
||||
### OSX Setup:
|
||||
|
||||
@ -170,7 +170,7 @@ Close the terminal and open a new one to update the python symlinks.
|
||||
|
||||
|
||||
cd $SWAP_DATADIR
|
||||
git clone https://github.com/basicswap/basicswap.git
|
||||
git clone https://github.com/tecnovert/basicswap.git
|
||||
cd $SWAP_DATADIR/basicswap
|
||||
|
||||
|
||||
@ -182,6 +182,7 @@ From https://pypi.org/project/certifi/
|
||||
|
||||
Continue installing Basicswap
|
||||
|
||||
protoc -I=basicswap --python_out=basicswap basicswap/messages.proto
|
||||
pip3 install .
|
||||
|
||||
|
||||
|
@ -99,7 +99,7 @@ Install coincurve
|
||||
|
||||
Install basicswap
|
||||
|
||||
git clone https://github.com/basicswap/basicswap.git
|
||||
git clone https://github.com/tecnovert/basicswap.git
|
||||
cd basicswap
|
||||
pip3 install .
|
||||
|
||||
|
@ -1,15 +1,3 @@
|
||||
0.13.2
|
||||
==============
|
||||
|
||||
- Remove protobuf and protoc dependencies.
|
||||
- Include mnemonic dependency directly.
|
||||
|
||||
|
||||
0.13.1
|
||||
==============
|
||||
|
||||
- coins: Add Decred.
|
||||
|
||||
|
||||
0.13.0
|
||||
==============
|
||||
|
@ -22,7 +22,7 @@ Docker will create directories instead of files if these don't exist.
|
||||
#### For a new install
|
||||
|
||||
Use the `--usetorproxy` argument to download the coin binaries over tor, then enable tor with `--enabletor`.
|
||||
Note that some download links may be unreachable when using tor.
|
||||
Note that some download links, notably for Litecoin, are unreachable when using tor.
|
||||
|
||||
docker compose -f docker-compose_with_tor.yml run --rm swapclient \
|
||||
basicswap-prepare --usetorproxy --datadir=/coindata --withcoins=monero,particl
|
||||
|
@ -1,16 +0,0 @@
|
||||
monero_wallet:
|
||||
image: i_decred_wallet
|
||||
build:
|
||||
context: decred_wallet
|
||||
dockerfile: Dockerfile
|
||||
container_name: decred_wallet
|
||||
volumes:
|
||||
- ${DATA_PATH}/decred_wallet:/data
|
||||
expose:
|
||||
- ${DCR_WALLET_RPC_PORT}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
restart: unless-stopped
|
@ -1,16 +0,0 @@
|
||||
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
|
@ -1,16 +0,0 @@
|
||||
monero_daemon:
|
||||
image: i_decred_daemon
|
||||
build:
|
||||
context: decred_daemon
|
||||
dockerfile: Dockerfile
|
||||
container_name: decred_daemon
|
||||
volumes:
|
||||
- ${DATA_PATH}/decred_daemon:/data
|
||||
expose:
|
||||
- ${DCR_RPC_PORT}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
restart: unless-stopped
|
@ -1,16 +0,0 @@
|
||||
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
|
@ -8,8 +8,6 @@
|
||||
- ${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
|
||||
@ -47,16 +45,6 @@
|
||||
- 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
|
||||
|
@ -1,25 +0,0 @@
|
||||
FROM i_swapclient as install_stage
|
||||
|
||||
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=decred --withoutcoins=particl && \
|
||||
find /coin_bin -name *.tar.gz -delete
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
COPY --from=install_stage /coin_bin .
|
||||
|
||||
ENV DCR_DATA /data
|
||||
|
||||
RUN groupadd -r decred && useradd -r -m -g decred decred \
|
||||
&& apt-get update \
|
||||
&& apt-get install -qq --no-install-recommends gosu \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir "$DCR_DATA" \
|
||||
&& chown -R decred:decred "$DCR_DATA" \
|
||||
&& ln -sfn "$DECRED_DATA" /home/decred/decred \
|
||||
&& chown -h decred:decred /home/decred/decred
|
||||
VOLUME /data
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
EXPOSE 9108 9109
|
||||
CMD ["/decred/dcrd", "--datadir=/data"]
|
@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [[ "$1" == "dcrctl" || "$1" == "dcrd" || "$1" == "dcrwallet" ]]; then
|
||||
mkdir -p "$DECRED_DATA"
|
||||
|
||||
chown -h decred:decred /home/decred/decred
|
||||
exec gosu decred "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
@ -1,19 +0,0 @@
|
||||
FROM i_decred_daemon
|
||||
|
||||
ENV DCR_DATA /data
|
||||
|
||||
RUN groupadd -r decred && useradd -r -m -g decred decred \
|
||||
&& apt-get update \
|
||||
&& apt-get install -qq --no-install-recommends gosu \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir "$DCR_DATA" \
|
||||
&& chown -R decred:decred "$DCR_DATA" \
|
||||
&& ln -sfn "$DECRED_DATA" /home/decred/decred \
|
||||
&& chown -h decred:decred /home/decred/decred
|
||||
VOLUME /data
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
EXPOSE 9209
|
||||
CMD ["/decred/dcrwallet", "--datadir=/data"]
|
@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [[ "$1" == "dcrctl" || "$1" == "dcrd" || "$1" == "dcrwallet" ]]; then
|
||||
mkdir -p "$DECRED_DATA"
|
||||
|
||||
chown -h decred:decred /home/decred/decred
|
||||
exec gosu decred "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
@ -14,25 +14,17 @@ PART_ZMQ_PORT=20792
|
||||
PART_RPC_USER=particl_user
|
||||
PART_RPC_PWD=particl_pwd
|
||||
|
||||
BTC_DATA_DIR=/data/bitcoin
|
||||
BTC_RPC_HOST=bitcoin_core
|
||||
BTC_RPC_PORT=19796
|
||||
BTC_RPC_USER=bitcoin_user
|
||||
BTC_RPC_PWD=bitcoin_pwd
|
||||
|
||||
LTC_DATA_DIR=/data/litecoin
|
||||
LTC_RPC_HOST=litecoin_core
|
||||
LTC_RPC_PORT=19795
|
||||
LTC_RPC_USER=litecoin_user
|
||||
LTC_RPC_PWD=litecoin_pwd
|
||||
|
||||
DCR_DATA_DIR=/data/decred
|
||||
DCR_RPC_HOST=decred_daemon
|
||||
DCR_RPC_PORT=DCR_RPC_PORT
|
||||
DCR_WALLET_RPC_HOST=decred_wallet
|
||||
DCR_WALLET_RPC_PORT=9209
|
||||
DCR_RPC_USER=decred_user
|
||||
DCR_RPC_PWD=decred_pass
|
||||
BTC_DATA_DIR=/data/bitcoin
|
||||
BTC_RPC_HOST=bitcoin_core
|
||||
BTC_RPC_PORT=19796
|
||||
BTC_RPC_USER=bitcoin_user
|
||||
BTC_RPC_PWD=bitcoin_pwd
|
||||
|
||||
XMR_DATA_DIR=/data/monero_daemon
|
||||
XMR_RPC_HOST=monero_daemon
|
||||
@ -44,16 +36,6 @@ 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
|
||||
|
@ -3,13 +3,6 @@
|
||||
This will setup Basicswap so that each coin runs in it's own container.
|
||||
|
||||
|
||||
### Coin notes
|
||||
|
||||
- Decred is only partially supported, the wallet will need to be initialised manually.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
Install dependencies:
|
||||
|
||||
sudo apt install basez docker-compose
|
||||
@ -40,14 +33,13 @@ Create docker-compose config:
|
||||
# Using the helper script:
|
||||
./scripts/build_yml_files.py -c bitcoin monero
|
||||
|
||||
# Or manually:
|
||||
# Or
|
||||
|
||||
cat compose-fragments/0_start.yml > docker-compose.yml
|
||||
|
||||
# Add the relevant coin fragments
|
||||
cat compose-fragments/1_bitcoin.yml >> docker-compose.yml
|
||||
cat compose-fragments/1_litecoin.yml >> docker-compose.yml
|
||||
cat compose-fragments/1_decred-wallet.yml >> docker-compose.yml
|
||||
cat compose-fragments/1_monero-wallet.yml >> docker-compose.yml
|
||||
cat compose-fragments/1_pivx.yml >> docker-compose.yml
|
||||
cat compose-fragments/1_dash.yml >> docker-compose.yml
|
||||
@ -129,7 +121,6 @@ Start BasicSwap:
|
||||
popd
|
||||
|
||||
docker-compose build monero_daemon
|
||||
docker-compose build decred_daemon
|
||||
docker-compose build
|
||||
|
||||
docker-compose build --no-cache swapclient
|
||||
@ -187,4 +178,4 @@ Prepare wallet:
|
||||
# Notes
|
||||
|
||||
Switch Basicswap repo:
|
||||
docker-compose build swapclient --build-arg BASICSWAP_URL=https://github.com/basicswap/basicswap/archive/refs/heads/dev.zip --build-arg BASICSWAP_DIR=basicswap-dev
|
||||
docker-compose build swapclient --build-arg BASICSWAP_URL=https://github.com/tecnovert/basicswap/archive/refs/heads/dev.zip --build-arg BASICSWAP_DIR=basicswap-dev
|
||||
|
@ -57,21 +57,12 @@ def main():
|
||||
if coin_name == 'particl':
|
||||
# Nothing to do
|
||||
continue
|
||||
if coin_name in ('monero', 'wownero'):
|
||||
with open(os.path.join(fragments_dir, '1_{coin_name}-wallet.yml'), 'rb') as fp_in:
|
||||
if coin_name == 'monero':
|
||||
with open(os.path.join(fragments_dir, '1_monero-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_{coin_name}-daemon.yml'), 'rb') as fp_in:
|
||||
for line in fp_in:
|
||||
fp.write(line)
|
||||
continue
|
||||
if coin_name == 'decred':
|
||||
with open(os.path.join(fragments_dir, '1_decred-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_decred-daemon.yml'), 'rb') as fp_in:
|
||||
with open(os.path.join(fragments_dir, '8_monero-daemon.yml'), 'rb') as fp_in:
|
||||
for line in fp_in:
|
||||
fp.write(line)
|
||||
continue
|
||||
|
@ -7,6 +7,14 @@ ENV LANG=C.UTF-8 \
|
||||
RUN apt-get update; \
|
||||
apt-get install -y wget python3-pip gnupg unzip make g++ autoconf automake libtool pkg-config gosu tzdata;
|
||||
|
||||
# Must install protoc directly as latest package is not up to date
|
||||
RUN wget -O protobuf_src.tar.gz https://github.com/protocolbuffers/protobuf/releases/download/v21.1/protobuf-python-4.21.1.tar.gz && \
|
||||
tar xvf protobuf_src.tar.gz && \
|
||||
cd protobuf-3.21.1 && \
|
||||
./configure --prefix=/usr && \
|
||||
make -j$(nproc) install && \
|
||||
ldconfig
|
||||
|
||||
ARG COINCURVE_VERSION=v0.2
|
||||
RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_$COINCURVE_VERSION.zip && \
|
||||
unzip coincurve-anonswap.zip && \
|
||||
@ -14,16 +22,18 @@ RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archiv
|
||||
cd coincurve-anonswap && \
|
||||
python3 setup.py install --force
|
||||
|
||||
ARG BASICSWAP_URL=https://github.com/basicswap/basicswap/archive/master.zip
|
||||
ARG BASICSWAP_URL=https://github.com/tecnovert/basicswap/archive/master.zip
|
||||
ARG BASICSWAP_DIR=basicswap-master
|
||||
RUN wget -O basicswap-repo.zip $BASICSWAP_URL; \
|
||||
unzip basicswap-repo.zip; \
|
||||
mv $BASICSWAP_DIR /basicswap; \
|
||||
cd /basicswap; \
|
||||
protoc -I=basicswap --python_out=basicswap basicswap/messages.proto; \
|
||||
pip3 install .;
|
||||
|
||||
#COPY ./test_code basicswap
|
||||
#RUN cd basicswap; \
|
||||
# protoc -I=basicswap --python_out=basicswap basicswap/messages.proto; \
|
||||
# pip3 install .;
|
||||
|
||||
RUN groupadd -r swap_user && useradd -g swap_user -ms /bin/bash swap_user && \
|
||||
|
@ -1,24 +0,0 @@
|
||||
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"]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user