60 Commits
0.0.32 ... pivx

Author SHA1 Message Date
tecnovert
f57888c455 preparescript: Add temporary pivx release. 2022-08-18 00:18:50 +02:00
tecnovert
e1528c9d63 coins: Add PIVX
No CSV or segwit.
sethdseed requires a fully synced chain, manual intervention required to set a key derived from the master mnemonic.
Requires a pivxd version with a backported scantxoutset command.
2022-08-17 01:05:32 +02:00
tecnovert
48e5dcbcc9 refactor: Add createRawSignedTransaction to interface 2022-08-16 20:53:58 +02:00
tecnovert
b179667cc5 ui: Fix missing coin from data. 2022-08-10 23:58:53 +02:00
tecnovert
85bbccf82a preparescript: with_coins should be a set. 2022-08-09 01:29:51 +02:00
tecnovert
20c0c372d0 refactor: Move all coin interfaces to a dir 2022-08-09 00:10:37 +02:00
tecnovert
80f0098a3d ui: Rates table example 2022-08-04 11:47:27 +02:00
tecnovert
412770d399 ui: rateslist returns js list of rates. 2022-08-03 23:59:57 +02:00
tecnovert
1ee2db137b ui: Expose min bid amount. 2022-07-31 23:40:58 +02:00
tecnovert
1c4f208d27 refactor: E275 missing whitespace after keyword 2022-07-31 20:09:43 +02:00
tecnovert
1601a57aed ui: Add websocket notifications. 2022-07-31 19:33:01 +02:00
tecnovert
6cc54d9c61 server: Serve more static directories. 2022-07-30 19:00:53 +02:00
tecnovert
7a3b41a11b ui: Fix js value updates when sending bid. 2022-07-29 00:47:32 +02:00
tecnovert
871bdb918e ui: Update to bittrex v3 api. 2022-07-28 17:01:11 +02:00
tecnovert
cbcf90c492 coins: Raise Monero version to 0.18.0.0 2022-07-28 11:23:45 +02:00
tecnovert
2f1a9cbfae tests: Fix tests 2022-07-26 23:23:49 +02:00
tecnovert
cd5af7032f ui: Use coin tickers as wallet keys in json/wallets 2022-07-25 23:10:58 +02:00
tecnovert
18a444b071 ui: Add json endpoint to list all coin types. 2022-07-25 12:55:55 +02:00
tecnovert
8b09607083 tests: Prevent out of sequence refund in test_13_itx_refund 2022-07-21 00:27:22 +02:00
tecnovert
fa74b9982c tests: Add xmr swap failure states to tests. 2022-07-20 00:24:14 +02:00
tecnovert
2c49d13aa0 tests: Add non xmr swap failure states to tests. 2022-07-18 22:57:16 +02:00
tecnovert
3ad87df844 tests: Add swap to wallet restore test. 2022-07-17 22:34:39 +02:00
tecnovert
ede01d3fc8 tests: Start wallet restore test.
Fix LTC pidfile in config.
Update LTC onion port for core version 21.
2022-07-15 17:04:24 +02:00
tecnovert
a2830afc06 tests: Deduplicate setup code. 2022-07-13 23:28:38 +02:00
tecnovert
e03f32ea5f docker: Fix and document isolated coins config. 2022-07-11 23:36:28 +02:00
tecnovert
48e0cac5ab refactor: Prepare script uses swap_client to create all wallets. 2022-07-09 23:58:40 +02:00
tecnovert
585bef6076 tests: return mnemonic from prepare script. 2022-07-08 18:41:01 +02:00
tecnovert
868dc27d64 ui: Add indication when XMR node is bootstrapping 2022-07-06 15:32:44 +02:00
tecnovert
1b7550ff76 ui: Add XMR rpc variant options. 2022-07-06 13:16:18 +02:00
tecnovert
91e285bf4a ui: Split wallet cached data into balance and blockchain state.
Add XMR synced indicator.
2022-07-06 00:46:37 +02:00
tecnovert
0580f9ebac tests: Fix NMC tests. 2022-07-04 22:29:49 +02:00
tecnovert
02bd90053a refactor: Use read_json_api in more tests. 2022-07-04 00:47:30 +02:00
tecnovert
0c620ea388 doc, tests: Test sequence diagrams are accurate.
Add delay between detecting PTX and redeeming ITX.
Add bid state history to json api.
Hide Tx none states in bid state history.
2022-07-03 23:58:16 +02:00
tecnovert
a2afd3f00f refactor: Separate MSG4F and lock txn sending 2022-07-01 16:37:10 +02:00
tecnovert
43048cffc0 doc, docker: Pin coincurve src to tag. 2022-06-29 15:11:11 +02:00
tecnovert
3976b9c203 tests: Fix ci tests. 2022-06-29 13:46:25 +02:00
tecnovert
d5e35b8168 doc, ui: Add sequence diagrams 2022-06-29 01:45:46 +02:00
tecnovert
f7aadd1b9d doc: Update docker install notes. 2022-06-27 22:29:16 +02:00
tecnovert
beaff23ac3 osx: Embed install_certifi.py script. 2022-06-26 02:17:59 +02:00
tecnovert
e7a62a6a82 debug: Log auto accepting event. 2022-06-22 22:51:39 +02:00
tecnovert
d2324ad097 preparescript: Fix missing btc wallet with --addcoin=bitcoin and --usebtcfastsync
doc: Fetch latest xmr chain height
2022-06-18 19:28:40 +02:00
tecnovert
f787bdb203 ui: Add refresh link to bids page. 2022-06-16 23:31:30 +02:00
tecnovert
b64437db84 coins: Raise Litecoin version to 0.21.2 2022-06-16 15:58:59 +02:00
tecnovert
844db9c541 coins: Raise Monero version to 0.17.3.2 2022-06-16 14:45:56 +02:00
tecnovert
a51a895141 docker: Manually install protobuf to avoid error.
Error: "TypeError: Descriptors cannot not be created directly"

Use pycryptodome ripemd160 implementation else it must be manually enabled in hashlib/openssl.
2022-06-16 14:28:52 +02:00
tecnovert
cddc4daf70 ui: Show offer amount swapped. 2022-06-16 00:19:06 +02:00
tecnovert
3ed6eca95f ui: Show bids that can be accepted. 2022-06-15 00:35:33 +02:00
tecnovert
0edcf249aa refactor: Add bid states to db. 2022-06-11 23:56:21 +02:00
tecnovert
89c60851ac automation: Accept multiple concurrent bids. 2022-06-08 22:23:44 +02:00
tecnovert
d909115ea4 refactor: Rename EventQueue table to Action 2022-06-06 23:03:31 +02:00
tecnovert
d47a69c7cb preparescript: Add expected keyids. 2022-06-05 11:38:14 +02:00
tecnovert
0c2c86070f preparescript: --usebtcfastsync option will initialise the BTC datadir from a chain snapshot. 2022-06-04 23:08:22 +02:00
tecnovert
a659eb3931 preparescript: Core versions can be set by env vars.
Use no usb variant of Particl for linux.
2022-06-03 21:31:07 +02:00
tecnovert
6153b76ec0 ui: Allow selecting automation strategy when creating offer.
Add alternate pgp key urls.
2022-06-01 00:38:50 +02:00
tecnovert
08c10bc69e Raise Particl version to 0.21.2.9 2022-05-23 23:51:48 +02:00
tecnovert
8daa76f937 refactor: Add automation tables. 2022-05-23 23:51:06 +02:00
tecnovert
f4649d34b2 Raise Particl version to 0.21.2.8 2022-04-13 20:59:41 +02:00
tecnovert
88c94c4acd Set default anon tx ring size to 12 and add setting. 2022-04-11 00:11:51 +02:00
tecnovert
a4683c8450 ui: Add sent filter to offers page. 2022-04-10 22:24:56 +02:00
tecnovert
7bc9d64233 ui: Display sent status on offers page. 2022-04-10 22:08:05 +02:00
134 changed files with 11312 additions and 3933 deletions

View File

@@ -7,8 +7,8 @@ lint_task:
- pip install codespell - pip install codespell
script: script:
- flake8 --version - flake8 --version
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,messages_pb2.py,.eggs,.tox - 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,gitianpubkeys,*.pyc,*basicswap/contrib,*mnemonics.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
test_task: test_task:
environment: environment:
@@ -24,8 +24,9 @@ test_task:
- apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config - apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config
- pip install tox pytest - pip install tox pytest
- python3 setup.py install - python3 setup.py install
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/anonswap.zip - wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.1.zip
- unzip coincurve-anonswap.zip - unzip -d coincurve-anonswap coincurve-anonswap.zip
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
- cd coincurve-anonswap - cd coincurve-anonswap
- python3 setup.py install --force - python3 setup.py install --force
bins_cache: bins_cache:

View File

@@ -22,8 +22,9 @@ before_install:
install: install:
- travis_retry pip install tox pytest - travis_retry pip install tox pytest
before_script: before_script:
- wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/anonswap.zip - wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.1.zip
- unzip coincurve-anonswap.zip - unzip -d coincurve-anonswap coincurve-anonswap.zip
- mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
- cd coincurve-anonswap - cd coincurve-anonswap
- python3 setup.py install --force - python3 setup.py install --force
script: script:
@@ -51,8 +52,8 @@ jobs:
- travis_retry pip install codespell==1.15.0 - travis_retry pip install codespell==1.15.0
before_script: before_script:
script: script:
- PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,messages_pb2.py,.eggs,.tox - 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,gitianpubkeys,*.pyc,*basicswap/contrib,*mnemonics.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
after_success: after_success:
- echo "End lint" - echo "End lint"
- stage: test - stage: test

View File

@@ -1,14 +1,24 @@
FROM ubuntu:20.04 FROM ubuntu:22.04
ENV LANG=C.UTF-8 \ ENV LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \
DATADIRS="/coindata" DATADIRS="/coindata"
RUN apt-get update; \ RUN apt-get update; \
apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config gosu tzdata; apt-get install -y wget python3-pip gnupg unzip make g++ autoconf automake libtool pkg-config gosu tzdata;
RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/anonswap.zip && \ # 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.1
RUN wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_$COINCURVE_VERSION.zip && \
unzip coincurve-anonswap.zip && \ unzip coincurve-anonswap.zip && \
mv ./coincurve-anonswap_$COINCURVE_VERSION ./coincurve-anonswap && \
cd coincurve-anonswap && \ cd coincurve-anonswap && \
python3 setup.py install --force python3 setup.py install --force
@@ -24,8 +34,10 @@ RUN cd basicswap-master; \
RUN useradd -ms /bin/bash swap_user && \ RUN useradd -ms /bin/bash swap_user && \
mkdir /coindata && chown swap_user -R /coindata mkdir /coindata && chown swap_user -R /coindata
# Expose html port # html port
EXPOSE 12700 EXPOSE 12700
# websocket port
EXPOSE 11700
VOLUME /coindata VOLUME /coindata

View File

@@ -1,3 +1,3 @@
name = "basicswap" name = "basicswap"
__version__ = "0.0.32" __version__ = "0.11.36"

View File

@@ -118,10 +118,12 @@ class BaseApp:
return bytes(segwit_addr.decode(chainparams[coin_type][self.chain]['hrp'], addr)[1]) return bytes(segwit_addr.decode(chainparams[coin_type][self.chain]['hrp'], addr)[1])
def callrpc(self, method, params=[], wallet=None): def callrpc(self, method, params=[], wallet=None):
return callrpc(self.coin_clients[Coins.PART]['rpcport'], self.coin_clients[Coins.PART]['rpcauth'], method, params, wallet) cc = self.coin_clients[Coins.PART]
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost'])
def callcoinrpc(self, coin, method, params=[], wallet=None): def callcoinrpc(self, coin, method, params=[], wallet=None):
return callrpc(self.coin_clients[coin]['rpcport'], self.coin_clients[coin]['rpcauth'], method, params, wallet) cc = self.coin_clients[coin]
return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost'])
def calltx(self, cmd): def calltx(self, cmd):
bindir = self.coin_clients[Coins.PART]['bindir'] bindir = self.coin_clients[Coins.PART]['bindir']

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2021 tecnovert # Copyright (c) 2021-2022 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -64,38 +64,40 @@ class SwapTypes(IntEnum):
class OfferStates(IntEnum): class OfferStates(IntEnum):
OFFER_SENT = auto() OFFER_SENT = 1
OFFER_RECEIVED = auto() OFFER_RECEIVED = 2
OFFER_ABANDONED = auto() OFFER_ABANDONED = 3
class BidStates(IntEnum): class BidStates(IntEnum):
BID_SENT = auto() BID_SENT = 1
BID_RECEIVING = auto() # Partially received BID_RECEIVING = 2 # Partially received
BID_RECEIVED = auto() BID_RECEIVED = 3
BID_RECEIVING_ACC = auto() # Partially received accept message BID_RECEIVING_ACC = 4 # Partially received accept message
BID_ACCEPTED = auto() # BidAcceptMessage received/sent BID_ACCEPTED = 5 # BidAcceptMessage received/sent
SWAP_INITIATED = auto() # Initiate txn validated SWAP_INITIATED = 6 # Initiate txn validated
SWAP_PARTICIPATING = auto() # Participate txn validated SWAP_PARTICIPATING = 7 # Participate txn validated
SWAP_COMPLETED = auto() # All swap txns spent SWAP_COMPLETED = 8 # All swap txns spent
XMR_SWAP_SCRIPT_COIN_LOCKED = auto() XMR_SWAP_SCRIPT_COIN_LOCKED = 9
XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX = auto() XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX = 10
XMR_SWAP_NOSCRIPT_COIN_LOCKED = auto() XMR_SWAP_NOSCRIPT_COIN_LOCKED = 11
XMR_SWAP_LOCK_RELEASED = auto() XMR_SWAP_LOCK_RELEASED = 12
XMR_SWAP_SCRIPT_TX_REDEEMED = auto() XMR_SWAP_SCRIPT_TX_REDEEMED = 13
XMR_SWAP_SCRIPT_TX_PREREFUND = auto() # script txo moved into pre-refund tx XMR_SWAP_SCRIPT_TX_PREREFUND = 14 # script txo moved into pre-refund tx
XMR_SWAP_NOSCRIPT_TX_REDEEMED = auto() XMR_SWAP_NOSCRIPT_TX_REDEEMED = 15
XMR_SWAP_NOSCRIPT_TX_RECOVERED = auto() XMR_SWAP_NOSCRIPT_TX_RECOVERED = 16
XMR_SWAP_FAILED_REFUNDED = auto() XMR_SWAP_FAILED_REFUNDED = 17
XMR_SWAP_FAILED_SWIPED = auto() XMR_SWAP_FAILED_SWIPED = 18
XMR_SWAP_FAILED = auto() XMR_SWAP_FAILED = 19
SWAP_DELAYING = auto() SWAP_DELAYING = 20
SWAP_TIMEDOUT = auto() SWAP_TIMEDOUT = 21
BID_ABANDONED = auto() # Bid will no longer be processed BID_ABANDONED = 22 # Bid will no longer be processed
BID_ERROR = auto() # An error occurred BID_ERROR = 23 # An error occurred
BID_STALLED_FOR_TEST = auto() BID_STALLED_FOR_TEST = 24
BID_REJECTED = auto() BID_REJECTED = 25
BID_STATE_UNKNOWN = auto() BID_STATE_UNKNOWN = 26
XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS = 27 # XmrBidLockTxSigsMessage
XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX = 28 # XmrBidLockSpendTxMessage
class TxStates(IntEnum): class TxStates(IntEnum):
@@ -122,7 +124,7 @@ class TxTypes(IntEnum):
XMR_SWAP_B_LOCK = auto() XMR_SWAP_B_LOCK = auto()
class EventTypes(IntEnum): class ActionTypes(IntEnum):
ACCEPT_BID = auto() ACCEPT_BID = auto()
ACCEPT_XMR_BID = auto() ACCEPT_XMR_BID = auto()
SIGN_XMR_SWAP_LOCK_TX_A = auto() SIGN_XMR_SWAP_LOCK_TX_A = auto()
@@ -132,6 +134,8 @@ class EventTypes(IntEnum):
REDEEM_XMR_SWAP_LOCK_TX_A = auto() # Follower REDEEM_XMR_SWAP_LOCK_TX_A = auto() # Follower
REDEEM_XMR_SWAP_LOCK_TX_B = auto() # Leader REDEEM_XMR_SWAP_LOCK_TX_B = auto() # Leader
RECOVER_XMR_SWAP_LOCK_TX_B = auto() RECOVER_XMR_SWAP_LOCK_TX_B = auto()
SEND_XMR_SWAP_LOCK_SPEND_MSG = auto()
REDEEM_ITX = auto()
class EventLogTypes(IntEnum): class EventLogTypes(IntEnum):
@@ -155,6 +159,9 @@ class EventLogTypes(IntEnum):
LOCK_TX_B_SPEND_TX_PUBLISHED = auto() LOCK_TX_B_SPEND_TX_PUBLISHED = auto()
LOCK_TX_A_REFUND_TX_SEEN = auto() LOCK_TX_A_REFUND_TX_SEEN = auto()
LOCK_TX_A_REFUND_SPEND_TX_SEEN = auto() LOCK_TX_A_REFUND_SPEND_TX_SEEN = auto()
ERROR = auto()
AUTOMATION_CONSTRAINT = auto()
AUTOMATION_ACCEPTING_BID = auto()
class XmrSplitMsgTypes(IntEnum): class XmrSplitMsgTypes(IntEnum):
@@ -163,12 +170,14 @@ class XmrSplitMsgTypes(IntEnum):
class DebugTypes(IntEnum): class DebugTypes(IntEnum):
NONE = 0
BID_STOP_AFTER_COIN_A_LOCK = auto() BID_STOP_AFTER_COIN_A_LOCK = auto()
BID_DONT_SPEND_COIN_A_LOCK_REFUND = auto() BID_DONT_SPEND_COIN_A_LOCK_REFUND = auto()
CREATE_INVALID_COIN_B_LOCK = auto() CREATE_INVALID_COIN_B_LOCK = auto()
BUYER_STOP_AFTER_ITX = auto() BUYER_STOP_AFTER_ITX = auto()
MAKE_INVALID_PTX = auto() MAKE_INVALID_PTX = auto()
DONT_SPEND_ITX = auto() DONT_SPEND_ITX = auto()
SKIP_LOCK_TX_REFUND = auto()
def strOfferState(state): def strOfferState(state):
@@ -181,6 +190,13 @@ def strOfferState(state):
return 'Unknown' return 'Unknown'
class NotificationTypes(IntEnum):
NONE = 0
OFFER_RECEIVED = auto()
BID_RECEIVED = auto()
BID_ACCEPTED = auto()
def strBidState(state): def strBidState(state):
if state == BidStates.BID_SENT: if state == BidStates.BID_SENT:
return 'Sent' return 'Sent'
@@ -232,6 +248,10 @@ def strBidState(state):
return 'Failed' return 'Failed'
if state == BidStates.SWAP_DELAYING: if state == BidStates.SWAP_DELAYING:
return 'Delaying' return 'Delaying'
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS:
return 'Exchanged script lock tx sigs msg'
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX:
return 'Exchanged script lock spend tx msg'
return 'Unknown' + ' ' + str(state) return 'Unknown' + ' ' + str(state)
@@ -331,6 +351,12 @@ def describeEventEntry(event_type, event_msg):
return 'Lock tx A refund spend tx seen in chain' return 'Lock tx A refund spend tx seen in chain'
if event_type == EventLogTypes.SYSTEM_WARNING: if event_type == EventLogTypes.SYSTEM_WARNING:
return 'Warning: ' + event_msg return 'Warning: ' + event_msg
if event_type == EventLogTypes.ERROR:
return 'Error: ' + event_msg
if event_type == EventLogTypes.AUTOMATION_CONSTRAINT:
return 'Failed auto accepting'
if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID:
return 'Auto accepting'
def getVoutByAddress(txjs, p2sh): def getVoutByAddress(txjs, p2sh):
@@ -381,6 +407,10 @@ def getLastBidState(packed_states):
def isActiveBidState(state): def isActiveBidState(state):
if state >= BidStates.BID_ACCEPTED and state < BidStates.SWAP_COMPLETED:
return True
if state == BidStates.SWAP_DELAYING:
return True
if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX: if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
return True return True
if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
@@ -395,4 +425,8 @@ def isActiveBidState(state):
return True return True
if state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND: if state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND:
return True return True
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS:
return True
if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX:
return True
return False return False

View File

@@ -26,6 +26,9 @@ class Coins(IntEnum):
XMR = 6 XMR = 6
PART_BLIND = 7 PART_BLIND = 7
PART_ANON = 8 PART_ANON = 8
# ZANO = 9
# NDAU = 10
PIVX = 11
chainparams = { chainparams = {
@@ -116,7 +119,8 @@ chainparams = {
'mainnet': { 'mainnet': {
'rpcport': 9332, 'rpcport': 9332,
'pubkey_address': 48, 'pubkey_address': 48,
'script_address': 50, 'script_address': 5,
'script_address2': 50,
'key_prefix': 176, 'key_prefix': 176,
'hrp': 'ltc', 'hrp': 'ltc',
'bip44': 2, 'bip44': 2,
@@ -126,7 +130,8 @@ chainparams = {
'testnet': { 'testnet': {
'rpcport': 19332, 'rpcport': 19332,
'pubkey_address': 111, 'pubkey_address': 111,
'script_address': 58, 'script_address': 196,
'script_address2': 58,
'key_prefix': 239, 'key_prefix': 239,
'hrp': 'tltc', 'hrp': 'tltc',
'bip44': 1, 'bip44': 1,
@@ -137,7 +142,8 @@ chainparams = {
'regtest': { 'regtest': {
'rpcport': 19443, 'rpcport': 19443,
'pubkey_address': 111, 'pubkey_address': 111,
'script_address': 58, 'script_address': 196,
'script_address2': 58,
'key_prefix': 239, 'key_prefix': 239,
'hrp': 'rltc', 'hrp': 'rltc',
'bip44': 1, 'bip44': 1,
@@ -203,10 +209,45 @@ chainparams = {
'min_amount': 100000, 'min_amount': 100000,
'max_amount': 10000 * XMR_COIN, 'max_amount': 10000 * XMR_COIN,
} }
},
Coins.PIVX: {
'name': 'pivx',
'ticker': 'PIVX',
'message_magic': 'DarkNet Signed Message:\n',
'blocks_target': 60 * 1,
'decimal_places': 8,
'has_csv': False,
'has_segwit': False,
'mainnet': {
'rpcport': 51473,
'pubkey_address': 30,
'script_address': 13,
'key_prefix': 212,
'bip44': 119,
'min_amount': 1000,
'max_amount': 100000 * COIN,
},
'testnet': {
'rpcport': 51475,
'pubkey_address': 139,
'script_address': 19,
'key_prefix': 239,
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
'name': 'testnet4',
},
'regtest': {
'rpcport': 51477,
'pubkey_address': 139,
'script_address': 19,
'key_prefix': 239,
'bip44': 1,
'min_amount': 1000,
'max_amount': 100000 * COIN,
} }
},
} }
ticker_map = {} ticker_map = {}

View File

@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2019 tecnovert # Copyright (c) 2019-2022 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os import os
CONFIG_FILENAME = 'basicswap.json' CONFIG_FILENAME = 'basicswap.json'
DEFAULT_DATADIR = '~/.basicswap' BASICSWAP_DATADIR = os.getenv('BASICSWAP_DATADIR', '~/.basicswap')
DEFAULT_ALLOW_CORS = False DEFAULT_ALLOW_CORS = False
TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap')) TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap'))
DEFAULT_TEST_BINDIR = os.path.expanduser(os.getenv('DEFAULT_TEST_BINDIR', '~/tmp/bin')) DEFAULT_TEST_BINDIR = os.path.expanduser(os.getenv('DEFAULT_TEST_BINDIR', '~/tmp/bin'))
@@ -36,3 +36,8 @@ NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix)
XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero'))) XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero')))
XMRD = os.getenv('XMRD', 'monerod' + bin_suffix) XMRD = os.getenv('XMRD', 'monerod' + bin_suffix)
XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix) XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix)
PIVX_BINDIR = os.path.expanduser(os.getenv('PIVX_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'pivx')))
PIVXD = os.getenv('PIVXD', 'pivxd' + bin_suffix)
PIVX_CLI = os.getenv('PIVX_CLI', 'pivx-cli' + bin_suffix)
PIVX_TX = os.getenv('PIVX_TX', 'pivx-tx' + bin_suffix)

View File

@@ -10,6 +10,7 @@ import hashlib
import struct import struct
import unittest import unittest
from typing import List, Dict from typing import List, Dict
from basicswap.util.crypto import ripemd160
from .messages import ( from .messages import (
CTransaction, CTransaction,
@@ -25,7 +26,7 @@ MAX_SCRIPT_ELEMENT_SIZE = 520
OPCODE_NAMES = {} # type: Dict[CScriptOp, str] OPCODE_NAMES = {} # type: Dict[CScriptOp, str]
def hash160(s): def hash160(s):
return hashlib.new('ripemd160', sha256(s)).digest() return ripemd160(sha256(s))
def bn2vch(v): def bn2vch(v):
"""Convert number to bitcoin-specific little endian format.""" """Convert number to bitcoin-specific little endian format."""

View File

@@ -0,0 +1 @@
from .websocket_server import *

View File

@@ -0,0 +1,38 @@
import threading
class ThreadWithLoggedException(threading.Thread):
"""
Similar to Thread but will log exceptions to passed logger.
Args:
logger: Logger instance used to log any exception in child thread
Exception is also reachable via <thread>.exception from the main thread.
"""
DIVIDER = "*"*80
def __init__(self, *args, **kwargs):
try:
self.logger = kwargs.pop("logger")
except KeyError:
raise Exception("Missing 'logger' in kwargs")
super().__init__(*args, **kwargs)
self.exception = None
def run(self):
try:
if self._target is not None:
self._target(*self._args, **self._kwargs)
except Exception as exception:
thread = threading.current_thread()
self.exception = exception
self.logger.exception(f"{self.DIVIDER}\nException in child thread {thread}: {exception}\n{self.DIVIDER}")
finally:
del self._target, self._args, self._kwargs
class WebsocketServerThread(ThreadWithLoggedException):
"""Dummy wrapper to make debug messages a bit more readable"""
pass

View File

@@ -0,0 +1,495 @@
# Author: Johan Hanssen Seferidis
# License: MIT
import sys
import struct
import ssl
from base64 import b64encode
from hashlib import sha1
import logging
from socket import error as SocketError
import errno
import threading
from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler
from .thread import WebsocketServerThread
logger = logging.getLogger(__name__)
logging.basicConfig()
'''
+-+-+-+-+-------+-+-------------+-------------------------------+
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| Payload Data continued ... |
+---------------------------------------------------------------+
'''
FIN = 0x80
OPCODE = 0x0f
MASKED = 0x80
PAYLOAD_LEN = 0x7f
PAYLOAD_LEN_EXT16 = 0x7e
PAYLOAD_LEN_EXT64 = 0x7f
OPCODE_CONTINUATION = 0x0
OPCODE_TEXT = 0x1
OPCODE_BINARY = 0x2
OPCODE_CLOSE_CONN = 0x8
OPCODE_PING = 0x9
OPCODE_PONG = 0xA
CLOSE_STATUS_NORMAL = 1000
DEFAULT_CLOSE_REASON = bytes('', encoding='utf-8')
class API():
def run_forever(self, threaded=False):
return self._run_forever(threaded)
def new_client(self, client, server):
pass
def client_left(self, client, server):
pass
def message_received(self, client, server, message):
pass
def set_fn_new_client(self, fn):
self.new_client = fn
def set_fn_client_left(self, fn):
self.client_left = fn
def set_fn_message_received(self, fn):
self.message_received = fn
def send_message(self, client, msg):
self._unicast(client, msg)
def send_message_to_all(self, msg):
self._multicast(msg)
def deny_new_connections(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON):
self._deny_new_connections(status, reason)
def allow_new_connections(self):
self._allow_new_connections()
def shutdown_gracefully(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON):
self._shutdown_gracefully(status, reason)
def shutdown_abruptly(self):
self._shutdown_abruptly()
def disconnect_clients_gracefully(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON):
self._disconnect_clients_gracefully(status, reason)
def disconnect_clients_abruptly(self):
self._disconnect_clients_abruptly()
class WebsocketServer(ThreadingMixIn, TCPServer, API):
"""
A websocket server waiting for clients to connect.
Args:
port(int): Port to bind to
host(str): Hostname or IP to listen for connections. By default 127.0.0.1
is being used. To accept connections from any client, you should use
0.0.0.0.
loglevel: Logging level from logging module to use for logging. By default
warnings and errors are being logged.
Properties:
clients(list): A list of connected clients. A client is a dictionary
like below.
{
'id' : id,
'handler' : handler,
'address' : (addr, port)
}
"""
allow_reuse_address = True
daemon_threads = True # comment to keep threads alive until finished
def __init__(self, host='127.0.0.1', port=0, loglevel=logging.WARNING, key=None, cert=None):
logger.setLevel(loglevel)
TCPServer.__init__(self, (host, port), WebSocketHandler)
self.host = host
self.port = self.socket.getsockname()[1]
self.url = f'ws://{self.host}:{self.port}/'
self.key = key
self.cert = cert
self.clients = []
self.id_counter = 0
self.thread = None
self._deny_clients = False
def _run_forever(self, threaded):
cls_name = self.__class__.__name__
try:
logger.info("Listening on port %d for clients.." % self.port)
if threaded:
self.daemon = True
self.thread = WebsocketServerThread(target=super().serve_forever, daemon=True, logger=logger)
if sys.version_info[0] > 3 or (sys.version_info[0] == 3 and sys.version_info[1] >= 10):
logger.info(f"Starting {cls_name} on thread {self.thread.name}.")
else:
logger.info(f"Starting {cls_name} on thread {self.thread.getName()}.")
self.thread.start()
else:
self.thread = threading.current_thread()
logger.info(f"Starting {cls_name} on main thread.")
super().serve_forever()
except KeyboardInterrupt:
self.server_close()
logger.info("Server terminated.")
except Exception as e:
logger.error(str(e), exc_info=True)
sys.exit(1)
def _message_received_(self, handler, msg):
self.message_received(self.handler_to_client(handler), self, msg)
def _ping_received_(self, handler, msg):
handler.send_pong(msg)
def _pong_received_(self, handler, msg):
pass
def _new_client_(self, handler):
if self._deny_clients:
status = self._deny_clients["status"]
reason = self._deny_clients["reason"]
handler.send_close(status, reason)
self._terminate_client_handler(handler)
return
self.id_counter += 1
client = {
'id': self.id_counter,
'handler': handler,
'address': handler.client_address
}
self.clients.append(client)
self.new_client(client, self)
def _client_left_(self, handler):
client = self.handler_to_client(handler)
self.client_left(client, self)
if client in self.clients:
self.clients.remove(client)
def _unicast(self, receiver_client, msg):
receiver_client['handler'].send_message(msg)
def _multicast(self, msg):
for client in self.clients:
self._unicast(client, msg)
def handler_to_client(self, handler):
for client in self.clients:
if client['handler'] == handler:
return client
def _terminate_client_handler(self, handler):
handler.keep_alive = False
handler.finish()
handler.connection.close()
def _terminate_client_handlers(self):
"""
Ensures request handler for each client is terminated correctly
"""
for client in self.clients:
self._terminate_client_handler(client["handler"])
def _shutdown_gracefully(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON):
"""
Send a CLOSE handshake to all connected clients before terminating server
"""
self.keep_alive = False
self._disconnect_clients_gracefully(status, reason)
self.server_close()
self.shutdown()
def _shutdown_abruptly(self):
"""
Terminate server without sending a CLOSE handshake
"""
self.keep_alive = False
self._disconnect_clients_abruptly()
self.server_close()
self.shutdown()
def _disconnect_clients_gracefully(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON):
"""
Terminate clients gracefully without shutting down the server
"""
for client in self.clients:
client["handler"].send_close(status, reason)
self._terminate_client_handlers()
def _disconnect_clients_abruptly(self):
"""
Terminate clients abruptly (no CLOSE handshake) without shutting down the server
"""
self._terminate_client_handlers()
def _deny_new_connections(self, status, reason):
self._deny_clients = {
"status": status,
"reason": reason,
}
def _allow_new_connections(self):
self._deny_clients = False
class WebSocketHandler(StreamRequestHandler):
def __init__(self, socket, addr, server):
self.server = server
self.timeout = 1000 # Must set a timeout or rfile.read timesout in the tests
assert not hasattr(self, "_send_lock"), "_send_lock already exists"
self._send_lock = threading.Lock()
if server.key and server.cert:
try:
socket = ssl.wrap_socket(socket, server_side=True, certfile=server.cert, keyfile=server.key)
except: # Not sure which exception it throws if the key/cert isn't found
logger.warning("SSL not available (are the paths {} and {} correct for the key and cert?)".format(server.key, server.cert))
StreamRequestHandler.__init__(self, socket, addr, server)
def setup(self):
StreamRequestHandler.setup(self)
self.keep_alive = True
self.handshake_done = False
self.valid_client = False
def handle(self):
while self.keep_alive:
if not self.handshake_done:
self.handshake()
elif self.valid_client:
self.read_next_message()
def read_bytes(self, num):
return self.rfile.read(num)
def read_next_message(self):
try:
b1, b2 = self.read_bytes(2)
except TimeoutError:
return
except SocketError as e: # to be replaced with ConnectionResetError for py3
if e.errno == errno.ECONNRESET:
logger.info("Client closed connection.")
self.keep_alive = 0
return
b1, b2 = 0, 0
except ValueError as e:
b1, b2 = 0, 0
fin = b1 & FIN
opcode = b1 & OPCODE
masked = b2 & MASKED
payload_length = b2 & PAYLOAD_LEN
if opcode == OPCODE_CLOSE_CONN:
logger.info("Client asked to close connection.")
self.keep_alive = 0
return
if not masked:
logger.warning("Client must always be masked.")
self.keep_alive = 0
return
if opcode == OPCODE_CONTINUATION:
logger.warning("Continuation frames are not supported.")
return
elif opcode == OPCODE_BINARY:
logger.warning("Binary frames are not supported.")
return
elif opcode == OPCODE_TEXT:
opcode_handler = self.server._message_received_
elif opcode == OPCODE_PING:
opcode_handler = self.server._ping_received_
elif opcode == OPCODE_PONG:
opcode_handler = self.server._pong_received_
else:
logger.warning("Unknown opcode %#x." % opcode)
self.keep_alive = 0
return
if payload_length == 126:
payload_length = struct.unpack(">H", self.rfile.read(2))[0]
elif payload_length == 127:
payload_length = struct.unpack(">Q", self.rfile.read(8))[0]
masks = self.read_bytes(4)
message_bytes = bytearray()
for message_byte in self.read_bytes(payload_length):
message_byte ^= masks[len(message_bytes) % 4]
message_bytes.append(message_byte)
opcode_handler(self, message_bytes.decode('utf8'))
def send_message(self, message):
self.send_text(message)
def send_pong(self, message):
self.send_text(message, OPCODE_PONG)
def send_close(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON):
"""
Send CLOSE to client
Args:
status: Status as defined in https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1
reason: Text with reason of closing the connection
"""
if status < CLOSE_STATUS_NORMAL or status > 1015:
raise Exception(f"CLOSE status must be between 1000 and 1015, got {status}")
header = bytearray()
payload = struct.pack('!H', status) + reason
payload_length = len(payload)
assert payload_length <= 125, "We only support short closing reasons at the moment"
# Send CLOSE with status & reason
header.append(FIN | OPCODE_CLOSE_CONN)
header.append(payload_length)
with self._send_lock:
self.request.send(header + payload)
def send_text(self, message, opcode=OPCODE_TEXT):
"""
Important: Fragmented(=continuation) messages are not supported since
their usage cases are limited - when we don't know the payload length.
"""
# Validate message
if isinstance(message, bytes):
message = try_decode_UTF8(message) # this is slower but ensures we have UTF-8
if not message:
logger.warning("Can\'t send message, message is not valid UTF-8")
return False
elif not isinstance(message, str):
logger.warning('Can\'t send message, message has to be a string or bytes. Got %s' % type(message))
return False
header = bytearray()
payload = encode_to_UTF8(message)
payload_length = len(payload)
# Normal payload
if payload_length <= 125:
header.append(FIN | opcode)
header.append(payload_length)
# Extended payload
elif payload_length >= 126 and payload_length <= 65535:
header.append(FIN | opcode)
header.append(PAYLOAD_LEN_EXT16)
header.extend(struct.pack(">H", payload_length))
# Huge extended payload
elif payload_length < 18446744073709551616:
header.append(FIN | opcode)
header.append(PAYLOAD_LEN_EXT64)
header.extend(struct.pack(">Q", payload_length))
else:
raise Exception("Message is too big. Consider breaking it into chunks.")
return
with self._send_lock:
self.request.send(header + payload)
def read_http_headers(self):
headers = {}
# first line should be HTTP GET
http_get = self.rfile.readline().decode().strip()
assert http_get.upper().startswith('GET')
# remaining should be headers
while True:
header = self.rfile.readline().decode().strip()
if not header:
break
head, value = header.split(':', 1)
headers[head.lower().strip()] = value.strip()
return headers
def handshake(self):
headers = self.read_http_headers()
try:
assert headers['upgrade'].lower() == 'websocket'
except AssertionError:
self.keep_alive = False
return
try:
key = headers['sec-websocket-key']
except KeyError:
logger.warning("Client tried to connect but was missing a key")
self.keep_alive = False
return
response = self.make_handshake_response(key)
with self._send_lock:
self.handshake_done = self.request.send(response.encode())
self.valid_client = True
self.server._new_client_(self)
@classmethod
def make_handshake_response(cls, key):
return \
'HTTP/1.1 101 Switching Protocols\r\n'\
'Upgrade: websocket\r\n' \
'Connection: Upgrade\r\n' \
'Sec-WebSocket-Accept: %s\r\n' \
'\r\n' % cls.calculate_response_key(key)
@classmethod
def calculate_response_key(cls, key):
GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
hash = sha1(key.encode() + GUID.encode())
response_key = b64encode(hash.digest()).strip()
return response_key.decode('ASCII')
def finish(self):
self.server._client_left_(self)
def encode_to_UTF8(data):
try:
return data.encode('UTF-8')
except UnicodeEncodeError as e:
logger.error("Could not encode data to UTF-8 -- %s" % e)
return False
except Exception as e:
raise(e)
return False
def try_decode_UTF8(data):
try:
return data.decode('utf-8')
except UnicodeDecodeError:
return False
except Exception as e:
raise(e)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2019-2021 tecnovert # Copyright (c) 2019-2022 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -12,13 +12,26 @@ from enum import IntEnum, auto
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
CURRENT_DB_VERSION = 13 CURRENT_DB_VERSION = 15
CURRENT_DB_DATA_VERSION = 2
Base = declarative_base() Base = declarative_base()
class TableTypes(IntEnum): class Concepts(IntEnum):
OFFER = auto() OFFER = auto()
BID = auto() BID = auto()
NETWORK_MESSAGE = auto()
AUTOMATION = auto()
def strConcepts(state):
if state == Concepts.OFFER:
return 'Offer'
if state == Concepts.BID:
return 'Bid'
if state == Concepts.NETWORK_MESSAGE:
return 'Network Message'
return 'Unknown'
class DBKVInt(Base): class DBKVInt(Base):
@@ -236,16 +249,16 @@ class SmsgAddress(Base):
note = sa.Column(sa.String) note = sa.Column(sa.String)
class EventQueue(Base): class Action(Base):
__tablename__ = 'eventqueue' __tablename__ = 'actions'
event_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) action_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer) active_ind = sa.Column(sa.Integer)
created_at = sa.Column(sa.BigInteger) created_at = sa.Column(sa.BigInteger)
trigger_at = sa.Column(sa.BigInteger) trigger_at = sa.Column(sa.BigInteger)
linked_id = sa.Column(sa.LargeBinary) linked_id = sa.Column(sa.LargeBinary)
event_type = sa.Column(sa.Integer) action_type = sa.Column(sa.Integer)
event_data = sa.Column(sa.LargeBinary) action_data = sa.Column(sa.LargeBinary)
class EventLog(Base): class EventLog(Base):
@@ -288,7 +301,8 @@ class XmrSwap(Base):
bid_accept_msg_id3 = sa.Column(sa.LargeBinary) bid_accept_msg_id3 = sa.Column(sa.LargeBinary)
coin_a_lock_tx_sigs_l_msg_id = sa.Column(sa.LargeBinary) # MSG3L F -> L coin_a_lock_tx_sigs_l_msg_id = sa.Column(sa.LargeBinary) # MSG3L F -> L
coin_a_lock_refund_spend_tx_msg_id = sa.Column(sa.LargeBinary) # MSG4F L -> F coin_a_lock_spend_tx_msg_id = sa.Column(sa.LargeBinary) # MSG4F L -> F
coin_a_lock_release_msg_id = sa.Column(sa.LargeBinary) # MSG5F L -> F
contract_count = sa.Column(sa.Integer) contract_count = sa.Column(sa.Integer)
@@ -372,12 +386,11 @@ class Wallets(Base):
__tablename__ = 'wallets' __tablename__ = 'wallets'
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
coin_id = sa.Column(sa.Integer) coin_id = sa.Column(sa.Integer)
wallet_name = sa.Column(sa.String) wallet_name = sa.Column(sa.String)
wallet_data = sa.Column(sa.String) wallet_data = sa.Column(sa.String)
balance_type = sa.Column(sa.Integer) balance_type = sa.Column(sa.Integer)
amount = sa.Column(sa.BigInteger)
updated_at = sa.Column(sa.BigInteger)
created_at = sa.Column(sa.BigInteger) created_at = sa.Column(sa.BigInteger)
@@ -385,6 +398,7 @@ class KnownIdentity(Base):
__tablename__ = 'knownidentities' __tablename__ = 'knownidentities'
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
address = sa.Column(sa.String) address = sa.Column(sa.String)
label = sa.Column(sa.String) label = sa.Column(sa.String)
publickey = sa.Column(sa.LargeBinary) publickey = sa.Column(sa.LargeBinary)
@@ -397,3 +411,64 @@ class KnownIdentity(Base):
note = sa.Column(sa.String) note = sa.Column(sa.String)
updated_at = sa.Column(sa.BigInteger) updated_at = sa.Column(sa.BigInteger)
created_at = sa.Column(sa.BigInteger) created_at = sa.Column(sa.BigInteger)
class AutomationStrategy(Base):
__tablename__ = 'automationstrategies'
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
label = sa.Column(sa.String)
type_ind = sa.Column(sa.Integer)
only_known_identities = sa.Column(sa.Integer)
num_concurrent = sa.Column(sa.Integer)
data = sa.Column(sa.LargeBinary)
note = sa.Column(sa.String)
created_at = sa.Column(sa.BigInteger)
class AutomationLink(Base):
__tablename__ = 'automationlinks'
# Contains per order/bid options
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
linked_type = sa.Column(sa.Integer)
linked_id = sa.Column(sa.LargeBinary)
strategy_id = sa.Column(sa.Integer)
data = sa.Column(sa.LargeBinary)
repeat_limit = sa.Column(sa.Integer)
repeat_count = sa.Column(sa.Integer)
note = sa.Column(sa.String)
created_at = sa.Column(sa.BigInteger)
__table_args__ = (sa.Index('linked_index', 'linked_type', 'linked_id'), )
class History(Base):
__tablename__ = 'history'
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
concept_type = sa.Column(sa.Integer)
concept_id = sa.Column(sa.Integer)
changed_data = sa.Column(sa.LargeBinary)
created_at = sa.Column(sa.BigInteger)
class BidState(Base):
__tablename__ = 'bidstates'
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
active_ind = sa.Column(sa.Integer)
state_id = sa.Column(sa.Integer)
label = sa.Column(sa.String)
in_progress = sa.Column(sa.Integer)
note = sa.Column(sa.String)
created_at = sa.Column(sa.BigInteger)

230
basicswap/db_upgrades.py Normal file
View File

@@ -0,0 +1,230 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json
import time
from sqlalchemy.orm import scoped_session
from .db import (
BidState,
Concepts,
AutomationStrategy,
CURRENT_DB_VERSION,
CURRENT_DB_DATA_VERSION)
from .basicswap_util import (
BidStates,
strBidState,
isActiveBidState)
def upgradeDatabaseData(self, data_version):
if data_version >= CURRENT_DB_DATA_VERSION:
return
self.log.info('Upgrading database records from version %d to %d.', data_version, CURRENT_DB_DATA_VERSION)
with self.mxDB:
try:
session = scoped_session(self.session_factory)
now = int(time.time())
if data_version < 1:
session.add(AutomationStrategy(
active_ind=1,
label='Accept All',
type_ind=Concepts.OFFER,
data=json.dumps({'exact_rate_only': True,
'max_concurrent_bids': 5}).encode('utf-8'),
only_known_identities=False,
created_at=now))
session.add(AutomationStrategy(
active_ind=1,
label='Accept Known',
type_ind=Concepts.OFFER,
data=json.dumps({'exact_rate_only': True,
'max_concurrent_bids': 5}).encode('utf-8'),
only_known_identities=True,
note='Accept bids from identities with previously successful swaps only',
created_at=now))
for state in BidStates:
session.add(BidState(
active_ind=1,
state_id=int(state),
in_progress=isActiveBidState(state),
label=strBidState(state),
created_at=now))
if data_version < 2:
for state in (BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS, BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX):
session.add(BidState(
active_ind=1,
state_id=int(state),
in_progress=isActiveBidState(state),
label=strBidState(state),
created_at=now))
self.db_data_version = CURRENT_DB_DATA_VERSION
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:
session.close()
session.remove()
def upgradeDatabase(self, db_version):
if db_version >= CURRENT_DB_VERSION:
return
self.log.info('Upgrading database from version %d to %d.', db_version, CURRENT_DB_VERSION)
while True:
session = scoped_session(self.session_factory)
current_version = db_version
if current_version == 6:
session.execute('ALTER TABLE bids ADD COLUMN security_token BLOB')
session.execute('ALTER TABLE offers ADD COLUMN security_token BLOB')
db_version += 1
elif current_version == 7:
session.execute('ALTER TABLE transactions ADD COLUMN block_hash BLOB')
session.execute('ALTER TABLE transactions ADD COLUMN block_height INTEGER')
session.execute('ALTER TABLE transactions ADD COLUMN block_time INTEGER')
db_version += 1
elif current_version == 8:
session.execute('''
CREATE TABLE wallets (
record_id INTEGER NOT NULL,
coin_id INTEGER,
wallet_name VARCHAR,
wallet_data VARCHAR,
balance_type INTEGER,
created_at BIGINT,
PRIMARY KEY (record_id))''')
db_version += 1
elif current_version == 9:
session.execute('ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR')
db_version += 1
elif current_version == 10:
session.execute('ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER')
session.execute('ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER')
session.execute('ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR')
session.execute('ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR')
session.execute('UPDATE smsgaddresses SET active_ind = 1, created_at = 1')
session.execute('ALTER TABLE offers ADD COLUMN addr_to VARCHAR')
session.execute(f'UPDATE offers SET addr_to = "{self.network_addr}"')
db_version += 1
elif current_version == 11:
session.execute('ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER')
session.execute('ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER')
session.execute('ALTER TABLE bids ADD COLUMN protocol_version INTEGER')
session.execute('ALTER TABLE offers ADD COLUMN protocol_version INTEGER')
session.execute('ALTER TABLE transactions ADD COLUMN tx_data BLOB')
db_version += 1
elif current_version == 12:
session.execute('''
CREATE TABLE knownidentities (
record_id INTEGER NOT NULL,
address VARCHAR,
label VARCHAR,
publickey BLOB,
num_sent_bids_successful INTEGER,
num_recv_bids_successful INTEGER,
num_sent_bids_rejected INTEGER,
num_recv_bids_rejected INTEGER,
num_sent_bids_failed INTEGER,
num_recv_bids_failed INTEGER,
note VARCHAR,
updated_at BIGINT,
created_at BIGINT,
PRIMARY KEY (record_id))''')
session.execute('ALTER TABLE bids ADD COLUMN reject_code INTEGER')
session.execute('ALTER TABLE bids ADD COLUMN rate INTEGER')
session.execute('ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER')
session.execute('ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER')
db_version += 1
elif current_version == 13:
db_version += 1
session.execute('''
CREATE TABLE automationstrategies (
record_id INTEGER NOT NULL,
active_ind INTEGER,
label VARCHAR,
type_ind INTEGER,
only_known_identities INTEGER,
num_concurrent INTEGER,
data BLOB,
note VARCHAR,
created_at BIGINT,
PRIMARY KEY (record_id))''')
session.execute('''
CREATE TABLE automationlinks (
record_id INTEGER NOT NULL,
active_ind INTEGER,
linked_type INTEGER,
linked_id BLOB,
strategy_id INTEGER,
data BLOB,
repeat_limit INTEGER,
repeat_count INTEGER,
note VARCHAR,
created_at BIGINT,
PRIMARY KEY (record_id))''')
session.execute('''
CREATE TABLE history (
record_id INTEGER NOT NULL,
concept_type INTEGER,
concept_id INTEGER,
changed_data BLOB,
note VARCHAR,
created_at BIGINT,
PRIMARY KEY (record_id))''')
session.execute('''
CREATE TABLE bidstates (
record_id INTEGER NOT NULL,
active_ind INTEGER,
state_id INTEGER,
label VARCHAR,
in_progress INTEGER,
note VARCHAR,
created_at BIGINT,
PRIMARY KEY (record_id))''')
session.execute('ALTER TABLE wallets ADD COLUMN active_ind INTEGER')
session.execute('ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER')
session.execute('ALTER TABLE eventqueue RENAME TO actions')
session.execute('ALTER TABLE actions RENAME COLUMN event_id TO action_id')
session.execute('ALTER TABLE actions RENAME COLUMN event_type TO action_type')
session.execute('ALTER TABLE actions RENAME COLUMN event_data TO action_data')
elif current_version == 14:
db_version += 1
session.execute('ALTER TABLE xmr_swaps ADD COLUMN coin_a_lock_release_msg_id BLOB')
session.execute('ALTER TABLE xmr_swaps RENAME COLUMN coin_a_lock_refund_spend_tx_msg_id TO coin_a_lock_spend_tx_msg_id')
if current_version != db_version:
self.db_version = db_version
self.setIntKVInSession('db_version', db_version, session)
session.commit()
session.close()
session.remove()
self.log.info('Upgraded database to version {}'.format(self.db_version))
continue
break
if db_version != CURRENT_DB_VERSION:
raise ValueError('Unable to upgrade database.')

View File

@@ -75,7 +75,7 @@ class ExplorerBitAps(Explorer):
# Can't get unspents return only if exactly one transaction exists # Can't get unspents return only if exactly one transaction exists
data = json.loads(self.readURL(self.base_url + '/address/transactions/' + address)) data = json.loads(self.readURL(self.base_url + '/address/transactions/' + address))
try: try:
assert(data['data']['list'] == 1) assert data['data']['list'] == 1
except Exception as ex: except Exception as ex:
self.log.debug('Explorer error: {}'.format(str(ex))) self.log.debug('Explorer error: {}'.format(str(ex)))
return None return None

File diff suppressed because it is too large Load Diff

View File

View File

@@ -14,22 +14,22 @@ import traceback
from io import BytesIO from io import BytesIO
from basicswap.contrib.test_framework import segwit_addr from basicswap.contrib.test_framework import segwit_addr
from .util import ( from basicswap.util import (
dumpj, dumpj,
ensure, ensure,
make_int, make_int,
b2h, i2b, b2i, i2h) b2h, i2b, b2i, i2h)
from .util.ecc import ( from basicswap.util.ecc import (
ep, ep,
pointToCPK, CPKToPoint, pointToCPK, CPKToPoint,
getSecretInt) getSecretInt)
from .util.script import ( from basicswap.util.script import (
decodeScriptNum, decodeScriptNum,
getCompactSizeLen, getCompactSizeLen,
SerialiseNumCompact, SerialiseNumCompact,
getWitnessElementLen, getWitnessElementLen,
) )
from .util.address import ( from basicswap.util.address import (
toWIF, toWIF,
b58encode, b58encode,
decodeWif, decodeWif,
@@ -47,7 +47,7 @@ from coincurve.ecdsaotves import (
ecdsaotves_dec_sig, ecdsaotves_dec_sig,
ecdsaotves_rec_enc_key) ecdsaotves_rec_enc_key)
from .contrib.test_framework.messages import ( from basicswap.contrib.test_framework.messages import (
COIN, COIN,
COutPoint, COutPoint,
CTransaction, CTransaction,
@@ -56,7 +56,7 @@ from .contrib.test_framework.messages import (
CTxOut, CTxOut,
FromHex) FromHex)
from .contrib.test_framework.script import ( from basicswap.contrib.test_framework.script import (
CScript, CScriptOp, CScript, CScriptOp,
OP_IF, OP_ELSE, OP_ENDIF, OP_IF, OP_ELSE, OP_ENDIF,
OP_0, OP_2, OP_0, OP_2,
@@ -68,11 +68,11 @@ from .contrib.test_framework.script import (
SegwitV0SignatureHash, SegwitV0SignatureHash,
hash160) hash160)
from .basicswap_util import ( from basicswap.basicswap_util import (
TxLockTypes) TxLockTypes)
from .chainparams import CoinInterface, Coins from basicswap.chainparams import CoinInterface, Coins
from .rpc import make_rpc_func, openrpc from basicswap.rpc import make_rpc_func, openrpc
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
@@ -185,9 +185,16 @@ class BTCInterface(CoinInterface):
self.blocks_confirmed = coin_settings['blocks_confirmed'] self.blocks_confirmed = coin_settings['blocks_confirmed']
self.setConfTarget(coin_settings['conf_target']) self.setConfTarget(coin_settings['conf_target'])
self._use_segwit = coin_settings['use_segwit'] self._use_segwit = coin_settings['use_segwit']
self._connection_type = coin_settings['connection_type']
self._sc = swap_client self._sc = swap_client
self._log = self._sc.log if self._sc and self._sc.log else logging self._log = self._sc.log if self._sc and self._sc.log else logging
def using_segwit(self):
return self._use_segwit
def get_connection_type(self):
return self._connection_type
def open_rpc(self, wallet=None): def open_rpc(self, wallet=None):
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host) return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
@@ -208,11 +215,14 @@ class BTCInterface(CoinInterface):
rpc_conn.close() rpc_conn.close()
def setConfTarget(self, new_conf_target): def setConfTarget(self, new_conf_target):
assert(new_conf_target >= 1 and new_conf_target < 33), 'Invalid conf_target value' ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
self._conf_target = new_conf_target self._conf_target = new_conf_target
def testDaemonRPC(self): def testDaemonRPC(self, with_wallet=True):
if with_wallet:
self.rpc_callback('getwalletinfo', []) self.rpc_callback('getwalletinfo', [])
else:
self.rpc_callback('getblockchaininfo', [])
def getDaemonVersion(self): def getDaemonVersion(self):
return self.rpc_callback('getnetworkinfo')['version'] return self.rpc_callback('getnetworkinfo')['version']
@@ -282,12 +292,14 @@ class BTCInterface(CoinInterface):
def get_fee_rate(self, conf_target=2): def get_fee_rate(self, conf_target=2):
try: try:
return self.rpc_callback('estimatesmartfee', [conf_target])['feerate'], 'estimatesmartfee' fee_rate = self.rpc_callback('estimatesmartfee', [conf_target])['feerate']
assert (fee_rate > 0.0), 'Non positive feerate'
return fee_rate, 'estimatesmartfee'
except Exception: except Exception:
try: try:
fee_rate = self.rpc_callback('getwalletinfo')['paytxfee'], 'paytxfee' fee_rate = self.rpc_callback('getwalletinfo')['paytxfee']
assert(fee_rate > 0.0), '0 feerate' assert (fee_rate > 0.0), 'Non positive feerate'
return fee_rate return fee_rate, 'paytxfee'
except Exception: except Exception:
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee' return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
@@ -985,7 +997,7 @@ class BTCInterface(CoinInterface):
if not addr_info['iswatchonly']: if not addr_info['iswatchonly']:
ro = self.rpc_callback('importaddress', [dest_address, 'bid', False]) ro = self.rpc_callback('importaddress', [dest_address, 'bid', False])
self._log.info('Imported watch-only addr: {}'.format(dest_address)) self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning chain from height: {}'.format(rescan_from)) self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
self.rpc_callback('rescanblockchain', [rescan_from]) self.rpc_callback('rescanblockchain', [rescan_from])
return_txid = True if txid is None else False return_txid = True if txid is None else False
@@ -1147,6 +1159,20 @@ class BTCInterface(CoinInterface):
address = self.getNewAddress(self._use_segwit, 'create_utxo') address = self.getNewAddress(self._use_segwit, 'create_utxo')
return self.withdrawCoin(self.format_amount(value_sats), address, False), address return self.withdrawCoin(self.format_amount(value_sats), address, False), address
def createRawSignedTransaction(self, addr_to, amount):
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
options = {
'lockUnspents': True,
'conf_target': self._conf_target,
}
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
txn_signed = self.rpc_callback('signrawtransactionwithwallet', [txn_funded])['hex']
return txn_signed
def getBlockWithTxns(self, block_hash):
return self.rpc_callback('getblock', [block_hash, 2])
def testBTCInterface(): def testBTCInterface():
print('testBTCInterface') print('testBTCInterface')

View File

View File

@@ -0,0 +1,180 @@
# Copyright (c) 2011 Jeff Garzik
#
# Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
#
# Copyright (c) 2007 Jan-Klaas Kollhof
#
# This file is part of jsonrpc.
#
# jsonrpc is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this software; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""HTTP proxy for opening RPC connection to pivxd.
AuthServiceProxy has the following improvements over python-jsonrpc's
ServiceProxy class:
- HTTP connections persist for the life of the AuthServiceProxy object
(if server supports HTTP/1.1)
- sends protocol 'version', per JSON-RPC 1.1
- sends proper, incrementing 'id'
- sends Basic HTTP authentication headers
- parses all JSON numbers that look like floats as Decimal
- uses standard Python json lib
"""
import base64
import decimal
import http.client
import json
import logging
import socket
import time
import urllib.parse
HTTP_TIMEOUT = 300
USER_AGENT = "AuthServiceProxy/0.1"
log = logging.getLogger("BitcoinRPC")
class JSONRPCException(Exception):
def __init__(self, rpc_error):
try:
errmsg = '%(message)s (%(code)i)' % rpc_error
except (KeyError, TypeError):
errmsg = ''
super().__init__(errmsg)
self.error = rpc_error
def EncodeDecimal(o):
if isinstance(o, decimal.Decimal):
return str(o)
raise TypeError(repr(o) + " is not JSON serializable")
class AuthServiceProxy():
__id_count = 0
# ensure_ascii: escape unicode as \uXXXX, passed to json.dumps
def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True):
self.__service_url = service_url
self._service_name = service_name
self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests
self.__url = urllib.parse.urlparse(service_url)
port = 80 if self.__url.port is None else self.__url.port
user = None if self.__url.username is None else self.__url.username.encode('utf8')
passwd = None if self.__url.password is None else self.__url.password.encode('utf8')
authpair = user + b':' + passwd
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
if connection:
# Callables re-use the connection of the original proxy
self.__conn = connection
elif self.__url.scheme == 'https':
self.__conn = http.client.HTTPSConnection(self.__url.hostname, port, timeout=timeout)
else:
self.__conn = http.client.HTTPConnection(self.__url.hostname, port, timeout=timeout)
def __getattr__(self, name):
if name.startswith('__') and name.endswith('__'):
# Python internal stuff
raise AttributeError
if self._service_name is not None:
name = "%s.%s" % (self._service_name, name)
return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
def _request(self, method, path, postdata):
'''
Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout).
This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5.
'''
headers = {'Host': self.__url.hostname,
'User-Agent': USER_AGENT,
'Authorization': self.__auth_header,
'Content-type': 'application/json'}
try:
self.__conn.request(method, path, postdata, headers)
return self._get_response()
except http.client.BadStatusLine as e:
if e.line == "''": # if connection was closed, try again
self.__conn.close()
self.__conn.request(method, path, postdata, headers)
return self._get_response()
else:
raise
except (BrokenPipeError, ConnectionResetError):
# Python 3.5+ raises BrokenPipeError instead of BadStatusLine when the connection was reset
# ConnectionResetError happens on FreeBSD with Python 3.4
self.__conn.close()
self.__conn.request(method, path, postdata, headers)
return self._get_response()
def get_request(self, *args, **argsn):
AuthServiceProxy.__id_count += 1
log.debug("-%s-> %s %s" % (AuthServiceProxy.__id_count, self._service_name,
json.dumps(args, default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
if args and argsn:
raise ValueError('Cannot handle both named and positional arguments')
return {'version': '1.1',
'method': self._service_name,
'params': args or argsn,
'id': AuthServiceProxy.__id_count}
def __call__(self, *args, **argsn):
postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
response = self._request('POST', self.__url.path, postdata.encode('utf-8'))
if response['error'] is not None:
raise JSONRPCException(response['error'])
elif 'result' not in response:
raise JSONRPCException({
'code': -343, 'message': 'missing JSON-RPC result'})
else:
return response['result']
def batch(self, rpc_call_list):
postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
log.debug("--> " + postdata)
return self._request('POST', self.__url.path, postdata.encode('utf-8'))
def _get_response(self):
req_start_time = time.time()
try:
http_response = self.__conn.getresponse()
except socket.timeout:
raise JSONRPCException({
'code': -344,
'message': '%r RPC took longer than %f seconds. Consider '
'using larger timeout for calls that take '
'longer to return.' % (self._service_name,
self.__conn.timeout)})
if http_response is None:
raise JSONRPCException({
'code': -342, 'message': 'missing HTTP response from server'})
content_type = http_response.getheader('Content-Type')
if content_type != 'application/json':
raise JSONRPCException({
'code': -342, 'message': 'non-JSON HTTP response with \'%i %s\' from server' % (http_response.status, http_response.reason)})
responsedata = http_response.read().decode('utf8')
response = json.loads(responsedata, parse_float=decimal.Decimal)
elapsed = time.time() - req_start_time
if "error" in response and response["error"] is None:
log.debug("<-%s- [%.6f] %s" % (response["id"], elapsed, json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii)))
else:
log.debug("<-- [%.6f] %s" % (elapsed, responsedata))
return response
def __truediv__(self, relative_uri):
return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)

View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Utilities for doing coverage analysis on the RPC interface.
Provides a way to track which RPC commands are exercised during
testing.
"""
import os
REFERENCE_FILENAME = 'rpc_interface.txt'
class AuthServiceProxyWrapper():
"""
An object that wraps AuthServiceProxy to record specific RPC calls.
"""
def __init__(self, auth_service_proxy_instance, coverage_logfile=None):
"""
Kwargs:
auth_service_proxy_instance (AuthServiceProxy): the instance
being wrapped.
coverage_logfile (str): if specified, write each service_name
out to a file when called.
"""
self.auth_service_proxy_instance = auth_service_proxy_instance
self.coverage_logfile = coverage_logfile
def __getattr__(self, name):
return_val = getattr(self.auth_service_proxy_instance, name)
if not isinstance(return_val, type(self.auth_service_proxy_instance)):
# If proxy getattr returned an unwrapped value, do the same here.
return return_val
return AuthServiceProxyWrapper(return_val, self.coverage_logfile)
def __call__(self, *args, **kwargs):
"""
Delegates to AuthServiceProxy, then writes the particular RPC method
called to a file.
"""
return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
self._log_call()
return return_val
def _log_call(self):
rpc_method = self.auth_service_proxy_instance._service_name
if self.coverage_logfile:
with open(self.coverage_logfile, 'a+', encoding='utf8') as f:
f.write("%s\n" % rpc_method)
def __truediv__(self, relative_uri):
return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri,
self.coverage_logfile)
def get_request(self, *args, **kwargs):
self._log_call()
return self.auth_service_proxy_instance.get_request(*args)
def get_filename(dirname, n_node):
"""
Get a filename unique to the test process ID and node.
This file will contain a list of RPC commands covered.
"""
pid = str(os.getpid())
return os.path.join(
dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node)))
def write_all_rpc_commands(dirname, node):
"""
Write out a list of all RPC functions available in `pivx-cli` for
coverage comparison. This will only happen once per coverage
directory.
Args:
dirname (str): temporary test dir
node (AuthServiceProxy): client
Returns:
bool. if the RPC interface file was written.
"""
filename = os.path.join(dirname, REFERENCE_FILENAME)
if os.path.isfile(filename):
return False
help_output = node.help().split('\n')
commands = set()
for line in help_output:
line = line.strip()
# Ignore blanks and headers
if line and not line.startswith('='):
commands.add("%s\n" % line.split()[0])
with open(filename, 'w', encoding='utf8') as f:
f.writelines(list(commands))
return True

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
#!/usr/bin/env python3
# Copyright (c) 2016-2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Specialized SipHash-2-4 implementations.
This implements SipHash-2-4 for 256-bit integers.
"""
def rotl64(n, b):
return n >> (64 - b) | (n & ((1 << (64 - b)) - 1)) << b
def siphash_round(v0, v1, v2, v3):
v0 = (v0 + v1) & ((1 << 64) - 1)
v1 = rotl64(v1, 13)
v1 ^= v0
v0 = rotl64(v0, 32)
v2 = (v2 + v3) & ((1 << 64) - 1)
v3 = rotl64(v3, 16)
v3 ^= v2
v0 = (v0 + v3) & ((1 << 64) - 1)
v3 = rotl64(v3, 21)
v3 ^= v0
v2 = (v2 + v1) & ((1 << 64) - 1)
v1 = rotl64(v1, 17)
v1 ^= v2
v2 = rotl64(v2, 32)
return (v0, v1, v2, v3)
def siphash256(k0, k1, h):
n0 = h & ((1 << 64) - 1)
n1 = (h >> 64) & ((1 << 64) - 1)
n2 = (h >> 128) & ((1 << 64) - 1)
n3 = (h >> 192) & ((1 << 64) - 1)
v0 = 0x736f6d6570736575 ^ k0
v1 = 0x646f72616e646f6d ^ k1
v2 = 0x6c7967656e657261 ^ k0
v3 = 0x7465646279746573 ^ k1 ^ n0
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0 ^= n0
v3 ^= n1
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0 ^= n1
v3 ^= n2
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0 ^= n2
v3 ^= n3
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0 ^= n3
v3 ^= 0x2000000000000000
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0 ^= 0x2000000000000000
v2 ^= 0xFF
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
v0, v1, v2, v3 = siphash_round(v0, v1, v2, v3)
return v0 ^ v1 ^ v2 ^ v3

View File

@@ -0,0 +1,625 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Helpful routines for regression testing."""
from base64 import b64encode
from binascii import hexlify, unhexlify
from decimal import Decimal, ROUND_DOWN
import hashlib
import json
import logging
import os
import random
import re
from subprocess import CalledProcessError
import time
from . import coverage, messages
from .authproxy import AuthServiceProxy, JSONRPCException
logger = logging.getLogger("TestFramework.utils")
# Assert functions
##################
def assert_fee_amount(fee, tx_size, fee_per_kB):
"""Assert the fee was in range"""
target_fee = round(tx_size * fee_per_kB / 1000, 8)
if fee < target_fee:
raise AssertionError("Fee of %s PIV too low! (Should be %s PIV)" % (str(fee), str(target_fee)))
# allow the wallet's estimation to be at most 2 bytes off
if fee > (tx_size + 20) * fee_per_kB / 1000:
raise AssertionError("Fee of %s PIV too high! (Should be %s PIV)" % (str(fee), str(target_fee)))
def assert_equal(thing1, thing2, *args):
if thing1 != thing2 or any(thing1 != arg for arg in args):
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
def assert_true(condition, message = ""):
if not condition:
raise AssertionError(message)
def assert_false(condition, message = ""):
assert_true(not condition, message)
def assert_greater_than(thing1, thing2):
if thing1 <= thing2:
raise AssertionError("%s <= %s" % (str(thing1), str(thing2)))
def assert_greater_than_or_equal(thing1, thing2):
if thing1 < thing2:
raise AssertionError("%s < %s" % (str(thing1), str(thing2)))
def assert_raises(exc, fun, *args, **kwds):
assert_raises_message(exc, None, fun, *args, **kwds)
def assert_raises_message(exc, message, fun, *args, **kwds):
try:
fun(*args, **kwds)
except JSONRPCException:
raise AssertionError("Use assert_raises_rpc_error() to test RPC failures")
except exc as e:
if message is not None and message not in e.error['message']:
raise AssertionError("Expected substring not found:" + e.error['message'])
except Exception as e:
raise AssertionError("Unexpected exception raised: " + type(e).__name__)
else:
raise AssertionError("No exception raised")
def assert_raises_process_error(returncode, output, fun, *args, **kwds):
"""Execute a process and asserts the process return code and output.
Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError
and verifies that the return code and output are as expected. Throws AssertionError if
no CalledProcessError was raised or if the return code and output are not as expected.
Args:
returncode (int): the process return code.
output (string): [a substring of] the process output.
fun (function): the function to call. This should execute a process.
args*: positional arguments for the function.
kwds**: named arguments for the function.
"""
try:
fun(*args, **kwds)
except CalledProcessError as e:
if returncode != e.returncode:
raise AssertionError("Unexpected returncode %i" % e.returncode)
if output not in e.output:
raise AssertionError("Expected substring not found:" + e.output)
else:
raise AssertionError("No exception raised")
def assert_raises_rpc_error(code, message, fun, *args, **kwds):
"""Run an RPC and verify that a specific JSONRPC exception code and message is raised.
Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException
and verifies that the error code and message are as expected. Throws AssertionError if
no JSONRPCException was raised or if the error code/message are not as expected.
Args:
code (int), optional: the error code returned by the RPC call (defined
in src/rpc/protocol.h). Set to None if checking the error code is not required.
message (string), optional: [a substring of] the error string returned by the
RPC call. Set to None if checking the error string is not required.
fun (function): the function to call. This should be the name of an RPC.
args*: positional arguments for the function.
kwds**: named arguments for the function.
"""
assert try_rpc(code, message, fun, *args, **kwds), "No exception raised"
def try_rpc(code, message, fun, *args, **kwds):
"""Tries to run an rpc command.
Test against error code and message if the rpc fails.
Returns whether a JSONRPCException was raised."""
try:
fun(*args, **kwds)
except JSONRPCException as e:
# JSONRPCException was thrown as expected. Check the code and message values are correct.
if (code is not None) and (code != e.error["code"]):
raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"])
if (message is not None) and (message not in e.error['message']):
raise AssertionError("Expected substring (%s) not found in: %s" % (message, e.error['message']))
return True
except Exception as e:
raise AssertionError("Unexpected exception raised: " + type(e).__name__)
else:
return False
def assert_is_hex_string(string):
try:
int(string, 16)
except Exception as e:
raise AssertionError(
"Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
def assert_is_hash_string(string, length=64):
if not isinstance(string, str):
raise AssertionError("Expected a string, got type %r" % type(string))
elif length and len(string) != length:
raise AssertionError(
"String of length %d expected; got %d" % (length, len(string)))
elif not re.match('[abcdef0-9]+$', string):
raise AssertionError(
"String %r contains invalid characters for a hash." % string)
def assert_array_result(object_array, to_match, expected, should_not_find=False):
"""
Pass in array of JSON objects, a dictionary with key/value pairs
to match against, and another dictionary with expected key/value
pairs.
If the should_not_find flag is true, to_match should not be found
in object_array
"""
if should_not_find:
assert_equal(expected, {})
num_matched = 0
for item in object_array:
all_match = True
for key, value in to_match.items():
if item[key] != value:
all_match = False
if not all_match:
continue
elif should_not_find:
num_matched = num_matched + 1
for key, value in expected.items():
if item[key] != value:
raise AssertionError("%s : expected %s=%s" % (str(item), str(key), str(value)))
num_matched = num_matched + 1
if num_matched == 0 and not should_not_find:
raise AssertionError("No objects matched %s" % (str(to_match)))
if num_matched > 0 and should_not_find:
raise AssertionError("Objects were found %s" % (str(to_match)))
# Utility functions
###################
def check_json_precision():
"""Make sure json library being used does not lose precision converting BTC values"""
n = Decimal("20000000.00000003")
satoshis = int(json.loads(json.dumps(float(n))) * 1.0e8)
if satoshis != 2000000000000003:
raise RuntimeError("JSON encode/decode loses precision")
def count_bytes(hex_string):
return len(bytearray.fromhex(hex_string))
def bytes_to_hex_str(byte_str):
return hexlify(byte_str).decode('ascii')
def hash256(byte_str):
sha256 = hashlib.sha256()
sha256.update(byte_str)
sha256d = hashlib.sha256()
sha256d.update(sha256.digest())
return sha256d.digest()[::-1]
def hex_str_to_bytes(hex_str):
return unhexlify(hex_str.encode('ascii'))
def str_to_b64str(string):
return b64encode(string.encode('utf-8')).decode('ascii')
def satoshi_round(amount):
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
def wait_until(predicate,
*,
attempts=float('inf'),
timeout=float('inf'),
lock=None,
sendpings=None,
mocktime=None):
if attempts == float('inf') and timeout == float('inf'):
timeout = 60
attempt = 0
timeout += time.time()
while attempt < attempts and time.time() < timeout:
if lock:
with lock:
if predicate():
return
else:
if predicate():
return
attempt += 1
time.sleep(0.5)
if sendpings is not None:
sendpings()
if mocktime is not None:
mocktime(1)
# Print the cause of the timeout
assert_greater_than(attempts, attempt)
assert_greater_than(timeout, time.time())
raise RuntimeError('Unreachable')
# RPC/P2P connection constants and functions
############################################
# The maximum number of nodes a single test can spawn
MAX_NODES = 8
# Don't assign rpc or p2p ports lower than this
PORT_MIN = 11000
# The number of ports to "reserve" for p2p and rpc, each
PORT_RANGE = 5000
class PortSeed:
# Must be initialized with a unique integer for each process
n = None
def get_rpc_proxy(url, node_number, timeout=None, coveragedir=None):
"""
Args:
url (str): URL of the RPC server to call
node_number (int): the node number (or id) that this calls to
Kwargs:
timeout (int): HTTP timeout in seconds
Returns:
AuthServiceProxy. convenience object for making RPC calls.
"""
proxy_kwargs = {}
if timeout is not None:
proxy_kwargs['timeout'] = timeout
proxy = AuthServiceProxy(url, **proxy_kwargs)
proxy.url = url # store URL on proxy for info
coverage_logfile = coverage.get_filename(
coveragedir, node_number) if coveragedir else None
return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile)
def p2p_port(n):
assert(n <= MAX_NODES)
return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
def rpc_port(n):
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
def rpc_url(datadir, i, rpchost=None):
rpc_u, rpc_p = get_auth_cookie(datadir)
host = '127.0.0.1'
port = rpc_port(i)
if rpchost:
parts = rpchost.split(':')
if len(parts) == 2:
host, port = parts
else:
host = rpchost
return "http://%s:%s@%s:%d" % (rpc_u, rpc_p, host, int(port))
# Node functions
################
def initialize_datadir(dirname, n):
datadir = get_datadir_path(dirname, n)
if not os.path.isdir(datadir):
os.makedirs(datadir)
with open(os.path.join(datadir, "pivx.conf"), 'w', encoding='utf8') as f:
f.write("regtest=1\n")
f.write("[regtest]\n")
f.write("port=" + str(p2p_port(n)) + "\n")
f.write("rpcport=" + str(rpc_port(n)) + "\n")
f.write("server=1\n")
f.write("keypool=1\n")
f.write("discover=0\n")
f.write("listenonion=0\n")
f.write("spendzeroconfchange=1\n")
f.write("printtoconsole=0\n")
f.write("natpmp=0\n")
return datadir
def get_datadir_path(dirname, n):
return os.path.join(dirname, "node" + str(n))
def append_config(dirname, n, options):
datadir = get_datadir_path(dirname, n)
with open(os.path.join(datadir, "pivx.conf"), 'a', encoding='utf8') as f:
for option in options:
f.write(option + "\n")
def get_auth_cookie(datadir):
user = None
password = None
if os.path.isfile(os.path.join(datadir, "pivx.conf")):
with open(os.path.join(datadir, "pivx.conf"), 'r', encoding='utf8') as f:
for line in f:
if line.startswith("rpcuser="):
assert user is None # Ensure that there is only one rpcuser line
user = line.split("=")[1].strip("\n")
if line.startswith("rpcpassword="):
assert password is None # Ensure that there is only one rpcpassword line
password = line.split("=")[1].strip("\n")
if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")):
with open(os.path.join(datadir, "regtest", ".cookie"), 'r', encoding="utf8") as f:
userpass = f.read()
split_userpass = userpass.split(':')
user = split_userpass[0]
password = split_userpass[1]
if user is None or password is None:
raise ValueError("No RPC credentials")
return user, password
# If a cookie file exists in the given datadir, delete it.
def delete_cookie_file(datadir):
if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")):
logger.debug("Deleting leftover cookie file")
os.remove(os.path.join(datadir, "regtest", ".cookie"))
def get_bip9_status(node, key):
info = node.getblockchaininfo()
return info['bip9_softforks'][key]
def set_node_times(nodes, t):
for node in nodes:
node.setmocktime(t)
def disconnect_nodes(from_connection, node_num):
for addr in [peer['addr'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']]:
try:
from_connection.disconnectnode(addr)
except JSONRPCException as e:
# If this node is disconnected between calculating the peer id
# and issuing the disconnect, don't worry about it.
# This avoids a race condition if we're mass-disconnecting peers.
if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
raise
# wait to disconnect
wait_until(lambda: [peer['addr'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']] == [], timeout=5)
def connect_nodes(from_connection, node_num):
ip_port = "127.0.0.1:" + str(p2p_port(node_num))
from_connection.addnode(ip_port, "onetry")
# poll until version handshake complete to avoid race conditions
# with transaction relaying
wait_until(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
def connect_nodes_clique(nodes):
l = len(nodes)
for a in range(l):
for b in range(a, l):
connect_nodes(nodes[a], b)
connect_nodes(nodes[b], a)
# Transaction/Block functions
#############################
def find_output(node, txid, amount):
"""
Return index to output of txid with value amount
Raises exception if there is none.
"""
txdata = node.getrawtransaction(txid, 1)
for i in range(len(txdata["vout"])):
if txdata["vout"][i]["value"] == amount:
return i
raise RuntimeError("find_output txid %s : %s not found" % (txid, str(amount)))
def gather_inputs(from_node, amount_needed, confirmations_required=1):
"""
Return a random set of unspent txouts that are enough to pay amount_needed
"""
assert(confirmations_required >= 0)
utxo = from_node.listunspent(confirmations_required)
random.shuffle(utxo)
inputs = []
total_in = Decimal("0.00000000")
while total_in < amount_needed and len(utxo) > 0:
t = utxo.pop()
total_in += t["amount"]
inputs.append({"txid": t["txid"], "vout": t["vout"], "address": t["address"]})
if total_in < amount_needed:
raise RuntimeError("Insufficient funds: need %d, have %d" % (amount_needed, total_in))
return (total_in, inputs)
def make_change(from_node, amount_in, amount_out, fee):
"""
Create change output(s), return them
"""
outputs = {}
amount = amount_out + fee
change = amount_in - amount
if change > amount * 2:
# Create an extra change output to break up big inputs
change_address = from_node.getnewaddress()
# Split change in two, being careful of rounding:
outputs[change_address] = Decimal(change / 2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN)
change = amount_in - amount - outputs[change_address]
if change > 0:
outputs[from_node.getnewaddress()] = change
return outputs
def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
"""
Create a random transaction.
Returns (txid, hex-encoded-transaction-data, fee)
"""
from_node = random.choice(nodes)
to_node = random.choice(nodes)
fee = min_fee + fee_increment * random.randint(0, fee_variants)
(total_in, inputs) = gather_inputs(from_node, amount + fee)
outputs = make_change(from_node, total_in, amount, fee)
outputs[to_node.getnewaddress()] = float(amount)
rawtx = from_node.createrawtransaction(inputs, outputs)
signresult = from_node.signrawtransaction(rawtx)
txid = from_node.sendrawtransaction(signresult["hex"], True)
return (txid, signresult["hex"], fee)
# Helper to create at least "count" utxos
# Pass in a fee that is sufficient for relay and mining new transactions.
def create_confirmed_utxos(fee, node, count):
to_generate = int(0.5 * count) + 101
while to_generate > 0:
node.generate(min(25, to_generate))
to_generate -= 25
utxos = node.listunspent()
iterations = count - len(utxos)
addr1 = node.getnewaddress()
addr2 = node.getnewaddress()
if iterations <= 0:
return utxos
for i in range(iterations):
t = utxos.pop()
inputs = []
inputs.append({"txid": t["txid"], "vout": t["vout"]})
outputs = {}
send_value = t['amount'] - fee
outputs[addr1] = float(satoshi_round(send_value / 2))
outputs[addr2] = float(satoshi_round(send_value / 2))
raw_tx = node.createrawtransaction(inputs, outputs)
signed_tx = node.signrawtransaction(raw_tx)["hex"]
node.sendrawtransaction(signed_tx)
while (node.getmempoolinfo()['size'] > 0):
node.generate(1)
utxos = node.listunspent()
assert(len(utxos) >= count)
return utxos
# Create large OP_RETURN txouts that can be appended to a transaction
# to make it large (helper for constructing large transactions).
def gen_return_txouts():
# Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create
# So we have big transactions (and therefore can't fit very many into each block)
# create one script_pubkey
script_pubkey = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes
for i in range(512):
script_pubkey = script_pubkey + "01"
# concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
txouts = "81"
for k in range(128):
# add txout value
txouts = txouts + "0000000000000000"
# add length of script_pubkey
txouts = txouts + "fd0402"
# add script_pubkey
txouts = txouts + script_pubkey
return txouts
def create_tx(node, coinbase, to_address, amount):
inputs = [{"txid": coinbase, "vout": 0}]
outputs = {to_address: amount}
rawtx = node.createrawtransaction(inputs, outputs)
signresult = node.signrawtransaction(rawtx)
assert_equal(signresult["complete"], True)
return signresult["hex"]
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
# transaction to make it large. See gen_return_txouts() above.
def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
addr = node.getnewaddress()
txids = []
for _ in range(num):
t = utxos.pop()
inputs = [{"txid": t["txid"], "vout": t["vout"]}]
outputs = {}
change = t['amount'] - fee
outputs[addr] = float(satoshi_round(change))
rawtx = node.createrawtransaction(inputs, outputs)
newtx = rawtx[0:92]
newtx = newtx + txouts
newtx = newtx + rawtx[94:]
signresult = node.signrawtransaction(newtx, None, None, "NONE")
txid = node.sendrawtransaction(signresult["hex"], True)
txids.append(txid)
return txids
def mine_large_block(node, utxos=None):
# generate a 66k transaction,
# and 14 of them is close to the 1MB block limit
num = 14
txouts = gen_return_txouts()
utxos = utxos if utxos is not None else []
if len(utxos) < num:
utxos.clear()
utxos.extend(node.listunspent())
fee = 100 * node.getnetworkinfo()["relayfee"]
create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee)
node.generate(1)
def find_vout_for_address(node, txid, addr):
"""
Locate the vout index of the given transaction sending to the
given address. Raises runtime error exception if not found.
"""
tx = node.getrawtransaction(txid, True)
for i in range(len(tx["vout"])):
if any([addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]):
return i
raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr))
# PIVX specific utils
DEFAULT_FEE = 0.01
SPORK_ACTIVATION_TIME = 1563253447
SPORK_DEACTIVATION_TIME = 4070908800
def DecimalAmt(x):
"""Return Decimal from float for equality checks against rpc outputs"""
return Decimal("{:0.8f}".format(x))
# Find a coinstake/coinbase address on the node, filtering by the number of UTXOs it has.
# If no filter is provided, returns the coinstake/coinbase address on the node containing
# the greatest number of spendable UTXOs.
# The default cached chain has one address per coinbase output.
def get_coinstake_address(node, expected_utxos=None):
addrs = [utxo['address'] for utxo in node.listunspent() if utxo['generated']]
assert(len(set(addrs)) > 0)
if expected_utxos is None:
addrs = [(addrs.count(a), a) for a in set(addrs)]
return sorted(addrs, reverse=True)[0][1]
addrs = [a for a in set(addrs) if addrs.count(a) == expected_utxos]
assert(len(addrs) > 0)
return addrs[0]
# Deterministic masternodes
def is_coin_locked_by(node, outpoint):
return outpoint.to_json() in node.listlockunspent()
def get_collateral_vout(json_tx):
funding_txidn = -1
for o in json_tx["vout"]:
if o["value"] == Decimal('100'):
funding_txidn = o["n"]
break
assert_greater_than(funding_txidn, -1)
return funding_txidn
# owner and voting keys are created from controller node.
# operator keys are created, if operator_keys is None.
def create_new_dmn(idx, controller, payout_addr, operator_keys):
port = p2p_port(idx) if idx <= MAX_NODES else p2p_port(MAX_NODES) + (idx - MAX_NODES)
ipport = "127.0.0.1:" + str(port)
owner_addr = controller.getnewaddress("mnowner-%d" % idx)
voting_addr = controller.getnewaddress("mnvoting-%d" % idx)
if operator_keys is None:
bls_keypair = controller.generateblskeypair()
operator_pk = bls_keypair["public"]
operator_sk = bls_keypair["secret"]
else:
operator_pk = operator_keys[0]
operator_sk = operator_keys[1]
return messages.Masternode(idx, owner_addr, operator_pk, voting_addr, ipport, payout_addr, operator_sk)
def spend_mn_collateral(spender, dmn):
inputs = [dmn.collateral.to_json()]
outputs = {spender.getnewaddress(): Decimal('99.99')}
sig_res = spender.signrawtransaction(spender.createrawtransaction(inputs, outputs))
assert_equal(sig_res['complete'], True)
return spender.sendrawtransaction(sig_res['hex'])

View File

@@ -5,8 +5,8 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .interface_btc import BTCInterface from .btc import BTCInterface
from .chainparams import Coins from basicswap.chainparams import Coins
class LTCInterface(BTCInterface): class LTCInterface(BTCInterface):

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .btc import BTCInterface
from basicswap.chainparams import Coins
from basicswap.util import (
make_int,
)
class NMCInterface(BTCInterface):
@staticmethod
def coin_type():
return Coins.NMC
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_callback('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible
self._log.debug('[rm] scantxoutset end')
return_txid = True if txid is None else False
for o in ro['unspents']:
if txid and o['txid'] != txid.hex():
continue
# Verify 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
rv = {
'depth': 0,
'height': o['height']}
if o['height'] > 0:
rv['depth'] = ro['height'] - o['height']
if find_index:
rv['index'] = o['vout']
if return_txid:
rv['txid'] = o['txid']
return rv

View File

@@ -8,30 +8,30 @@
import hashlib import hashlib
from enum import IntEnum from enum import IntEnum
from .contrib.test_framework.messages import ( from basicswap.contrib.test_framework.messages import (
CTxOutPart, CTxOutPart,
) )
from .contrib.test_framework.script import ( from basicswap.contrib.test_framework.script import (
CScript, CScript,
OP_0, OP_0,
OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG
) )
from .util import ( from basicswap.util import (
i2b, i2b,
ensure, ensure,
make_int, make_int,
TemporaryError, TemporaryError,
) )
from .util.script import ( from basicswap.util.script import (
getP2WSH, getP2WSH,
getCompactSizeLen, getCompactSizeLen,
getWitnessElementLen, getWitnessElementLen,
) )
from .util.address import ( from basicswap.util.address import (
toWIF, toWIF,
encodeStealthAddress) encodeStealthAddress)
from .chainparams import Coins, chainparams from basicswap.chainparams import Coins, chainparams
from .interface_btc import BTCInterface from .btc import BTCInterface
class BalanceTypes(IntEnum): class BalanceTypes(IntEnum):
@@ -65,9 +65,13 @@ class PARTInterface(BTCInterface):
def txoType(): def txoType():
return CTxOutPart return CTxOutPart
def setDefaults(self) -> None: def __init__(self, coin_settings, network, swap_client=None):
super().setDefaults() super().__init__(coin_settings, network, swap_client)
self._anon_tx_ring_size = 8 # TODO: Make option self.setAnonTxRingSize(int(coin_settings.get('anon_tx_ring_size', 12)))
def setAnonTxRingSize(self, value):
ensure(value >= 3 and value < 33, 'Invalid anon_tx_ring_size value')
self._anon_tx_ring_size = value
def knownWalletSeed(self): def knownWalletSeed(self):
# TODO: Double check # TODO: Double check
@@ -663,7 +667,7 @@ class PARTInterfaceAnon(PARTInterface):
wif_scan_key = toWIF(wif_prefix, kbv) wif_scan_key = toWIF(wif_prefix, kbv)
self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()]) self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()])
self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr)) self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning chain from height: {}'.format(restore_height)) self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
self.rpc_callback('rescanblockchain', [restore_height]) self.rpc_callback('rescanblockchain', [restore_height])
params = [{'include_watchonly': True, 'search': sx_addr}] params = [{'include_watchonly': True, 'search': sx_addr}]
@@ -696,7 +700,7 @@ class PARTInterfaceAnon(PARTInterface):
wif_spend_key = toWIF(wif_prefix, kbs) wif_spend_key = toWIF(wif_prefix, kbs)
self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key]) self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key])
self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr)) self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr))
self._log.info('Rescanning chain from height: {}'.format(restore_height)) self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height))
self.rpc_callback('rescanblockchain', [restore_height]) self.rpc_callback('rescanblockchain', [restore_height])
autxos = self.rpc_callback('listunspentanon', [1, 9999999, [sx_addr]]) autxos = self.rpc_callback('listunspentanon', [1, 9999999, [sx_addr]])
@@ -708,6 +712,7 @@ class PARTInterfaceAnon(PARTInterface):
utxo = autxos[0] utxo = autxos[0]
utxo_sats = make_int(utxo['amount']) utxo_sats = make_int(utxo['amount'])
if spend_actual_balance and utxo_sats != cb_swap_value: 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)) self._log.warning('Spending actual balance {}, not swap value {}.'.format(utxo_sats, cb_swap_value))
cb_swap_value = utxo_sats cb_swap_value = utxo_sats

View File

@@ -5,8 +5,8 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .interface_btc import BTCInterface from .btc import BTCInterface
from .contrib.test_framework.messages import ( from basicswap.contrib.test_framework.messages import (
CTxOut) CTxOut)

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .btc import BTCInterface
from basicswap.chainparams import Coins
from .contrib.pivx_test_framework.messages import (
CBlock,
ToHex,
FromHex)
class PIVXInterface(BTCInterface):
@staticmethod
def coin_type():
return Coins.PIVX
def createRawSignedTransaction(self, addr_to, amount):
txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}])
fee_rate, fee_src = self.get_fee_rate(self._conf_target)
self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}')
options = {
'lockUnspents': True,
'feeRate': fee_rate,
}
txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex']
txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex']
return txn_signed
def getBlockWithTxns(self, block_hash):
# TODO: Bypass decoderawtransaction and getblockheader
block = self.rpc_callback('getblock', [block_hash, False])
block_header = self.rpc_callback('getblockheader', [block_hash])
decoded_block = CBlock()
decoded_block = FromHex(decoded_block, block)
tx_rv = []
for tx in decoded_block.vtx:
tx_dec = self.rpc_callback('decoderawtransaction', [ToHex(tx)])
tx_rv.append(tx_dec)
block_rv = {
'hash': block_hash,
'tx': tx_rv,
'confirmations': block_header['confirmations'],
'height': block_header['height'],
'version': block_header['version'],
'merkleroot': block_header['merkleroot'],
}
return block_rv

View File

@@ -1,41 +1,41 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2021 tecnovert # Copyright (c) 2020-2022 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import time import json
import logging import logging
import basicswap.contrib.ed25519_fast as edf import basicswap.contrib.ed25519_fast as edf
import basicswap.ed25519_fast_util as edu import basicswap.ed25519_fast_util as edu
import basicswap.util_xmr as xmr_util import basicswap.util_xmr as xmr_util
from coincurve.ed25519 import ( from coincurve.ed25519 import (
ed25519_add,
ed25519_get_pubkey, ed25519_get_pubkey,
ed25519_scalar_add, ed25519_scalar_add,
ed25519_add) )
from coincurve.keys import PrivateKey from coincurve.keys import PrivateKey
from coincurve.dleag import ( from coincurve.dleag import (
verify_ed25519_point, dleag_prove,
dleag_proof_len,
dleag_verify, dleag_verify,
dleag_prove) dleag_proof_len,
verify_ed25519_point,
)
from .util import ( from basicswap.util import (
ensure,
dumpj, dumpj,
ensure,
make_int, make_int,
TemporaryError) TemporaryError)
from .rpc_xmr import ( from basicswap.rpc_xmr import (
make_xmr_rpc_func, make_xmr_rpc_func,
make_xmr_rpc2_func, make_xmr_rpc2_func,
make_xmr_wallet_rpc_func) make_xmr_wallet_rpc_func)
from .util import ( from basicswap.util import (
b2i, b2h) b2i, b2h)
from .chainparams import CoinInterface, Coins from basicswap.chainparams import XMR_COIN, CoinInterface, Coins
XMR_COIN = 10 ** 12
class XMRInterface(CoinInterface): class XMRInterface(CoinInterface):
@@ -67,7 +67,7 @@ class XMRInterface(CoinInterface):
super().__init__(network) super().__init__(network)
self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1')) self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1'))
self.rpc_cb2 = make_xmr_rpc2_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint self.rpc_cb2 = make_xmr_rpc2_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint
self.rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth']) self.rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'))
self.blocks_confirmed = coin_settings['blocks_confirmed'] self.blocks_confirmed = coin_settings['blocks_confirmed']
self._restore_height = coin_settings.get('restore_height', 0) self._restore_height = coin_settings.get('restore_height', 0)
@@ -110,29 +110,41 @@ class XMRInterface(CoinInterface):
with self._mx_wallet: with self._mx_wallet:
self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename})
def testDaemonRPC(self): def testDaemonRPC(self, with_wallet=True):
self.rpc_wallet_cb('get_languages') self.rpc_wallet_cb('get_languages')
def getDaemonVersion(self): def getDaemonVersion(self):
return self.rpc_wallet_cb('get_version')['version'] return self.rpc_wallet_cb('get_version')['version']
def getBlockchainInfo(self): def getBlockchainInfo(self):
rv = {} get_height = self.rpc_cb2('get_height', timeout=30)
rv = {
'blocks': get_height['height'],
'verificationprogress': 0.0,
}
try:
# get_block_count.block_count is how many blocks are in the longest chain known to the node.
# get_block_count returns "Internal error" if bootstrap-daemon is active # get_block_count returns "Internal error" if bootstrap-daemon is active
# rv['blocks'] = self.rpc_cb('get_block_count')['count'] if get_height['untrusted'] is True:
rv['blocks'] = self.rpc_cb2('get_height', timeout=30)['height'] rv['bootstrapping'] = True
get_info = self.rpc_cb2('get_info', timeout=30)
if 'height_without_bootstrap' in get_info:
rv['blocks'] = get_info['height_without_bootstrap']
# sync_info = self.rpc_cb('sync_info', timeout=30) rv['known_block_count'] = get_info['height']
# rv['verificationprogress'] = 0.0 if 'spans' in sync_info else 1.0 if rv['known_block_count'] > rv['blocks']:
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
else:
rv['known_block_count'] = self.rpc_cb('get_block_count', timeout=30)['count']
rv['verificationprogress'] = rv['blocks'] / rv['known_block_count']
except Exception as e:
self._log.warning('XMR get_block_count failed with: %s', str(e))
rv['verificationprogress'] = 0.0 rv['verificationprogress'] = 0.0
return rv return rv
def getChainHeight(self): def getChainHeight(self):
# get_block_count returns "Internal error" if bootstrap-daemon is active
# return self.rpc_cb('get_info')['height']
# return self.rpc_cb('get_block_count')['count']
return self.rpc_cb2('get_height', timeout=30)['height'] return self.rpc_cb2('get_height', timeout=30)['height']
def getWalletInfo(self): def getWalletInfo(self):
@@ -155,7 +167,6 @@ class XMRInterface(CoinInterface):
def getNewAddress(self, placeholder): def getNewAddress(self, placeholder):
with self._mx_wallet: with self._mx_wallet:
self._log.warning('TODO - subaddress?')
self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename})
return self.rpc_wallet_cb('create_address', {'account_index': 0})['address'] return self.rpc_wallet_cb('create_address', {'account_index': 0})['address']
@@ -217,7 +228,7 @@ class XMRInterface(CoinInterface):
def encodeSharedAddress(self, Kbv, Kbs): def encodeSharedAddress(self, Kbv, Kbs):
return xmr_util.encode_address(Kbv, Kbs) return xmr_util.encode_address(Kbv, Kbs)
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate): def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for=10):
with self._mx_wallet: with self._mx_wallet:
self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename})
@@ -230,14 +241,17 @@ class XMRInterface(CoinInterface):
self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr) self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr)
tx_hash = bytes.fromhex(rv['tx_hash']) tx_hash = bytes.fromhex(rv['tx_hash'])
# Debug if self._sc.debug:
for i in range(10): i = 0
while not self._sc.delay_event.is_set():
params = {'out': True, 'pending': True, 'failed': True, 'pool': True, } params = {'out': True, 'pending': True, 'failed': True, 'pool': True, }
rv = self.rpc_wallet_cb('get_transfers', params) rv = self.rpc_wallet_cb('get_transfers', params)
self._log.info('[rm] get_transfers {}'.format(dumpj(rv))) self._log.debug('get_transfers {}'.format(dumpj(rv)))
if 'pending' not in rv: if 'pending' not in rv:
break break
time.sleep(1) if i >= delay_for:
break
self._sc.delay_event.wait(1.0)
return tx_hash return tx_hash
@@ -333,7 +347,6 @@ class XMRInterface(CoinInterface):
return True return True
# TODO: Is it necessary to check the address? # TODO: Is it necessary to check the address?
''' '''
rv = self.rpc_wallet_cb('get_balance') rv = self.rpc_wallet_cb('get_balance')
print('get_balance', rv) print('get_balance', rv)
@@ -346,7 +359,9 @@ class XMRInterface(CoinInterface):
if i >= num_tries: if i >= num_tries:
raise ValueError('Balance not confirming on node') raise ValueError('Balance not confirming on node')
time.sleep(1) self._sc.delay_event.wait(1.0)
if self._sc.delay_event.is_set():
raise ValueError('Stopped')
return False return False
@@ -431,7 +446,7 @@ class XMRInterface(CoinInterface):
params['priority'] = self._fee_priority params['priority'] = self._fee_priority
rv = self.rpc_wallet_cb('sweep_all', params) rv = self.rpc_wallet_cb('sweep_all', params)
print('sweep_all', rv) self._log.debug('sweep_all {}'.format(json.dumps(rv)))
return bytes.fromhex(rv['tx_hash_list'][0]) return bytes.fromhex(rv['tx_hash_list'][0])

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2021 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .interface_btc import BTCInterface
from .chainparams import Coins
class NMCInterface(BTCInterface):
@staticmethod
def coin_type():
return Coins.NMC
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
raise ValueError('TODO: Use scantxoutset')

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020-2021 tecnovert # Copyright (c) 2020-2022 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -16,6 +16,7 @@ from .basicswap_util import (
) )
from .chainparams import ( from .chainparams import (
Coins, Coins,
chainparams,
) )
from .ui.util import ( from .ui.util import (
PAGE_LIMIT, PAGE_LIMIT,
@@ -27,7 +28,9 @@ from .ui.util import (
get_data_entry_or, get_data_entry_or,
have_data_entry, have_data_entry,
tickerToCoinId, tickerToCoinId,
listOldBidStates,
) )
from .ui.page_offers import postNewOffer
from .protocols.xmr_swap_1 import recoverNoScriptTxnWithKey, getChainBSplitKey from .protocols.xmr_swap_1 import recoverNoScriptTxnWithKey, getChainBSplitKey
@@ -59,7 +62,29 @@ def withdraw_coin(swap_client, coin_type, post_string, is_json):
return {'txid': txid_hex} return {'txid': txid_hex}
def js_coins(self, url_split, post_string, is_json):
swap_client = self.server.swap_client
coins = []
for coin in Coins:
cc = swap_client.coin_clients[coin]
entry = {
'id': int(coin),
'ticker': chainparams[cc['coin']]['ticker'],
'name': cc['name'].capitalize(),
'active': False if cc['connection_type'] == 'none' else True,
}
if coin == Coins.PART_ANON:
entry['variant'] = 'Anon'
elif coin == Coins.PART_BLIND:
entry['variant'] = 'Blind'
coins.append(entry)
return bytes(json.dumps(coins), 'UTF-8')
def js_wallets(self, url_split, post_string, is_json): def js_wallets(self, url_split, post_string, is_json):
swap_client = self.server.swap_client
if len(url_split) > 3: if len(url_split) > 3:
ticker_str = url_split[3] ticker_str = url_split[3]
coin_type = tickerToCoinId(ticker_str) coin_type = tickerToCoinId(ticker_str)
@@ -67,13 +92,19 @@ def js_wallets(self, url_split, post_string, is_json):
if len(url_split) > 4: if len(url_split) > 4:
cmd = url_split[4] cmd = url_split[4]
if cmd == 'withdraw': if cmd == 'withdraw':
return bytes(json.dumps(withdraw_coin(self.server.swap_client, coin_type, post_string, is_json)), 'UTF-8') return bytes(json.dumps(withdraw_coin(swap_client, coin_type, post_string, is_json)), 'UTF-8')
if cmd == 'nextdepositaddr':
return bytes(json.dumps(swap_client.cacheNewAddressForCoin(coin_type)), 'UTF-8')
raise ValueError('Unknown command') raise ValueError('Unknown command')
return bytes(json.dumps(self.server.swap_client.getWalletInfo(coin_type)), 'UTF-8')
return bytes(json.dumps(self.server.swap_client.getWalletsInfo()), 'UTF-8') rv = swap_client.getWalletInfo(coin_type)
rv.update(swap_client.getBlockchainInfo(coin_type))
return bytes(json.dumps(rv), 'UTF-8')
return bytes(json.dumps(self.server.swap_client.getWalletsInfo({'ticker_key': True})), 'UTF-8')
def js_offers(self, url_split, post_string, is_json, sent=False): def js_offers(self, url_split, post_string, is_json, sent=False):
swap_client = self.server.swap_client
offer_id = None offer_id = None
if len(url_split) > 3: if len(url_split) > 3:
if url_split[3] == 'new': if url_split[3] == 'new':
@@ -84,7 +115,7 @@ def js_offers(self, url_split, post_string, is_json, sent=False):
form_data['is_json'] = True form_data['is_json'] = True
else: else:
form_data = urllib.parse.parse_qs(post_string) form_data = urllib.parse.parse_qs(post_string)
offer_id = self.postNewOffer(form_data) offer_id = postNewOffer(swap_client, form_data)
rv = {'offer_id': offer_id.hex()} rv = {'offer_id': offer_id.hex()}
return bytes(json.dumps(rv), 'UTF-8') return bytes(json.dumps(rv), 'UTF-8')
offer_id = bytes.fromhex(url_split[3]) offer_id = bytes.fromhex(url_split[3])
@@ -228,6 +259,10 @@ def js_bids(self, url_split, post_string, is_json):
remote_key = get_data_entry(post_data, 'remote_key') remote_key = get_data_entry(post_data, 'remote_key')
return bytes(json.dumps({'txid': recoverNoScriptTxnWithKey(swap_client, bid_id, remote_key).hex()}), 'UTF-8') return bytes(json.dumps({'txid': recoverNoScriptTxnWithKey(swap_client, bid_id, remote_key).hex()}), 'UTF-8')
if len(url_split) > 4 and url_split[4] == 'states':
old_states = listOldBidStates(bid)
return bytes(json.dumps(old_states), 'UTF-8')
edit_bid = False edit_bid = False
show_txns = False show_txns = False
data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, for_api=True) data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, for_api=True)
@@ -304,6 +339,15 @@ def js_rates(self, url_split, post_string, is_json):
return bytes(json.dumps(sc.lookupRates(coin_from, coin_to)), 'UTF-8') return bytes(json.dumps(sc.lookupRates(coin_from, coin_to)), 'UTF-8')
def js_rates_list(self, url_split, query_string, is_json):
get_data = urllib.parse.parse_qs(query_string)
sc = self.server.swap_client
coin_from = getCoinType(get_data['from'][0])
coin_to = getCoinType(get_data['to'][0])
return bytes(json.dumps(sc.lookupRates(coin_from, coin_to, True)), 'UTF-8')
def js_rate(self, url_split, post_string, is_json): def js_rate(self, url_split, post_string, is_json):
if post_string == '': if post_string == '':
raise ValueError('No post data') raise ValueError('No post data')

View File

@@ -2,9 +2,9 @@
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: messages.proto # source: messages.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports) # @@protoc_insertion_point(imports)
@@ -13,872 +13,35 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor( DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0emessages.proto\x12\tbasicswap\"\xa6\x04\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\x12\x18\n\x10protocol_version\x18\x10 \x01(\r\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\"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\"\xb4\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x05 \x01(\x0c\x12\x15\n\rproof_address\x18\x06 \x01(\t\x12\x17\n\x0fproof_signature\x18\x07 \x01(\t\x12\x18\n\x10protocol_version\x18\x08 \x01(\r\"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\"\xb2\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x0c\n\x04pkaf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x08 \x01(\x0c\x12\x18\n\x10protocol_version\x18\t \x01(\r\"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\x03 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x04 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x05 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x08 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\t \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\n \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0b \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\x62\x06proto3')
name='messages.proto',
package='basicswap',
syntax='proto3',
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xa6\x04\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\x12\x18\n\x10protocol_version\x18\x10 \x01(\r\x12\x19\n\x11\x61mount_negotiable\x18\x11 \x01(\x08\x12\x17\n\x0frate_negotiable\x18\x12 \x01(\x08\"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\"\xb4\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x05 \x01(\x0c\x12\x15\n\rproof_address\x18\x06 \x01(\t\x12\x17\n\x0fproof_signature\x18\x07 \x01(\t\x12\x18\n\x10protocol_version\x18\x08 \x01(\r\"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\"\xb2\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x0c\n\x04pkaf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x08 \x01(\x0c\x12\x18\n\x10protocol_version\x18\t \x01(\r\"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\x03 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x04 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x05 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x06 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\x08 \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\t \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\n \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0b \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\x62\x06proto3'
)
_OFFERMESSAGE_LOCKTYPE = _descriptor.EnumDescriptor(
name='LockType',
full_name='basicswap.OfferMessage.LockType',
filename=None,
file=DESCRIPTOR,
create_key=_descriptor._internal_create_key,
values=[
_descriptor.EnumValueDescriptor(
name='NOT_SET', index=0, number=0,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='SEQUENCE_LOCK_BLOCKS', index=1, number=1,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='SEQUENCE_LOCK_TIME', index=2, number=2,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='ABS_LOCK_BLOCKS', index=3, number=3,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
_descriptor.EnumValueDescriptor(
name='ABS_LOCK_TIME', index=4, number=4,
serialized_options=None,
type=None,
create_key=_descriptor._internal_create_key),
],
containing_type=None,
serialized_options=None,
serialized_start=467,
serialized_end=580,
)
_sym_db.RegisterEnumDescriptor(_OFFERMESSAGE_LOCKTYPE)
_OFFERMESSAGE = _descriptor.Descriptor(
name='OfferMessage',
full_name='basicswap.OfferMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='coin_from', full_name='basicswap.OfferMessage.coin_from', index=0,
number=1, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='coin_to', full_name='basicswap.OfferMessage.coin_to', index=1,
number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='amount_from', full_name='basicswap.OfferMessage.amount_from', index=2,
number=3, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='rate', full_name='basicswap.OfferMessage.rate', index=3,
number=4, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='min_bid_amount', full_name='basicswap.OfferMessage.min_bid_amount', index=4,
number=5, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='time_valid', full_name='basicswap.OfferMessage.time_valid', index=5,
number=6, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='lock_type', full_name='basicswap.OfferMessage.lock_type', index=6,
number=7, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='lock_value', full_name='basicswap.OfferMessage.lock_value', index=7,
number=8, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='swap_type', full_name='basicswap.OfferMessage.swap_type', index=8,
number=9, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='proof_address', full_name='basicswap.OfferMessage.proof_address', index=9,
number=10, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='proof_signature', full_name='basicswap.OfferMessage.proof_signature', index=10,
number=11, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='pkhash_seller', full_name='basicswap.OfferMessage.pkhash_seller', index=11,
number=12, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='secret_hash', full_name='basicswap.OfferMessage.secret_hash', index=12,
number=13, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='fee_rate_from', full_name='basicswap.OfferMessage.fee_rate_from', index=13,
number=14, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='fee_rate_to', full_name='basicswap.OfferMessage.fee_rate_to', index=14,
number=15, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='protocol_version', full_name='basicswap.OfferMessage.protocol_version', index=15,
number=16, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='amount_negotiable', full_name='basicswap.OfferMessage.amount_negotiable', index=16,
number=17, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='rate_negotiable', full_name='basicswap.OfferMessage.rate_negotiable', index=17,
number=18, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
_OFFERMESSAGE_LOCKTYPE,
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=30,
serialized_end=580,
)
_BIDMESSAGE = _descriptor.Descriptor(
name='BidMessage',
full_name='basicswap.BidMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='offer_msg_id', full_name='basicswap.BidMessage.offer_msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='time_valid', full_name='basicswap.BidMessage.time_valid', index=1,
number=2, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='amount', full_name='basicswap.BidMessage.amount', index=2,
number=3, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='rate', full_name='basicswap.BidMessage.rate', index=3,
number=4, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='pkhash_buyer', full_name='basicswap.BidMessage.pkhash_buyer', index=4,
number=5, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='proof_address', full_name='basicswap.BidMessage.proof_address', index=5,
number=6, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='proof_signature', full_name='basicswap.BidMessage.proof_signature', index=6,
number=7, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=b"".decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='protocol_version', full_name='basicswap.BidMessage.protocol_version', index=7,
number=8, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=583,
serialized_end=763,
)
_BIDACCEPTMESSAGE = _descriptor.Descriptor(
name='BidAcceptMessage',
full_name='basicswap.BidAcceptMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='bid_msg_id', full_name='basicswap.BidAcceptMessage.bid_msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='initiate_txid', full_name='basicswap.BidAcceptMessage.initiate_txid', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='contract_script', full_name='basicswap.BidAcceptMessage.contract_script', index=2,
number=3, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=765,
serialized_end=851,
)
_OFFERREVOKEMESSAGE = _descriptor.Descriptor(
name='OfferRevokeMessage',
full_name='basicswap.OfferRevokeMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='offer_msg_id', full_name='basicswap.OfferRevokeMessage.offer_msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='signature', full_name='basicswap.OfferRevokeMessage.signature', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=853,
serialized_end=914,
)
_BIDREJECTMESSAGE = _descriptor.Descriptor(
name='BidRejectMessage',
full_name='basicswap.BidRejectMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='bid_msg_id', full_name='basicswap.BidRejectMessage.bid_msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='reject_code', full_name='basicswap.BidRejectMessage.reject_code', index=1,
number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=916,
serialized_end=975,
)
_XMRBIDMESSAGE = _descriptor.Descriptor(
name='XmrBidMessage',
full_name='basicswap.XmrBidMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='offer_msg_id', full_name='basicswap.XmrBidMessage.offer_msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='time_valid', full_name='basicswap.XmrBidMessage.time_valid', index=1,
number=2, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='amount', full_name='basicswap.XmrBidMessage.amount', index=2,
number=3, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='rate', full_name='basicswap.XmrBidMessage.rate', index=3,
number=4, type=4, cpp_type=4, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='pkaf', full_name='basicswap.XmrBidMessage.pkaf', index=4,
number=5, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='kbvf', full_name='basicswap.XmrBidMessage.kbvf', index=5,
number=6, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='kbsf_dleag', full_name='basicswap.XmrBidMessage.kbsf_dleag', index=6,
number=7, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='dest_af', full_name='basicswap.XmrBidMessage.dest_af', index=7,
number=8, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='protocol_version', full_name='basicswap.XmrBidMessage.protocol_version', index=8,
number=9, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=978,
serialized_end=1156,
)
_XMRSPLITMESSAGE = _descriptor.Descriptor(
name='XmrSplitMessage',
full_name='basicswap.XmrSplitMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='msg_id', full_name='basicswap.XmrSplitMessage.msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='msg_type', full_name='basicswap.XmrSplitMessage.msg_type', index=1,
number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='sequence', full_name='basicswap.XmrSplitMessage.sequence', index=2,
number=3, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='dleag', full_name='basicswap.XmrSplitMessage.dleag', index=3,
number=4, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=1158,
serialized_end=1242,
)
_XMRBIDACCEPTMESSAGE = _descriptor.Descriptor(
name='XmrBidAcceptMessage',
full_name='basicswap.XmrBidAcceptMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='bid_msg_id', full_name='basicswap.XmrBidAcceptMessage.bid_msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='pkal', full_name='basicswap.XmrBidAcceptMessage.pkal', index=1,
number=3, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='kbvl', full_name='basicswap.XmrBidAcceptMessage.kbvl', index=2,
number=4, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='kbsl_dleag', full_name='basicswap.XmrBidAcceptMessage.kbsl_dleag', index=3,
number=5, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_tx', full_name='basicswap.XmrBidAcceptMessage.a_lock_tx', index=4,
number=6, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_tx_script', full_name='basicswap.XmrBidAcceptMessage.a_lock_tx_script', index=5,
number=7, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_refund_tx', full_name='basicswap.XmrBidAcceptMessage.a_lock_refund_tx', index=6,
number=8, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_refund_tx_script', full_name='basicswap.XmrBidAcceptMessage.a_lock_refund_tx_script', index=7,
number=9, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_refund_spend_tx', full_name='basicswap.XmrBidAcceptMessage.a_lock_refund_spend_tx', index=8,
number=10, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='al_lock_refund_tx_sig', full_name='basicswap.XmrBidAcceptMessage.al_lock_refund_tx_sig', index=9,
number=11, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=1245,
serialized_end=1501,
)
_XMRBIDLOCKTXSIGSMESSAGE = _descriptor.Descriptor(
name='XmrBidLockTxSigsMessage',
full_name='basicswap.XmrBidLockTxSigsMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='bid_msg_id', full_name='basicswap.XmrBidLockTxSigsMessage.bid_msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='af_lock_refund_spend_tx_esig', full_name='basicswap.XmrBidLockTxSigsMessage.af_lock_refund_spend_tx_esig', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='af_lock_refund_tx_sig', full_name='basicswap.XmrBidLockTxSigsMessage.af_lock_refund_tx_sig', index=2,
number=3, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=1503,
serialized_end=1617,
)
_XMRBIDLOCKSPENDTXMESSAGE = _descriptor.Descriptor(
name='XmrBidLockSpendTxMessage',
full_name='basicswap.XmrBidLockSpendTxMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='bid_msg_id', full_name='basicswap.XmrBidLockSpendTxMessage.bid_msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='a_lock_spend_tx', full_name='basicswap.XmrBidLockSpendTxMessage.a_lock_spend_tx', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='kal_sig', full_name='basicswap.XmrBidLockSpendTxMessage.kal_sig', index=2,
number=3, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=1619,
serialized_end=1707,
)
_XMRBIDLOCKRELEASEMESSAGE = _descriptor.Descriptor(
name='XmrBidLockReleaseMessage',
full_name='basicswap.XmrBidLockReleaseMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='bid_msg_id', full_name='basicswap.XmrBidLockReleaseMessage.bid_msg_id', index=0,
number=1, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='al_lock_spend_tx_esig', full_name='basicswap.XmrBidLockReleaseMessage.al_lock_spend_tx_esig', index=1,
number=2, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=b"",
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
],
extensions=[
],
nested_types=[],
enum_types=[
],
serialized_options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=1709,
serialized_end=1786,
)
_OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE
_OFFERMESSAGE_LOCKTYPE.containing_type = _OFFERMESSAGE
DESCRIPTOR.message_types_by_name['OfferMessage'] = _OFFERMESSAGE
DESCRIPTOR.message_types_by_name['BidMessage'] = _BIDMESSAGE
DESCRIPTOR.message_types_by_name['BidAcceptMessage'] = _BIDACCEPTMESSAGE
DESCRIPTOR.message_types_by_name['OfferRevokeMessage'] = _OFFERREVOKEMESSAGE
DESCRIPTOR.message_types_by_name['BidRejectMessage'] = _BIDREJECTMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidMessage'] = _XMRBIDMESSAGE
DESCRIPTOR.message_types_by_name['XmrSplitMessage'] = _XMRSPLITMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidAcceptMessage'] = _XMRBIDACCEPTMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidLockTxSigsMessage'] = _XMRBIDLOCKTXSIGSMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidLockSpendTxMessage'] = _XMRBIDLOCKSPENDTXMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidLockReleaseMessage'] = _XMRBIDLOCKRELEASEMESSAGE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), {
'DESCRIPTOR' : _OFFERMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.OfferMessage)
})
_sym_db.RegisterMessage(OfferMessage)
BidMessage = _reflection.GeneratedProtocolMessageType('BidMessage', (_message.Message,), {
'DESCRIPTOR' : _BIDMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.BidMessage)
})
_sym_db.RegisterMessage(BidMessage)
BidAcceptMessage = _reflection.GeneratedProtocolMessageType('BidAcceptMessage', (_message.Message,), {
'DESCRIPTOR' : _BIDACCEPTMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.BidAcceptMessage)
})
_sym_db.RegisterMessage(BidAcceptMessage)
OfferRevokeMessage = _reflection.GeneratedProtocolMessageType('OfferRevokeMessage', (_message.Message,), {
'DESCRIPTOR' : _OFFERREVOKEMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.OfferRevokeMessage)
})
_sym_db.RegisterMessage(OfferRevokeMessage)
BidRejectMessage = _reflection.GeneratedProtocolMessageType('BidRejectMessage', (_message.Message,), {
'DESCRIPTOR' : _BIDREJECTMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.BidRejectMessage)
})
_sym_db.RegisterMessage(BidRejectMessage)
XmrBidMessage = _reflection.GeneratedProtocolMessageType('XmrBidMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRBIDMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrBidMessage)
})
_sym_db.RegisterMessage(XmrBidMessage)
XmrSplitMessage = _reflection.GeneratedProtocolMessageType('XmrSplitMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRSPLITMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrSplitMessage)
})
_sym_db.RegisterMessage(XmrSplitMessage)
XmrBidAcceptMessage = _reflection.GeneratedProtocolMessageType('XmrBidAcceptMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRBIDACCEPTMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrBidAcceptMessage)
})
_sym_db.RegisterMessage(XmrBidAcceptMessage)
XmrBidLockTxSigsMessage = _reflection.GeneratedProtocolMessageType('XmrBidLockTxSigsMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRBIDLOCKTXSIGSMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrBidLockTxSigsMessage)
})
_sym_db.RegisterMessage(XmrBidLockTxSigsMessage)
XmrBidLockSpendTxMessage = _reflection.GeneratedProtocolMessageType('XmrBidLockSpendTxMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRBIDLOCKSPENDTXMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrBidLockSpendTxMessage)
})
_sym_db.RegisterMessage(XmrBidLockSpendTxMessage)
XmrBidLockReleaseMessage = _reflection.GeneratedProtocolMessageType('XmrBidLockReleaseMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRBIDLOCKRELEASEMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrBidLockReleaseMessage)
})
_sym_db.RegisterMessage(XmrBidLockReleaseMessage)
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'messages_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
_OFFERMESSAGE._serialized_start=30
_OFFERMESSAGE._serialized_end=580
_OFFERMESSAGE_LOCKTYPE._serialized_start=467
_OFFERMESSAGE_LOCKTYPE._serialized_end=580
_BIDMESSAGE._serialized_start=583
_BIDMESSAGE._serialized_end=763
_BIDACCEPTMESSAGE._serialized_start=765
_BIDACCEPTMESSAGE._serialized_end=851
_OFFERREVOKEMESSAGE._serialized_start=853
_OFFERREVOKEMESSAGE._serialized_end=914
_BIDREJECTMESSAGE._serialized_start=916
_BIDREJECTMESSAGE._serialized_end=975
_XMRBIDMESSAGE._serialized_start=978
_XMRBIDMESSAGE._serialized_end=1156
_XMRSPLITMESSAGE._serialized_start=1158
_XMRSPLITMESSAGE._serialized_end=1242
_XMRBIDACCEPTMESSAGE._serialized_start=1245
_XMRBIDACCEPTMESSAGE._serialized_end=1501
_XMRBIDLOCKTXSIGSMESSAGE._serialized_start=1503
_XMRBIDLOCKTXSIGSMESSAGE._serialized_end=1617
_XMRBIDLOCKSPENDTXMESSAGE._serialized_start=1619
_XMRBIDLOCKSPENDTXMESSAGE._serialized_end=1707
_XMRBIDLOCKRELEASEMESSAGE._serialized_start=1709
_XMRBIDLOCKRELEASEMESSAGE._serialized_end=1786
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2020 tecnovert # Copyright (c) 2020-2022 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -11,7 +11,6 @@ from basicswap.script import (
OpCodes, OpCodes,
) )
INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin
@@ -48,3 +47,14 @@ def buildContractScript(lock_val, secret_hash, pkh_redeem, pkh_refund, op_lock=O
def extractScriptSecretHash(script): def extractScriptSecretHash(script):
return script[7:39] return script[7:39]
def redeemITx(self, bid_id, 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')
txid = ci_from.publishTx(bytes.fromhex(txn))
bid.initiate_tx.spend_txid = bytes.fromhex(txid)
self.log.debug('Submitted initiate redeem txn %s to %s chain for bid %s', txid, ci_from.coin_name(), bid_id.hex())

View File

@@ -46,12 +46,18 @@ def recoverNoScriptTxnWithKey(self, bid_id, encoded_key):
ci_to = self.ci(offer.coin_to) ci_to = self.ci(offer.coin_to)
for_ed25519 = True if Coins(offer.coin_to) == Coins.XMR else False for_ed25519 = True if Coins(offer.coin_to) == Coins.XMR else False
try:
decoded_key_half = ci_to.decodeKey(encoded_key)
except Exception as e:
raise ValueError('Failed to decode provided key-half: ', str(e))
if bid.was_sent: if bid.was_sent:
kbsl = ci_to.decodeKey(encoded_key) kbsl = decoded_key_half
kbsf = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSF, for_ed25519) kbsf = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSF, for_ed25519)
else: else:
kbsl = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSL, for_ed25519) kbsl = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSL, for_ed25519)
kbsf = ci_to.decodeKey(encoded_key) kbsf = decoded_key_half
ensure(ci_to.verifyKey(kbsl), 'Invalid kbsl') ensure(ci_to.verifyKey(kbsl), 'Invalid kbsl')
ensure(ci_to.verifyKey(kbsf), 'Invalid kbsf') ensure(ci_to.verifyKey(kbsf), 'Invalid kbsf')
vkbs = ci_to.sumKeys(kbsl, kbsf) vkbs = ci_to.sumKeys(kbsl, kbsf)

View File

@@ -20,10 +20,13 @@ from xmlrpc.client import (
from .util import jsonDecimal from .util import jsonDecimal
def waitForRPC(rpc_func, wallet=None, max_tries=7): def waitForRPC(rpc_func, expect_wallet=True, max_tries=7):
for i in range(max_tries + 1): for i in range(max_tries + 1):
try: try:
if expect_wallet:
rpc_func('getwalletinfo') rpc_func('getwalletinfo')
else:
rpc_func('getblockchaininfo')
return return
except Exception as ex: except Exception as ex:
if i < max_tries: if i < max_tries:

View File

@@ -0,0 +1,34 @@
.padded_row td
{
padding-top:1.5em;
}
.bold
{
font-weight:bold;
}
.monospace
{
font-family:monospace;
}
.floatright
{
position:fixed;
top:10px;
right:18px;
margin: 0;
width:calc(33.33% - 25px);
}
.error
{
background:#ff5b5b;
}
.error_msg
{
color:red;
}

View File

Before

Width:  |  Height:  |  Size: 715 B

After

Width:  |  Height:  |  Size: 715 B

View File

@@ -0,0 +1,27 @@
window.addEventListener('DOMContentLoaded', (event) => {
let err_msgs = document.querySelectorAll('p.error_msg');
for (let i=0; i < err_msgs.length; i++) {
err_msg = err_msgs[i].innerText
if (err_msg.indexOf('coin_to') >= 0 || err_msg.indexOf('Coin To') >= 0) {
e = document.getElementById('coin_to');
e.classList.add('error');
}
if (err_msg.indexOf('Coin From') >= 0) {
e = document.getElementById('coin_from');
e.classList.add('error');
}
if (err_msg.indexOf('Amount From') >= 0) {
e = document.getElementById('amt_from');
e.classList.add('error');
}
if (err_msg.indexOf('Amount To') >= 0) {
e = document.getElementById('amt_to');
e.classList.add('error');
}
if (err_msg.indexOf('Minimum Bid Amount') >= 0) {
e = document.getElementById('amt_bid_min');
e.classList.add('error');
}
}
});

View File

@@ -0,0 +1,343 @@
<svg version="1.1" id="mscgenjsreplaceme" class="mscgenjsreplaceme" xmlns="http://www.w3.org/2000/svg" width="1176" height="1664.36" style="font-family:Helvetica,sans-serif;font-size:12px;font-weight:400;font-style:normal;text-decoration:none;background-color:#fff;stroke:#000;stroke-width:2">
<defs>
<marker orient="auto" id="mscgenjsreplacemecallback-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#00f"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-l-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="M17 1 9 3l8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#00f"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-#008800" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#080"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-l-#008800" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="M17 1 9 3l8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#080"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:red"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-l-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="M17 1 9 3l8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:red"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="#00F" fill="#00F" d="m1 1 8 2-8 2z"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-l-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="#00F" fill="#00F" d="M17 1 9 3l8 2z"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="red" fill="red" d="m1 1 8 2-8 2z"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-l-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="red" fill="red" d="M17 1 9 3l8 2z"/>
</marker>
<style>
.mscgenjsreplaceme path,.mscgenjsreplaceme rect{fill:none}.mscgenjsreplaceme .label-text-background{fill:#fff;stroke:#fff;stroke-width:0}.mscgenjsreplaceme .return{stroke-dasharray:5,3}.mscgenjsreplaceme text{color:inherit;stroke:none;text-anchor:middle}.mscgenjsreplaceme text.anchor-start{text-anchor:start}.mscgenjsreplaceme .arrow-marker{overflow:visible}.mscgenjsreplaceme .arrow-style{stroke-width:1}.mscgenjsreplaceme .arcrow{stroke-linecap:butt}.mscgenjsreplaceme .box,.mscgenjsreplaceme .entity{fill:#fff;stroke-linejoin:round}
</style>
</defs>
<g id="mscgenjsreplaceme_body" transform="translate(48 3)">
<path class="bglayer" style="fill:#fff;stroke:#fff;stroke-width:0" d="M-48-3h1176v1664.36H-48z" id="mscgenjsreplaceme_background"/>
<g id="mscgenjsreplaceme_arcspans">
<path class="box inline_expression alt" d="M-38 859.12h964v780.24H-38z"/>
<path class="box inline_expression alt" d="M-34 1206.18h956v195.06H-34z"/>
</g>
<g id="mscgenjsreplaceme_lifelines">
<path class="arcrow" style="stroke:transparent" d="M60 38v38"/>
<path class="arcrow" style="stroke:#080" d="M252 38v38"/>
<path class="arcrow" style="stroke:red" d="M444 38v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 38v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 38v38M1020 38v38M60 76v38"/>
<path class="arcrow" style="stroke:#080" d="M252 76v38"/>
<path class="arcrow" style="stroke:red" d="M444 76v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 76v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 76v38M1020 76v38M60 114v38"/>
<path class="arcrow" style="stroke:#080" d="M252 114v38"/>
<path class="arcrow" style="stroke:red" d="M444 114v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 114v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 114v38M1020 114v38M60 152v38"/>
<path class="arcrow" style="stroke:#080" d="M252 152v38"/>
<path class="arcrow" style="stroke:red" d="M444 152v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 152v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 152v38M1020 152v38M60 190v38"/>
<path class="arcrow" style="stroke:#080" d="M252 190v38"/>
<path class="arcrow" style="stroke:red" d="M444 190v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 190v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 190v38M1020 190v38M60 228v38"/>
<path class="arcrow" style="stroke:#080" d="M252 228v38"/>
<path class="arcrow" style="stroke:red" d="M444 228v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 228v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 228v38M1020 228v38M60 266v38"/>
<path class="arcrow" style="stroke:#080" d="M252 266v38"/>
<path class="arcrow" style="stroke:red" d="M444 266v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 266v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 266v38M1020 266v38M60 304v54"/>
<path class="arcrow" style="stroke:#080" d="M252 304v54"/>
<path class="arcrow" style="stroke:red" d="M444 304v54"/>
<path class="arcrow" style="stroke:#00f" d="M636 304v54"/>
<path class="arcrow" style="stroke:transparent" d="M828 304v54M1020 304v54M60 358v38"/>
<path class="arcrow" style="stroke:#080" d="M252 358v38"/>
<path class="arcrow" style="stroke:red" d="M444 358v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 358v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 358v38M1020 358v38M60 396v38"/>
<path class="arcrow" style="stroke:#080" d="M252 396v38"/>
<path class="arcrow" style="stroke:red" d="M444 396v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 396v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 396v38M1020 396v38M60 434v38"/>
<path class="arcrow" style="stroke:#080" d="M252 434v38"/>
<path class="arcrow" style="stroke:red" d="M444 434v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 434v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 434v38M1020 434v38M60 472v43.06"/>
<path class="arcrow" style="stroke:#080" d="M252 472v43.06"/>
<path class="arcrow" style="stroke:red" d="M444 472v43.06"/>
<path class="arcrow" style="stroke:#00f" d="M636 472v43.06"/>
<path class="arcrow" style="stroke:transparent" d="M828 472v43.06M1020 472v43.06M60 515.06v38"/>
<path class="arcrow" style="stroke:#080" d="M252 515.06v38"/>
<path class="arcrow" style="stroke:red" d="M444 515.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 515.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 515.06v38M1020 515.06v38M60 553.06v38"/>
<path class="arcrow" style="stroke:#080" d="M252 553.06v38"/>
<path class="arcrow" style="stroke:red" d="M444 553.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 553.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 553.06v38M1020 553.06v38M60 591.06v54"/>
<path class="arcrow" style="stroke:#080" d="M252 591.06v54"/>
<path class="arcrow" style="stroke:red" d="M444 591.06v54"/>
<path class="arcrow" style="stroke:#00f" d="M636 591.06v54"/>
<path class="arcrow" style="stroke:transparent" d="M828 591.06v54M1020 591.06v54M60 645.06v38"/>
<path class="arcrow" style="stroke:#080" d="M252 645.06v38"/>
<path class="arcrow" style="stroke:red" d="M444 645.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 645.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 645.06v38M1020 645.06v38M60 683.06v38"/>
<path class="arcrow" style="stroke:#080" d="M252 683.06v38"/>
<path class="arcrow" style="stroke:red" d="M444 683.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 683.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 683.06v38M1020 683.06v38M60 721.06v43.06"/>
<path class="arcrow" style="stroke:#080" d="M252 721.06v43.06"/>
<path class="arcrow" style="stroke:red" d="M444 721.06v43.06"/>
<path class="arcrow" style="stroke:#00f" d="M636 721.06v43.06"/>
<path class="arcrow" style="stroke:transparent" d="M828 721.06v43.06M1020 721.06v43.06M60 764.12v38"/>
<path class="arcrow" style="stroke:#080" d="M252 764.12v38"/>
<path class="arcrow" style="stroke:red" d="M444 764.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 764.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 764.12v38M1020 764.12v38M60 802.12v38"/>
<path class="arcrow" style="stroke:#080" d="M252 802.12v38"/>
<path class="arcrow" style="stroke:red" d="M444 802.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 802.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 802.12v38M1020 802.12v38M60 840.12v38"/>
<path class="arcrow" style="stroke:#080" d="M252 840.12v38"/>
<path class="arcrow" style="stroke:red" d="M444 840.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 840.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 840.12v38M1020 840.12v38M60 878.12v38"/>
<path class="arcrow" style="stroke:#080" d="M252 878.12v38"/>
<path class="arcrow" style="stroke:red" d="M444 878.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 878.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 878.12v38M1020 878.12v38M60 916.12v38"/>
<path class="arcrow" style="stroke:#080" d="M252 916.12v38"/>
<path class="arcrow" style="stroke:red" d="M444 916.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 916.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 916.12v38M1020 916.12v38M60 954.12v38"/>
<path class="arcrow" style="stroke:#080" d="M252 954.12v38"/>
<path class="arcrow" style="stroke:red" d="M444 954.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 954.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 954.12v38M1020 954.12v38M60 992.12v38"/>
<path class="arcrow" style="stroke:#080" d="M252 992.12v38"/>
<path class="arcrow" style="stroke:red" d="M444 992.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 992.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 992.12v38M1020 992.12v38M60 1030.12v43.06"/>
<path class="arcrow" style="stroke:#080" d="M252 1030.12v43.06"/>
<path class="arcrow" style="stroke:red" d="M444 1030.12v43.06"/>
<path class="arcrow" style="stroke:#00f" d="M636 1030.12v43.06"/>
<path class="arcrow" style="stroke:transparent" d="M828 1030.12v43.06M1020 1030.12v43.06M60 1073.18v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1073.18v38"/>
<path class="arcrow" style="stroke:red" d="M444 1073.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1073.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1073.18v38M1020 1073.18v38M60 1111.18v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1111.18v38"/>
<path class="arcrow" style="stroke:red" d="M444 1111.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1111.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1111.18v38M1020 1111.18v38M60 1149.18v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1149.18v38"/>
<path class="arcrow" style="stroke:red" d="M444 1149.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1149.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1149.18v38M1020 1149.18v38M60 1187.18v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1187.18v38"/>
<path class="arcrow" style="stroke:red" d="M444 1187.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1187.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1187.18v38M1020 1187.18v38M60 1225.18v43.06"/>
<path class="arcrow" style="stroke:#080" d="M252 1225.18v43.06"/>
<path class="arcrow" style="stroke:red" d="M444 1225.18v43.06"/>
<path class="arcrow" style="stroke:#00f" d="M636 1225.18v43.06"/>
<path class="arcrow" style="stroke:transparent" d="M828 1225.18v43.06M1020 1225.18v43.06M60 1268.24v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1268.24v38"/>
<path class="arcrow" style="stroke:red" d="M444 1268.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1268.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1268.24v38M1020 1268.24v38M60 1306.24v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1306.24v38"/>
<path class="arcrow" style="stroke:red" d="M444 1306.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1306.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1306.24v38M1020 1306.24v38M60 1344.24v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1344.24v38"/>
<path class="arcrow" style="stroke:red" d="M444 1344.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1344.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1344.24v38M1020 1344.24v38M60 1382.24v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1382.24v38"/>
<path class="arcrow" style="stroke:red" d="M444 1382.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1382.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1382.24v38M1020 1382.24v38M60 1420.24v43.06"/>
<path class="arcrow" style="stroke:#080" d="M252 1420.24v43.06"/>
<path class="arcrow" style="stroke:red" d="M444 1420.24v43.06"/>
<path class="arcrow" style="stroke:#00f" d="M636 1420.24v43.06"/>
<path class="arcrow" style="stroke:transparent" d="M828 1420.24v43.06M1020 1420.24v43.06M60 1463.3v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1463.3v38"/>
<path class="arcrow" style="stroke:red" d="M444 1463.3v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1463.3v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1463.3v38M1020 1463.3v38M60 1501.3v43.06"/>
<path class="arcrow" style="stroke:#080" d="M252 1501.3v43.06"/>
<path class="arcrow" style="stroke:red" d="M444 1501.3v43.06"/>
<path class="arcrow" style="stroke:#00f" d="M636 1501.3v43.06"/>
<path class="arcrow" style="stroke:transparent" d="M828 1501.3v43.06M1020 1501.3v43.06M60 1544.36v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1544.36v38"/>
<path class="arcrow" style="stroke:red" d="M444 1544.36v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1544.36v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1544.36v38M1020 1544.36v38M60 1582.36v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1582.36v38"/>
<path class="arcrow" style="stroke:red" d="M444 1582.36v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1582.36v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1582.36v38M1020 1582.36v38M60 1620.36v38"/>
<path class="arcrow" style="stroke:#080" d="M252 1620.36v38"/>
<path class="arcrow" style="stroke:red" d="M444 1620.36v38"/>
<path class="arcrow" style="stroke:#00f" d="M636 1620.36v38"/>
<path class="arcrow" style="stroke:transparent" d="M828 1620.36v38M1020 1620.36v38"/>
</g>
<g id="mscgenjsreplaceme_sequence">
<path class="entity" style="stroke:transparent" d="M0 0h120v38H0z"/>
<text x="60" y="22.75" class="entity-text"><tspan> </tspan></text>
<path class="entity" style="fill:#cfc;stroke:#080" d="M192 0h120v38H192z"/>
<text x="252" y="22.75" class="entity-text"><tspan>Network</tspan></text>
<path class="entity" style="fill:#fcc;stroke:red" d="M384 0h120v38H384z"/>
<text x="444" y="22.75" class="entity-text"><tspan>Offerer</tspan></text>
<path class="entity" style="fill:#ccf;stroke:#00f" d="M576 0h120v38H576z"/>
<text x="636" y="22.75" class="entity-text"><tspan>Bidder</tspan></text>
<path class="entity" style="stroke:transparent" d="M768 0h120v38H768z"/>
<text x="828" y="22.75" class="entity-text"><tspan> </tspan></text>
<path class="entity" style="stroke:transparent" d="M960 0h120v38H960z"/>
<text x="1020" y="22.75" class="entity-text"><tspan> </tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M444 95H252"/>
<path class="label-text-background" d="M316.08 79.25h63.84v14h-63.84z"/>
<text x="348" y="90.25" class="directional-text callback-text"><tspan>Sends Offer</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M252 133h384"/>
<path class="label-text-background" d="M408.75 117.25h70.5v14h-70.5z"/>
<text x="444" y="128.25" class="directional-text return-text"><tspan>Detects Offer</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M636 171H444"/>
<path class="label-text-background" d="M512.64 155.25h54.72v14h-54.72z"/>
<text x="540" y="166.25" class="directional-text callback-text"><tspan>Sends Bid</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M444 285H252"/>
<path class="label-text-background" d="M303.3 269.25h89.41v14H303.3z"/>
<text x="348" y="280.25" class="directional-text callback-text"><tspan>Sends Initiate Tx</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M444 331h192"/>
<path class="label-text-background" d="M494.3 315.25h92.08v14H494.3z"/>
<text x="540" y="326.25" class="directional-text callback-text"><tspan>Sends BidAccept</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M252 415h384"/>
<path class="label-text-background" d="M395.97 399.25h96.06v14h-96.06z"/>
<text x="444" y="410.25" class="directional-text return-text"><tspan>Detects Initiate Tx</tspan></text>
<path d="M636 485.93c96 .1 96 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M639 469.67h118.92v14.02H639z"/>
<text x="639" y="480.68" class="directional-text method-text anchor-start"><tspan>Wait for ITX to confirm</tspan></text>
<path d="M444 485.93c96 .1 96 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M447 469.67h118.92v14.02H447z"/>
<text x="447" y="480.68" class="directional-text method-text anchor-start"><tspan>Wait for ITX to confirm</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M636 618.06H252"/>
<path class="label-text-background" d="M388.64 602.3h110.72v14.02H388.64z"/>
<text x="444" y="613.31" class="directional-text callback-text"><tspan>Sends Participate Tx</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M252 702.06h192"/>
<path class="label-text-background" d="M289.31 686.3h117.38v14.02H289.31z"/>
<text x="348" y="697.31" class="directional-text return-text"><tspan>Detects Participate Tx</tspan></text>
<path d="M636 734.99c96 .1 96 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M639 718.73h123.59v14.02H639z"/>
<text x="639" y="729.74" class="directional-text method-text anchor-start"><tspan>Wait for PTX to confirm</tspan></text>
<path d="M444 734.99c96 .1 96 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M447 718.73h123.59v14.02H447z"/>
<text x="447" y="729.74" class="directional-text method-text anchor-start"><tspan>Wait for PTX to confirm</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M444 897.12H252"/>
<path class="label-text-background" d="M268.28 881.36h159.44v14.02H268.28z"/>
<text x="348" y="892.37" class="directional-text callback-text"><tspan>Sends Participate Redeem Tx</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M252 935.12h384"/>
<path class="label-text-background" d="M360.95 919.36h166.09v14.02H360.95z"/>
<text x="444" y="930.37" class="directional-text return-text"><tspan>Detects Participate Redeem Tx</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M636 1011.12H252"/>
<path class="label-text-background" d="M374.95 995.36h138.09v14.02H374.95z"/>
<text x="444" y="1006.37" class="directional-text callback-text"><tspan>Sends Initiate Redeem Tx</tspan></text>
<path d="M636 1044.05c96 .1 96 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M639 1027.8h167.61v14.02H639z"/>
<text x="639" y="1038.8" class="directional-text method-text anchor-start"><tspan>Wait for ITX Redeem to confirm</tspan></text>
<path class="inline_expression_divider" style="stroke-dasharray:10,5" d="M-38 1168.18h964"/>
<path class="label-text-background" d="M422.98 1160.92h42.03v14.02h-42.03z"/>
<text x="444" y="1171.93" class="empty-text comment-row-text"><tspan>fail path</tspan></text>
<path d="M444 1239.11c96 .1 96 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M447 1222.86h159.94v14.02H447z"/>
<text x="447" y="1233.86" class="directional-text method-text anchor-start"><tspan>Wait for ITX locktime to expire</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M444 1287.24H252"/>
<path class="label-text-background" d="M309.31 1271.48h77.38v14.02h-77.38z"/>
<text x="348" y="1282.49" class="directional-text callback-text"><tspan>ITX Refund Tx</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M252 1325.24h384"/>
<path class="label-text-background" d="M368.97 1309.48h150.06v14.02H368.97z"/>
<text x="444" y="1320.49" class="directional-text return-text"><tspan>Detects Initiate Tx refund Tx</tspan></text>
<path d="M636 1434.17c96 .1 96 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M639 1417.91h164.59v14.02H639z"/>
<text x="639" y="1428.92" class="directional-text method-text anchor-start"><tspan>Wait for PTX locktime to expire</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M636 1482.3H252"/>
<path class="label-text-background" d="M402.98 1466.55h82.03v14.02h-82.03z"/>
<text x="444" y="1477.55" class="directional-text callback-text"><tspan>PTX Refund Tx</tspan></text>
<path d="M636 1515.23c96 .1 96 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M639 1498.97h165.63v14.02H639z"/>
<text x="639" y="1509.98" class="directional-text method-text anchor-start"><tspan>Wait for PTX Refund to confirm</tspan></text>
</g>
<g id="mscgenjsreplaceme_notes">
<path d="m546 209 3-17h174l3 17-3 17H549z" class="box abox" style="stroke:#00f"/>
<text x="636" y="212.75" class="box-text abox-text"><tspan>Bid Sent</tspan></text>
<path class="box" style="stroke:red" d="M354 230h180v34H354z"/>
<text x="444" y="250.75" class="box-text"><tspan>User accepts bid</tspan></text>
<path d="M738 268h363v9h9m-9-9 9 9v25H738v-34z" class="box note" style="fill:#ffc"/>
<text x="924" y="280.75" class="box-text note-text"><tspan>Offerer generates secret_value and sends Hash(secret_value) to</tspan></text>
<text x="924" y="296.75" class="box-text note-text"><tspan>the Bidder</tspan></text>
<path d="M738 306h363v9h9m-9-9 9 9v41H738v-50z" class="box note" style="fill:#ffc"/>
<text x="924" y="318.75" class="box-text note-text"><tspan>ITX can be spent by knowledge of the</tspan></text>
<text x="924" y="334.75" class="box-text note-text"><tspan>secret_value and the bidder_redeem_key or after a timeout</tspan></text>
<text x="924" y="350.75" class="box-text note-text"><tspan>by the offerer_refund_key</tspan></text>
<path d="m546 377 3-17h174l3 17-3 17H549z" class="box abox" style="stroke:#00f"/>
<text x="636" y="380.75" class="box-text abox-text"><tspan>Bid Accepted</tspan></text>
<path d="m546 453 3-17h174l3 17-3 17H549z" class="box abox" style="fill:#4bdbf1;stroke:#00f"/>
<text x="636" y="456.75" class="box-text abox-text"><tspan>ITX Sent</tspan></text>
<path d="m546 534.06 3-17h174l3 17-3 17H549z" class="box abox" style="stroke:#00f"/>
<text x="636" y="537.81" class="box-text abox-text"><tspan>Bid Initiated</tspan></text>
<path d="m546 572.06 3-17h174l3 17-3 17H549z" class="box abox" style="fill:#4bdbf1;stroke:#00f"/>
<text x="636" y="575.81" class="box-text abox-text"><tspan>ITX Confirmed</tspan></text>
<path d="M738 593.05h363v9h9m-9-9 9 9v41.02H738v-50.02z" class="box note" style="fill:#ffc"/>
<text x="924" y="605.81" class="box-text note-text"><tspan>PTX can be spent by knowledge of the</tspan></text>
<text x="924" y="621.81" class="box-text note-text"><tspan>secret_value and the offerer_redeem_key or after a timeout</tspan></text>
<text x="924" y="637.81" class="box-text note-text"><tspan>by the bidder_refund_key</tspan></text>
<path d="m546 664.06 3-17h174l3 17-3 17H549z" class="box abox" style="fill:#f1db4b;stroke:#00f"/>
<text x="636" y="667.81" class="box-text abox-text"><tspan>PTX Sent</tspan></text>
<path d="m546 783.12 3-17h174l3 17-3 17H549z" class="box abox" style="fill:#f1db4b;stroke:#00f"/>
<text x="636" y="786.87" class="box-text abox-text"><tspan>PTX Confirmed</tspan></text>
<path d="m546 821.12 3-17h174l3 17-3 17H549z" class="box abox" style="stroke:#00f"/>
<text x="636" y="824.87" class="box-text abox-text"><tspan>Bid Participating</tspan></text>
<path d="M-37 859.12h98.39v11.02l-7 7H-37" class="box inline_expression_label"/>
<text x="-35" y="872.37" class="inline_expression-text alt-text anchor-start"><tspan>alt: success path</tspan></text>
<path d="M738 880.12h363v9h9m-9-9 9 9v25H738v-34z" class="box note" style="fill:#ffc"/>
<text x="924" y="900.87" class="box-text note-text"><tspan>Reveals secret_value</tspan></text>
<path d="m546 973.12 3-17h174l3 17-3 17H549z" class="box abox" style="fill:#f1db4b;stroke:#00f"/>
<text x="636" y="976.87" class="box-text abox-text"><tspan>PTX Redeemed</tspan></text>
<path d="m546 1092.18 3-17h174l3 17-3 17H549z" class="box abox" style="fill:#4bdbf1;stroke:#00f"/>
<text x="636" y="1095.93" class="box-text abox-text"><tspan>ITX Redeemed</tspan></text>
<path d="m546 1130.18 3-17h174l3 17-3 17H549z" class="box abox" style="stroke:#00f"/>
<text x="636" y="1133.93" class="box-text abox-text"><tspan>Bid Completed</tspan></text>
<path d="M-33 1206.18h152.83v11.02l-7 7H-33" class="box inline_expression_label"/>
<text x="-31" y="1219.43" class="inline_expression-text alt-text anchor-start"><tspan>alt: offerer may reclaim ITX</tspan></text>
<path d="m546 1363.24 3-17h174l3 17-3 17H549z" class="box abox" style="fill:#4bdbf1;stroke:#00f"/>
<text x="636" y="1366.99" class="box-text abox-text"><tspan>ITX Refunded</tspan></text>
<path d="m546 1563.36 3-17h174l3 17-3 17H549z" class="box abox" style="fill:#f1db4b;stroke:#00f"/>
<text x="636" y="1567.11" class="box-text abox-text"><tspan>PTX Refunded</tspan></text>
<path d="m546 1601.36 3-17h174l3 17-3 17H549z" class="box abox" style="stroke:#00f"/>
<text x="636" y="1605.11" class="box-text abox-text"><tspan>Bid Completed</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,414 @@
<svg version="1.1" id="mscgenjsreplaceme" class="mscgenjsreplaceme" xmlns="http://www.w3.org/2000/svg" width="1272" height="2063.3" style="font-family:Helvetica,sans-serif;font-size:12px;font-weight:400;font-style:normal;text-decoration:none;background-color:#fff;stroke:#000;stroke-width:2">
<defs>
<marker orient="auto" id="mscgenjsreplacemecallback-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#00f"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-l-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="M17 1 9 3l8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#00f"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-#008800" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#080"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-l-#008800" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="M17 1 9 3l8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#080"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:red"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-l-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="M17 1 9 3l8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:red"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="#00F" fill="#00F" d="m1 1 8 2-8 2z"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-l-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="#00F" fill="#00F" d="M17 1 9 3l8 2z"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="red" fill="red" d="m1 1 8 2-8 2z"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-l-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="red" fill="red" d="M17 1 9 3l8 2z"/>
</marker>
<style>
.mscgenjsreplaceme path,.mscgenjsreplaceme rect{fill:none}.mscgenjsreplaceme .label-text-background{fill:#fff;stroke:#fff;stroke-width:0}.mscgenjsreplaceme .return{stroke-dasharray:5,3}.mscgenjsreplaceme .inline_expression_divider{stroke-dasharray:10,5}.mscgenjsreplaceme text{color:inherit;stroke:none;text-anchor:middle}.mscgenjsreplaceme text.anchor-start{text-anchor:start}.mscgenjsreplaceme .arrow-marker{overflow:visible}.mscgenjsreplaceme .arrow-style{stroke-width:1}.mscgenjsreplaceme .arcrow{stroke-linecap:butt}.mscgenjsreplaceme .box,.mscgenjsreplaceme .entity{fill:#fff;stroke-linejoin:round}
</style>
</defs>
<g id="mscgenjsreplaceme_body" transform="translate(51 3)">
<path class="bglayer" style="fill:#fff;stroke:#fff;stroke-width:0" d="M-51-3h1272v2063.3H-51z" id="mscgenjsreplaceme_background"/>
<g id="mscgenjsreplaceme_arcspans">
<path class="box inline_expression alt" d="M-41 869.12h1044V2038.3H-41z"/>
<path class="box inline_expression alt" d="M-37 1410.18H999v590.12H-37z"/>
</g>
<g id="mscgenjsreplaceme_lifelines">
<path class="arcrow" style="stroke:transparent" d="M65 38v38"/>
<path class="arcrow" style="stroke:#080" d="M273 38v38"/>
<path class="arcrow" style="stroke:red" d="M481 38v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 38v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 38v38M1105 38v38M65 76v38"/>
<path class="arcrow" style="stroke:#080" d="M273 76v38"/>
<path class="arcrow" style="stroke:red" d="M481 76v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 76v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 76v38M1105 76v38M65 114v38"/>
<path class="arcrow" style="stroke:#080" d="M273 114v38"/>
<path class="arcrow" style="stroke:red" d="M481 114v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 114v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 114v38M1105 114v38M65 152v38"/>
<path class="arcrow" style="stroke:#080" d="M273 152v38"/>
<path class="arcrow" style="stroke:red" d="M481 152v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 152v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 152v38M1105 152v38M65 190v38"/>
<path class="arcrow" style="stroke:#080" d="M273 190v38"/>
<path class="arcrow" style="stroke:red" d="M481 190v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 190v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 190v38M1105 190v38M65 228v38"/>
<path class="arcrow" style="stroke:#080" d="M273 228v38"/>
<path class="arcrow" style="stroke:red" d="M481 228v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 228v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 228v38M1105 228v38M65 266v54"/>
<path class="arcrow" style="stroke:#080" d="M273 266v54"/>
<path class="arcrow" style="stroke:red" d="M481 266v54"/>
<path class="arcrow" style="stroke:#00f" d="M689 266v54"/>
<path class="arcrow" style="stroke:transparent" d="M897 266v54M1105 266v54M65 320v38"/>
<path class="arcrow" style="stroke:#080" d="M273 320v38"/>
<path class="arcrow" style="stroke:red" d="M481 320v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 320v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 320v38M1105 320v38M65 358v38"/>
<path class="arcrow" style="stroke:#080" d="M273 358v38"/>
<path class="arcrow" style="stroke:red" d="M481 358v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 358v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 358v38M1105 358v38M65 396v38"/>
<path class="arcrow" style="stroke:#080" d="M273 396v38"/>
<path class="arcrow" style="stroke:red" d="M481 396v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 396v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 396v38M1105 396v38M65 434v38"/>
<path class="arcrow" style="stroke:#080" d="M273 434v38"/>
<path class="arcrow" style="stroke:red" d="M481 434v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 434v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 434v38M1105 434v38M65 472v38"/>
<path class="arcrow" style="stroke:#080" d="M273 472v38"/>
<path class="arcrow" style="stroke:red" d="M481 472v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 472v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 472v38M1105 472v38M65 510v38"/>
<path class="arcrow" style="stroke:#080" d="M273 510v38"/>
<path class="arcrow" style="stroke:red" d="M481 510v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 510v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 510v38M1105 510v38M65 548v38"/>
<path class="arcrow" style="stroke:#080" d="M273 548v38"/>
<path class="arcrow" style="stroke:red" d="M481 548v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 548v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 548v38M1105 548v38M65 586v75.06"/>
<path class="arcrow" style="stroke:#080" d="M273 586v75.06"/>
<path class="arcrow" style="stroke:red" d="M481 586v75.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 586v75.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 586v75.06M1105 586v75.06M65 661.06v38"/>
<path class="arcrow" style="stroke:#080" d="M273 661.06v38"/>
<path class="arcrow" style="stroke:red" d="M481 661.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 661.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 661.06v38M1105 661.06v38M65 699.06v38"/>
<path class="arcrow" style="stroke:#080" d="M273 699.06v38"/>
<path class="arcrow" style="stroke:red" d="M481 699.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 699.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 699.06v38M1105 699.06v38M65 737.06v75.06"/>
<path class="arcrow" style="stroke:#080" d="M273 737.06v75.06"/>
<path class="arcrow" style="stroke:red" d="M481 737.06v75.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 737.06v75.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 737.06v75.06M1105 737.06v75.06M65 812.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 812.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 812.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 812.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 812.12v38M1105 812.12v38M65 850.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 850.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 850.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 850.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 850.12v38M1105 850.12v38M65 888.12v86"/>
<path class="arcrow" style="stroke:#080" d="M273 888.12v86"/>
<path class="arcrow" style="stroke:red" d="M481 888.12v86"/>
<path class="arcrow" style="stroke:#00f" d="M689 888.12v86"/>
<path class="arcrow" style="stroke:transparent" d="M897 888.12v86M1105 888.12v86M65 974.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 974.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 974.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 974.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 974.12v38M1105 974.12v38M65 1012.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1012.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1012.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1012.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1012.12v38M1105 1012.12v38M65 1050.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1050.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1050.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1050.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1050.12v38M1105 1050.12v38M65 1088.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1088.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1088.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1088.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1088.12v38M1105 1088.12v38M65 1126.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1126.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1126.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1126.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1126.12v38M1105 1126.12v38M65 1164.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1164.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1164.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1164.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1164.12v38M1105 1164.12v38M65 1202.12v75.06"/>
<path class="arcrow" style="stroke:#080" d="M273 1202.12v75.06"/>
<path class="arcrow" style="stroke:red" d="M481 1202.12v75.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 1202.12v75.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 1202.12v75.06M1105 1202.12v75.06M65 1277.18v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1277.18v38"/>
<path class="arcrow" style="stroke:red" d="M481 1277.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1277.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1277.18v38M1105 1277.18v38M65 1315.18v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1315.18v38"/>
<path class="arcrow" style="stroke:red" d="M481 1315.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1315.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1315.18v38M1105 1315.18v38M65 1353.18v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1353.18v38"/>
<path class="arcrow" style="stroke:red" d="M481 1353.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1353.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1353.18v38M1105 1353.18v38M65 1391.18v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1391.18v38"/>
<path class="arcrow" style="stroke:red" d="M481 1391.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1391.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1391.18v38M1105 1391.18v38M65 1429.18v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1429.18v38"/>
<path class="arcrow" style="stroke:red" d="M481 1429.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1429.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1429.18v38M1105 1429.18v38M65 1467.18v59.06"/>
<path class="arcrow" style="stroke:#080" d="M273 1467.18v59.06"/>
<path class="arcrow" style="stroke:red" d="M481 1467.18v59.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 1467.18v59.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 1467.18v59.06M1105 1467.18v59.06M65 1526.24v54"/>
<path class="arcrow" style="stroke:#080" d="M273 1526.24v54"/>
<path class="arcrow" style="stroke:red" d="M481 1526.24v54"/>
<path class="arcrow" style="stroke:#00f" d="M689 1526.24v54"/>
<path class="arcrow" style="stroke:transparent" d="M897 1526.24v54M1105 1526.24v54M65 1580.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1580.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1580.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1580.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1580.24v38M1105 1580.24v38M65 1618.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1618.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1618.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1618.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1618.24v38M1105 1618.24v38M65 1656.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1656.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1656.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1656.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1656.24v38M1105 1656.24v38M65 1694.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1694.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1694.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1694.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1694.24v38M1105 1694.24v38M65 1732.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1732.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1732.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1732.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1732.24v38M1105 1732.24v38M65 1770.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1770.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1770.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1770.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1770.24v38M1105 1770.24v38M65 1808.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1808.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1808.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1808.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1808.24v38M1105 1808.24v38M65 1846.24v59.06"/>
<path class="arcrow" style="stroke:#080" d="M273 1846.24v59.06"/>
<path class="arcrow" style="stroke:red" d="M481 1846.24v59.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 1846.24v59.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 1846.24v59.06M1105 1846.24v59.06M65 1905.3v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1905.3v38"/>
<path class="arcrow" style="stroke:red" d="M481 1905.3v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1905.3v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1905.3v38M1105 1905.3v38M65 1943.3v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1943.3v38"/>
<path class="arcrow" style="stroke:red" d="M481 1943.3v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1943.3v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1943.3v38M1105 1943.3v38M65 1981.3v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1981.3v38"/>
<path class="arcrow" style="stroke:red" d="M481 1981.3v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1981.3v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1981.3v38M1105 1981.3v38M65 2019.3v38"/>
<path class="arcrow" style="stroke:#080" d="M273 2019.3v38"/>
<path class="arcrow" style="stroke:red" d="M481 2019.3v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 2019.3v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 2019.3v38M1105 2019.3v38"/>
</g>
<g id="mscgenjsreplaceme_sequence">
<path class="entity" style="stroke:transparent" d="M0 0h130v38H0z"/>
<text x="65" y="22.75" class="entity-text"><tspan> </tspan></text>
<path class="entity" style="fill:#cfc;stroke:#080" d="M208 0h130v38H208z"/>
<text x="273" y="22.75" class="entity-text"><tspan>Network</tspan></text>
<path class="entity" style="fill:#fcc;stroke:red" d="M416 0h130v38H416z"/>
<text x="481" y="22.75" class="entity-text"><tspan>Offerer</tspan></text>
<path class="entity" style="fill:#ccf;stroke:#00f" d="M624 0h130v38H624z"/>
<text x="689" y="22.75" class="entity-text"><tspan>Bidder</tspan></text>
<path class="entity" style="stroke:transparent" d="M832 0h130v38H832z"/>
<text x="897" y="22.75" class="entity-text"><tspan> </tspan></text>
<path class="entity" style="stroke:transparent" d="M1040 0h130v38h-130z"/>
<text x="1105" y="22.75" class="entity-text"><tspan> </tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 95H273"/>
<path class="label-text-background" d="M345.08 79.25h63.84v14h-63.84z"/>
<text x="377" y="90.25" class="directional-text callback-text"><tspan>Sends Offer</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 133h416"/>
<path class="label-text-background" d="M445.75 117.25h70.5v14h-70.5z"/>
<text x="481" y="128.25" class="directional-text return-text"><tspan>Detects Offer</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 171H481"/>
<path class="label-text-background" d="M557.64 155.25h54.72v14h-54.72z"/>
<text x="585" y="166.25" class="directional-text callback-text"><tspan>Sends Bid</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 293h208"/>
<path class="label-text-background" d="M513.28 277.25h143.77v14H513.28z"/>
<text x="585" y="288.25" class="directional-text callback-text"><tspan>Sends BidAccept message</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 415H481"/>
<path class="label-text-background" d="M491.28 399.25h187.77v14H491.28z"/>
<text x="585" y="410.25" class="directional-text callback-text"><tspan>Sends XmrBidLockTxSigsMessage</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 491h208"/>
<path class="label-text-background" d="M485.61 475.25h199.11v14H485.61z"/>
<text x="585" y="486.25" class="directional-text callback-text"><tspan>Sends XmrBidLockSpendTxMessage</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 529H273"/>
<path class="label-text-background" d="M311.64 513.25h130.72v14H311.64z"/>
<text x="377" y="524.25" class="directional-text callback-text"><tspan>Sends script-coin-lock-tx</tspan></text>
<path d="M689 615.93c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M692 567.67h40.91v14.02H692z"/>
<text x="692" y="578.68" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M692 583.67h107.02v14.02H692z"/>
<text x="692" y="594.68" class="directional-text method-text anchor-start"><tspan>script-coin-lock-tx to</tspan></text>
<path class="label-text-background" d="M692 599.67h39.34v14.02H692z"/>
<text x="692" y="610.68" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 718.06H273"/>
<path class="label-text-background" d="M408.97 702.3h144.06v14.02H408.97z"/>
<text x="481" y="713.31" class="directional-text callback-text"><tspan>Sends noscript-coin-lock-tx</tspan></text>
<path d="M689 766.99c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M692 718.73h40.91v14.02H692z"/>
<text x="692" y="729.74" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M692 734.73h120.38v14.02H692z"/>
<text x="692" y="745.74" class="directional-text method-text anchor-start"><tspan>noscript-coin-lock-tx to</tspan></text>
<path class="label-text-background" d="M692 750.73h39.34v14.02H692z"/>
<text x="692" y="761.74" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
<path d="M481 766.99c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M484 718.73h40.91v14.02H484z"/>
<text x="484" y="729.74" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M484 734.73h120.38v14.02H484z"/>
<text x="484" y="745.74" class="directional-text method-text anchor-start"><tspan>noscript-coin-lock-tx to</tspan></text>
<path class="label-text-background" d="M484 750.73h39.34v14.02H484z"/>
<text x="484" y="761.74" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
<path class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 931.12h208"/>
<path class="label-text-background" d="M519.64 915.36h130.72v14.02H519.64z"/>
<text x="585" y="926.37" class="directional-text method-text"><tspan>Sends script-coin-lock-tx</tspan></text>
<path class="label-text-background" d="M539.3 933.36h91.73v14.02H539.3z"/>
<text x="585" y="944.37" class="directional-text method-text"><tspan>release message</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 1031.12H273"/>
<path class="label-text-background" d="M397.3 1015.36h167.41v14.02H397.3z"/>
<text x="481" y="1026.37" class="directional-text callback-text"><tspan>Sends script-coin-lock-spend-tx</tspan></text>
<path class="inline_expression_divider" d="M-41 1145.12h1044"/>
<path class="label-text-background" d="M459.98 1137.86h42.03v14.02h-42.03z"/>
<text x="481" y="1148.87" class="empty-text comment-row-text"><tspan>fail path</tspan></text>
<path d="M689 1232.05c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M692 1183.8h40.91v14.02H692z"/>
<text x="692" y="1194.8" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M692 1199.8h131.69v14.02H692z"/>
<text x="692" y="1210.8" class="directional-text method-text anchor-start"><tspan>script-coin-lock-tx lock to</tspan></text>
<path class="label-text-background" d="M692 1215.8h33.02v14.02H692z"/>
<text x="692" y="1226.8" class="directional-text method-text anchor-start"><tspan>expire</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1296.18H273"/>
<path class="label-text-background" d="M359.98 1280.42h34.03v14.02h-34.03z"/>
<text x="377" y="1291.43" class="directional-text callback-text"><tspan>Sends</tspan></text>
<path class="label-text-background" d="M300.64 1298.42h152.72v14.02H300.64z"/>
<text x="377" y="1309.43" class="directional-text callback-text"><tspan>script-coin-lock-pre-refund-tx</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 1334.18h208"/>
<path class="label-text-background" d="M300.64 1318.42h152.72v14.02H300.64z"/>
<text x="377" y="1329.43" class="directional-text return-text"><tspan>script-coin-lock-pre-refund-tx</tspan></text>
<path d="M481 1489.11c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M484 1456.86h40.91v14.02H484z"/>
<text x="484" y="1467.86" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M484 1472.86h124.06v14.02H484z"/>
<text x="484" y="1483.86" class="directional-text method-text anchor-start"><tspan>pre-refund tx to confirm</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1553.24H273"/>
<path class="label-text-background" d="M359.98 1537.48h34.03v14.02h-34.03z"/>
<text x="377" y="1548.49" class="directional-text callback-text"><tspan>Sends</tspan></text>
<path class="label-text-background" d="M282.3 1555.48h189.41v14.02H282.3z"/>
<text x="377" y="1566.49" class="directional-text callback-text"><tspan>script-coin-lock-pre-refund-spend-tx</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 1637.24h416"/>
<path class="label-text-background" d="M364.28 1621.48h233.44v14.02H364.28z"/>
<text x="481" y="1632.49" class="directional-text return-text"><tspan>Detects script-coin-lock-pre-refund-spend-tx</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 1675.24H273"/>
<path class="label-text-background" d="M382.97 1659.48h196.06v14.02H382.97z"/>
<text x="481" y="1670.49" class="directional-text callback-text"><tspan>Sends scriptless-coin-lock-recover-tx</tspan></text>
<path class="inline_expression_divider" d="M-37 1789.24H999"/>
<path class="label-text-background" d="M396.95 1781.98h168.09V1796H396.95z"/>
<text x="481" y="1792.99" class="empty-text comment-row-text"><tspan>bidder swipes script coin lock tx</tspan></text>
<path d="M689 1868.17c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M692 1835.91h40.91v14.02H692z"/>
<text x="692" y="1846.92" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M692 1851.91h142.41v14.02H692z"/>
<text x="692" y="1862.92" class="directional-text method-text anchor-start"><tspan>pre-refund tx lock to expire</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 1924.3H273"/>
<path class="label-text-background" d="M368.63 1908.55h224.75v14.02H368.63z"/>
<text x="481" y="1919.55" class="directional-text callback-text"><tspan>Sends script-coin-lock-pre-refund-swipe-tx</tspan></text>
</g>
<g id="mscgenjsreplaceme_notes">
<path d="m591 209 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="212.75" class="box-text abox-text"><tspan>Bid Sent</tspan></text>
<path class="box" style="stroke:red" d="M383 230h196v34H383z"/>
<text x="481" y="250.75" class="box-text"><tspan>User accepts bid</tspan></text>
<path d="M799 268h395v9h9m-9-9 9 9v41H799v-50z" class="box note" style="fill:#ffc"/>
<text x="1001" y="280.75" class="box-text note-text"><tspan>The BidAccept message contains the pubkeys the offerer will use and</tspan></text>
<text x="1001" y="296.75" class="box-text note-text"><tspan>a DLEAG proof one key will work across both chains of the swapping</tspan></text>
<text x="1001" y="312.75" class="box-text note-text"><tspan>coins</tspan></text>
<path d="m591 339 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="342.75" class="box-text abox-text"><tspan>Bid Receiving accept</tspan></text>
<path d="m591 377 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="380.75" class="box-text abox-text"><tspan>Bid Accepted</tspan></text>
<path d="M799 398h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
<text x="1001" y="410.75" class="box-text note-text"><tspan>The XmrBidLockTxSigsMessage contains the bidder&apos;s signatures for the</tspan></text>
<text x="1001" y="426.75" class="box-text note-text"><tspan>script-coin-lock-refund and script-coin-lock-refund-spend txns.</tspan></text>
<path d="m591 453 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="448.75" class="box-text abox-text"><tspan>Exchanged script lock tx sigs</tspan></text>
<text x="689" y="464.75" class="box-text abox-text"><tspan>msg</tspan></text>
<path d="M799 474h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
<text x="1001" y="486.75" class="box-text note-text"><tspan>The XmrBidLockSpendTxMessage contains the script-coin-lock-tx and</tspan></text>
<text x="1001" y="502.75" class="box-text note-text"><tspan>the offerer&apos;s signature for it.</tspan></text>
<path d="m591 529 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="532.75" class="box-text abox-text"><tspan>Bid Script coin spend tx valid</tspan></text>
<path d="m591 567 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="562.75" class="box-text abox-text"><tspan>Exchanged script lock spend tx</tspan></text>
<text x="689" y="578.75" class="box-text abox-text"><tspan>msg</tspan></text>
<path d="m591 680.06 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="683.81" class="box-text abox-text"><tspan>Bid Script coin locked</tspan></text>
<path d="m591 831.12 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="834.87" class="box-text abox-text"><tspan>Bid Scriptless coin locked</tspan></text>
<path d="M-40 869.12h98.39v11.02l-7 7H-40" class="box inline_expression_label"/>
<text x="-38" y="882.37" class="inline_expression-text alt-text anchor-start"><tspan>alt: success path</tspan></text>
<path d="M799 890.11h395v9h9m-9-9 9 9v73.02H799v-82.02z" class="box note" style="fill:#ffc"/>
<text x="1001" y="902.87" class="box-text note-text"><tspan>The XmrBidLockReleaseMessage contains the offerer&apos;s OTVES for it. </tspan></text>
<text x="1001" y="918.87" class="box-text note-text"><tspan> The bidder decodes the offerer&apos;s signature</tspan></text>
<text x="1001" y="934.87" class="box-text note-text"><tspan>from the OTVES. When the offerer has the</tspan></text>
<text x="1001" y="950.87" class="box-text note-text"><tspan>plaintext signature, they can decode the bidder&apos;s noscript-coin-lock-tx</tspan></text>
<text x="1001" y="966.87" class="box-text note-text"><tspan>signature.</tspan></text>
<path d="m591 993.12 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="996.87" class="box-text abox-text"><tspan>Script coin lock released</tspan></text>
<path d="m591 1069.12 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="1072.87" class="box-text abox-text"><tspan>Script tx redeemed</tspan></text>
<path d="m591 1107.12 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="1110.87" class="box-text abox-text"><tspan>Bid Completed</tspan></text>
<path d="M799 1279.18h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
<text x="1001" y="1299.93" class="box-text note-text"><tspan>tx can be sent by either party.</tspan></text>
<path d="m591 1372.18 3-17.01h190l3 17.01-3 17.01H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="1367.93" class="box-text abox-text"><tspan>Bid Script pre-refund tx in</tspan></text>
<text x="689" y="1383.93" class="box-text abox-text"><tspan>chain</tspan></text>
<path d="M-36 1410.18h200.86v11.02l-7 7H-36" class="box inline_expression_label"/>
<text x="-34" y="1423.43" class="inline_expression-text alt-text anchor-start"><tspan>alt: offerer refunds script coin lock tx</tspan></text>
<path d="M799 1528.23h395v9h9m-9-9 9 9v41.02H799v-50.02z" class="box note" style="fill:#ffc"/>
<text x="1001" y="1540.99" class="box-text note-text"><tspan>Refunds the script lock tx, with the offerer&apos;s cleartext signature</tspan></text>
<text x="1001" y="1556.99" class="box-text note-text"><tspan>the bidder can refund the noscript lock tx. </tspan></text>
<text x="1001" y="1572.99" class="box-text note-text"><tspan>Once the lock expires the pre-refund tx can be spent by the bidder.</tspan></text>
<path d="m383 1599.24 3-17h190l3 17-3 17H386z" class="box abox" style="stroke:red"/>
<text x="481" y="1602.99" class="box-text abox-text"><tspan>Bid Failed, refunded</tspan></text>
<path d="M799 1620.24h395v9h9m-9-9 9 9v25H799v-34z" class="box note" style="fill:#ffc"/>
<text x="1001" y="1640.99" class="box-text note-text"><tspan>Bidder recovers the offerer&apos;s scriptless chain key-shard.</tspan></text>
<path d="m591 1713.24 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="1716.99" class="box-text abox-text"><tspan>Bid Scriptless tx recovered</tspan></text>
<path d="m591 1751.24 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="1754.99" class="box-text abox-text"><tspan>Bid Failed, refunded</tspan></text>
<path d="m591 1962.3 3-17h190l3 17-3 17H594z" class="box abox" style="stroke:#00f"/>
<text x="689" y="1966.05" class="box-text abox-text"><tspan>Bid Failed, swiped</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,388 @@
<svg version="1.1" id="mscgenjsreplaceme" class="mscgenjsreplaceme" xmlns="http://www.w3.org/2000/svg" width="1264" height="1933.3" style="font-family:Helvetica,sans-serif;font-size:12px;font-weight:400;font-style:normal;text-decoration:none;background-color:#fff;stroke:#000;stroke-width:2">
<defs>
<marker orient="auto" id="mscgenjsreplacemecallback-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#00f"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-l-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="M17 1 9 3l8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#00f"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-#008800" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#080"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-l-#008800" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="M17 1 9 3l8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:#080"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="m1 1 8 2-8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:red"/>
</marker>
<marker orient="auto" id="mscgenjsreplacemecallback-l-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path d="M17 1 9 3l8 2" class="arrow-style" style="stroke-dasharray:100,1;stroke:red"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="#00F" fill="#00F" d="m1 1 8 2-8 2z"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-l-#0000FF" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="#00F" fill="#00F" d="M17 1 9 3l8 2z"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="red" fill="red" d="m1 1 8 2-8 2z"/>
</marker>
<marker orient="auto" id="mscgenjsreplacememethod-l-#FF0000" class="arrow-marker" viewBox="0 0 10 10" refX="9" refY="3" markerUnits="strokeWidth" markerWidth="10" markerHeight="10">
<path class="arrow-style" stroke="red" fill="red" d="M17 1 9 3l8 2z"/>
</marker>
<style>
.mscgenjsreplaceme path,.mscgenjsreplaceme rect{fill:none}.mscgenjsreplaceme .label-text-background{fill:#fff;stroke:#fff;stroke-width:0}.mscgenjsreplaceme .return{stroke-dasharray:5,3}.mscgenjsreplaceme text{color:inherit;stroke:none;text-anchor:middle}.mscgenjsreplaceme text.anchor-start{text-anchor:start}.mscgenjsreplaceme .arrow-marker{overflow:visible}.mscgenjsreplaceme .arrow-style{stroke-width:1}.mscgenjsreplaceme .arcrow{stroke-linecap:butt}.mscgenjsreplaceme .box,.mscgenjsreplaceme .entity{fill:#fff;stroke-linejoin:round}
</style>
</defs>
<g id="mscgenjsreplaceme_body" transform="translate(47 3)">
<path class="bglayer" style="fill:#fff;stroke:#fff;stroke-width:0" d="M-47-3h1264v1933.3H-47z" id="mscgenjsreplaceme_background"/>
<path class="box inline_expression alt" d="M-39 778.06h1040V1908.3H-39z" id="mscgenjsreplaceme_arcspans"/>
<g id="mscgenjsreplaceme_lifelines">
<path class="arcrow" style="stroke:transparent" d="M65 38v38"/>
<path class="arcrow" style="stroke:#080" d="M273 38v38"/>
<path class="arcrow" style="stroke:red" d="M481 38v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 38v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 38v38M1105 38v38M65 76v38"/>
<path class="arcrow" style="stroke:#080" d="M273 76v38"/>
<path class="arcrow" style="stroke:red" d="M481 76v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 76v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 76v38M1105 76v38M65 114v38"/>
<path class="arcrow" style="stroke:#080" d="M273 114v38"/>
<path class="arcrow" style="stroke:red" d="M481 114v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 114v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 114v38M1105 114v38M65 152v38"/>
<path class="arcrow" style="stroke:#080" d="M273 152v38"/>
<path class="arcrow" style="stroke:red" d="M481 152v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 152v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 152v38M1105 152v38M65 190v38"/>
<path class="arcrow" style="stroke:#080" d="M273 190v38"/>
<path class="arcrow" style="stroke:red" d="M481 190v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 190v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 190v38M1105 190v38M65 228v38"/>
<path class="arcrow" style="stroke:#080" d="M273 228v38"/>
<path class="arcrow" style="stroke:red" d="M481 228v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 228v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 228v38M1105 228v38M65 266v38"/>
<path class="arcrow" style="stroke:#080" d="M273 266v38"/>
<path class="arcrow" style="stroke:red" d="M481 266v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 266v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 266v38M1105 266v38M65 304v38"/>
<path class="arcrow" style="stroke:#080" d="M273 304v38"/>
<path class="arcrow" style="stroke:red" d="M481 304v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 304v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 304v38M1105 304v38M65 342v38"/>
<path class="arcrow" style="stroke:#080" d="M273 342v38"/>
<path class="arcrow" style="stroke:red" d="M481 342v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 342v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 342v38M1105 342v38M65 380v38"/>
<path class="arcrow" style="stroke:#080" d="M273 380v38"/>
<path class="arcrow" style="stroke:red" d="M481 380v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 380v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 380v38M1105 380v38M65 418v38"/>
<path class="arcrow" style="stroke:#080" d="M273 418v38"/>
<path class="arcrow" style="stroke:red" d="M481 418v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 418v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 418v38M1105 418v38M65 456v38"/>
<path class="arcrow" style="stroke:#080" d="M273 456v38"/>
<path class="arcrow" style="stroke:red" d="M481 456v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 456v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 456v38M1105 456v38M65 494v38"/>
<path class="arcrow" style="stroke:#080" d="M273 494v38"/>
<path class="arcrow" style="stroke:red" d="M481 494v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 494v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 494v38M1105 494v38M65 532v38"/>
<path class="arcrow" style="stroke:#080" d="M273 532v38"/>
<path class="arcrow" style="stroke:red" d="M481 532v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 532v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 532v38M1105 532v38M65 570v38"/>
<path class="arcrow" style="stroke:#080" d="M273 570v38"/>
<path class="arcrow" style="stroke:red" d="M481 570v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 570v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 570v38M1105 570v38M65 608v38"/>
<path class="arcrow" style="stroke:#080" d="M273 608v38"/>
<path class="arcrow" style="stroke:red" d="M481 608v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 608v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 608v38M1105 608v38M65 646v75.06"/>
<path class="arcrow" style="stroke:#080" d="M273 646v75.06"/>
<path class="arcrow" style="stroke:red" d="M481 646v75.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 646v75.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 646v75.06M1105 646v75.06M65 721.06v38"/>
<path class="arcrow" style="stroke:#080" d="M273 721.06v38"/>
<path class="arcrow" style="stroke:red" d="M481 721.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 721.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 721.06v38M1105 721.06v38M65 759.06v38"/>
<path class="arcrow" style="stroke:#080" d="M273 759.06v38"/>
<path class="arcrow" style="stroke:red" d="M481 759.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 759.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 759.06v38M1105 759.06v38M65 797.06v38"/>
<path class="arcrow" style="stroke:#080" d="M273 797.06v38"/>
<path class="arcrow" style="stroke:red" d="M481 797.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 797.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 797.06v38M1105 797.06v38M65 835.06v38"/>
<path class="arcrow" style="stroke:#080" d="M273 835.06v38"/>
<path class="arcrow" style="stroke:red" d="M481 835.06v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 835.06v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 835.06v38M1105 835.06v38M65 873.06v75.06"/>
<path class="arcrow" style="stroke:#080" d="M273 873.06v75.06"/>
<path class="arcrow" style="stroke:red" d="M481 873.06v75.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 873.06v75.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 873.06v75.06M1105 873.06v75.06M65 948.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 948.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 948.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 948.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 948.12v38M1105 948.12v38M65 986.12v86"/>
<path class="arcrow" style="stroke:#080" d="M273 986.12v86"/>
<path class="arcrow" style="stroke:red" d="M481 986.12v86"/>
<path class="arcrow" style="stroke:#00f" d="M689 986.12v86"/>
<path class="arcrow" style="stroke:transparent" d="M897 986.12v86M1105 986.12v86M65 1072.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1072.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1072.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1072.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1072.12v38M1105 1072.12v38M65 1110.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1110.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1110.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1110.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1110.12v38M1105 1110.12v38M65 1148.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1148.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1148.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1148.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1148.12v38M1105 1148.12v38M65 1186.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1186.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1186.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1186.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1186.12v38M1105 1186.12v38M65 1224.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1224.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1224.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1224.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1224.12v38M1105 1224.12v38M65 1262.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1262.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1262.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1262.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1262.12v38M1105 1262.12v38M65 1300.12v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1300.12v38"/>
<path class="arcrow" style="stroke:red" d="M481 1300.12v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1300.12v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1300.12v38M1105 1300.12v38M65 1338.12v75.06"/>
<path class="arcrow" style="stroke:#080" d="M273 1338.12v75.06"/>
<path class="arcrow" style="stroke:red" d="M481 1338.12v75.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 1338.12v75.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 1338.12v75.06M1105 1338.12v75.06M65 1413.18v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1413.18v38"/>
<path class="arcrow" style="stroke:red" d="M481 1413.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1413.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1413.18v38M1105 1413.18v38M65 1451.18v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1451.18v38"/>
<path class="arcrow" style="stroke:red" d="M481 1451.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1451.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1451.18v38M1105 1451.18v38M65 1489.18v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1489.18v38"/>
<path class="arcrow" style="stroke:red" d="M481 1489.18v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1489.18v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1489.18v38M1105 1489.18v38M65 1527.18v75.06"/>
<path class="arcrow" style="stroke:#080" d="M273 1527.18v75.06"/>
<path class="arcrow" style="stroke:red" d="M481 1527.18v75.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 1527.18v75.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 1527.18v75.06M1105 1527.18v75.06M65 1602.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1602.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1602.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1602.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1602.24v38M1105 1602.24v38M65 1640.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1640.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1640.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1640.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1640.24v38M1105 1640.24v38M65 1678.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1678.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1678.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1678.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1678.24v38M1105 1678.24v38M65 1716.24v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1716.24v38"/>
<path class="arcrow" style="stroke:red" d="M481 1716.24v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1716.24v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1716.24v38M1105 1716.24v38M65 1754.24v59.06"/>
<path class="arcrow" style="stroke:#080" d="M273 1754.24v59.06"/>
<path class="arcrow" style="stroke:red" d="M481 1754.24v59.06"/>
<path class="arcrow" style="stroke:#00f" d="M689 1754.24v59.06"/>
<path class="arcrow" style="stroke:transparent" d="M897 1754.24v59.06M1105 1754.24v59.06M65 1813.3v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1813.3v38"/>
<path class="arcrow" style="stroke:red" d="M481 1813.3v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1813.3v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1813.3v38M1105 1813.3v38M65 1851.3v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1851.3v38"/>
<path class="arcrow" style="stroke:red" d="M481 1851.3v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1851.3v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1851.3v38M1105 1851.3v38M65 1889.3v38"/>
<path class="arcrow" style="stroke:#080" d="M273 1889.3v38"/>
<path class="arcrow" style="stroke:red" d="M481 1889.3v38"/>
<path class="arcrow" style="stroke:#00f" d="M689 1889.3v38"/>
<path class="arcrow" style="stroke:transparent" d="M897 1889.3v38M1105 1889.3v38"/>
</g>
<g id="mscgenjsreplaceme_sequence">
<path class="entity" style="stroke:transparent" d="M0 0h130v38H0z"/>
<text x="65" y="22.75" class="entity-text"><tspan> </tspan></text>
<path class="entity" style="fill:#cfc;stroke:#080" d="M208 0h130v38H208z"/>
<text x="273" y="22.75" class="entity-text"><tspan>Network</tspan></text>
<path class="entity" style="fill:#fcc;stroke:red" d="M416 0h130v38H416z"/>
<text x="481" y="22.75" class="entity-text"><tspan>Offerer</tspan></text>
<path class="entity" style="fill:#ccf;stroke:#00f" d="M624 0h130v38H624z"/>
<text x="689" y="22.75" class="entity-text"><tspan>Bidder</tspan></text>
<path class="entity" style="stroke:transparent" d="M832 0h130v38H832z"/>
<text x="897" y="22.75" class="entity-text"><tspan> </tspan></text>
<path class="entity" style="stroke:transparent" d="M1040 0h130v38h-130z"/>
<text x="1105" y="22.75" class="entity-text"><tspan> </tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 95H273"/>
<path class="label-text-background" d="M345.08 79.25h63.84v14h-63.84z"/>
<text x="377" y="90.25" class="directional-text callback-text"><tspan>Sends Offer</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 133h416"/>
<path class="label-text-background" d="M445.75 117.25h70.5v14h-70.5z"/>
<text x="481" y="128.25" class="directional-text return-text"><tspan>Detects Offer</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 171H481"/>
<path class="label-text-background" d="M557.64 155.25h54.72v14h-54.72z"/>
<text x="585" y="166.25" class="directional-text callback-text"><tspan>Sends Bid</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 323h208"/>
<path class="label-text-background" d="M513.28 307.25h143.77v14H513.28z"/>
<text x="585" y="318.25" class="directional-text callback-text"><tspan>Sends BidAccept message</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 399H481"/>
<path class="label-text-background" d="M491.28 383.25h187.77v14H491.28z"/>
<text x="585" y="394.25" class="directional-text callback-text"><tspan>Sends XmrBidLockTxSigsMessage</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 475H273"/>
<path class="label-text-background" d="M311.64 459.25h130.72v14H311.64z"/>
<text x="377" y="470.25" class="directional-text callback-text"><tspan>Sends script-coin-lock-tx</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 551h208"/>
<path class="label-text-background" d="M485.61 535.25h199.11v14H485.61z"/>
<text x="585" y="546.25" class="directional-text callback-text"><tspan>Sends XmrBidLockSpendTxMessage</tspan></text>
<path d="M689 675.93c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:#00f" marker-end="url(#mscgenjsreplacememethod-#0000FF)"/>
<path class="label-text-background" d="M692 627.67h40.91v14.02H692z"/>
<text x="692" y="638.68" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M692 643.67h107.02v14.02H692z"/>
<text x="692" y="654.68" class="directional-text method-text anchor-start"><tspan>script-coin-lock-tx to</tspan></text>
<path class="label-text-background" d="M692 659.67h39.34v14.02H692z"/>
<text x="692" y="670.68" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
<path d="M481 675.93c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M484 627.67h40.91v14.02H484z"/>
<text x="484" y="638.68" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M484 643.67h107.02v14.02H484z"/>
<text x="484" y="654.68" class="directional-text method-text anchor-start"><tspan>script-coin-lock-tx to</tspan></text>
<path class="label-text-background" d="M484 659.67h39.34v14.02H484z"/>
<text x="484" y="670.68" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 816.06H273"/>
<path class="label-text-background" d="M408.97 800.3h144.06v14.02H408.97z"/>
<text x="481" y="811.31" class="directional-text callback-text"><tspan>Sends noscript-coin-lock-tx</tspan></text>
<path d="M481 902.99c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M484 854.73h40.91v14.02H484z"/>
<text x="484" y="865.74" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M484 870.73h120.38v14.02H484z"/>
<text x="484" y="881.74" class="directional-text method-text anchor-start"><tspan>noscript-coin-lock-tx to</tspan></text>
<path class="label-text-background" d="M484 886.73h39.34v14.02H484z"/>
<text x="484" y="897.74" class="directional-text method-text anchor-start"><tspan>confirm</tspan></text>
<path class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)" d="M481 1029.12h208"/>
<path class="label-text-background" d="M519.64 1013.36h130.72v14.02H519.64z"/>
<text x="585" y="1024.37" class="directional-text method-text"><tspan>Sends script-coin-lock-tx</tspan></text>
<path class="label-text-background" d="M539.3 1031.36h91.73v14.02H539.3z"/>
<text x="585" y="1042.37" class="directional-text method-text"><tspan>release message</tspan></text>
<path class="arc directional callback" style="stroke:#00f" marker-end="url(#mscgenjsreplacemecallback-#0000FF)" d="M689 1129.12H273"/>
<path class="label-text-background" d="M397.3 1113.36h167.41v14.02H397.3z"/>
<text x="481" y="1124.37" class="directional-text callback-text"><tspan>Sends script-coin-lock-spend-tx</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 1167.12h208"/>
<path class="label-text-background" d="M289.97 1151.36h174.06v14.02H289.97z"/>
<text x="377" y="1162.37" class="directional-text return-text"><tspan>Detects script-coin-lock-spend-tx</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1243.12H273"/>
<path class="label-text-background" d="M286.63 1227.36h180.75v14.02H286.63z"/>
<text x="377" y="1238.37" class="directional-text callback-text"><tspan>Sends noscript-coin-lock-spend-tx</tspan></text>
<path d="M481 1368.05c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M484 1319.8h40.91v14.02H484z"/>
<text x="484" y="1330.8" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M484 1335.8h143.39v14.02H484z"/>
<text x="484" y="1346.8" class="directional-text method-text anchor-start"><tspan>noscript-coin-lock-spend-tx</tspan></text>
<path class="label-text-background" d="M484 1351.8h52.69v14.02H484z"/>
<text x="484" y="1362.8" class="directional-text method-text anchor-start"><tspan>to confirm</tspan></text>
<path class="inline_expression_divider" style="stroke-dasharray:10,5" d="M-39 1470.18h1040"/>
<path class="label-text-background" d="M459.98 1462.92h42.03v14.02h-42.03z"/>
<text x="481" y="1473.93" class="empty-text comment-row-text"><tspan>fail path</tspan></text>
<path d="M481 1557.11c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M484 1508.86h40.91v14.02H484z"/>
<text x="484" y="1519.86" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M484 1524.86h93.36v14.02H484z"/>
<text x="484" y="1535.86" class="directional-text method-text anchor-start"><tspan>script-coin-lock-tx</tspan></text>
<path class="label-text-background" d="M484 1540.86h93.7v14.02H484z"/>
<text x="484" y="1551.86" class="directional-text method-text anchor-start"><tspan>locktime to expire</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1621.24H273"/>
<path class="label-text-background" d="M359.98 1605.48h34.03v14.02h-34.03z"/>
<text x="377" y="1616.49" class="directional-text callback-text"><tspan>Sends</tspan></text>
<path class="label-text-background" d="M300.64 1623.48h152.72v14.02H300.64z"/>
<text x="377" y="1634.49" class="directional-text callback-text"><tspan>script-coin-lock-pre-refund-tx</tspan></text>
<path class="arc directional return" style="stroke:#080" marker-end="url(#mscgenjsreplacemecallback-#008800)" d="M273 1659.24h208"/>
<path class="label-text-background" d="M300.64 1643.48h152.72v14.02H300.64z"/>
<text x="377" y="1654.49" class="directional-text return-text"><tspan>script-coin-lock-pre-refund-tx</tspan></text>
<path d="M481 1776.17c104 .1 104 22.8 0 22.8" class="arc directional method" style="stroke:red" marker-end="url(#mscgenjsreplacememethod-#FF0000)"/>
<path class="label-text-background" d="M484 1743.91h40.91v14.02H484z"/>
<text x="484" y="1754.92" class="directional-text method-text anchor-start"><tspan>Wait for</tspan></text>
<path class="label-text-background" d="M484 1759.91h124.06v14.02H484z"/>
<text x="484" y="1770.92" class="directional-text method-text anchor-start"><tspan>pre-refund tx to confirm</tspan></text>
<path class="arc directional callback" style="stroke:red" marker-end="url(#mscgenjsreplacemecallback-#FF0000)" d="M481 1832.3H273"/>
<path class="label-text-background" d="M359.98 1816.55h34.03v14.02h-34.03z"/>
<text x="377" y="1827.55" class="directional-text callback-text"><tspan>Sends</tspan></text>
<path class="label-text-background" d="M282.3 1834.55h189.41v14.02H282.3z"/>
<text x="377" y="1845.55" class="directional-text callback-text"><tspan>script-coin-lock-pre-refund-spend-tx</tspan></text>
</g>
<g id="mscgenjsreplaceme_notes">
<path d="m381 209 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="212.75" class="box-text abox-text"><tspan>Bid Receiving</tspan></text>
<path d="m381 247 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="250.75" class="box-text abox-text"><tspan>Bid Received</tspan></text>
<path class="box" style="stroke:red" d="M381 268h200v34H381z"/>
<text x="481" y="288.75" class="box-text"><tspan>User accepts bid</tspan></text>
<path d="M797 306h399v9h9m-9-9 9 9v25H797v-34z" class="box note" style="fill:#ffc"/>
<text x="1001" y="318.75" class="box-text note-text"><tspan>The BidAccept message contains the pubkeys the offerer will use and a</tspan></text>
<text x="1001" y="334.75" class="box-text note-text"><tspan>DLEAG proof one key will work across both chains of the swapping coins</tspan></text>
<path d="m381 361 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="364.75" class="box-text abox-text"><tspan>Bid Accepted</tspan></text>
<path d="M797 382h399v9h9m-9-9 9 9v25H797v-34z" class="box note" style="fill:#ffc"/>
<text x="1001" y="394.75" class="box-text note-text"><tspan>The XmrBidLockTxSigsMessage contains the bidder&apos;s signatures for the</tspan></text>
<text x="1001" y="410.75" class="box-text note-text"><tspan>script-coin-lock-refund and script-coin-lock-refund-spend txns.</tspan></text>
<path d="m381 437 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="432.75" class="box-text abox-text"><tspan>Exchanged script lock tx sigs</tspan></text>
<text x="481" y="448.75" class="box-text abox-text"><tspan>msg</tspan></text>
<path d="m381 513 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="516.75" class="box-text abox-text"><tspan>Bid Script coin spend tx valid</tspan></text>
<path d="M797 534h399v9h9m-9-9 9 9v25H797v-34z" class="box note" style="fill:#ffc"/>
<text x="1001" y="546.75" class="box-text note-text"><tspan>The XmrBidLockSpendTxMessage contains the script-coin-lock-tx and the</tspan></text>
<text x="1001" y="562.75" class="box-text note-text"><tspan>offerer&apos;s signature for it.</tspan></text>
<path d="m381 589 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="584.75" class="box-text abox-text"><tspan>Exchanged script lock spend tx</tspan></text>
<text x="481" y="600.75" class="box-text abox-text"><tspan>msg</tspan></text>
<path d="m381 740.06 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="743.81" class="box-text abox-text"><tspan>Bid Script coin locked</tspan></text>
<path d="M-38 778.06h98.39v11.02l-7 7H-38" class="box inline_expression_label"/>
<text x="-36" y="791.31" class="inline_expression-text alt-text anchor-start"><tspan>alt: success path</tspan></text>
<path d="m381 967.12 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="970.87" class="box-text abox-text"><tspan>Bid Scriptless coin locked</tspan></text>
<path d="M797 988.11h399v9h9m-9-9 9 9v73.02H797v-82.02z" class="box note" style="fill:#ffc"/>
<text x="1001" y="1000.87" class="box-text note-text"><tspan>The XmrBidLockReleaseMessage contains the offerer&apos;s OTVES for the</tspan></text>
<text x="1001" y="1016.87" class="box-text note-text"><tspan>script-coin-lock-tx. The bidder decodes the</tspan></text>
<text x="1001" y="1032.87" class="box-text note-text"><tspan>offerer&apos;s signature from the OTVES. When the</tspan></text>
<text x="1001" y="1048.87" class="box-text note-text"><tspan>offerer has the plaintext signature, they can decode the bidder&apos;s key</tspan></text>
<text x="1001" y="1064.87" class="box-text note-text"><tspan>for the noscript-lock-tx.</tspan></text>
<path d="m381 1091.12 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="1094.87" class="box-text abox-text"><tspan>Bid Script coin lock released</tspan></text>
<path d="m381 1205.12 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="1208.87" class="box-text abox-text"><tspan>Bid Script tx redeemed</tspan></text>
<path d="M797 1188.11h399v9h9m-9-9 9 9v25.02H797v-34.02z" class="box note" style="fill:#ffc"/>
<text x="1001" y="1200.87" class="box-text note-text"><tspan>The offerer extracts the bidder&apos;s plaintext signature and derives the</tspan></text>
<text x="1001" y="1216.87" class="box-text note-text"><tspan>bidder&apos;s noscript-lock-tx keyhalf.</tspan></text>
<path d="m381 1281.12 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="1284.87" class="box-text abox-text"><tspan>Bid Scriptless tx redeemed</tspan></text>
<path d="m381 1432.18 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="1435.93" class="box-text abox-text"><tspan>Bid Completed</tspan></text>
<path d="M797 1604.24h399v9h9m-9-9 9 9v25H797v-34z" class="box note" style="fill:#ffc"/>
<text x="1001" y="1624.99" class="box-text note-text"><tspan>tx can be sent by either party.</tspan></text>
<path d="m381 1697.24 3-17.01h194l3 17.01-3 17.01H384z" class="box abox" style="stroke:red"/>
<text x="481" y="1692.99" class="box-text abox-text"><tspan>Bid Script pre-refund tx in</tspan></text>
<text x="481" y="1708.99" class="box-text abox-text"><tspan>chain</tspan></text>
<path d="M797 1815.29h399v9h9m-9-9 9 9v25.02H797v-34.02z" class="box note" style="fill:#ffc"/>
<text x="1001" y="1828.05" class="box-text note-text"><tspan>Refunds the script lock tx, with the offerer&apos;s cleartext signature</tspan></text>
<text x="1001" y="1844.05" class="box-text note-text"><tspan>the bidder can refund the noscript lock tx.</tspan></text>
<path d="m381 1870.3 3-17h194l3 17-3 17H384z" class="box abox" style="stroke:red"/>
<text x="481" y="1874.05" class="box-text abox-text"><tspan>Bid Failed, refunded</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1,15 +0,0 @@
.padded_row td
{
padding-top:1.5em;
}
.bold
{
font-weight:bold;
}
.monospace
{
font-family:monospace;
}

View File

@@ -0,0 +1,38 @@
{% include 'header.html' %}
<h3>Automation Strategies</h3>
{% for m in messages %}
<p>{{ m }}</p>
{% endfor %}
<form method="post">
<table>
<tr><td>Sort By</td><td>
<select name="sort_by">
<option value="created_at"{% if filters.sort_by=='created_at' %} selected{% endif %}>Created At</option>
</select>
<select name="sort_dir">
<option value="asc"{% if filters.sort_dir=='asc' %} selected{% endif %}>Ascending</option>
<option value="desc"{% if filters.sort_dir=='desc' %} selected{% endif %}>Descending</option>
</select>
</td></tr>
<tr><td><input type="submit" name='applyfilters' value="Apply Filters"></td><td><input type="submit" name='clearfilters' value="Clear Filters"></td></tr>
<tr><td><input type="submit" name='pageback' value="Page Back"></td><td>Page: {{ filters.page_no }}</td><td><input type="submit" name='pageforwards' value="Page Forwards"></td></tr>
</table>
<input type="hidden" name="formid" value="{{ form_id }}">
<input type="hidden" name="pageno" value="{{ filters.page_no }}">
</form>
<p><a href="/newautomationstrategy">Create New Strategy</a></p>
<table>
<tr><th>Name</th><th>Type</th></tr>
{% for s in strategies %}
<tr><td><a class="monospace" href=/automationstrategy/{{ s[0] }}>{{ s[1] }}</a></td><td>{{ s[2] }}</td></tr>
{% endfor %}
</table>
<p><a href="/">home</a></p>
</body></html>

View File

@@ -0,0 +1,27 @@
{% include 'header.html' %}
<h3>Automation Strategy {{ strategy_id }}</h3>
{% for m in messages %}
<p>{{ m }}</p>
{% endfor %}
<table>
<tr><td>Label</td><td>{{ strategy.label }}</td></tr>
<tr><td>Type</td><td>{{ strategy.type }}</td></tr>
<tr><td>Only known identities</td><td>{{ strategy.only_known_identities }}</td></tr>
<tr><td>Data</td><td>
<textarea class="monospace" rows="10" cols="150" readonly>
{{ strategy.data }}
</textarea>
<tr><td>Notes</td><td>
<textarea rows="10" cols="150" readonly>
{{ strategy.note }}
</textarea>
</td></tr>
</table>
<p><a href="/">home</a></p>
</body></html>

View File

@@ -0,0 +1,11 @@
{% include 'header.html' %}
<h3>New Automation Strategy</h3>
{% for m in messages %}
<p>{{ m }}</p>
{% endfor %}
<p>TODO</p>
<p><a href="/">home</a></p>
</body></html>

View File

@@ -2,7 +2,9 @@
<h3>Bid {{ bid_id }}</h3> <h3>Bid {{ bid_id }}</h3>
{% if refresh %} {% if refresh %}
<p>Page Refresh: {{ refresh }} seconds</p> <p><a href=/bid/{{ bid_id }}>Page Refresh: {{ refresh }} second</a></p>
{% else %}
<p><a href=/bid/{{ bid_id }}>refresh</a></p>
{% endif %} {% endif %}
{% for m in messages %} {% for m in messages %}
@@ -75,9 +77,29 @@
{% endif %} {% endif %}
<input name="edit_bid" type="submit" value="Edit Bid"> <input name="edit_bid" type="submit" value="Edit Bid">
{% endif %} {% endif %}
<br/>
{% if data.show_bidder_seq_diagram %}
<input name="hide_bidder_seq_diagram" type="submit" value="Hide Bidder Sequence Diagram">
{% else %}
<input name="show_bidder_seq_diagram" type="submit" value="Show Bidder Sequence Diagram">
{% endif %}
{% if data.show_offerer_seq_diagram %}
<input name="hide_offerer_seq_diagram" type="submit" value="Hide Offerer Sequence Diagram">
{% else %}
<input name="show_offerer_seq_diagram" type="submit" value="Show Offerer Sequence Diagram">
{% endif %}
<input type="hidden" name="formid" value="{{ form_id }}"> <input type="hidden" name="formid" value="{{ form_id }}">
</form> </form>
{% if data.show_bidder_seq_diagram %}
<img src="/static/sequence_diagrams/bidder.alt.xu.min.svg" />
{% endif %}
{% if data.show_offerer_seq_diagram %}
<img src="/static/sequence_diagrams/offerer.alt.xu.min.svg" />
{% endif %}
<h4>Old States</h4> <h4>Old States</h4>
<table> <table>

View File

@@ -2,7 +2,9 @@
<h3>Bid {{ bid_id }}</h3> <h3>Bid {{ bid_id }}</h3>
{% if refresh %} {% if refresh %}
<p>Page Refresh: {{ refresh }} seconds</p> <p><a href=/bid/{{ bid_id }}>Page Refresh: {{ refresh }} second</a></p>
{% else %}
<p><a href=/bid/{{ bid_id }}>refresh</a></p>
{% endif %} {% endif %}
{% for m in messages %} {% for m in messages %}
@@ -72,6 +74,17 @@
{% endif %} {% endif %}
<input name="edit_bid" type="submit" value="Edit Bid"> <input name="edit_bid" type="submit" value="Edit Bid">
{% endif %} {% endif %}
<br/>
{% if data.show_bidder_seq_diagram %}
<input name="hide_bidder_seq_diagram" type="submit" value="Hide Bidder Sequence Diagram">
{% else %}
<input name="show_bidder_seq_diagram" type="submit" value="Show Bidder Sequence Diagram">
{% endif %}
{% if data.show_offerer_seq_diagram %}
<input name="hide_offerer_seq_diagram" type="submit" value="Hide Offerer Sequence Diagram">
{% else %}
<input name="show_offerer_seq_diagram" type="submit" value="Show Offerer Sequence Diagram">
{% endif %}
<input type="hidden" name="formid" value="{{ form_id }}"> <input type="hidden" name="formid" value="{{ form_id }}">
{% if data.show_txns %} {% if data.show_txns %}
@@ -117,6 +130,14 @@
{% endif %} {% endif %}
</form> </form>
{% if data.show_bidder_seq_diagram %}
<img src="/static/sequence_diagrams/xmr.bidder.alt.xu.min.svg" />
{% endif %}
{% if data.show_offerer_seq_diagram %}
<img src="/static/sequence_diagrams/xmr.offerer.alt.xu.min.svg" />
{% endif %}
{% if data.chain_a_lock_tx_inputs %} {% if data.chain_a_lock_tx_inputs %}
<h5>Chain A Lock TX Inputs:</h5> <h5>Chain A Lock TX Inputs:</h5>
<table> <table>

View File

@@ -20,6 +20,19 @@
<option value="desc"{% if filters.sort_dir=='desc' %} selected{% endif %}>Descending</option> <option value="desc"{% if filters.sort_dir=='desc' %} selected{% endif %}>Descending</option>
</select> </select>
</td></tr> </td></tr>
<tr><td>State</td><td>
<select name="state">
<option value="-1"{% if filters.bid_state_ind==-1 %} selected{% endif %}>-- Any --</option>
{% for s in data.bid_states %}
<option value="{{ s[0] }}"{% if filters.bid_state_ind==s[0] %} selected{% endif %}>{{ s[1] }}</option>
{% endfor %}
</select>
<tr><td>Include Expired</td><td>
<select name="with_expired">
<option value="true"{% if filters.with_expired==true %} selected{% endif %}>Include</option>
<option value="false"{% if filters.with_expired==false %} selected{% endif %}>Exclude</option>
</select>
</td></tr>
<tr><td><input type="submit" name='applyfilters' value="Apply Filters"></td><td><input type="submit" name='clearfilters' value="Clear Filters"></td></tr> <tr><td><input type="submit" name='applyfilters' value="Apply Filters"></td><td><input type="submit" name='clearfilters' value="Clear Filters"></td></tr>
<tr><td><input type="submit" name='pageback' value="Page Back"></td><td>Page: {{ filters.page_no }}</td><td><input type="submit" name='pageforwards' value="Page Forwards"></td></tr> <tr><td><input type="submit" name='pageback' value="Page Back"></td><td>Page: {{ filters.page_no }}</td><td><input type="submit" name='pageforwards' value="Page Forwards"></td></tr>

View File

@@ -4,11 +4,42 @@
{% if refresh %} {% if refresh %}
<meta http-equiv="refresh" content="{{ refresh }}"> <meta http-equiv="refresh" content="{{ refresh }}">
{% endif %} {% endif %}
<link type="text/css" media="all" href="/static/style.css" rel="stylesheet"> <link type="text/css" media="all" href="/static/css/simple/style.css" rel="stylesheet">
<link rel=icon sizes="32x32" type="image/png" href="/static/favicon-32.png"> <link rel=icon sizes="32x32" type="image/png" href="/static/images/favicon-32.png">
<title>{{ title }}</title> <title>{{ title }}</title>
</head> </head>
<body> <body>
{% if h2 %} {% if h2 %}
<h2>{{ h2 }}</h2> <h2>{{ h2 }}</h2>
{% endif %} {% endif %}
{% if ws_url %}
<script>
var ws = new WebSocket("{{ ws_url }}"),
floating_div = document.createElement('div');
floating_div.classList.add('floatright');
messages = document.createElement('ul');
messages.setAttribute('id', 'ul_updates');
ws.onmessage = function (event) {
let json = JSON.parse(event.data);
let event_message = 'Unknown event';
if (json['event'] == 'new_offer') {
event_message = '<a href=/offer/' + json['offer_id'] + '>New offer</a>';
} else
if (json['event'] == 'new_bid') {
event_message = '<a href=/bid/' + json['bid_id'] + '>New bid</a> on offer <a href=/offer/' + json['offer_id'] + '>' + json['offer_id'] + '</a>';
} else
if (json['event'] == 'bid_accepted') {
event_message = '<a href=/bid/' + json['bid_id'] + '>Bid accepted</a>';
}
let messages = document.getElementById('ul_updates'),
message = document.createElement('li');
message.innerHTML = event_message;
messages.appendChild(message);
};
floating_div.appendChild(messages);
document.body.appendChild(floating_div);
</script>
{% endif %}

View File

@@ -16,9 +16,11 @@ Version: {{ version }}
<a href="/active">Swaps in Progress: {{ summary.num_swapping }}</a><br/> <a href="/active">Swaps in Progress: {{ summary.num_swapping }}</a><br/>
<a href="/offers">Network Offers: {{ summary.num_network_offers }}</a><br/> <a href="/offers">Network Offers: {{ summary.num_network_offers }}</a><br/>
<a href="/sentoffers">Sent Offers: {{ summary.num_sent_offers }}</a><br/> <a href="/sentoffers">Sent Offers: {{ summary.num_sent_offers }}</a><br/>
<a href="/availablebids">Available Bids: {{ summary.num_available_bids }}</a><br/>
<a href="/bids">Received Bids: {{ summary.num_recv_bids }}</a><br/> <a href="/bids">Received Bids: {{ summary.num_recv_bids }}</a><br/>
<a href="/sentbids">Sent Bids: {{ summary.num_sent_bids }}</a><br/> <a href="/sentbids">Sent Bids: {{ summary.num_sent_bids }}</a><br/>
<a href="/watched">Watched Outputs: {{ summary.num_watched_outputs }}</a><br/> <a href="/watched">Watched Outputs: {{ summary.num_watched_outputs }}</a><br/>
<a href="/automation">Automation Strategies</a><br/>
{% if use_tor_proxy %} <a href="/tor">TOR Information</a><br/> {% endif %} {% if use_tor_proxy %} <a href="/tor">TOR Information</a><br/> {% endif %}
</p> </p>

View File

@@ -2,12 +2,17 @@
<h3>Offer {{ offer_id }}</h3> <h3>Offer {{ offer_id }}</h3>
{% if refresh %} {% if refresh %}
<p>Page Refresh: {{ refresh }} seconds</p> <p><a href=/offer/{{ offer_id }}>Page Refresh: {{ refresh }} second</a></p>
{% else %}
<p><a href=/offer/{{ offer_id }}>refresh</a></p>
{% endif %} {% endif %}
{% for m in messages %} {% for m in messages %}
<p>{{ m }}</p> <p>{{ m }}</p>
{% endfor %} {% endfor %}
{% for m in err_messages %}
<p class="error_msg">Error: {{ m }}</p>
{% endfor %}
{% if sent_bid_id %} {% if sent_bid_id %}
<p><a href="/bid/{{ sent_bid_id }}">Sent Bid {{ sent_bid_id }}</a></p> <p><a href="/bid/{{ sent_bid_id }}">Sent Bid {{ sent_bid_id }}</a></p>
@@ -19,9 +24,11 @@
<tr><td>Coin To</td><td>{{ data.coin_to }}</td></tr> <tr><td>Coin To</td><td>{{ data.coin_to }}</td></tr>
<tr><td>Amount From</td><td>{{ data.amt_from }} {{ data.tla_from }}</td></tr> <tr><td>Amount From</td><td>{{ data.amt_from }} {{ data.tla_from }}</td></tr>
<tr><td>Amount To</td><td>{{ data.amt_to }} {{ data.tla_to }}</td></tr> <tr><td>Amount To</td><td>{{ data.amt_to }} {{ data.tla_to }}</td></tr>
<tr><td>Minimum Bid Amount</td><td>{{ data.amt_bid_min }} {{ data.tla_from }}</td></tr>
<tr><td>Rate</td><td>{{ data.rate }}</td></tr> <tr><td>Rate</td><td>{{ data.rate }}</td></tr>
<tr><td>Amount Variable</td><td>{{ data.amount_negotiable }}</td></tr> <tr><td title="Total coin-from value of completed bids, that this node is involved in">Amount Swapped</td><td>{{ data.amt_swapped }} {{ data.tla_from }}</td></tr>
<tr><td>Rate Variable</td><td>{{ data.rate_negotiable }}</td></tr> <tr><td title="If bids can be sent with a different amount">Amount Variable</td><td>{{ data.amount_negotiable }}</td></tr>
<tr><td title="If bids can be sent with a different amount">Rate Variable</td><td>{{ data.rate_negotiable }}</td></tr>
<tr><td>Script Lock Type</td><td>{{ data.lock_type }}</td></tr> <tr><td>Script Lock Type</td><td>{{ data.lock_type }}</td></tr>
<tr><td>Script Lock Value</td><td>{{ data.lock_value }} {{ data.lock_value_hr }}</td></tr> <tr><td>Script Lock Value</td><td>{{ data.lock_value }} {{ data.lock_value_hr }}</td></tr>
{% if data.addr_to == "Public" %} {% if data.addr_to == "Public" %}
@@ -35,7 +42,14 @@
<tr><td>Sent</td><td>{{ data.sent }}</td></tr> <tr><td>Sent</td><td>{{ data.sent }}</td></tr>
<tr><td>Revoked</td><td>{{ data.was_revoked }}</td></tr> <tr><td>Revoked</td><td>{{ data.was_revoked }}</td></tr>
{% if data.sent == 'True' %} {% if data.sent == 'True' %}
<tr><td>Auto Accept Bids</td><td>{{ data.auto_accept }}</td></tr> <tr><td>Auto Accept Strategy</td>
<td>
{% if data.automation_strat_id == -1 %}
None
{% else %}
<a class="monospace" href="/automationstrategy/{{ data.automation_strat_id }}">{{ data.automation_strat_label }}</a>
{% endif %}
</td></tr>
{% endif %} {% endif %}
{% if data.xmr_type == true %} {% if data.xmr_type == true %}
@@ -159,12 +173,12 @@ function updateBidParams(value_changed) {
let amt_from = ''; let amt_from = '';
let rate = ''; let rate = '';
if (amt_var) { if (amt_var == 'True') {
amt_from = document.getElementById('bid_amount').value; amt_from = document.getElementById('bid_amount').value;
} else { } else {
amt_from = document.getElementById('amount_from').value; amt_from = document.getElementById('amount_from').value;
} }
if (rate_var) { if (rate_var == 'True') {
rate = document.getElementById('bid_rate').value; rate = document.getElementById('bid_rate').value;
} else { } else {
rate = document.getElementById('offer_rate').value; rate = document.getElementById('offer_rate').value;

View File

@@ -4,6 +4,9 @@
{% for m in messages %} {% for m in messages %}
<p>{{ m }}</p> <p>{{ m }}</p>
{% endfor %} {% endfor %}
{% for m in err_messages %}
<p class="error_msg">Error: {{ m }}</p>
{% endfor %}
<form method="post"> <form method="post">
@@ -59,9 +62,10 @@
<option value="100"{% if data.fee_to_extra==100 %} selected{% endif %}>100%</option> <option value="100"{% if data.fee_to_extra==100 %} selected{% endif %}>100%</option>
</select></td></tr> </select></td></tr>
{% endif %} {% endif %}
</td><td>Rate</td><td><input type="text" id="rate" name="rate" value="{{ data.rate }}" readonly></td></tr> <tr><td>Minimum Bid Amount</td><td><input type="text" id="amt_bid_min" name="amt_bid_min" value="{{ data.amt_bid_min }}" title="Bids with an amount below the minimum bid value will be discarded" readonly></td></tr>
<tr><td>Amount Variable</td><td colspan=3><input type="checkbox" id="amt_var" name="amt_var_" value="av" {% if data.amt_var==true %} checked="true"{% endif %} disabled></td></tr> <tr><td>Rate</td><td><input type="text" id="rate" name="rate" value="{{ data.rate }}" readonly></td></tr>
<tr><td>Rate Variable</td><td colspan=3><input type="checkbox" id="rate_var" name="rate_var_" value="rv" {% if data.rate_var==true %} checked="true"{% endif %} disabled></td></tr> <tr><td>Amount Variable</td><td colspan=3><input type="checkbox" id="amt_var" name="amt_var_" value="av" {% if data.amt_var==true %} checked=checked{% endif %} disabled></td></tr>
<tr><td>Rate Variable</td><td colspan=3><input type="checkbox" id="rate_var" name="rate_var_" value="rv" {% if data.rate_var==true %} checked=checked{% endif %} disabled></td></tr>
<tr class="padded_row"><td>Offer valid (hrs)</td><td><input type="number" name="validhrs" min="1" max="48" value="{{ data.validhrs }}" readonly></td></tr> <tr class="padded_row"><td>Offer valid (hrs)</td><td><input type="number" name="validhrs" min="1" max="48" value="{{ data.validhrs }}" readonly></td></tr>
{% if data.debug_ui == true %} {% if data.debug_ui == true %}
@@ -69,7 +73,13 @@
{% else %} {% else %}
<tr><td>Contract locked (hrs)</td><td><input type="number" name="lockhrs" min="1" max="64" value="{{ data.lockhrs }}" readonly></td>{% if data.swap_style != 'xmr' %}<td colspan=2>Participate txn will be locked for half the time.</td>{% endif %}</tr> <tr><td>Contract locked (hrs)</td><td><input type="number" name="lockhrs" min="1" max="64" value="{{ data.lockhrs }}" readonly></td>{% if data.swap_style != 'xmr' %}<td colspan=2>Participate txn will be locked for half the time.</td>{% endif %}</tr>
{% endif %} {% endif %}
<tr><td>Auto Accept Bids</td><td colspan=3><input type="checkbox" id="autoaccept" name="autoaccept_" value="aa" {% if data.autoaccept==true %} checked="true"{% endif %} disabled></td></tr> <tr><td>Auto Accept Strategy</td><td colspan=3>
<select name="automation_strat_id_" disabled><option value="-1"{% if data.automation_strat_id==-1 %} selected{% endif %}>-- None --</option>
{% for a in automation_strategies %}
<option value="{{ a[0] }}"{% if data.automation_strat_id==a[0] %} selected{% endif %}>{{ a[1] }}</option>
{% endfor %}
</select>
</td></tr>
</table> </table>
<input name="submit_offer" type="submit" value="Confirm Offer"> <input name="submit_offer" type="submit" value="Confirm Offer">
@@ -81,8 +91,8 @@
<input type="hidden" name="fee_from_extra" value="{{ data.fee_from_extra }}"> <input type="hidden" name="fee_from_extra" value="{{ data.fee_from_extra }}">
<input type="hidden" name="coin_to" value="{{ data.coin_to }}"> <input type="hidden" name="coin_to" value="{{ data.coin_to }}">
<input type="hidden" name="fee_to_extra" value="{{ data.fee_to_extra }}"> <input type="hidden" name="fee_to_extra" value="{{ data.fee_to_extra }}">
{% if data.autoaccept==true %} {% if data.automation_strat_id != -1 %}
<input type="hidden" name="autoaccept" value="aa"> <input type="hidden" name="automation_strat_id" value="{{ data.automation_strat_id }}">
{% endif %} {% endif %}
{% if data.amt_var==true %} {% if data.amt_var==true %}
<input type="hidden" name="amt_var" value="av"> <input type="hidden" name="amt_var" value="av">
@@ -91,5 +101,6 @@
<input type="hidden" name="rate_var" value="rv"> <input type="hidden" name="rate_var" value="rv">
{% endif %} {% endif %}
</form> </form>
<script src="static/js/new_offer.js"></script>
</body></html> </body></html>

View File

@@ -4,6 +4,9 @@
{% for m in messages %} {% for m in messages %}
<p>{{ m }}</p> <p>{{ m }}</p>
{% endfor %} {% endfor %}
{% for m in err_messages %}
<p class="error_msg">Error: {{ m }}</p>
{% endfor %}
<form method="post"> <form method="post">
@@ -36,14 +39,16 @@
{% endfor %} {% endfor %}
</select> </select>
</td><td>Amount To</td><td><input type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" onchange="set_rate('amt_to');"></td><td>The amount you will receive.</td></tr> </td><td>Amount To</td><td><input type="text" id="amt_to" name="amt_to" value="{{ data.amt_to }}" onchange="set_rate('amt_to');"></td><td>The amount you will receive.</td></tr>
</td><td>Rate</td><td><input type="text" id="rate" name="rate" value="{{ data.rate }}" onchange="set_rate('rate');"></td><td>Lock Rate: <input type="checkbox" id="rate_lock" name="rate_lock" value="rl" checked="true"></td></tr> <tr><td>Minimum Bid Amount</td><td><input type="text" id="amt_bid_min" name="amt_bid_min" value="{{ data.amt_bid_min }}" title="Bids with an amount below the minimum bid value will be discarded"></td></tr>
<tr><td>Rate</td><td><input type="text" id="rate" name="rate" value="{{ data.rate }}" onchange="set_rate('rate');"></td><td>Lock Rate: <input type="checkbox" id="rate_lock" name="rate_lock" value="rl" checked=checked></td></tr>
<tr><td>Amount Variable</td><td><input type="checkbox" id="amt_var" name="amt_var" value="av" {% if data.amt_var==true %} checked="true"{% endif %}></td></tr> <tr><td>Amount Variable</td><td><input type="checkbox" id="amt_var" name="amt_var" value="av" {% if data.amt_var==true %} checked=checked{% endif %}></td></tr>
<tr><td>Rate Variable</td><td><input type="checkbox" id="rate_var" name="rate_var" value="rv" {% if data.rate_var==true %} checked="true"{% endif %}></td></tr> <tr><td>Rate Variable</td><td><input type="checkbox" id="rate_var" name="rate_var" value="rv" {% if data.rate_var==true %} checked=checked{% endif %}></td></tr>
</table> </table>
<input name="continue" type="submit" value="Continue"> <input name="continue" type="submit" value="Continue">
<input name="check_rates" type="button" value="Lookup Rates" onclick='lookup_rates();'> <input name="check_rates" type="button" value="Lookup Rates" onclick='lookup_rates();'>
<input name="show_rates_table" type="button" value="Show Rates Table" onclick='lookup_rates_table();'>
<input type="hidden" name="formid" value="{{ form_id }}"> <input type="hidden" name="formid" value="{{ form_id }}">
<input type="hidden" name="step1" value="a"> <input type="hidden" name="step1" value="a">
</form> </form>
@@ -79,6 +84,43 @@ xhr_rate.onload = () => {
} }
} }
const xhr_rates_table = new XMLHttpRequest();
xhr_rates_table.onload = () => {
if (xhr_rates_table.status == 200) {
const list = JSON.parse(xhr_rates_table.response);
headings = ['Source', 'Coin From', 'Coin To', 'Coin From USD Rate', 'Coin To USD Rate', 'Coin From BTC Rate', 'Coin To BTC Rate', 'Relative Rate'];
table = document.createElement('table');
headings_row = document.createElement('tr');
for (let i = 0; i < headings.length; i++) {
column = document.createElement('th');
column.textContent = headings[i];
headings_row.appendChild(column);
}
table.appendChild(headings_row);
for (let i = 0; i < list.length; i++) {
data_row = document.createElement('tr');
for (let j = 0; j < list[i].length; j++) {
column = document.createElement('td');
column.textContent = list[i][j];
data_row.appendChild(column);
}
table.appendChild(data_row);
}
// Clear existing
const display_node = document.getElementById("rates_display");
while (display_node.lastElementChild) {
display_node.removeChild(display_node.lastElementChild);
}
heading = document.createElement('h4');
heading.textContent = 'Rates'
display_node.appendChild(heading);
display_node.appendChild(table);
}
}
function lookup_rates() { function lookup_rates() {
const coin_from = document.getElementById('coin_from').value; const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value; const coin_to = document.getElementById('coin_to').value;
@@ -96,6 +138,22 @@ function lookup_rates() {
xhr_rates.send('coin_from='+coin_from+'&coin_to='+coin_to); xhr_rates.send('coin_from='+coin_from+'&coin_to='+coin_to);
} }
function lookup_rates_table() {
const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value;
if (coin_from == '-1' || coin_to == '-1') {
alert('Coins from and to must be set first.');
return;
}
inner_html = '<h4>Rates</h4><p>Updating...</p>';
document.getElementById('rates_display').innerHTML = inner_html;
xhr_rates_table.open('GET', '/json/rateslist?from='+coin_from+'&to='+coin_to);
xhr_rates_table.send();
}
function set_rate(value_changed) { function set_rate(value_changed) {
const coin_from = document.getElementById('coin_from').value; const coin_from = document.getElementById('coin_from').value;
const coin_to = document.getElementById('coin_to').value; const coin_to = document.getElementById('coin_to').value;
@@ -131,6 +189,6 @@ function set_rate(value_changed) {
xhr_rate.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr_rate.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr_rate.send(params); xhr_rate.send(params);
} }
</script> </script>
<script src="static/js/new_offer.js"></script>
</body></html> </body></html>

View File

@@ -4,6 +4,9 @@
{% for m in messages %} {% for m in messages %}
<p>{{ m }}</p> <p>{{ m }}</p>
{% endfor %} {% endfor %}
{% for m in err_messages %}
<p class="error_msg">Error: {{ m }}</p>
{% endfor %}
<form method="post"> <form method="post">
@@ -56,9 +59,10 @@
<option value="100"{% if data.fee_to_extra==100 %} selected{% endif %}>100%</option> <option value="100"{% if data.fee_to_extra==100 %} selected{% endif %}>100%</option>
</select></td></tr> </select></td></tr>
{% endif %} {% endif %}
</td><td>Rate</td><td><input type="text" id="rate" name="rate" value="{{ data.rate }}" readonly></td></tr> <tr><td>Minimum Bid Amount</td><td><input type="text" id="amt_bid_min" name="amt_bid_min" value="{{ data.amt_bid_min }}" title="Bids with an amount below the minimum bid value will be discarded" readonly></td></tr>
<tr><td>Amount Variable</td><td colspan=3><input type="checkbox" id="amt_var" name="amt_var_" value="av" {% if data.amt_var==true %} checked="true"{% endif %} disabled></td></tr> <tr><td>Rate</td><td><input type="text" id="rate" name="rate" value="{{ data.rate }}" readonly></td></tr>
<tr><td>Rate Variable</td><td colspan=3><input type="checkbox" id="rate_var" name="rate_var_" value="rv" {% if data.rate_var==true %} checked="true"{% endif %} disabled></td></tr> <tr><td>Amount Variable</td><td colspan=3><input type="checkbox" id="amt_var" name="amt_var_" value="av" {% if data.amt_var==true %} checked=checked{% endif %} disabled></td></tr>
<tr><td>Rate Variable</td><td colspan=3><input type="checkbox" id="rate_var" name="rate_var_" value="rv" {% if data.rate_var==true %} checked=checked{% endif %} disabled></td></tr>
<tr class="padded_row"><td>Offer valid (hrs)</td><td><input type="number" name="validhrs" min="1" max="48" value="{{ data.validhrs }}"></td></tr> <tr class="padded_row"><td>Offer valid (hrs)</td><td><input type="number" name="validhrs" min="1" max="48" value="{{ data.validhrs }}"></td></tr>
@@ -67,7 +71,13 @@
{% else %} {% else %}
<tr><td>Contract locked (hrs)</td><td><input type="number" name="lockhrs" min="1" max="96" value="{{ data.lockhrs }}"></td>{% if data.swap_style != 'xmr' %}<td colspan=2>Participate txn will be locked for half the time.</td>{% endif %}</tr> <tr><td>Contract locked (hrs)</td><td><input type="number" name="lockhrs" min="1" max="96" value="{{ data.lockhrs }}"></td>{% if data.swap_style != 'xmr' %}<td colspan=2>Participate txn will be locked for half the time.</td>{% endif %}</tr>
{% endif %} {% endif %}
<tr><td>Auto Accept Bids</td><td colspan=3><input type="checkbox" id="autoaccept" name="autoaccept" value="aa" {% if data.autoaccept==true %} checked="true"{% endif %}></td></tr> <tr><td>Auto Accept Strategy</td><td colspan=3>
<select name="automation_strat_id"><option value="-1"{% if data.automation_strat_id==-1 %} selected{% endif %}>-- None --</option>
{% for a in automation_strategies %}
<option value="{{ a[0] }}"{% if data.automation_strat_id==a[0] %} selected{% endif %}>{{ a[1] }}</option>
{% endfor %}
</select>
</td></tr>
</table> </table>
@@ -86,4 +96,5 @@
{% endif %} {% endif %}
</form> </form>
<script src="static/js/new_offer.js"></script>
</body></html> </body></html>

View File

@@ -1,6 +1,6 @@
{% include 'header.html' %} {% include 'header.html' %}
<h3>{{ page_type }} Offers</h3> <h3>Network Offers</h3>
{% if refresh %} {% if refresh %}
<p>Page Refresh: {{ refresh }} seconds</p> <p>Page Refresh: {{ refresh }} seconds</p>
{% endif %} {% endif %}
@@ -35,6 +35,12 @@
<option value="desc"{% if filters.sort_dir=='desc' %} selected{% endif %}>Descending</option> <option value="desc"{% if filters.sort_dir=='desc' %} selected{% endif %}>Descending</option>
</select> </select>
</td></tr> </td></tr>
<tr><td>Sent From Node</td><td>
<select name="sent_from">
<option value="any"{% if filters.sent_from=='any' %} selected{% endif %}>Any</option>
<option value="only"{% if filters.sent_from=='only' %} selected{% endif %}>Only</option>
</select>
</td></tr>
<tr><td><input type="submit" name='applyfilters' value="Apply Filters"></td><td><input type="submit" name='clearfilters' value="Clear Filters"></td></tr> <tr><td><input type="submit" name='applyfilters' value="Apply Filters"></td><td><input type="submit" name='clearfilters' value="Clear Filters"></td></tr>
<tr><td><input type="submit" name='pageback' value="Page Back"></td><td>Page: {{ filters.page_no }}</td><td><input type="submit" name='pageforwards' value="Page Forwards"></td></tr> <tr><td><input type="submit" name='pageback' value="Page Back"></td><td>Page: {{ filters.page_no }}</td><td><input type="submit" name='pageforwards' value="Page Forwards"></td></tr>
@@ -45,9 +51,9 @@
<table> <table>
<tr><th>At</th><th>Recipient</th><th>Offer ID</th><th>Coin From</th><th>Coin To</th><th>Amount From</th><th>Amount To</th><th>Rate</th></tr> <tr><th>At</th><th>From</th><th>Recipient</th><th>Offer ID</th><th>Coin From</th><th>Coin To</th><th>Amount From</th><th>Amount To</th><th>Rate</th><th>Amount From Swapped</th></tr>
{% for o in offers %} {% for o in offers %}
<tr><td>{{ o[0] }}</td><td>{{ o[7] }}</td><td><a class="monospace" href=/offer/{{ o[1] }}>{{ o[1] }}</a></td><td>{{ o[2] }}</td><td>{{ o[3] }}</td><td>{{ o[4] }}</td><td>{{ o[5] }}</td><td>{{ o[6] }}</td></tr> <tr><td>{{ o[0] }}</td><td class="monospace">{{ o[8]|truncate(12, True) }}{% if o[9]==true %} <b>Sent</b>{% endif %}</td><td class="monospace">{{ o[7] }}</td><td><a class="monospace" href=/offer/{{ o[1] }}>{{ o[1] }}</a></td><td>{{ o[2] }}</td><td>{{ o[3] }}</td><td>{{ o[4] }}</td><td>{{ o[5] }}</td><td>{{ o[6] }}</td><td>{{ o[10] }}</td></tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@@ -59,6 +59,9 @@ node.xmr.to:18081<br/>
{% else %} {% else %}
<tr><td>Blocks Confirmed Target</td><td><input type="number" name="conf_target_{{ c.name }}" min="1" max="32" value="{{ c.conf_target }}"></td></tr> <tr><td>Blocks Confirmed Target</td><td><input type="number" name="conf_target_{{ c.name }}" min="1" max="32" value="{{ c.conf_target }}"></td></tr>
{% endif %} {% endif %}
{% if c.name == 'particl' %}
<tr><td>Anon Tx Ring Size</td><td><input type="number" name="rct_ring_size_{{ c.name }}" min="3" max="32" value="{{ c.anon_tx_ring_size }}"></td></tr>
{% endif %}
<tr><td><input type="submit" name="apply_{{ c.name }}" value="Apply"> <tr><td><input type="submit" name="apply_{{ c.name }}" value="Apply">
{% if c.can_disable == true %} {% if c.can_disable == true %}
<input type="submit" name="disable_{{ c.name }}" value="Disable" onclick="return confirmPopup('Disable', '{{ c.name|capitalize }}');"> <input type="submit" name="disable_{{ c.name }}" value="Disable" onclick="return confirmPopup('Disable', '{{ c.name|capitalize }}');">

View File

@@ -34,8 +34,11 @@
{% endif %} {% endif %}
<tr><td>Blocks:</td><td>{{ w.blocks }}</td></tr> <tr><td>Blocks:</td><td>{{ w.blocks }} {% if w.known_block_count %} / {{ w.known_block_count }} {% endif %}</td></tr>
<tr><td>Synced:</td><td>{{ w.synced }}</td></tr> <tr><td>Synced:</td><td>{{ w.synced }}</td></tr>
{% if w.bootstrapping %}
<tr><td>Bootstrapping:</td><td>{{ w.bootstrapping }}</td></tr>
{% endif %}
<tr><td>Expected Seed:</td><td>{{ w.expected_seed }}</td>{% if w.expected_seed != true %}<td><input type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"></td>{% endif %}</tr> <tr><td>Expected Seed:</td><td>{{ w.expected_seed }}</td>{% if w.expected_seed != true %}<td><input type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"></td>{% endif %}</tr>
{% if w.cid == '1' %} {% if w.cid == '1' %}
<tr><td>Stealth Address</td><td colspan=2>{{ w.stealth_address }}</td></tr> <tr><td>Stealth Address</td><td colspan=2>{{ w.stealth_address }}</td></tr>
@@ -46,7 +49,7 @@
{% else %} {% else %}
<tr><td><input type="submit" name="newaddr_{{ w.cid }}" value="New Deposit Address"></td><td colspan=2>{{ w.deposit_address }}</td></tr> <tr><td><input type="submit" name="newaddr_{{ w.cid }}" value="New Deposit Address"></td><td colspan=2>{{ w.deposit_address }}</td></tr>
{% endif %} {% endif %}
<tr><td><input type="submit" name="withdraw_{{ w.cid }}" value="Withdraw" onclick="return confirmWithdrawal();"></td><td>Amount: <input type="text" name="amt_{{ w.cid }}" value="{{ w.wd_value }}"></td><td>Address: <input type="text" name="to_{{ w.cid }}" value="{{ w.wd_address }}"></td><td>Subtract fee: <input type="checkbox" name="subfee_{{ w.cid }}" {% if w.wd_subfee==true %} checked="true"{% endif %}></td></tr> <tr><td><input type="submit" name="withdraw_{{ w.cid }}" value="Withdraw" onclick="return confirmWithdrawal();"></td><td>Amount: <input type="text" name="amt_{{ w.cid }}" value="{{ w.wd_value }}"></td><td>Address: <input type="text" name="to_{{ w.cid }}" value="{{ w.wd_address }}"></td><td>Subtract fee: <input type="checkbox" name="subfee_{{ w.cid }}" {% if w.wd_subfee==true %} checked=checked{% endif %}></td></tr>
{% if w.cid == '1' %} {% if w.cid == '1' %}
<tr><td>Type From, To</td><td> <tr><td>Type From, To</td><td>
<select name="withdraw_type_from_{{ w.cid }}"> <select name="withdraw_type_from_{{ w.cid }}">

View File

@@ -33,8 +33,11 @@
{% endif %} {% endif %}
<tr><td>Blocks:</td><td>{{ w.blocks }}</td></tr> <tr><td>Blocks:</td><td>{{ w.blocks }} {% if w.known_block_count %} / {{ w.known_block_count }} {% endif %}</td></tr>
<tr><td>Synced:</td><td>{{ w.synced }}</td></tr> <tr><td>Synced:</td><td>{{ w.synced }}</td></tr>
{% if w.bootstrapping %}
<tr><td>Bootstrapping:</td><td>{{ w.bootstrapping }}</td></tr>
{% endif %}
<tr><td>Expected Seed:</td><td>{{ w.expected_seed }}</td></tr> <tr><td>Expected Seed:</td><td>{{ w.expected_seed }}</td></tr>
<tr><td><a href="/wallet/{{ w.ticker }}">Manage</a></td></tr> <tr><td><a href="/wallet/{{ w.ticker }}">Manage</a></td></tr>
</table> </table>

View File

@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .util import (
PAGE_LIMIT,
get_data_entry,
have_data_entry,
set_pagination_filters,
)
from basicswap.util import (
ensure,
)
from basicswap.db import (
strConcepts,
)
def page_automation_strategies(self, url_split, post_string):
server = self.server
swap_client = server.swap_client
filters = {
'page_no': 1,
'limit': PAGE_LIMIT,
'sort_by': 'created_at',
'sort_dir': 'desc',
}
messages = []
form_data = self.checkForm(post_string, 'automationstrategies', messages)
if form_data and have_data_entry(form_data, 'applyfilters'):
if have_data_entry(form_data, 'sort_by'):
sort_by = get_data_entry(form_data, 'sort_by')
ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by')
filters['sort_by'] = sort_by
if have_data_entry(form_data, 'sort_dir'):
sort_dir = get_data_entry(form_data, 'sort_dir')
ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir')
filters['sort_dir'] = sort_dir
set_pagination_filters(form_data, filters)
formatted_strategies = []
for s in swap_client.listAutomationStrategies(filters):
formatted_strategies.append((s[0], s[1], strConcepts(s[2])))
template = server.env.get_template('automation_strategies.html')
return self.render_template(template, {
'messages': messages,
'filters': filters,
'strategies': formatted_strategies,
})
def page_automation_strategy_new(self, url_split, post_string):
server = self.server
swap_client = self.server.swap_client
messages = []
form_data = self.checkForm(post_string, 'automationstrategynew', messages)
template = server.env.get_template('automation_strategy_new.html')
return self.render_template(template, {
'messages': messages,
})
def page_automation_strategy(self, url_split, post_string):
ensure(len(url_split) > 2, 'Strategy ID not specified')
try:
strategy_id = int(url_split[2])
except Exception:
raise ValueError('Bad strategy ID')
server = self.server
swap_client = self.server.swap_client
messages = []
strategy = swap_client.getAutomationStrategy(strategy_id)
formatted_strategy = {
'label': strategy.label,
'type': strConcepts(strategy.type_ind),
'only_known_identities': 'True' if strategy.only_known_identities is True else 'False',
'data': strategy.data,
'note': strategy.note,
'created_at': strategy.created_at,
}
template = server.env.get_template('automation_strategy.html')
return self.render_template(template, {
'messages': messages,
'strategy': formatted_strategy,
})

562
basicswap/ui/page_offers.py Normal file
View File

@@ -0,0 +1,562 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import traceback
from .util import (
PAGE_LIMIT,
getCoinType,
inputAmount,
setCoinFilter,
get_data_entry,
have_data_entry,
get_data_entry_or,
listAvailableCoins,
set_pagination_filters,
)
from basicswap.db import (
Concepts,
)
from basicswap.util import (
ensure,
format_amount,
format_timestamp,
)
from basicswap.basicswap_util import (
SwapTypes,
DebugTypes,
getLockName,
strBidState,
TxLockTypes,
strOfferState,
)
from basicswap.chainparams import (
Coins,
)
def value_or_none(v):
if v == -1 or v == '-1':
return None
return v
def parseOfferFormData(swap_client, form_data, page_data, options={}):
errors = []
parsed_data = {}
if have_data_entry(form_data, 'addr_to'):
page_data['addr_to'] = get_data_entry(form_data, 'addr_to')
addr_to = value_or_none(page_data['addr_to'])
if addr_to is not None:
parsed_data['addr_to'] = addr_to
if have_data_entry(form_data, 'addr_from'):
page_data['addr_from'] = get_data_entry(form_data, 'addr_from')
parsed_data['addr_from'] = value_or_none(page_data['addr_from'])
else:
parsed_data['addr_from'] = None
try:
page_data['coin_from'] = getCoinType(get_data_entry(form_data, 'coin_from'))
coin_from = Coins(page_data['coin_from'])
ci_from = swap_client.ci(coin_from)
if coin_from != Coins.XMR:
page_data['fee_from_conf'] = ci_from._conf_target # Set default value
parsed_data['coin_from'] = coin_from
except Exception:
errors.append('Unknown Coin From')
try:
page_data['coin_to'] = getCoinType(get_data_entry(form_data, 'coin_to'))
coin_to = Coins(page_data['coin_to'])
ci_to = swap_client.ci(coin_to)
if coin_to != Coins.XMR:
page_data['fee_to_conf'] = ci_to._conf_target # Set default value
parsed_data['coin_to'] = coin_to
except Exception:
errors.append('Unknown Coin To')
if parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON):
page_data['swap_style'] = 'xmr'
else:
page_data['swap_style'] = 'atomic'
try:
page_data['amt_from'] = get_data_entry(form_data, 'amt_from')
parsed_data['amt_from'] = inputAmount(page_data['amt_from'], ci_from)
except Exception:
errors.append('Amount From')
try:
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:
raise ValueError('missing')
else:
page_data['amt_bid_min'] = get_data_entry(form_data, 'amt_bid_min')
parsed_data['amt_bid_min'] = inputAmount(page_data['amt_bid_min'], ci_from)
if parsed_data['amt_bid_min'] < 0 or parsed_data['amt_bid_min'] > parsed_data['amt_from']:
errors.append('Minimum Bid Amount out of range')
except Exception:
errors.append('Minimum Bid Amount')
try:
page_data['amt_to'] = get_data_entry(form_data, 'amt_to')
parsed_data['amt_to'] = inputAmount(page_data['amt_to'], ci_to)
except Exception:
errors.append('Amount To')
if 'amt_to' in parsed_data and 'amt_from' in parsed_data:
parsed_data['rate'] = ci_from.make_int(parsed_data['amt_to'] / parsed_data['amt_from'], r=1)
page_data['rate'] = ci_to.format_amount(parsed_data['rate'])
page_data['amt_var'] = True if have_data_entry(form_data, 'amt_var') else False
parsed_data['amt_var'] = page_data['amt_var']
page_data['rate_var'] = True if have_data_entry(form_data, 'rate_var') else False
parsed_data['rate_var'] = page_data['rate_var']
if have_data_entry(form_data, 'step1'):
if len(errors) == 0 and have_data_entry(form_data, 'continue'):
page_data['step2'] = True
return parsed_data, errors
page_data['step2'] = True
if have_data_entry(form_data, 'fee_from_conf'):
page_data['fee_from_conf'] = int(get_data_entry(form_data, 'fee_from_conf'))
parsed_data['fee_from_conf'] = page_data['fee_from_conf']
if have_data_entry(form_data, 'fee_from_extra'):
page_data['fee_from_extra'] = int(get_data_entry(form_data, 'fee_from_extra'))
parsed_data['fee_from_extra'] = page_data['fee_from_extra']
if have_data_entry(form_data, 'fee_to_conf'):
page_data['fee_to_conf'] = int(get_data_entry(form_data, 'fee_to_conf'))
parsed_data['fee_to_conf'] = page_data['fee_to_conf']
if have_data_entry(form_data, 'fee_to_extra'):
page_data['fee_to_extra'] = int(get_data_entry(form_data, 'fee_to_extra'))
parsed_data['fee_to_extra'] = page_data['fee_to_extra']
if have_data_entry(form_data, 'check_offer'):
page_data['check_offer'] = True
if have_data_entry(form_data, 'submit_offer'):
page_data['submit_offer'] = True
if have_data_entry(form_data, 'lockhrs'):
page_data['lockhrs'] = int(get_data_entry(form_data, 'lockhrs'))
parsed_data['lock_seconds'] = page_data['lockhrs'] * 60 * 60
elif have_data_entry(form_data, 'lockmins'):
page_data['lockmins'] = int(get_data_entry(form_data, 'lockmins'))
parsed_data['lock_seconds'] = page_data['lockmins'] * 60
elif have_data_entry(form_data, 'lockseconds'):
parsed_data['lock_seconds'] = int(get_data_entry(form_data, 'lockseconds'))
if have_data_entry(form_data, 'validhrs'):
page_data['validhrs'] = int(get_data_entry(form_data, 'validhrs'))
parsed_data['valid_for_seconds'] = page_data['validhrs'] * 60 * 60
elif have_data_entry(form_data, 'valid_for_seconds'):
parsed_data['valid_for_seconds'] = int(get_data_entry(form_data, 'valid_for_seconds'))
page_data['automation_strat_id'] = int(get_data_entry_or(form_data, 'automation_strat_id', -1))
parsed_data['automation_strat_id'] = page_data['automation_strat_id']
try:
if len(errors) == 0 and page_data['swap_style'] == 'xmr':
if have_data_entry(form_data, 'fee_rate_from'):
page_data['from_fee_override'] = get_data_entry(form_data, 'fee_rate_from')
parsed_data['from_fee_override'] = page_data['from_fee_override']
else:
from_fee_override, page_data['from_fee_src'] = swap_client.getFeeRateForCoin(parsed_data['coin_from'], page_data['fee_from_conf'])
if page_data['fee_from_extra'] > 0:
from_fee_override += from_fee_override * (float(page_data['fee_from_extra']) / 100.0)
page_data['from_fee_override'] = ci_from.format_amount(ci_from.make_int(from_fee_override, r=1))
parsed_data['from_fee_override'] = page_data['from_fee_override']
lock_spend_tx_vsize = ci_from.xmr_swap_alock_spend_tx_vsize()
lock_spend_tx_fee = ci_from.make_int(ci_from.make_int(from_fee_override, r=1) * lock_spend_tx_vsize / 1000, r=1)
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 coin_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']
else:
to_fee_override, page_data['to_fee_src'] = swap_client.getFeeRateForCoin(parsed_data['coin_to'], page_data['fee_to_conf'])
if page_data['fee_to_extra'] > 0:
to_fee_override += to_fee_override * (float(page_data['fee_to_extra']) / 100.0)
page_data['to_fee_override'] = ci_to.format_amount(ci_to.make_int(to_fee_override, r=1))
parsed_data['to_fee_override'] = page_data['to_fee_override']
except Exception as e:
print('Error setting fee', str(e)) # Expected if missing fields
return parsed_data, errors
def postNewOfferFromParsed(swap_client, parsed_data):
swap_type = SwapTypes.SELLER_FIRST
if parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON):
swap_type = SwapTypes.XMR_SWAP
if swap_client.coin_clients[parsed_data['coin_from']]['use_csv'] and swap_client.coin_clients[parsed_data['coin_to']]['use_csv']:
lock_type = TxLockTypes.SEQUENCE_LOCK_TIME
else:
lock_type = TxLockTypes.ABS_LOCK_TIME
extra_options = {}
if 'fee_from_conf' in parsed_data:
extra_options['from_fee_conf_target'] = parsed_data['fee_from_conf']
if 'from_fee_multiplier_percent' in parsed_data:
extra_options['from_fee_multiplier_percent'] = parsed_data['fee_from_extra']
if 'from_fee_override' in parsed_data:
extra_options['from_fee_override'] = parsed_data['from_fee_override']
if 'fee_to_conf' in parsed_data:
extra_options['to_fee_conf_target'] = parsed_data['fee_to_conf']
if 'to_fee_multiplier_percent' in parsed_data:
extra_options['to_fee_multiplier_percent'] = parsed_data['fee_to_extra']
if 'to_fee_override' in parsed_data:
extra_options['to_fee_override'] = parsed_data['to_fee_override']
if 'valid_for_seconds' in parsed_data:
extra_options['valid_for_seconds'] = parsed_data['valid_for_seconds']
if 'addr_to' in parsed_data:
extra_options['addr_send_to'] = parsed_data['addr_to']
if parsed_data.get('amt_var', False):
extra_options['amount_negotiable'] = parsed_data['amt_var']
if parsed_data.get('rate_var', False):
extra_options['rate_negotiable'] = parsed_data['rate_var']
if parsed_data.get('rate_var', None) is not None:
extra_options['rate_negotiable'] = parsed_data['rate_var']
if parsed_data.get('automation_strat_id', None) is not None:
extra_options['automation_id'] = parsed_data['automation_strat_id']
offer_id = swap_client.postOffer(
parsed_data['coin_from'],
parsed_data['coin_to'],
parsed_data['amt_from'],
parsed_data['rate'],
parsed_data['amt_bid_min'],
swap_type,
lock_type=lock_type,
lock_value=parsed_data['lock_seconds'],
addr_send_from=parsed_data['addr_from'],
extra_options=extra_options)
return offer_id
def postNewOffer(swap_client, form_data):
page_data = {}
parsed_data, errors = parseOfferFormData(swap_client, form_data, page_data, options={'add_min_bid_amt': True})
if len(errors) > 0:
raise ValueError('Parse errors: ' + ' '.join(errors))
return postNewOfferFromParsed(swap_client, parsed_data)
def page_newoffer(self, url_split, post_string):
server = self.server
swap_client = server.swap_client
messages = []
err_messages = []
page_data = {
# Set defaults
'addr_to': -1,
'fee_from_conf': 2,
'fee_to_conf': 2,
'validhrs': 1,
'lockhrs': 32,
'lockmins': 30, # used in debug mode
'debug_ui': swap_client.debug_ui,
'automation_strat_id': -1,
'amt_bid_min': format_amount(1000, 8),
}
form_data = self.checkForm(post_string, 'newoffer', err_messages)
if form_data:
try:
parsed_data, errors = parseOfferFormData(swap_client, form_data, page_data)
for e in errors:
err_messages.append(str(e))
except Exception as e:
if swap_client.debug is True:
swap_client.log.error(traceback.format_exc())
err_messages.append(str(e))
if len(err_messages) == 0 and 'submit_offer' in page_data:
try:
offer_id = postNewOfferFromParsed(swap_client, parsed_data)
messages.append('<a href="/offer/' + offer_id.hex() + '">Sent Offer {}</a>'.format(offer_id.hex()))
page_data = {}
except Exception as e:
if swap_client.debug is True:
swap_client.log.error(traceback.format_exc())
err_messages.append(str(e))
if len(err_messages) == 0 and 'check_offer' in page_data:
template = server.env.get_template('offer_confirm.html')
elif 'step2' in page_data:
template = server.env.get_template('offer_new_2.html')
else:
template = server.env.get_template('offer_new_1.html')
if swap_client.debug_ui:
messages.append('Debug mode active.')
coins_from, coins_to = listAvailableCoins(swap_client, split_from=True)
automation_filters = {}
automation_filters['sort_by'] = 'label'
automation_filters['type_ind'] = Concepts.OFFER
automation_strategies = swap_client.listAutomationStrategies(automation_filters)
return self.render_template(template, {
'messages': messages,
'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'),
'data': page_data,
'automation_strategies': automation_strategies,
})
def page_offer(self, url_split, post_string):
ensure(len(url_split) > 2, 'Offer ID not specified')
try:
offer_id = bytes.fromhex(url_split[2])
ensure(len(offer_id) == 28, 'Bad offer ID')
except Exception:
raise ValueError('Bad offer ID')
server = self.server
swap_client = server.swap_client
offer, xmr_offer = swap_client.getXmrOffer(offer_id)
ensure(offer, 'Unknown offer ID')
extend_data = { # Defaults
'nb_validmins': 10,
}
messages = []
if swap_client.debug_ui:
messages.append('Debug mode active.')
sent_bid_id = None
show_bid_form = None
form_data = self.checkForm(post_string, 'offer', messages)
ci_from = swap_client.ci(Coins(offer.coin_from))
ci_to = swap_client.ci(Coins(offer.coin_to))
debugind = -1
# Set defaults
bid_amount = ci_from.format_amount(offer.amount_from)
bid_rate = ci_to.format_amount(offer.rate)
if form_data:
if b'revoke_offer' in form_data:
try:
swap_client.revokeOffer(offer_id)
messages.append('Offer revoked')
except Exception as ex:
messages.append('Revoke offer failed: ' + str(ex))
elif b'newbid' in form_data:
show_bid_form = True
elif b'sendbid' in form_data:
try:
addr_from = form_data[b'addr_from'][0].decode('utf-8')
extend_data['nb_addr_from'] = addr_from
if addr_from == '-1':
addr_from = None
minutes_valid = int(form_data[b'validmins'][0].decode('utf-8'))
extend_data['nb_validmins'] = minutes_valid
extra_options = {
'valid_for_seconds': minutes_valid * 60,
}
if have_data_entry(form_data, 'bid_rate'):
bid_rate = get_data_entry(form_data, 'bid_rate')
extra_options['bid_rate'] = ci_to.make_int(bid_rate, r=1)
if have_data_entry(form_data, 'bid_amount'):
bid_amount = get_data_entry(form_data, 'bid_amount')
amount_from = inputAmount(bid_amount, ci_from)
else:
amount_from = offer.amount_from
debugind = int(get_data_entry_or(form_data, 'debugind', -1))
sent_bid_id = swap_client.postBid(offer_id, amount_from, addr_send_from=addr_from, extra_options=extra_options).hex()
if debugind > -1:
swap_client.setBidDebugInd(bytes.fromhex(sent_bid_id), debugind)
except Exception as ex:
if self.server.swap_client.debug is True:
self.server.swap_client.log.error(traceback.format_exc())
messages.append('Error: Send bid failed: ' + str(ex))
show_bid_form = True
data = {
'tla_from': ci_from.ticker(),
'tla_to': ci_to.ticker(),
'state': strOfferState(offer.state),
'coin_from': ci_from.coin_name(),
'coin_to': ci_to.coin_name(),
'coin_from_ind': int(ci_from.coin_type()),
'coin_to_ind': int(ci_to.coin_type()),
'amt_from': ci_from.format_amount(offer.amount_from),
'amt_to': ci_to.format_amount((offer.amount_from * offer.rate) // ci_from.COIN()),
'amt_bid_min': ci_from.format_amount(offer.min_bid_amount),
'rate': ci_to.format_amount(offer.rate),
'lock_type': getLockName(offer.lock_type),
'lock_value': offer.lock_value,
'addr_from': offer.addr_from,
'addr_to': 'Public' if offer.addr_to == swap_client.network_addr else offer.addr_to,
'created_at': offer.created_at,
'expired_at': offer.expire_at,
'sent': 'True' if offer.was_sent else 'False',
'was_revoked': 'True' if offer.active_ind == 2 else 'False',
'show_bid_form': show_bid_form,
'amount_negotiable': offer.amount_negotiable,
'rate_negotiable': offer.rate_negotiable,
'bid_amount': bid_amount,
'bid_rate': bid_rate,
'debug_ui': swap_client.debug_ui,
'automation_strat_id': -1,
}
data.update(extend_data)
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME or offer.lock_type == TxLockTypes.ABS_LOCK_TIME:
if offer.lock_value > 60 * 60:
data['lock_value_hr'] = ' ({} hours)'.format(offer.lock_value / (60 * 60))
else:
data['lock_value_hr'] = ' ({} minutes)'.format(offer.lock_value / 60)
addr_from_label, addr_to_label = swap_client.getAddressLabel([offer.addr_from, offer.addr_to])
if len(addr_from_label) > 0:
data['addr_from_label'] = '(' + addr_from_label + ')'
if len(addr_to_label) > 0:
data['addr_to_label'] = '(' + addr_to_label + ')'
if swap_client.debug_ui:
data['debug_ind'] = debugind
data['debug_options'] = [(int(t), t.name) for t in DebugTypes]
if xmr_offer:
int_fee_rate_now, fee_source = ci_from.get_fee_rate()
data['xmr_type'] = True
data['a_fee_rate'] = ci_from.format_amount(xmr_offer.a_fee_rate)
data['a_fee_rate_verify'] = ci_from.format_amount(int_fee_rate_now, conv_int=True)
data['a_fee_rate_verify_src'] = fee_source
data['a_fee_warn'] = xmr_offer.a_fee_rate < int_fee_rate_now
lock_spend_tx_vsize = ci_from.xmr_swap_alock_spend_tx_vsize()
lock_spend_tx_fee = ci_from.make_int(xmr_offer.a_fee_rate * lock_spend_tx_vsize / 1000, r=1)
data['amt_from_lock_spend_tx_fee'] = ci_from.format_amount(lock_spend_tx_fee // ci_from.COIN())
if offer.was_sent:
try:
strategy = swap_client.getLinkedStrategy(Concepts.OFFER, offer_id)
data['automation_strat_id'] = strategy[0]
data['automation_strat_label'] = strategy[1]
except Exception:
pass # None found
bids = swap_client.listBids(offer_id=offer_id)
formatted_bids = []
amt_swapped = 0
for b in bids:
amt_swapped += b[4]
formatted_bids.append((b[2].hex(), ci_from.format_amount(b[4]), strBidState(b[5]), ci_to.format_amount(b[10]), b[11]))
data['amt_swapped'] = ci_from.format_amount(amt_swapped)
template = server.env.get_template('offer.html')
return self.render_template(template, {
'offer_id': offer_id.hex(),
'sent_bid_id': sent_bid_id,
'messages': messages,
'data': data,
'bids': formatted_bids,
'addrs': None if show_bid_form is None else swap_client.listSmsgAddresses('bid'),
})
def page_offers(self, url_split, post_string, sent=False):
server = self.server
swap_client = server.swap_client
filters = {
'coin_from': -1,
'coin_to': -1,
'page_no': 1,
'limit': PAGE_LIMIT,
'sort_by': 'created_at',
'sort_dir': 'desc',
'sent_from': 'any' if sent is False else 'only',
}
messages = []
form_data = self.checkForm(post_string, 'offers', messages)
if form_data and have_data_entry(form_data, 'applyfilters'):
filters['coin_from'] = setCoinFilter(form_data, 'coin_from')
filters['coin_to'] = setCoinFilter(form_data, 'coin_to')
if have_data_entry(form_data, 'sort_by'):
sort_by = get_data_entry(form_data, 'sort_by')
ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by')
filters['sort_by'] = sort_by
if have_data_entry(form_data, 'sort_dir'):
sort_dir = get_data_entry(form_data, 'sort_dir')
ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir')
filters['sort_dir'] = sort_dir
if have_data_entry(form_data, 'sent_from'):
sent_from = get_data_entry(form_data, 'sent_from')
ensure(sent_from in ['any', 'only'], 'Invalid sent filter')
filters['sent_from'] = sent_from
set_pagination_filters(form_data, filters)
if filters['sent_from'] == 'only':
sent = True
else:
sent = False
offers = swap_client.listOffers(sent, filters, with_bid_info=True)
formatted_offers = []
for row in offers:
o, completed_amount = row
ci_from = swap_client.ci(Coins(o.coin_from))
ci_to = swap_client.ci(Coins(o.coin_to))
formatted_offers.append((
format_timestamp(o.created_at),
o.offer_id.hex(),
ci_from.coin_name(), ci_to.coin_name(),
ci_from.format_amount(o.amount_from),
ci_to.format_amount((o.amount_from * o.rate) // ci_from.COIN()),
ci_to.format_amount(o.rate),
'Public' if o.addr_to == swap_client.network_addr else o.addr_to,
o.addr_from,
o.was_sent,
ci_from.format_amount(completed_amount)))
coins_from, coins_to = listAvailableCoins(swap_client, split_from=True)
template = server.env.get_template('offers.html')
return self.render_template(template, {
'messages': messages,
'coins_from': coins_from,
'coins': coins_to,
'messages': messages,
'filters': filters,
'offers': formatted_offers,
})

View File

@@ -4,9 +4,6 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
def extract_data(bytes_in): def extract_data(bytes_in):
str_in = bytes_in.decode('utf-8') str_in = bytes_in.decode('utf-8')
start = str_in.find('=') start = str_in.find('=')
@@ -20,7 +17,6 @@ def extract_data(bytes_in):
def page_tor(self, url_split, post_string): def page_tor(self, url_split, post_string):
template = self.server.env.get_template('tor.html')
swap_client = self.server.swap_client swap_client = self.server.swap_client
@@ -37,10 +33,8 @@ def page_tor(self, url_split, post_string):
messages = [] messages = []
return bytes(template.render( template = self.server.env.get_template('tor.html')
title=self.server.title, return self.render_template(template, {
h2=self.server.title, 'messages': messages,
messages=messages, 'data': page_data,
data=page_data, })
form_id=os.urandom(8).hex(),
), 'UTF-8')

306
basicswap/ui/page_wallet.py Normal file
View File

@@ -0,0 +1,306 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import traceback
from .util import (
get_data_entry,
have_data_entry,
)
from basicswap.util import (
ensure,
format_timestamp,
)
from basicswap.chainparams import (
Coins,
chainparams,
getCoinIdFromTicker,
)
def format_wallet_data(ci, w):
wf = {
'name': ci.coin_name(),
'version': w.get('version', '?'),
'ticker': ci.ticker_mainnet(),
'cid': str(int(ci.coin_type())),
'balance': w.get('balance', '?'),
'blocks': w.get('blocks', '?'),
'synced': w.get('synced', '?'),
'expected_seed': w.get('expected_seed', '?'),
'updating': w.get('updating', '?'),
'havedata': True,
}
if w.get('bootstrapping', False) is True:
wf['bootstrapping'] = True
if 'known_block_count' in w:
wf['known_block_count'] = w['known_block_count']
if 'balance' in w and 'unconfirmed' in w:
wf['balance_all'] = float(w['balance']) + float(w['unconfirmed'])
if 'lastupdated' in w:
wf['lastupdated'] = format_timestamp(w['lastupdated'])
if 'unconfirmed' in w and float(w['unconfirmed']) > 0.0:
wf['unconfirmed'] = w['unconfirmed']
if ci.coin_type() == Coins.PART:
wf['stealth_address'] = w.get('stealth_address', '?')
wf['blind_balance'] = w.get('blind_balance', '?')
if 'blind_unconfirmed' in w and float(w['blind_unconfirmed']) > 0.0:
wf['blind_unconfirmed'] = w['blind_unconfirmed']
wf['anon_balance'] = w.get('anon_balance', '?')
if 'anon_pending' in w and float(w['anon_pending']) > 0.0:
wf['anon_pending'] = w['anon_pending']
return wf
def page_wallets(self, url_split, post_string):
server = self.server
swap_client = server.swap_client
page_data = {}
messages = []
form_data = self.checkForm(post_string, 'wallets', messages)
if form_data:
for c in Coins:
if c not in chainparams:
continue
cid = str(int(c))
if bytes('newaddr_' + cid, 'utf-8') in form_data:
swap_client.cacheNewAddressForCoin(c)
elif bytes('reseed_' + cid, 'utf-8') in form_data:
try:
swap_client.reseedWallet(c)
messages.append('Reseed complete ' + str(c))
except Exception as ex:
messages.append('Reseed failed ' + str(ex))
swap_client.updateWalletsInfo(True, c)
elif bytes('withdraw_' + cid, 'utf-8') in form_data:
try:
value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_value_' + cid] = value
except Exception as e:
messages.append('Error: Missing value')
try:
address = form_data[bytes('to_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_address_' + cid] = address
except Exception as e:
messages.append('Error: Missing address')
subfee = True if bytes('subfee_' + cid, 'utf-8') in form_data else False
page_data['wd_subfee_' + cid] = subfee
if c == Coins.PART:
try:
type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8')
type_to = form_data[bytes('withdraw_type_to_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_type_from_' + cid] = type_from
page_data['wd_type_to_' + cid] = type_to
except Exception as e:
messages.append('Error: Missing type')
if len(messages) == 0:
ci = swap_client.ci(c)
ticker = ci.ticker()
if c == Coins.PART:
try:
txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
messages.append('Withdrew {} {} ({} to {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, type_to, address, txid))
except Exception as e:
messages.append('Error: {}'.format(str(e)))
else:
try:
txid = swap_client.withdrawCoin(c, value, address, subfee)
messages.append('Withdrew {} {} to address {}<br/>In txid: {}'.format(value, ticker, address, txid))
except Exception as e:
messages.append('Error: {}'.format(str(e)))
swap_client.updateWalletsInfo(True, c)
swap_client.updateWalletsInfo()
wallets = swap_client.getCachedWalletsInfo()
wallets_formatted = []
sk = sorted(wallets.keys())
for k in sk:
w = wallets[k]
if 'error' in w:
wallets_formatted.append({
'cid': str(int(k)),
'error': w['error']
})
continue
if 'no_data' in w:
wallets_formatted.append({
'name': w['name'],
'havedata': False,
'updating': w['updating'],
})
continue
ci = swap_client.ci(k)
wf = format_wallet_data(ci, w)
wallets_formatted.append(wf)
template = server.env.get_template('wallets.html')
return self.render_template(template, {
'messages': messages,
'wallets': wallets_formatted,
})
def page_wallet(self, url_split, post_string):
ensure(len(url_split) > 2, 'Wallet not specified')
wallet_ticker = url_split[2]
server = self.server
swap_client = server.swap_client
coin_id = getCoinIdFromTicker(wallet_ticker)
page_data = {}
messages = []
form_data = self.checkForm(post_string, 'settings', messages)
show_utxo_groups = False
if form_data:
cid = str(int(coin_id))
if bytes('newaddr_' + cid, 'utf-8') in form_data:
swap_client.cacheNewAddressForCoin(coin_id)
elif bytes('reseed_' + cid, 'utf-8') in form_data:
try:
swap_client.reseedWallet(coin_id)
messages.append('Reseed complete ' + str(coin_id))
except Exception as ex:
messages.append('Reseed failed ' + str(ex))
swap_client.updateWalletsInfo(True, coin_id)
elif bytes('withdraw_' + cid, 'utf-8') in form_data:
try:
value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_value_' + cid] = value
except Exception as e:
messages.append('Error: Missing value')
try:
address = form_data[bytes('to_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_address_' + cid] = address
except Exception as e:
messages.append('Error: Missing address')
subfee = True if bytes('subfee_' + cid, 'utf-8') in form_data else False
page_data['wd_subfee_' + cid] = subfee
if coin_id == Coins.PART:
try:
type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8')
type_to = form_data[bytes('withdraw_type_to_' + cid, 'utf-8')][0].decode('utf-8')
page_data['wd_type_from_' + cid] = type_from
page_data['wd_type_to_' + cid] = type_to
except Exception as e:
messages.append('Error: Missing type')
if len(messages) == 0:
ci = swap_client.ci(coin_id)
ticker = ci.ticker()
if coin_id == Coins.PART:
try:
txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
messages.append('Withdrew {} {} ({} to {}) to address {}<br/>In txid: {}'.format(value, ticker, type_from, type_to, address, txid))
except Exception as e:
messages.append('Error: {}'.format(str(e)))
else:
try:
txid = swap_client.withdrawCoin(coin_id, value, address, subfee)
messages.append('Withdrew {} {} to address {}<br/>In txid: {}'.format(value, ticker, address, txid))
except Exception as e:
messages.append('Error: {}'.format(str(e)))
swap_client.updateWalletsInfo(True, coin_id)
elif have_data_entry(form_data, 'showutxogroups'):
show_utxo_groups = True
elif have_data_entry(form_data, 'create_utxo'):
show_utxo_groups = True
try:
value = get_data_entry(form_data, 'utxo_value')
page_data['utxo_value'] = value
ci = swap_client.ci(coin_id)
value_sats = ci.make_int(value)
txid, address = ci.createUTXO(value_sats)
messages.append('Created new utxo of value {} and address {}<br/>In txid: {}'.format(value, address, txid))
except Exception as e:
messages.append('Error: {}'.format(str(e)))
if swap_client.debug is True:
swap_client.log.error(traceback.format_exc())
swap_client.updateWalletsInfo()
wallets = swap_client.getCachedWalletsInfo({'coin_id': coin_id})
for k in wallets.keys():
w = wallets[k]
if 'error' in w:
wallet_data = {
'cid': str(int(k)),
'error': w['error']
}
continue
if 'no_data' in w:
wallet_data = {
'name': w['name'],
'havedata': False,
'updating': w['updating'],
}
continue
ci = swap_client.ci(k)
cid = str(int(coin_id))
wallet_data = format_wallet_data(ci, w)
fee_rate, fee_src = swap_client.getFeeRateForCoin(k)
est_fee = swap_client.estimateWithdrawFee(k, fee_rate)
wallet_data['fee_rate'] = ci.format_amount(int(fee_rate * ci.COIN()))
wallet_data['fee_rate_src'] = fee_src
wallet_data['est_fee'] = 'Unknown' if est_fee is None else ci.format_amount(int(est_fee * ci.COIN()))
wallet_data['deposit_address'] = w.get('deposit_address', 'Refresh necessary')
if k == Coins.XMR:
wallet_data['main_address'] = w.get('main_address', 'Refresh necessary')
if 'wd_type_from_' + cid in page_data:
wallet_data['wd_type_from'] = page_data['wd_type_from_' + cid]
if 'wd_type_to_' + cid in page_data:
wallet_data['wd_type_to'] = page_data['wd_type_to_' + cid]
if 'wd_value_' + cid in page_data:
wallet_data['wd_value'] = page_data['wd_value_' + cid]
if 'wd_address_' + cid in page_data:
wallet_data['wd_address'] = page_data['wd_address_' + cid]
if 'wd_subfee_' + cid in page_data:
wallet_data['wd_subfee'] = page_data['wd_subfee_' + cid]
if 'utxo_value' in page_data:
wallet_data['utxo_value'] = page_data['utxo_value']
if show_utxo_groups:
utxo_groups = ''
unspent_by_addr = swap_client.getUnspentsByAddr(k)
sorted_unspent_by_addr = sorted(unspent_by_addr.items(), key=lambda x: x[1], reverse=True)
for kv in sorted_unspent_by_addr:
utxo_groups += kv[0] + ' ' + ci.format_amount(kv[1]) + '\n'
wallet_data['show_utxo_groups'] = True
wallet_data['utxo_groups'] = utxo_groups
template = server.env.get_template('wallet.html')
return self.render_template(template, {
'messages': messages,
'w': wallet_data,
})

View File

@@ -5,6 +5,7 @@
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json import json
import struct
import traceback import traceback
from basicswap.util import ( from basicswap.util import (
make_int, make_int,
@@ -12,6 +13,7 @@ from basicswap.util import (
) )
from basicswap.chainparams import ( from basicswap.chainparams import (
Coins, Coins,
chainparams,
) )
from basicswap.basicswap_util import ( from basicswap.basicswap_util import (
TxTypes, TxTypes,
@@ -29,6 +31,7 @@ from basicswap.basicswap_util import (
from basicswap.protocols.xmr_swap_1 import getChainBSplitKey from basicswap.protocols.xmr_swap_1 import getChainBSplitKey
PAGE_LIMIT = 50 PAGE_LIMIT = 50
invalid_coins_from = (Coins.XMR, Coins.PART_ANON)
def tickerToCoinId(ticker): def tickerToCoinId(ticker):
@@ -94,6 +97,17 @@ def setCoinFilter(form_data, field_name):
raise ValueError('Unknown Coin Type {}'.format(str(field_name))) raise ValueError('Unknown Coin Type {}'.format(str(field_name)))
def set_pagination_filters(form_data, filters):
if form_data and have_data_entry(form_data, 'pageback'):
filters['page_no'] = int(form_data[b'pageno'][0]) - 1
if filters['page_no'] < 1:
filters['page_no'] = 1
elif form_data and have_data_entry(form_data, 'pageforwards'):
filters['page_no'] = int(form_data[b'pageno'][0]) + 1
if filters['page_no'] > 1:
filters['offset'] = (filters['page_no'] - 1) * PAGE_LIMIT
def getTxIdHex(bid, tx_type, suffix): def getTxIdHex(bid, tx_type, suffix):
if tx_type == TxTypes.ITX: if tx_type == TxTypes.ITX:
obj = bid.initiate_tx obj = bid.initiate_tx
@@ -343,3 +357,54 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b
data['view_tx_desc'] = json.dumps(ci_from.describeTx(data['view_tx_hex']), indent=4) data['view_tx_desc'] = json.dumps(ci_from.describeTx(data['view_tx_hex']), indent=4)
return data return data
def listOldBidStates(bid):
old_states = []
num_states = len(bid.states) // 12
for i in range(num_states):
up = struct.unpack_from('<iq', bid.states[i * 12:(i + 1) * 12])
old_states.append((up[1], 'Bid ' + strBidState(up[0])))
if bid.initiate_tx and bid.initiate_tx.states is not None:
num_states = len(bid.initiate_tx.states) // 12
for i in range(num_states):
up = struct.unpack_from('<iq', bid.initiate_tx.states[i * 12:(i + 1) * 12])
if up[0] != TxStates.TX_NONE:
old_states.append((up[1], 'ITX ' + strTxState(up[0])))
if bid.participate_tx and bid.participate_tx.states is not None:
num_states = len(bid.participate_tx.states) // 12
for i in range(num_states):
up = struct.unpack_from('<iq', bid.participate_tx.states[i * 12:(i + 1) * 12])
if up[0] != TxStates.TX_NONE:
old_states.append((up[1], 'PTX ' + strTxState(up[0])))
if len(old_states) > 0:
old_states.sort(key=lambda x: x[0])
return old_states
def getCoinName(c):
if c == Coins.PART_ANON:
return chainparams[Coins.PART]['name'].capitalize() + 'Anon'
if c == Coins.PART_BLIND:
return chainparams[Coins.PART]['name'].capitalize() + 'Blind'
return chainparams[c]['name'].capitalize()
def listAvailableCoins(swap_client, with_variants=True, split_from=False):
coins_from = []
coins = []
for k, v in swap_client.coin_clients.items():
if k not in chainparams:
continue
if v['connection_type'] == 'rpc':
coins.append((int(k), getCoinName(k)))
if split_from and k not in invalid_coins_from:
coins_from.append(coins[-1])
if with_variants and k == Coins.PART:
for v in (Coins.PART_ANON, Coins.PART_BLIND):
coins.append((int(v), getCoinName(v)))
if split_from and v not in invalid_coins_from:
coins_from.append(coins[-1])
if split_from:
return coins_from, coins
return coins

View File

@@ -21,6 +21,10 @@ class TemporaryError(ValueError):
pass pass
class AutomationConstraint(ValueError):
pass
def ensure(v, err_string): def ensure(v, err_string):
if not v: if not v:
raise ValueError(err_string) raise ValueError(err_string)

View File

@@ -6,7 +6,7 @@
import hashlib import hashlib
from basicswap.contrib.segwit_addr import bech32_decode, convertbits, bech32_encode from basicswap.contrib.segwit_addr import bech32_decode, convertbits, bech32_encode
from basicswap.util.crypto import ripemd160
__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
@@ -89,7 +89,7 @@ def toWIF(prefix_byte, b, compressed=True):
def getKeyID(bytes): def getKeyID(bytes):
data = hashlib.sha256(bytes).digest() data = hashlib.sha256(bytes).digest()
return hashlib.new('ripemd160', data).digest() return ripemd160(data)
def bech32Decode(hrp, addr): def bech32Decode(hrp, addr):

13
basicswap/util/crypto.py Normal file
View File

@@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from Crypto.Hash import RIPEMD160 # pycryptodome
def ripemd160(data):
h = RIPEMD160.new()
h.update(data)
return h.digest()

View File

@@ -14,7 +14,7 @@ def rfc2440_hash_password(password, salt=None):
if salt is None: if salt is None:
salt = secrets.token_bytes(8) salt = secrets.token_bytes(8)
assert(len(salt) == 8) assert len(salt) == 8
hashbytes = salt + password.encode('utf-8') hashbytes = salt + password.encode('utf-8')
len_hashbytes = len(hashbytes) len_hashbytes = len(hashbytes)

View File

@@ -24,10 +24,6 @@ import urllib.parse
from urllib.request import urlretrieve from urllib.request import urlretrieve
import basicswap.config as cfg import basicswap.config as cfg
from basicswap.rpc import (
callrpc_cli,
waitForRPC,
)
from basicswap.base import getaddrinfo_tor from basicswap.base import getaddrinfo_tor
from basicswap.basicswap import BasicSwap from basicswap.basicswap import BasicSwap
from basicswap.chainparams import Coins from basicswap.chainparams import Coins
@@ -36,14 +32,41 @@ from basicswap.util.rfc2440 import rfc2440_hash_password
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
from bin.basicswap_run import startDaemon, startXmrWalletDaemon from bin.basicswap_run import startDaemon, startXmrWalletDaemon
PARTICL_VERSION = os.getenv('PARTICL_VERSION', '0.21.2.9')
PARTICL_VERSION_TAG = os.getenv('PARTICL_VERSION_TAG', '')
PARTICL_LINUX_EXTRA = os.getenv('PARTICL_LINUX_EXTRA', '_nousb')
LITECOIN_VERSION = os.getenv('LITECOIN_VERSION', '0.21.2')
LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '')
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '22.0')
BITCOIN_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.0.0')
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
XMR_SITE_COMMIT = 'f093c0da2219d94e6bef5f3948ac61b4ecdcb95b' # Lock hashes.txt to monero version
PIVX_VERSION = os.getenv('PIVX_VERSION', '5.4.99')
PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '_scantxoutset')
# version, version tag eg. "rc1", signers # version, version tag eg. "rc1", signers
known_coins = { known_coins = {
'particl': ('0.21.2.7', '', ('tecnovert',)), 'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
'litecoin': ('0.18.1', '', ('thrasher',)), 'litecoin': (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ('davidburkett38',)),
'bitcoin': ('22.0', '', ('laanwj',)), 'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
'namecoin': ('0.18.0', '', ('JeremyRand',)), 'namecoin': ('0.18.0', '', ('JeremyRand',)),
'monero': ('0.17.3.0', '', ('',)), 'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('tecnovert',)),
}
expected_key_ids = {
'tecnovert': ('13F13651C9CF0D6B',),
'thrasher': ('FE3348877809386C',),
'laanwj': ('1E4AED62986CD25D',),
'JeremyRand': ('2DBE339E29F6294C',),
'binaryfate': ('F0AF4D462A0BDF92',),
'davidburkett38': ('3620E9D387E55666',),
'fuzzbawls': ('3BDCDA2D87A881D9',),
} }
if platform.system() == 'Darwin': if platform.system() == 'Darwin':
@@ -61,6 +84,17 @@ logger.level = logging.DEBUG
if not len(logger.handlers): if not len(logger.handlers):
logger.addHandler(logging.StreamHandler(sys.stdout)) logger.addHandler(logging.StreamHandler(sys.stdout))
UI_HTML_PORT = int(os.getenv('UI_HTML_PORT', 12700))
UI_WS_PORT = int(os.getenv('UI_WS_PORT', 11700))
COINS_RPCBIND_IP = os.getenv('COINS_RPCBIND_IP', '127.0.0.1')
PART_ZMQ_PORT = int(os.getenv('PART_ZMQ_PORT', 20792))
PART_RPC_HOST = os.getenv('PART_RPC_HOST', '127.0.0.1')
PART_RPC_PORT = int(os.getenv('PART_RPC_PORT', 19792))
PART_ONION_PORT = int(os.getenv('PART_ONION_PORT', 51734))
PART_RPC_USER = os.getenv('PART_RPC_USER', '')
PART_RPC_PWD = os.getenv('PART_RPC_PWD', '')
XMR_RPC_HOST = os.getenv('XMR_RPC_HOST', '127.0.0.1') XMR_RPC_HOST = os.getenv('XMR_RPC_HOST', '127.0.0.1')
BASE_XMR_RPC_PORT = int(os.getenv('BASE_XMR_RPC_PORT', 29798)) BASE_XMR_RPC_PORT = int(os.getenv('BASE_XMR_RPC_PORT', 29798))
BASE_XMR_ZMQ_PORT = int(os.getenv('BASE_XMR_ZMQ_PORT', 30898)) BASE_XMR_ZMQ_PORT = int(os.getenv('BASE_XMR_ZMQ_PORT', 30898))
@@ -68,35 +102,28 @@ BASE_XMR_WALLET_PORT = int(os.getenv('BASE_XMR_WALLET_PORT', 29998))
XMR_WALLET_RPC_HOST = os.getenv('XMR_WALLET_RPC_HOST', '127.0.0.1') XMR_WALLET_RPC_HOST = os.getenv('XMR_WALLET_RPC_HOST', '127.0.0.1')
XMR_WALLET_RPC_USER = os.getenv('XMR_WALLET_RPC_USER', 'xmr_wallet_user') XMR_WALLET_RPC_USER = os.getenv('XMR_WALLET_RPC_USER', 'xmr_wallet_user')
XMR_WALLET_RPC_PWD = os.getenv('XMR_WALLET_RPC_PWD', 'xmr_wallet_pwd') XMR_WALLET_RPC_PWD = os.getenv('XMR_WALLET_RPC_PWD', 'xmr_wallet_pwd')
XMR_SITE_COMMIT = 'be137696c3a1d93bfaddf619da0d05a3e6cee4f2' # Lock hashes.txt to monero version DEFAULT_XMR_RESTORE_HEIGHT = int(os.getenv('DEFAULT_XMR_RESTORE_HEIGHT', 2245107))
DEFAULT_XMR_RESTORE_HEIGHT = 2245107
UI_HTML_PORT = int(os.getenv('UI_HTML_PORT', 12700))
PART_ZMQ_PORT = int(os.getenv('PART_ZMQ_PORT', 20792))
PART_RPC_HOST = os.getenv('PART_RPC_HOST', '127.0.0.1')
LTC_RPC_HOST = os.getenv('LTC_RPC_HOST', '127.0.0.1') LTC_RPC_HOST = os.getenv('LTC_RPC_HOST', '127.0.0.1')
BTC_RPC_HOST = os.getenv('BTC_RPC_HOST', '127.0.0.1') LTC_RPC_PORT = int(os.getenv('LTC_RPC_PORT', 19895))
NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1') LTC_ONION_PORT = int(os.getenv('LTC_ONION_PORT', 9333))
PART_RPC_PORT = int(os.getenv('PART_RPC_PORT', 19792))
LTC_RPC_PORT = int(os.getenv('LTC_RPC_PORT', 19795))
BTC_RPC_PORT = int(os.getenv('BTC_RPC_PORT', 19796))
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19798))
PART_ONION_PORT = int(os.getenv('PART_ONION_PORT', 51734))
LTC_ONION_PORT = int(os.getenv('LTC_ONION_PORT', 9333)) # Still on 0.18 codebase, same port
BTC_ONION_PORT = int(os.getenv('BTC_ONION_PORT', 8334))
PART_RPC_USER = os.getenv('PART_RPC_USER', '')
PART_RPC_PWD = os.getenv('PART_RPC_PWD', '')
BTC_RPC_USER = os.getenv('BTC_RPC_USER', '')
BTC_RPC_PWD = os.getenv('BTC_RPC_PWD', '')
LTC_RPC_USER = os.getenv('LTC_RPC_USER', '') LTC_RPC_USER = os.getenv('LTC_RPC_USER', '')
LTC_RPC_PWD = os.getenv('LTC_RPC_PWD', '') LTC_RPC_PWD = os.getenv('LTC_RPC_PWD', '')
COINS_RPCBIND_IP = os.getenv('COINS_RPCBIND_IP', '127.0.0.1') BTC_RPC_HOST = os.getenv('BTC_RPC_HOST', '127.0.0.1')
BTC_RPC_PORT = int(os.getenv('BTC_RPC_PORT', 19996))
BTC_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', '')
NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1')
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698))
PIVX_RPC_HOST = os.getenv('PIVX_RPC_HOST', '127.0.0.1')
PIVX_RPC_PORT = int(os.getenv('PIVX_RPC_PORT', 51473))
PIVX_ONION_PORT = int(os.getenv('PIVX_ONION_PORT', 51472)) # nDefaultPort
PIVX_RPC_USER = os.getenv('PIVX_RPC_USER', '')
PIVX_RPC_PWD = os.getenv('PIVX_RPC_PWD', '')
TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1') TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050)) TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
@@ -105,7 +132,9 @@ TOR_DNS_PORT = int(os.getenv('TOR_DNS_PORT', 5353))
TEST_TOR_PROXY = toBool(os.getenv('TEST_TOR_PROXY', 'true')) # Expects a known exit node TEST_TOR_PROXY = toBool(os.getenv('TEST_TOR_PROXY', 'true')) # Expects a known exit node
TEST_ONION_LINK = toBool(os.getenv('TEST_ONION_LINK', 'false')) TEST_ONION_LINK = toBool(os.getenv('TEST_ONION_LINK', 'false'))
extract_core_overwrite = True BITCOIN_FASTSYNC_URL = os.getenv('BITCOIN_FASTSYNC_URL', 'http://utxosets.blob.core.windows.net/public/')
BITCOIN_FASTSYNC_FILE = os.getenv('BITCOIN_FASTSYNC_FILE', 'utxo-snapshot-bitcoin-mainnet-720179.tar')
use_tor_proxy = False use_tor_proxy = False
default_socket = socket.socket default_socket = socket.socket
@@ -187,9 +216,55 @@ def testOnionLink():
logger.info('Onion links work.') logger.info('Onion links work.')
def extractCore(coin, version_data, settings, bin_dir, release_path): def downloadPIVXParams(output_dir):
# util/fetch-params.sh
if os.path.exists(output_dir):
logger.info(f'Skipping PIVX params download, path exists: {output_dir}')
return
os.makedirs(output_dir)
source_url = 'https://download.z.cash/downloads/'
files = {
'sapling-spend.params': '8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13',
'sapling-output.params': '2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4',
}
try:
setConnectionParameters()
for k, v in files.items():
url = urllib.parse.urljoin(source_url, k)
path = os.path.join(output_dir, k)
downloadFile(url, path)
hasher = hashlib.sha256()
with open(path, 'rb') as fp:
hasher.update(fp.read())
file_hash = hasher.hexdigest()
logger.info('%s hash: %s', k, file_hash)
assert file_hash == v
finally:
popConnectionParameters()
def isValidSignature(result):
if result.valid is False \
and (result.status == 'signature valid' and result.key_status == 'signing key has expired'):
return True
return result.valid
def ensureValidSignatureBy(result, signing_key_name):
if not isValidSignature(result):
raise ValueError('Signature verification failed.')
if result.key_id not in expected_key_ids[signing_key_name]:
raise ValueError('Signature made by unexpected keyid: ' + result.key_id)
def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts={}):
version, version_tag, signers = version_data version, version_tag, signers = version_data
logger.info('extractCore %s v%s%s', coin, version, version_tag) logger.info('extractCore %s v%s%s', coin, version, version_tag)
extract_core_overwrite = extra_opts.get('extract_core_overwrite', True)
if coin == 'monero': if coin == 'monero':
bins = ['monerod', 'monero-wallet-rpc'] bins = ['monerod', 'monero-wallet-rpc']
@@ -240,7 +315,13 @@ def extractCore(coin, version_data, settings, bin_dir, release_path):
for b in bins: for b in bins:
out_path = os.path.join(bin_dir, b) out_path = os.path.join(bin_dir, b)
if not os.path.exists(out_path) or extract_core_overwrite: if not os.path.exists(out_path) or extract_core_overwrite:
with open(out_path, 'wb') as fout, ft.extractfile('{}-{}/bin/{}'.format(coin, version + version_tag, b)) as fi:
if coin == 'pivx':
filename = '{}-{}/bin/{}'.format(coin, version, b)
else:
filename = '{}-{}/bin/{}'.format(coin, version + version_tag, b)
with open(out_path, 'wb') as fout, ft.extractfile(filename) as fi:
fout.write(fi.read()) fout.write(fi.read())
try: try:
os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH) os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
@@ -248,7 +329,7 @@ def extractCore(coin, version_data, settings, bin_dir, release_path):
logging.warning('Unable to set file permissions: %s, for %s', str(e), out_path) logging.warning('Unable to set file permissions: %s, for %s', str(e), out_path)
def prepareCore(coin, version_data, settings, data_dir): def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
version, version_tag, signers = version_data version, version_tag, signers = version_data
logger.info('prepareCore %s v%s%s', coin, version, version_tag) logger.info('prepareCore %s v%s%s', coin, version, version_tag)
@@ -256,6 +337,7 @@ def prepareCore(coin, version_data, settings, data_dir):
if not os.path.exists(bin_dir): if not os.path.exists(bin_dir):
os.makedirs(bin_dir) os.makedirs(bin_dir)
filename_extra = ''
if 'osx' in BIN_ARCH: if 'osx' in BIN_ARCH:
os_dir_name = 'osx-unsigned' os_dir_name = 'osx-unsigned'
os_name = 'osx' os_name = 'osx'
@@ -265,7 +347,10 @@ def prepareCore(coin, version_data, settings, data_dir):
else: else:
os_dir_name = 'linux' os_dir_name = 'linux'
os_name = 'linux' os_name = 'linux'
if coin == 'particl':
filename_extra = PARTICL_LINUX_EXTRA
signing_key_name = signers[0]
if coin == 'monero': if coin == 'monero':
use_file_ext = 'tar.bz2' if FILE_EXT == 'tar.gz' else FILE_EXT use_file_ext = 'tar.bz2' if FILE_EXT == 'tar.gz' else FILE_EXT
release_filename = '{}-{}-{}.{}'.format(coin, version, BIN_ARCH, use_file_ext) release_filename = '{}-{}-{}.{}'.format(coin, version, BIN_ARCH, use_file_ext)
@@ -284,15 +369,14 @@ def prepareCore(coin, version_data, settings, data_dir):
downloadFile(assert_url, assert_path) downloadFile(assert_url, assert_path)
else: else:
major_version = int(version.split('.')[0]) major_version = int(version.split('.')[0])
signing_key_name = signers[0] release_filename = '{}-{}-{}{}.{}'.format(coin, version + version_tag, BIN_ARCH, filename_extra, FILE_EXT)
release_filename = '{}-{}-{}.{}'.format(coin, version + version_tag, BIN_ARCH, FILE_EXT)
if coin == 'particl': if coin == 'particl':
release_url = 'https://github.com/particl/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename) release_url = 'https://github.com/particl/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename)
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version) assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version)
assert_url = 'https://raw.githubusercontent.com/particl/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename) assert_url = 'https://raw.githubusercontent.com/particl/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename)
elif coin == 'litecoin': elif coin == 'litecoin':
release_url = 'https://download.litecoin.org/litecoin-{}/{}/{}'.format(version, os_name, release_filename) release_url = 'https://download.litecoin.org/litecoin-{}/{}/{}'.format(version, os_name, release_filename)
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0]) assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
assert_url = 'https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename) assert_url = 'https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
elif coin == 'bitcoin': elif coin == 'bitcoin':
release_url = 'https://bitcoincore.org/bin/bitcoin-core-{}/{}'.format(version, release_filename) release_url = 'https://bitcoincore.org/bin/bitcoin-core-{}/{}'.format(version, release_filename)
@@ -305,6 +389,11 @@ def prepareCore(coin, version_data, settings, data_dir):
release_url = 'https://beta.namecoin.org/files/namecoin-core/namecoin-core-{}/{}'.format(version, release_filename) release_url = 'https://beta.namecoin.org/files/namecoin-core/namecoin-core-{}/{}'.format(version, release_filename)
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0]) assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
assert_url = 'https://raw.githubusercontent.com/namecoin/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename) assert_url = 'https://raw.githubusercontent.com/namecoin/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
elif coin == 'pivx':
release_filename = '{}-{}-{}{}.{}'.format(coin, version, BIN_ARCH, filename_extra, FILE_EXT)
release_url = 'https://github.com/tecnovert/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename)
assert_filename = 'pivx-linux-6.0-build.assert'
assert_url = 'https://raw.githubusercontent.com/tecnovert/gitian.sigs/pivx/5.4.99_scantxoutset-linux/tecnovert/{}'.format(assert_filename)
else: else:
raise ValueError('Unknown coin') raise ValueError('Unknown coin')
@@ -345,18 +434,26 @@ def prepareCore(coin, version_data, settings, data_dir):
""" """
gpg = gnupg.GPG() gpg = gnupg.GPG()
keysdirpath = extra_opts.get('keysdirpath', None)
if keysdirpath is not None:
logger.info(f'Loading PGP keys from: {keysdirpath}.')
for path in os.scandir(keysdirpath):
if path.is_file():
with open(path, 'rb') as fp:
rv = gpg.import_keys(fp.read())
for key in rv.fingerprints:
gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY')
if coin == 'monero': if coin == 'monero':
with open(assert_path, 'rb') as fp: with open(assert_path, 'rb') as fp:
verified = gpg.verify_file(fp) verified = gpg.verify_file(fp)
if verified.username is None: if not isValidSignature(verified) and verified.username is None:
logger.warning('Signature not verified.') logger.warning('Signature made by unknown key.')
pubkeyurl = 'https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc' pubkeyurl = 'https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc'
logger.info('Importing public key from url: ' + pubkeyurl) logger.info('Importing public key from url: ' + pubkeyurl)
rv = gpg.import_keys(downloadBytes(pubkeyurl)) rv = gpg.import_keys(downloadBytes(pubkeyurl))
print('import_keys', rv)
assert('F0AF4D462A0BDF92' in rv.fingerprints[0])
gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY') gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY')
with open(assert_path, 'rb') as fp: with open(assert_path, 'rb') as fp:
verified = gpg.verify_file(fp) verified = gpg.verify_file(fp)
@@ -364,13 +461,24 @@ def prepareCore(coin, version_data, settings, data_dir):
with open(assert_sig_path, 'rb') as fp: with open(assert_sig_path, 'rb') as fp:
verified = gpg.verify_file(fp, assert_path) verified = gpg.verify_file(fp, assert_path)
if verified.username is None: if not isValidSignature(verified) and verified.username is None:
logger.warning('Signature not verified.') logger.warning('Signature made by unknown key.')
pubkeyurl = 'https://raw.githubusercontent.com/tecnovert/basicswap/master/gitianpubkeys/{}_{}.pgp'.format(coin, signing_key_name) if coin == 'pivx':
logger.info('Importing public key from url: ' + pubkeyurl) filename = '{}_{}.pgp'.format('particl', signing_key_name)
else:
rv = gpg.import_keys(downloadBytes(pubkeyurl)) filename = '{}_{}.pgp'.format(coin, signing_key_name)
pubkeyurls = (
'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp/keys/' + filename,
'https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/' + filename,
)
for url in pubkeyurls:
try:
logger.info('Importing public key from url: ' + url)
rv = gpg.import_keys(downloadBytes(url))
break
except Exception as e:
logging.warning('Import from url failed: %s', str(e))
for key in rv.fingerprints: for key in rv.fingerprints:
gpg.trust_keys(key, 'TRUST_FULLY') gpg.trust_keys(key, 'TRUST_FULLY')
@@ -378,11 +486,9 @@ def prepareCore(coin, version_data, settings, data_dir):
with open(assert_sig_path, 'rb') as fp: with open(assert_sig_path, 'rb') as fp:
verified = gpg.verify_file(fp, assert_path) verified = gpg.verify_file(fp, assert_path)
if verified.valid is False \ ensureValidSignatureBy(verified, signing_key_name)
and not (verified.status == 'signature valid' and verified.key_status == 'signing key has expired'):
raise ValueError('Signature verification failed.')
extractCore(coin, version_data, settings, bin_dir, release_path) extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts)
def writeTorSettings(fp, coin, coin_settings, tor_control_password): def writeTorSettings(fp, coin, coin_settings, tor_control_password):
@@ -395,16 +501,17 @@ def writeTorSettings(fp, coin, coin_settings, tor_control_password):
fp.write(f'torpassword={tor_control_password}\n') fp.write(f'torpassword={tor_control_password}\n')
fp.write(f'torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n') fp.write(f'torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n')
if coin == 'litecoin': if coin_settings['core_version_group'] >= 21:
fp.write(f'bind=0.0.0.0:{onionport}\n')
else:
fp.write(f'bind=0.0.0.0:{onionport}=onion\n') fp.write(f'bind=0.0.0.0:{onionport}=onion\n')
else:
fp.write(f'bind=0.0.0.0:{onionport}\n')
def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False, tor_control_password=None): def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
core_settings = settings['chainclients'][coin] core_settings = settings['chainclients'][coin]
bin_dir = core_settings['bindir'] bin_dir = core_settings['bindir']
data_dir = core_settings['datadir'] data_dir = core_settings['datadir']
tor_control_password = extra_opts.get('tor_control_password', None)
if not os.path.exists(data_dir): if not os.path.exists(data_dir):
os.makedirs(data_dir) os.makedirs(data_dir)
@@ -423,7 +530,11 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False
fp.write('restricted-rpc=1\n') fp.write('restricted-rpc=1\n')
if chain == 'testnet': if chain == 'testnet':
fp.write('testnet=1\n') fp.write('testnet=1\n')
fp.write('data-dir={}\n'.format(data_dir)) config_datadir = data_dir
if core_settings['manage_daemon'] is False:
# Assume conf file is for isolated coin docker setup
config_datadir = '/data'
fp.write(f'data-dir={config_datadir}\n')
fp.write('rpc-bind-port={}\n'.format(core_settings['rpcport'])) fp.write('rpc-bind-port={}\n'.format(core_settings['rpcport']))
fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP)) fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
fp.write('zmq-rpc-bind-port={}\n'.format(core_settings['zmqport'])) fp.write('zmq-rpc-bind-port={}\n'.format(core_settings['zmqport']))
@@ -443,21 +554,26 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False
if os.path.exists(wallet_conf_path): if os.path.exists(wallet_conf_path):
exitWithError('{} exists'.format(wallet_conf_path)) exitWithError('{} exists'.format(wallet_conf_path))
with open(wallet_conf_path, 'w') as fp: with open(wallet_conf_path, 'w') as fp:
if use_containers: if extra_opts.get('use_containers', False) is True:
fp.write('daemon-address={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport'])) fp.write('daemon-address={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
fp.write('untrusted-daemon=1\n') fp.write('untrusted-daemon=1\n')
fp.write('no-dns=1\n') fp.write('no-dns=1\n')
fp.write('rpc-bind-port={}\n'.format(core_settings['walletrpcport'])) fp.write('rpc-bind-port={}\n'.format(core_settings['walletrpcport']))
fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP)) fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
fp.write('wallet-dir={}\n'.format(os.path.join(data_dir, 'wallets'))) config_datadir = os.path.join(data_dir, 'wallets')
fp.write('log-file={}\n'.format(os.path.join(data_dir, 'wallet.log'))) if core_settings['manage_wallet_daemon'] is False:
fp.write('shared-ringdb-dir={}\n'.format(os.path.join(data_dir, 'shared-ringdb'))) # Assume conf file is for isolated coin docker setup
config_datadir = '/data'
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'])) fp.write('rpc-login={}:{}\n'.format(core_settings['walletrpcuser'], core_settings['walletrpcpassword']))
if tor_control_password is not None: if tor_control_password is not None:
if not core_settings['manage_daemon']: if not core_settings['manage_daemon']:
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n') fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
return return
core_conf_path = os.path.join(data_dir, coin + '.conf') core_conf_path = os.path.join(data_dir, coin + '.conf')
if os.path.exists(core_conf_path): if os.path.exists(core_conf_path):
exitWithError('{} exists'.format(core_conf_path)) exitWithError('{} exists'.format(core_conf_path))
@@ -493,10 +609,11 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False
fp.write('staking=0\n') fp.write('staking=0\n')
if PART_RPC_USER != '': if PART_RPC_USER != '':
fp.write('rpcauth={}:{}${}\n'.format(PART_RPC_USER, salt, password_to_hmac(salt, PART_RPC_PWD))) fp.write('rpcauth={}:{}${}\n'.format(PART_RPC_USER, salt, password_to_hmac(salt, PART_RPC_PWD)))
if particl_mnemonic == 'none': if particl_mnemonic == 'auto':
fp.write('createdefaultmasterkey=1') fp.write('createdefaultmasterkey=1')
elif coin == 'litecoin': elif coin == 'litecoin':
fp.write('prune=2000\n') fp.write('prune=4000\n')
fp.write('pid=litecoind.pid\n')
if LTC_RPC_USER != '': if LTC_RPC_USER != '':
fp.write('rpcauth={}:{}${}\n'.format(LTC_RPC_USER, salt, password_to_hmac(salt, LTC_RPC_PWD))) fp.write('rpcauth={}:{}${}\n'.format(LTC_RPC_USER, salt, password_to_hmac(salt, LTC_RPC_PWD)))
elif coin == 'bitcoin': elif coin == 'bitcoin':
@@ -506,13 +623,51 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False
fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD))) fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD)))
elif coin == 'namecoin': elif coin == 'namecoin':
fp.write('prune=2000\n') fp.write('prune=2000\n')
elif coin == 'pivx':
base_dir = extra_opts.get('data_dir', data_dir)
params_dir = os.path.join(base_dir, 'pivx-params')
downloadPIVXParams(params_dir)
fp.write('prune=4000\n')
fp.write(f'paramsdir={params_dir}\n')
if PIVX_RPC_USER != '':
fp.write('rpcauth={}:{}${}\n'.format(PIVX_RPC_USER, salt, password_to_hmac(salt, PIVX_RPC_PWD)))
else: else:
logger.warning('Unknown coin %s', coin) logger.warning('Unknown coin %s', coin)
wallet_util = coin + '-wallet' if coin == 'bitcoin' and extra_opts.get('use_btc_fastsync', False) is True:
if os.path.exists(os.path.join(bin_dir, wallet_util)): logger.info('Initialising BTC chain with fastsync %s', BITCOIN_FASTSYNC_FILE)
logger.info('Creating wallet.dat for {}.'.format(wallet_util.capitalize())) base_dir = extra_opts['data_dir']
callrpc_cli(bin_dir, data_dir, chain, '-wallet=wallet.dat create', wallet_util)
for dirname in ('blocks', 'chainstate'):
if os.path.exists(os.path.join(data_dir, dirname)):
raise ValueError(f'{dirname} directory already exists, not overwriting.')
sync_file_path = os.path.join(base_dir, BITCOIN_FASTSYNC_FILE)
if not os.path.exists(sync_file_path):
sync_file_url = os.path.join(BITCOIN_FASTSYNC_URL, BITCOIN_FASTSYNC_FILE)
downloadFile(sync_file_url, sync_file_path)
asc_filename = BITCOIN_FASTSYNC_FILE + '.asc'
asc_file_path = os.path.join(base_dir, asc_filename)
if not os.path.exists(asc_file_path):
asc_file_urls = (
'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp/sigs/' + asc_filename,
'https://gitlab.com/particl/basicswap/-/raw/master/pgp/sigs/' + asc_filename,
)
for url in asc_file_urls:
try:
downloadFile(url, asc_file_path)
break
except Exception as e:
logging.warning('Download failed: %s', str(e))
gpg = gnupg.GPG()
with open(asc_file_path, 'rb') as fp:
verified = gpg.verify_file(fp, sync_file_path)
ensureValidSignatureBy(verified, 'tecnovert')
with tarfile.open(sync_file_path) as ft:
ft.extractall(path=data_dir)
def write_torrc(data_dir, tor_control_password): def write_torrc(data_dir, tor_control_password):
@@ -623,20 +778,6 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False):
writeTorSettings(fp, coin, coin_settings, tor_control_password) writeTorSettings(fp, coin, coin_settings, tor_control_password)
def make_rpc_func(bin_dir, data_dir, chain):
bin_dir = bin_dir
data_dir = data_dir
chain = chain
def rpc_func(cmd):
nonlocal bin_dir
nonlocal data_dir
nonlocal chain
return callrpc_cli(bin_dir, data_dir, chain, cmd, cfg.PARTICL_CLI)
return rpc_func
def exitWithError(error_msg): def exitWithError(error_msg):
sys.stderr.write('Error: {}, exiting.\n'.format(error_msg)) sys.stderr.write('Error: {}, exiting.\n'.format(error_msg))
sys.exit(1) sys.exit(1)
@@ -655,13 +796,14 @@ def printHelp():
logger.info('Usage: basicswap-prepare ') logger.info('Usage: basicswap-prepare ')
logger.info('\n--help, -h Print help.') logger.info('\n--help, -h Print help.')
logger.info('--version, -v Print version.') logger.info('--version, -v Print version.')
logger.info('--datadir=PATH Path to basicswap data directory, default:{}.'.format(cfg.DEFAULT_DATADIR)) logger.info('--datadir=PATH Path to basicswap data directory, default:{}.'.format(cfg.BASICSWAP_DATADIR))
logger.info('--bindir=PATH Path to cores directory, default:datadir/bin.') logger.info('--bindir=PATH Path to cores directory, default:datadir/bin.')
logger.info('--mainnet Run in mainnet mode.') logger.info('--mainnet Run in mainnet mode.')
logger.info('--testnet Run in testnet mode.') logger.info('--testnet Run in testnet mode.')
logger.info('--regtest Run in regtest mode.') logger.info('--regtest Run in regtest mode.')
logger.info('--particl_mnemonic= Recovery phrase to use for the Particl wallet, default is randomly generated,\n' logger.info('--particl_mnemonic= Recovery phrase to use for the Particl wallet, default is randomly generated,\n'
+ ' "none" to set autogenerate account mode.') + ' "auto" to create a wallet automatically - No mnemonic.'
+ ' "none" to disable wallet initialisation.')
logger.info('--withcoin= Prepare system to run daemon for coin.') logger.info('--withcoin= Prepare system to run daemon for coin.')
logger.info('--withoutcoin= Do not prepare system to run daemon for coin.') logger.info('--withoutcoin= Do not prepare system to run daemon for coin.')
logger.info('--addcoin= Add coin to existing setup.') logger.info('--addcoin= Add coin to existing setup.')
@@ -670,33 +812,121 @@ def printHelp():
logger.info('--nocores Don\'t download and extract any coin clients.') logger.info('--nocores Don\'t download and extract any coin clients.')
logger.info('--usecontainers Expect each core to run in a unique container.') logger.info('--usecontainers Expect each core to run in a unique container.')
logger.info('--portoffset=n Raise all ports by n.') logger.info('--portoffset=n Raise all ports by n.')
logger.info('--htmlhost= Interface to host on, default:127.0.0.1.') logger.info('--htmlhost= Interface to host html server on, default:127.0.0.1.')
logger.info('--wshost= Interface to host websocket server on, disable by setting to "none", default:127.0.0.1.')
logger.info('--xmrrestoreheight=n Block height to restore Monero wallet from, default:{}.'.format(DEFAULT_XMR_RESTORE_HEIGHT)) logger.info('--xmrrestoreheight=n Block height to restore Monero wallet from, default:{}.'.format(DEFAULT_XMR_RESTORE_HEIGHT))
logger.info('--noextractover Prevent extracting cores if files exist. Speeds up tests') logger.info('--noextractover Prevent extracting cores if files exist. Speeds up tests')
logger.info('--usetorproxy Use TOR proxy. Note that some download links may be inaccessible over TOR.') logger.info('--usetorproxy Use TOR proxy during setup. Note that some download links may be inaccessible over TOR.')
logger.info('--enabletor Setup Basicswap instance to use TOR.')
logger.info('--disabletor Setup Basicswap instance to not use TOR.')
logger.info('--usebtcfastsync Initialise the BTC chain with a snapshot from btcpayserver FastSync.\n'
+ ' See https://github.com/btcpayserver/btcpayserver-docker/blob/master/contrib/FastSync/README.md')
logger.info('--initwalletsonly Setup coin wallets only.')
logger.info('--keysdirpath Speed up tests by preloading all PGP keys in directory.')
logger.info('\n' + 'Known coins: %s', ', '.join(known_coins.keys())) logger.info('\n' + 'Known coins: %s', ', '.join(known_coins.keys()))
def finalise_daemon(d):
logging.info('Interrupting {}'.format(d.pid))
d.send_signal(signal.SIGINT)
d.wait(timeout=120)
for fp in (d.stdout, d.stderr, d.stdin):
if fp:
fp.close()
def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy):
daemons = []
daemon_args = ['-noconnect', '-nodnsseed']
if not use_tor_proxy:
# Cannot set -bind or -whitebind together with -listen=0
daemon_args.append('-nolisten')
try:
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
swap_client = BasicSwap(fp, data_dir, settings, chain)
start_daemons = {c for c in with_coins}
if 'particl' not in with_coins:
# Particl must be running to initialise a wallet in addcoin mode
start_daemons.add('particl')
for coin_name in start_daemons:
coin_settings = settings['chainclients'][coin_name]
c = swap_client.getCoinIdFromName(coin_name)
if c == Coins.XMR:
if coin_settings['manage_wallet_daemon']:
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], 'monero-wallet-rpc'))
else:
if coin_settings['manage_daemon']:
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else []
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
swap_client.setDaemonPID(c, daemons[-1].pid)
swap_client.setCoinRunParams(c)
swap_client.createCoinInterface(c)
if c in (Coins.PART, Coins.BTC, Coins.LTC, Coins.PIVX):
swap_client.waitForDaemonRPC(c, with_wallet=False)
# Create wallet if it doesn't exist yet
wallets = swap_client.callcoinrpc(c, 'listwallets')
if len(wallets) < 1:
logger.info('Creating wallet.dat for {}.'.format(coin_name.capitalize()))
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat'])
if 'particl' in with_coins:
logger.info('Loading Particl mnemonic')
if particl_wallet_mnemonic is None:
particl_wallet_mnemonic = swap_client.callcoinrpc(Coins.PART, 'mnemonic', ['new'])['mnemonic']
swap_client.callcoinrpc(Coins.PART, 'extkeyimportmaster', [particl_wallet_mnemonic])
for coin_name in with_coins:
c = swap_client.getCoinIdFromName(coin_name)
if c == Coins.PART:
continue
swap_client.waitForDaemonRPC(c)
swap_client.initialiseWallet(c)
swap_client.finalise()
del swap_client
finally:
for d in daemons:
finalise_daemon(d)
if particl_wallet_mnemonic is not None:
if particl_wallet_mnemonic:
# Print directly to stdout for tests
print('IMPORTANT - Save your particl wallet recovery phrase:\n{}\n'.format(particl_wallet_mnemonic))
def load_config(config_path):
if not os.path.exists(config_path):
exitWithError('{} does not exist'.format(config_path))
with open(config_path) as fs:
return json.load(fs)
def main(): def main():
global extract_core_overwrite
global use_tor_proxy global use_tor_proxy
data_dir = None data_dir = None
bin_dir = None bin_dir = None
port_offset = None port_offset = None
chain = 'mainnet' chain = 'mainnet'
particl_wallet_mnemonic = None particl_wallet_mnemonic = None
prepare_bin_only = False
no_cores = False
use_containers = False
with_coins = {'particl', } with_coins = {'particl', }
add_coin = '' add_coin = ''
disable_coin = '' disable_coin = ''
htmlhost = '127.0.0.1' htmlhost = '127.0.0.1'
wshost = '127.0.0.1'
xmr_restore_height = DEFAULT_XMR_RESTORE_HEIGHT xmr_restore_height = DEFAULT_XMR_RESTORE_HEIGHT
prepare_bin_only = False
no_cores = False
enable_tor = False enable_tor = False
disable_tor = False disable_tor = False
initwalletsonly = False
tor_control_password = None tor_control_password = None
extra_opts = {}
for v in sys.argv[1:]: for v in sys.argv[1:]:
if len(v) < 2 or v[0] != '-': if len(v) < 2 or v[0] != '-':
@@ -730,10 +960,10 @@ def main():
no_cores = True no_cores = True
continue continue
if name == 'usecontainers': if name == 'usecontainers':
use_containers = True extra_opts['use_containers'] = True
continue continue
if name == 'noextractover': if name == 'noextractover':
extract_core_overwrite = False extra_opts['extract_core_overwrite'] = False
continue continue
if name == 'usetorproxy': if name == 'usetorproxy':
use_tor_proxy = True use_tor_proxy = True
@@ -744,6 +974,12 @@ def main():
if name == 'disabletor': if name == 'disabletor':
disable_tor = True disable_tor = True
continue continue
if name == 'usebtcfastsync':
extra_opts['use_btc_fastsync'] = True
continue
if name == 'initwalletsonly':
initwalletsonly = True
continue
if len(s) == 2: if len(s) == 2:
if name == 'datadir': if name == 'datadir':
data_dir = os.path.expanduser(s[1].strip('"')) data_dir = os.path.expanduser(s[1].strip('"'))
@@ -775,7 +1011,7 @@ def main():
if s[1] not in known_coins: if s[1] not in known_coins:
exitWithError('Unknown coin {}'.format(s[1])) exitWithError('Unknown coin {}'.format(s[1]))
add_coin = s[1] add_coin = s[1]
with_coins = [add_coin, ] with_coins = {add_coin, }
continue continue
if name == 'disablecoin': if name == 'disablecoin':
if s[1] not in known_coins: if s[1] not in known_coins:
@@ -785,9 +1021,15 @@ def main():
if name == 'htmlhost': if name == 'htmlhost':
htmlhost = s[1].strip('"') htmlhost = s[1].strip('"')
continue continue
if name == 'wshost':
wshost = s[1].strip('"')
continue
if name == 'xmrrestoreheight': if name == 'xmrrestoreheight':
xmr_restore_height = int(s[1]) xmr_restore_height = int(s[1])
continue continue
if name == 'keysdirpath':
extra_opts['keysdirpath'] = os.path.expanduser(s[1].strip('"'))
continue
exitWithError('Unknown argument {}'.format(v)) exitWithError('Unknown argument {}'.format(v))
@@ -800,7 +1042,7 @@ def main():
testOnionLink() testOnionLink()
if data_dir is None: if data_dir is None:
data_dir = os.path.join(os.path.expanduser(cfg.DEFAULT_DATADIR)) data_dir = os.path.join(os.path.expanduser(cfg.BASICSWAP_DATADIR))
if bin_dir is None: if bin_dir is None:
bin_dir = os.path.join(data_dir, 'bin') bin_dir = os.path.join(data_dir, 'bin')
@@ -828,7 +1070,7 @@ def main():
'blocks_confirmed': 2, 'blocks_confirmed': 2,
'override_feerate': 0.002, 'override_feerate': 0.002,
'conf_target': 2, 'conf_target': 2,
'core_version_group': 18, 'core_version_group': 21,
'chain_lookups': 'local', 'chain_lookups': 'local',
}, },
'litecoin': { 'litecoin': {
@@ -842,7 +1084,7 @@ def main():
'use_segwit': True, 'use_segwit': True,
'blocks_confirmed': 2, 'blocks_confirmed': 2,
'conf_target': 2, 'conf_target': 2,
'core_version_group': 18, 'core_version_group': 21,
'chain_lookups': 'local', 'chain_lookups': 'local',
}, },
'bitcoin': { 'bitcoin': {
@@ -856,7 +1098,7 @@ def main():
'use_segwit': True, 'use_segwit': True,
'blocks_confirmed': 1, 'blocks_confirmed': 1,
'conf_target': 2, 'conf_target': 2,
'core_version_group': 18, 'core_version_group': 22,
'chain_lookups': 'local', 'chain_lookups': 'local',
}, },
'namecoin': { 'namecoin': {
@@ -889,6 +1131,21 @@ def main():
'bindir': os.path.join(bin_dir, 'monero'), 'bindir': os.path.join(bin_dir, 'monero'),
'restore_height': xmr_restore_height, 'restore_height': xmr_restore_height,
'blocks_confirmed': 7, # TODO: 10? 'blocks_confirmed': 7, # TODO: 10?
},
'pivx': {
'connection_type': 'rpc' if 'pivx' in with_coins else 'none',
'manage_daemon': True if ('pivx' in with_coins and PIVX_RPC_HOST == '127.0.0.1') else False,
'rpchost': PIVX_RPC_HOST,
'rpcport': PIVX_RPC_PORT + port_offset,
'onionport': PIVX_ONION_PORT + port_offset,
'datadir': os.getenv('PIVX_DATA_DIR', os.path.join(data_dir, 'pivx')),
'bindir': os.path.join(bin_dir, 'pivx'),
'use_segwit': False,
'use_csv': False,
'blocks_confirmed': 1,
'conf_target': 2,
'core_version_group': 20,
'chain_lookups': 'local',
} }
} }
@@ -901,16 +1158,26 @@ def main():
if BTC_RPC_USER != '': if BTC_RPC_USER != '':
chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
if PIVX_RPC_USER != '':
chainclients['pivx']['rpcuser'] = PIVX_RPC_USER
chainclients['pivx']['rpcpassword'] = PIVX_RPC_PWD
chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir']) chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])
if initwalletsonly:
logger.info('Initialising wallets')
settings = load_config(config_path)
init_coins = settings['chainclients'].keys()
logger.info('Active coins: %s', ', '.join(init_coins))
initialise_wallets(particl_wallet_mnemonic, init_coins, data_dir, settings, chain, use_tor_proxy)
print('Done.')
return 0
if enable_tor: if enable_tor:
logger.info('Enabling TOR') logger.info('Enabling TOR')
settings = load_config(config_path)
if not os.path.exists(config_path):
exitWithError('{} does not exist'.format(config_path))
with open(config_path) as fs:
settings = json.load(fs)
tor_control_password = settings.get('tor_control_password', None) tor_control_password = settings.get('tor_control_password', None)
if tor_control_password is None: if tor_control_password is None:
@@ -930,12 +1197,7 @@ def main():
if disable_tor: if disable_tor:
logger.info('Disabling TOR') logger.info('Disabling TOR')
settings = load_config(config_path)
if not os.path.exists(config_path):
exitWithError('{} does not exist'.format(config_path))
with open(config_path) as fs:
settings = json.load(fs)
settings['use_tor'] = False settings['use_tor'] = False
for coin in settings['chainclients']: for coin in settings['chainclients']:
modify_tor_config(settings, coin, tor_control_password=None, enable=False) modify_tor_config(settings, coin, tor_control_password=None, enable=False)
@@ -948,10 +1210,7 @@ def main():
if disable_coin != '': if disable_coin != '':
logger.info('Disabling coin: %s', disable_coin) logger.info('Disabling coin: %s', disable_coin)
if not os.path.exists(config_path): settings = load_config(config_path)
exitWithError('{} does not exist'.format(config_path))
with open(config_path) as fs:
settings = json.load(fs)
if disable_coin not in settings['chainclients']: if disable_coin not in settings['chainclients']:
exitWithError('{} has not been prepared'.format(disable_coin)) exitWithError('{} has not been prepared'.format(disable_coin))
@@ -964,12 +1223,12 @@ def main():
logger.info('Done.') logger.info('Done.')
return 0 return 0
extra_opts['data_dir'] = data_dir
extra_opts['tor_control_password'] = tor_control_password
if add_coin != '': if add_coin != '':
logger.info('Adding coin: %s', add_coin) logger.info('Adding coin: %s', add_coin)
if not os.path.exists(config_path): settings = load_config(config_path)
exitWithError('{} does not exist'.format(config_path))
with open(config_path) as fs:
settings = json.load(fs)
if add_coin in settings['chainclients']: if add_coin in settings['chainclients']:
coin_settings = settings['chainclients'][add_coin] coin_settings = settings['chainclients'][add_coin]
@@ -987,10 +1246,14 @@ def main():
settings['use_tor_proxy'] = use_tor_proxy settings['use_tor_proxy'] = use_tor_proxy
if not no_cores: if not no_cores:
prepareCore(add_coin, known_coins[add_coin], settings, data_dir) prepareCore(add_coin, known_coins[add_coin], settings, data_dir, extra_opts)
if not prepare_bin_only: if not prepare_bin_only:
prepareDataDir(add_coin, settings, chain, particl_wallet_mnemonic, use_containers=use_containers) prepareDataDir(add_coin, settings, chain, particl_wallet_mnemonic, extra_opts)
if particl_wallet_mnemonic not in ('none', 'auto'):
initialise_wallets(None, {add_coin, }, data_dir, settings, chain, use_tor_proxy)
with open(config_path, 'w') as fp: with open(config_path, 'w') as fp:
json.dump(settings, fp, indent=4) json.dump(settings, fp, indent=4)
@@ -1010,7 +1273,7 @@ def main():
settings = { settings = {
'debug': True, 'debug': True,
'zmqhost': 'tcp://127.0.0.1', 'zmqhost': f'tcp://{PART_RPC_HOST}',
'zmqport': PART_ZMQ_PORT + port_offset, 'zmqport': PART_ZMQ_PORT + port_offset,
'htmlhost': htmlhost, 'htmlhost': htmlhost,
'htmlport': UI_HTML_PORT + port_offset, 'htmlport': UI_HTML_PORT + port_offset,
@@ -1024,90 +1287,34 @@ def main():
'check_expired_seconds': 60 'check_expired_seconds': 60
} }
if wshost != 'none':
settings['wshost'] = wshost
settings['wsport'] = UI_WS_PORT + port_offset
if use_tor_proxy: if use_tor_proxy:
tor_control_password = generate_salt(24) tor_control_password = generate_salt(24)
addTorSettings(settings, tor_control_password) addTorSettings(settings, tor_control_password)
if not no_cores: if not no_cores:
for c in with_coins: for c in with_coins:
prepareCore(c, known_coins[c], settings, data_dir) prepareCore(c, known_coins[c], settings, data_dir, extra_opts)
if prepare_bin_only: if prepare_bin_only:
logger.info('Done.') logger.info('Done.')
return 0 return 0
for c in with_coins: for c in with_coins:
prepareDataDir(c, settings, chain, particl_wallet_mnemonic, use_containers=use_containers, tor_control_password=tor_control_password) prepareDataDir(c, settings, chain, particl_wallet_mnemonic, extra_opts)
with open(config_path, 'w') as fp: with open(config_path, 'w') as fp:
json.dump(settings, fp, indent=4) json.dump(settings, fp, indent=4)
if particl_wallet_mnemonic == 'none': if particl_wallet_mnemonic in ('none', 'auto'):
logger.info('Done.') logger.info('Done.')
return 0 return 0
logger.info('Loading Particl mnemonic') initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy)
print('Done.')
particl_settings = settings['chainclients']['particl']
partRpc = make_rpc_func(particl_settings['bindir'], particl_settings['datadir'], chain)
daemons = []
daemon_args = ['-noconnect', '-nodnsseed']
if not use_tor_proxy:
# Cannot set -bind or -whitebind together with -listen=0
daemon_args.append('-nolisten')
daemons.append(startDaemon(particl_settings['datadir'], particl_settings['bindir'], cfg.PARTICLD, daemon_args + ['-nofindpeers', '-nostaking']))
try:
waitForRPC(partRpc)
if particl_wallet_mnemonic is None:
particl_wallet_mnemonic = partRpc('mnemonic new')['mnemonic']
partRpc('extkeyimportmaster "{}"'.format(particl_wallet_mnemonic))
# Initialise wallets
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
swap_client = BasicSwap(fp, data_dir, settings, chain)
swap_client.setCoinConnectParams(Coins.PART)
swap_client.setDaemonPID(Coins.PART, daemons[-1].pid)
swap_client.setCoinRunParams(Coins.PART)
swap_client.createCoinInterface(Coins.PART)
for coin_name in with_coins:
coin_settings = settings['chainclients'][coin_name]
c = swap_client.getCoinIdFromName(coin_name)
if c == Coins.PART:
continue
swap_client.setCoinConnectParams(c)
if c == Coins.XMR:
if not coin_settings['manage_wallet_daemon']:
continue
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], 'monero-wallet-rpc'))
else:
if not coin_settings['manage_daemon']:
continue
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].pid)
swap_client.setCoinRunParams(c)
swap_client.createCoinInterface(c)
swap_client.waitForDaemonRPC(c)
swap_client.initialiseWallet(c)
swap_client.finalise()
del swap_client
finally:
for d in daemons:
logging.info('Interrupting {}'.format(d.pid))
d.send_signal(signal.SIGINT)
d.wait(timeout=120)
for fp in (d.stdout, d.stderr, d.stdin):
if fp:
fp.close()
logger.info('IMPORTANT - Save your particl wallet recovery phrase:\n{}\n'.format(particl_wallet_mnemonic))
logger.info('Done.')
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -19,6 +19,7 @@ import basicswap.config as cfg
from basicswap import __version__ from basicswap import __version__
from basicswap.basicswap import BasicSwap from basicswap.basicswap import BasicSwap
from basicswap.http_server import HttpThread from basicswap.http_server import HttpThread
from basicswap.contrib.websocket_server import WebsocketServer
logger = logging.getLogger() logger = logging.getLogger()
@@ -93,6 +94,25 @@ def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir) return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir)
def ws_new_client(client, server):
if swap_client:
swap_client.log.debug(f'ws_new_client {client["id"]}')
def ws_client_left(client, server):
if client is None:
return
if swap_client:
swap_client.log.debug(f'ws_client_left {client["id"]}')
def ws_message_received(client, server, message):
if len(message) > 200:
message = message[:200] + '..'
if swap_client:
swap_client.log.debug(f'ws_message_received {client["id"]} {message}')
def runClient(fp, data_dir, chain): def runClient(fp, data_dir, chain):
global swap_client global swap_client
settings_path = os.path.join(data_dir, cfg.CONFIG_FILENAME) settings_path = os.path.join(data_dir, cfg.CONFIG_FILENAME)
@@ -158,24 +178,45 @@ def runClient(fp, data_dir, chain):
swap_client.start() swap_client.start()
if 'htmlhost' in settings: if 'htmlhost' in settings:
swap_client.log.info('Starting server at http://%s:%d.' % (settings['htmlhost'], settings['htmlport'])) swap_client.log.info('Starting http server at http://%s:%d.' % (settings['htmlhost'], settings['htmlport']))
allow_cors = settings['allowcors'] if 'allowcors' in settings else cfg.DEFAULT_ALLOW_CORS allow_cors = settings['allowcors'] if 'allowcors' in settings else cfg.DEFAULT_ALLOW_CORS
tS1 = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client) thread_http = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client)
threads.append(tS1) threads.append(thread_http)
tS1.start() thread_http.start()
if 'wshost' in settings:
ws_url = 'ws://{}:{}'.format(settings['wshost'], settings['wsport'])
swap_client.log.info(f'Starting ws server at {ws_url}.')
swap_client.ws_server = WebsocketServer(host=settings['wshost'], port=settings['wsport'])
swap_client.ws_server.set_fn_new_client(ws_new_client)
swap_client.ws_server.set_fn_client_left(ws_client_left)
swap_client.ws_server.set_fn_message_received(ws_message_received)
swap_client.ws_server.run_forever(threaded=True)
logger.info('Exit with Ctrl + c.') logger.info('Exit with Ctrl + c.')
while swap_client.is_running: while swap_client.is_running:
time.sleep(0.5) time.sleep(0.5)
swap_client.update() swap_client.update()
except Exception as ex:
traceback.print_exc()
if swap_client.ws_server:
try:
swap_client.log.info('Stopping websocket server.')
swap_client.ws_server.shutdown_gracefully()
except Exception as ex: except Exception as ex:
traceback.print_exc() traceback.print_exc()
swap_client.finalise() swap_client.finalise()
swap_client.log.info('Stopping HTTP threads.') swap_client.log.info('Stopping HTTP threads.')
for t in threads: for t in threads:
try:
t.stop() t.stop()
t.join() t.join()
except Exception as ex:
traceback.print_exc()
closed_pids = [] closed_pids = []
for d in daemons: for d in daemons:
@@ -216,7 +257,7 @@ def printHelp():
logger.info('Usage: basicswap-run ') logger.info('Usage: basicswap-run ')
logger.info('\n--help, -h Print help.') logger.info('\n--help, -h Print help.')
logger.info('--version, -v Print version.') logger.info('--version, -v Print version.')
logger.info('--datadir=PATH Path to basicswap data directory, default:{}.'.format(cfg.DEFAULT_DATADIR)) logger.info('--datadir=PATH Path to basicswap data directory, default:{}.'.format(cfg.BASICSWAP_DATADIR))
logger.info('--mainnet Run in mainnet mode.') logger.info('--mainnet Run in mainnet mode.')
logger.info('--testnet Run in testnet mode.') logger.info('--testnet Run in testnet mode.')
logger.info('--regtest Run in regtest mode.') logger.info('--regtest Run in regtest mode.')
@@ -260,7 +301,7 @@ def main():
logger.warning('Unknown argument %s', v) logger.warning('Unknown argument %s', v)
if data_dir is None: if data_dir is None:
data_dir = os.path.join(os.path.expanduser(cfg.DEFAULT_DATADIR)) data_dir = os.path.join(os.path.expanduser(cfg.BASICSWAP_DATADIR))
logger.info('Using datadir: %s', data_dir) logger.info('Using datadir: %s', data_dir)
logger.info('Chain: %s', chain) logger.info('Chain: %s', chain)

43
bin/install_certifi.py Executable file
View File

@@ -0,0 +1,43 @@
# install_certifi.py
#
# sample script to install or update a set of default Root Certificates
# for the ssl module. Uses the certificates provided by the certifi package:
# https://pypi.org/project/certifi/
import os
import os.path
import ssl
import stat
import subprocess
import sys
STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
| stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
| stat.S_IROTH | stat.S_IXOTH )
def main():
openssl_dir, openssl_cafile = os.path.split(
ssl.get_default_verify_paths().openssl_cafile)
print(" -- pip install --upgrade certifi")
subprocess.check_call([sys.executable,
"-E", "-s", "-m", "pip", "install", "--upgrade", "certifi"])
import certifi
# change working directory to the default SSL directory
os.chdir(openssl_dir)
relpath_to_certifi_cafile = os.path.relpath(certifi.where())
print(" -- removing any existing file or link")
try:
os.remove(openssl_cafile)
except FileNotFoundError:
pass
print(" -- creating symlink to certifi certificate bundle")
os.symlink(relpath_to_certifi_cafile, openssl_cafile)
print(" -- setting permissions")
os.chmod(openssl_cafile, STAT_0o775)
print(" -- update complete")
if __name__ == '__main__':
main()

View File

@@ -1,35 +1,74 @@
## Source code ## Source code
$ git clone https://github.com/tecnovert/basicswap.git git clone https://github.com/tecnovert/basicswap.git
## Run Using Docker ## Run Using Docker
Install dependencies:
apt-get curl jq
Docker must be installed and started: Docker must be installed and started:
$ docker -v docker -v
Should return a line containing `Docker version`... Should return a line containing `Docker version`...
To install docker engine on your platform see:
https://docs.docker.com/engine/install/#server
It's recommended to setup docker to work without sudo.<br>
Without this step you will need to preface each `docker-compose` command with `sudo`:
https://docs.docker.com/engine/install/linux-postinstall/
#### Create the images: #### Create the images:
$ cd basicswap/docker COINDATA_PATH can be set to your preferance but must be exported each time you launch Basicswap.<br>
$ docker-compose build Consider adding COINDATA_PATH to the `.env` file in the docker directory file so it's always set.
export COINDATA_PATH=/var/data/coinswaps
cd basicswap/docker
docker-compose build
#### Prepare the datadir: #### Prepare the datadir:
Set XMR_RPC_HOST and BASE_XMR_RPC_PORT to a public XMR node or exclude to run a local node.
Set xmrrestoreheight to the current xmr chain height. Set xmrrestoreheight to the current xmr chain height.
CURRENT_XMR_HEIGHT=$(curl https://localmonero.co/blocks/api/get_stats | jq .height)
Adjust `--withcoins` and `--withoutcoins` as desired, eg: `--withcoins=monero,bitcoin`. By default only Particl is loaded. Adjust `--withcoins` and `--withoutcoins` as desired, eg: `--withcoins=monero,bitcoin`. By default only Particl is loaded.
$ export COINDATA_PATH=/var/data/coinswaps ##### FastSync
$ docker run --rm -e XMR_RPC_HOST="node.xmr.to" -e BASE_XMR_RPC_PORT=18081 -t --name swap_prepare -v $COINDATA_PATH:/coindata i_swapclient \
basicswap-prepare --datadir=/coindata --withcoins=monero --htmlhost="0.0.0.0" --xmrrestoreheight=2485205 Append `--usebtcfastsync` to the below command to optionally initialise the Bitcoin datadir with a chain snapshot from btcpayserver FastSync.<br>
[FastSync README.md](https://github.com/btcpayserver/btcpayserver-docker/blob/master/contrib/FastSync/README.md)
Setup with a local Monero daemon (recommended):
export COINDATA_PATH=/var/data/coinswaps
docker run --rm -t --name swap_prepare -v $COINDATA_PATH:/coindata i_swapclient basicswap-prepare --datadir=/coindata --withcoins=monero --htmlhost="0.0.0.0" --wshost="0.0.0.0" --xmrrestoreheight=$CURRENT_XMR_HEIGHT
To instead use Monero public nodes and not run a local Monero daemon<br>(it can be difficult to find reliable public nodes):
Set XMR_RPC_HOST and BASE_XMR_RPC_PORT to a public XMR node.
export COINDATA_PATH=/var/data/coinswaps
docker run --rm -e XMR_RPC_HOST="node.xmr.to" -e BASE_XMR_RPC_PORT=18081 -t --name swap_prepare -v $COINDATA_PATH:/coindata i_swapclient basicswap-prepare --datadir=/coindata --withcoins=monero --htmlhost="0.0.0.0" --wshost="0.0.0.0" --xmrrestoreheight=$CURRENT_XMR_HEIGHT
**Record the mnemonic from the output of the above command.** **Record the mnemonic from the output of the above command.**
And the output of `echo $CURRENT_XMR_HEIGHT` for use if you need to later restore your wallet.
#### Set the timezone (optional): #### Set the timezone (optional):
@@ -40,8 +79,8 @@ Valid options can be listed with: `timedatectl list-timezones`
#### Start the container: #### Start the container:
$ export COINDATA_PATH=/var/data/coinswaps export COINDATA_PATH=/var/data/coinswaps
$ docker-compose up docker-compose up
Open in browser: `http://localhost:12700` Open in browser: `http://localhost:12700`
@@ -49,9 +88,9 @@ Open in browser: `http://localhost:12700`
### Add a coin ### Add a coin
$ docker-compose stop docker-compose stop
$ export COINDATA_PATH=/var/data/coinswaps export COINDATA_PATH=/var/data/coinswaps
$ docker run --rm -t --name swap_prepare -v $COINDATA_PATH:/coindata i_swapclient basicswap-prepare --datadir=/coindata --addcoin=bitcoin docker run --rm -t --name swap_prepare -v $COINDATA_PATH:/coindata i_swapclient basicswap-prepare --datadir=/coindata --addcoin=bitcoin
You can copy an existing pruned datadir (excluding bitcoin.conf and any wallets) over to `$COINDATA_PATH/bitcoin` You can copy an existing pruned datadir (excluding bitcoin.conf and any wallets) over to `$COINDATA_PATH/bitcoin`
Remove any existing wallets after copying over a pruned chain or the Bitcoin daemon won't start. Remove any existing wallets after copying over a pruned chain or the Bitcoin daemon won't start.
@@ -70,14 +109,14 @@ Windows key + R -> "wsl" -> Enter
Install Git: Install Git:
$ sudo apt update sudo apt update
$ sudo apt install git sudo apt install git
Download the BasicSwap code: Download the BasicSwap code:
$ git clone https://github.com/tecnovert/basicswap.git git clone https://github.com/tecnovert/basicswap.git
$ cd basicswap/docker/ cd basicswap/docker/
It's significantly faster to set COINDATA_PATH in the linux filesystem. It's significantly faster to set COINDATA_PATH in the linux filesystem.
@@ -91,61 +130,78 @@ Continue from the [Run Using Docker](#run-using-docker) section.
### Ubuntu Setup: ### Ubuntu Setup:
$ apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config apt-get install -y wget python3-pip gnupg unzip protobuf-compiler automake libtool pkg-config curl jq
### OSX Setup: ### OSX Setup:
Install Homebrew: Install Homebrew (See https://brew.sh/):
https://brew.sh/ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Command Line Tools:
$ xcode-select --install
Dependencies: Dependencies:
$ brew install wget unzip python git protobuf gnupg automake libtool pkg-config brew install wget unzip python git protobuf gnupg automake libtool pkg-config curl jq
Close the terminal and open a new one to update the python symlinks.
### Basicswap: ### Basicswap:
$ export SWAP_DATADIR=/Users/$USER/coinswaps export SWAP_DATADIR=/Users/$USER/coinswaps
$ mkdir -p "$SWAP_DATADIR/venv" mkdir -p "$SWAP_DATADIR/venv"
$ python3 -m venv "$SWAP_DATADIR/venv" python3 -m venv "$SWAP_DATADIR/venv"
$ . $SWAP_DATADIR/venv/bin/activate && python -V . $SWAP_DATADIR/venv/bin/activate && python -V
$ cd $SWAP_DATADIR cd $SWAP_DATADIR
$ wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/anonswap.zip wget -O coincurve-anonswap.zip https://github.com/tecnovert/coincurve/archive/refs/tags/anonswap_v0.1.zip
$ unzip coincurve-anonswap.zip unzip -d coincurve-anonswap coincurve-anonswap.zip
$ cd $SWAP_DATADIR/coincurve-anonswap mv ./coincurve-anonswap/*/{.,}* ./coincurve-anonswap || true
$ pip3 install . cd $SWAP_DATADIR/coincurve-anonswap
pip3 install .
$ cd $SWAP_DATADIR cd $SWAP_DATADIR
$ git clone https://github.com/tecnovert/basicswap.git git clone https://github.com/tecnovert/basicswap.git
$ cd $SWAP_DATADIR/basicswap cd $SWAP_DATADIR/basicswap
$ protoc -I=basicswap --python_out=basicswap basicswap/messages.proto
$ pip3 install . If installed on OSX, you may need to install additional root ssl certificates for the ssl module.
From https://pypi.org/project/certifi/
sudo python3 bin/install_certifi.py
Continue installing Basicswap
protoc -I=basicswap --python_out=basicswap basicswap/messages.proto
pip3 install .
Prepare the datadir: Prepare the datadir:
XMR_RPC_HOST="node.xmr.to" BASE_XMR_RPC_PORT=18081 basicswap-prepare --datadir=$SWAP_DATADIR --withcoins=monero --xmrrestoreheight=2245107 CURRENT_XMR_HEIGHT=$(curl https://localmonero.co/blocks/api/get_stats | jq .height)
XMR_RPC_HOST="node.xmr.to" BASE_XMR_RPC_PORT=18081 basicswap-prepare --datadir=$SWAP_DATADIR --withcoins=monero --xmrrestoreheight=$CURRENT_XMR_HEIGHT
OR using a local XMR daemon: OR using a local XMR daemon:
basicswap-prepare --datadir=$SWAP_DATADIR --withcoins=monero --xmrrestoreheight=2245107 basicswap-prepare --datadir=$SWAP_DATADIR --withcoins=monero --xmrrestoreheight=$CURRENT_XMR_HEIGHT
Record the mnemonic from the output of the above command. Record the mnemonic from the output of the above command.
Start the app Start Basicswap:
$ basicswap-run --datadir=$SWAP_DATADIR basicswap-run --datadir=$SWAP_DATADIR
Open in browser: `http://localhost:12700` Open in browser: `http://localhost:12700`
It may take a few minutes to start as the coin daemons are started before the http interface. It may take a few minutes to start as the coin daemons are started before the http interface.
Add a coin (Stop basicswap first):
export SWAP_DATADIR=/Users/$USER/coinswaps
basicswap-prepare --usebtcfastsync --datadir=/$SWAP_DATADIR --addcoin=bitcoin
Start after installed: Start after installed:
$ export SWAP_DATADIR=/Users/$USER/coinswaps export SWAP_DATADIR=/Users/$USER/coinswaps
$ . $SWAP_DATADIR/venv/bin/activate && python -V . $SWAP_DATADIR/venv/bin/activate && python -V
$ basicswap-run --datadir=$SWAP_DATADIR basicswap-run --datadir=$SWAP_DATADIR

View File

@@ -0,0 +1 @@
*.svg

View File

@@ -0,0 +1,66 @@
xu {
hscale = "1.2";
CB [label=" ", linecolor="transparent"],
N [label="Network", linecolor="#008800", textbgcolor="#CCFFCC", arclinecolor="#008800"],
O [label="Offerer", linecolor="#FF0000", textbgcolor="#FFCCCC", arclinecolor="#FF0000"],
B [label="Bidder", linecolor="#0000FF", textbgcolor="#CCCCFF", arclinecolor="#0000FF"],
C [label=" ", linecolor="transparent"], C2 [label=" ", linecolor="transparent"];
O =>> N [label="Sends Offer"];
N >> B [label="Detects Offer"];
B =>> O [label="Sends Bid"];
B abox B [label="Bid Sent"];
O box O [label="User accepts bid"];
O =>> N [label="Sends Initiate Tx"],
C note C2
[label="Offerer generates secret_value and sends Hash(secret_value) to the Bidder",
textbgcolor="#FFFFCC"];
O =>> B [label="Sends BidAccept"],
C note C2
[label="ITX can be spent by
knowledge of the secret_value and the bidder_redeem_key or
after a timeout by the offerer_refund_key",
textbgcolor="#FFFFCC"];
B abox B [label="Bid Accepted"];
N >> B [label="Detects Initiate Tx"];
B abox B [label="ITX Sent", textbgcolor="#4bdbf1"];
B => B [label="Wait for ITX to confirm"], O => O [label="Wait for ITX to confirm"];
B abox B [label="Bid Initiated"];
B abox B [label="ITX Confirmed", textbgcolor="#4bdbf1"];
B =>> N [label="Sends Participate Tx"],
C note C2
[label="PTX can be spent by
knowledge of the secret_value and the offerer_redeem_key or
after a timeout by the bidder_refund_key",
textbgcolor="#FFFFCC"];
B abox B [label="PTX Sent", textbgcolor="#f1db4b"];
N >> O [label="Detects Participate Tx"];
B => B [label="Wait for PTX to confirm"], O => O [label="Wait for PTX to confirm"];
B abox B [label="PTX Confirmed", textbgcolor="#f1db4b"];
B abox B [label="Bid Participating"];
CB alt C [label="success path"] {
O =>> N [label="Sends Participate Redeem Tx"],
C note C2
[label="Reveals secret_value",
textbgcolor="#FFFFCC"];
N >> B [label="Detects Participate Redeem Tx"];
B abox B [label="PTX Redeemed", textbgcolor="#f1db4b"];
B =>> N [label="Sends Initiate Redeem Tx"];
B => B [label="Wait for ITX Redeem to confirm"];
B abox B [label="ITX Redeemed", textbgcolor="#4bdbf1"];
B abox B [label="Bid Completed"];
--- [label="fail path"];
CB alt C [label="offerer may reclaim ITX"] {
O => O [label="Wait for ITX locktime to expire"];
O =>> N [label="ITX Refund Tx"];
N >> B [label="Detects Initiate Tx refund Tx"];
B abox B [label="ITX Refunded", textbgcolor="#4bdbf1"];
};
B => B [label="Wait for PTX locktime to expire"];
B =>> N [label="PTX Refund Tx"];
B => B [label="Wait for PTX Refund to confirm"];
B abox B [label="PTX Refunded", textbgcolor="#f1db4b"];
B abox B [label="Bid Completed"];
};
}

View File

@@ -0,0 +1,69 @@
xu {
hscale = "1.2";
CB [label=" ", linecolor="transparent"],
N [label="Network", linecolor="#008800", textbgcolor="#CCFFCC", arclinecolor="#008800"],
O [label="Offerer", linecolor="#FF0000", textbgcolor="#FFCCCC", arclinecolor="#FF0000"],
B [label="Bidder", linecolor="#0000FF", textbgcolor="#CCCCFF", arclinecolor="#0000FF"],
C [label=" ", linecolor="transparent"], C2 [label=" ", linecolor="transparent"];
O =>> N [label="Sends Offer"];
N >> B [label="Detects Offer"];
B =>> O [label="Sends Bid"];
O abox O [label="Bid Received"];
O box O [label="User accepts bid"];
O =>> N [label="Sends Initiate Tx"],
C note C2
[label="Offerer generates secret_value and sends Hash(secret_value) to the Bidder",
textbgcolor="#FFFFCC"];
O =>> B [label="Sends BidAccept"],
C note C2
[label="ITX can be spent by
knowledge of the secret_value and the bidder_redeem_key or
after a timeout by the offerer_refund_key",
textbgcolor="#FFFFCC"];
O abox O [label="Bid Accepted"];
O abox O [label="ITX Sent", textbgcolor="#4bdbf1"];
N >> B [label="Detects Initiate Tx"];
B => B [label="Wait for ITX to confirm"], O => O [label="Wait for ITX to confirm"];
O abox O [label="Bid Initiated"];
O abox O [label="ITX Confirmed"];
CB alt C [label="success path"] {
B =>> N [label="Sends Participate Tx"],
C note C2
[label="PTX can be spent by
knowledge of the secret_value and the offerer_redeem_key or
after a timeout by the bidder_refund_key",
textbgcolor="#FFFFCC"];
N >> O [label="Detects Participate Tx"];
O abox O [label="PTX Sent", textbgcolor="#f1db4b"];
O => O [label="Wait for PTX to confirm"];
O abox O [label="PTX Confirmed", textbgcolor="#f1db4b"];
O abox O [label="Bid Participating"];
O =>> N [label="Sends Participate Redeem Tx"],
C note C2
[label="Reveals secret_value",
textbgcolor="#FFFFCC"];
N >> B [label="Detects Participate Redeem Tx"];
O abox O [label="PTX Redeemed", textbgcolor="#f1db4b"];
CB alt C [label="success path"] {
B =>> N [label="Sends Initiate Redeem Tx"];
O => O [label="Wait for ITX Redeem to confirm"];
O abox O [label="ITX Redeemed", textbgcolor="#4bdbf1"];
O abox O [label="Bid Completed"];
--- [label="fail path, offerer refunds ITx, bidder loses"];
O => O [label="Wait for ITX locktime to expire"];
O =>> N [label="ITX Refund Tx"];
O => O [label="Wait for ITX Refund to confirm"];
O abox O [label="ITX Refunded", textbgcolor="#4bdbf1"];
O abox O [label="Bid Completed"];
};
--- [label="fail path"];
O => O [label="Wait for ITX locktime to expire"];
O =>> N [label="ITX Refund Tx"];
O => O [label="Wait for ITX Refund to confirm"];
O abox O [label="ITX Refunded", textbgcolor="#4bdbf1"];
O abox O [label="Bid Completed"];
};
}

View File

@@ -0,0 +1,81 @@
xu {
hscale="1.3", wordwraparcs=on;
CB [label=" ", linecolor="transparent"],
N [label="Network", linecolor="#008800", textbgcolor="#CCFFCC", arclinecolor="#008800"],
O [label="Offerer", linecolor="#FF0000", textbgcolor="#FFCCCC", arclinecolor="#FF0000"],
B [label="Bidder", linecolor="#0000FF", textbgcolor="#CCCCFF", arclinecolor="#0000FF"],
C [label=" ", linecolor="transparent"], C2 [label=" ", linecolor="transparent"];
O =>> N [label="Sends Offer"];
N >> B [label="Detects Offer"];
B =>> O [label="Sends Bid"];
B abox B [label="Bid Sent"];
O box O [label="User accepts bid"];
O =>> B [label="Sends BidAccept message"],
C note C2
[label="The BidAccept message contains the pubkeys the offerer will use and a DLEAG proof one key will work across both chains of the swapping coins",
textbgcolor="#FFFFCC"];
B abox B [label="Bid Receiving accept"];
B abox B [label="Bid Accepted"];
B =>> O [label="Sends XmrBidLockTxSigsMessage"],
C note C2
[label="The XmrBidLockTxSigsMessage contains the bidder's signatures for the script-coin-lock-refund and script-coin-lock-refund-spend txns.",
textbgcolor="#FFFFCC"];
B abox B [label="Exchanged script lock tx sigs msg"];
O =>> B [label="Sends XmrBidLockSpendTxMessage"],
C note C2
[label="The XmrBidLockSpendTxMessage contains the script-coin-lock-tx and the offerer's signature for it.",
textbgcolor="#FFFFCC"];
O =>> N [label="Sends script-coin-lock-tx"],
B abox B [label="Bid Script coin spend tx valid"];
B abox B [label="Exchanged script lock spend tx msg"];
B => B [label="Wait for script-coin-lock-tx to confirm"];
B abox B [label="Bid Script coin locked"];
# Bidder would only send noscript-coin-lock-tx if script-coin-lock-tx validates
B =>> N [label="Sends noscript-coin-lock-tx"];
B => B [label="Wait for noscript-coin-lock-tx to confirm"], O => O [label="Wait for noscript-coin-lock-tx to confirm"];
B abox B [label="Bid Scriptless coin locked"];
CB alt C [label="success path"] {
O => B [label="Sends script-coin-lock-tx release message"],
C note C2
[label="The XmrBidLockReleaseMessage contains the offerer's OTVES for it.
The bidder decodes the offerer's signature from the OTVES.
When the offerer has the plaintext signature, they can decode the bidder's noscript-coin-lock-tx signature.",
textbgcolor="#FFFFCC"];
B abox B [label="Script coin lock released"];
B =>> N [label="Sends script-coin-lock-spend-tx"];
B abox B [label="Script tx redeemed"];
B abox B [label="Bid Completed"];
--- [label="fail path"];
|||;
B => B [label="Wait for script-coin-lock-tx lock to expire"];
O =>> N [label="Sends script-coin-lock-pre-refund-tx"],
C note C2
[label="tx can be sent by either party.",
textbgcolor="#FFFFCC"];
N >> O [label="script-coin-lock-pre-refund-tx"];
B abox B [label="Bid Script pre-refund tx in chain"];
CB alt C [label="offerer refunds script coin lock tx"] {
|||;
O => O [label="Wait for pre-refund tx to confirm"];
O =>> N [label="Sends script-coin-lock-pre-refund-spend-tx"],
C note C2
[label="Refunds the script lock tx, with the offerer's cleartext signature the bidder can refund the noscript lock tx.
Once the lock expires the pre-refund tx can be spent by the bidder.",
textbgcolor="#FFFFCC"];
O abox O [label="Bid Failed, refunded"];
N >> B [label="Detects script-coin-lock-pre-refund-spend-tx"],
C note C2
[label="Bidder recovers the offerer's scriptless chain key-shard.",
textbgcolor="#FFFFCC"];
B =>> N [label="Sends scriptless-coin-lock-recover-tx"];
B abox B [label="Bid Scriptless tx recovered"];
B abox B [label="Bid Failed, refunded"];
--- [label="bidder swipes script coin lock tx"];
|||;
B => B [label="Wait for pre-refund tx lock to expire"];
B =>> N [label="Sends script-coin-lock-pre-refund-swipe-tx"];
B abox B [label="Bid Failed, swiped"];
};
};
}

View File

@@ -0,0 +1,76 @@
xu {
hscale="1.3", wordwraparcs=on;
CB [label=" ", linecolor="transparent"],
N [label="Network", linecolor="#008800", textbgcolor="#CCFFCC", arclinecolor="#008800"],
O [label="Offerer", linecolor="#FF0000", textbgcolor="#FFCCCC", arclinecolor="#FF0000"],
B [label="Bidder", linecolor="#0000FF", textbgcolor="#CCCCFF", arclinecolor="#0000FF"],
C [label=" ", linecolor="transparent"], C2 [label=" ", linecolor="transparent"];
O =>> N [label="Sends Offer"];
N >> B [label="Detects Offer"];
B =>> O [label="Sends Bid"];
O abox O [label="Bid Receiving"];
O abox O [label="Bid Received"];
O box O [label="User accepts bid"];
O =>> B [label="Sends BidAccept message"],
C note C2
[label="The BidAccept message contains the pubkeys the offerer will use and a DLEAG proof one key will work across both chains of the swapping coins",
textbgcolor="#FFFFCC"];
O abox O [label="Bid Accepted"];
B =>> O [label="Sends XmrBidLockTxSigsMessage"],
C note C2
[label="The XmrBidLockTxSigsMessage contains the bidder's signatures for the script-coin-lock-refund and script-coin-lock-refund-spend txns.",
textbgcolor="#FFFFCC"];
O abox O [label="Exchanged script lock tx sigs msg"];
O =>> N [label="Sends script-coin-lock-tx"];
O abox O [label="Bid Script coin spend tx valid"];
O =>> B [label="Sends XmrBidLockSpendTxMessage"],
C note C2
[label="The XmrBidLockSpendTxMessage contains the script-coin-lock-tx and the offerer's signature for it.",
textbgcolor="#FFFFCC"];
O abox O [label="Exchanged script lock spend tx msg"];
|||;
B => B [label="Wait for script-coin-lock-tx to confirm"], O => O [label="Wait for script-coin-lock-tx to confirm"];
O abox O [label="Bid Script coin locked"];
CB alt C [label="success path"] {
B =>> N [label="Sends noscript-coin-lock-tx"];
|||;
O => O [label="Wait for noscript-coin-lock-tx to confirm"];
O abox O [label="Bid Scriptless coin locked"];
O => B [label="Sends script-coin-lock-tx release message"],
C note C2
[label="The XmrBidLockReleaseMessage contains the offerer's OTVES for the script-coin-lock-tx.
The bidder decodes the offerer's signature from the OTVES.
When the offerer has the plaintext signature, they can decode the bidder's key for the noscript-lock-tx.",
textbgcolor="#FFFFCC"];
O abox O [label="Bid Script coin lock released"];
B =>> N [label="Sends script-coin-lock-spend-tx"];
N >> O [label="Detects script-coin-lock-spend-tx"];
O abox O [label="Bid Script tx redeemed"],
C note C2
[label="The offerer extracts the bidder's plaintext signature and derives the bidder's noscript-lock-tx keyhalf.",
textbgcolor="#FFFFCC"];
O =>> N [label="Sends noscript-coin-lock-spend-tx"];
O abox O [label="Bid Scriptless tx redeemed"];
|||;
O => O [label="Wait for noscript-coin-lock-spend-tx to confirm"];
O abox O [label="Bid Completed"];
--- [label="fail path"];
|||;
O => O [label="Wait for script-coin-lock-tx locktime to expire"];
O =>> N [label="Sends script-coin-lock-pre-refund-tx"],
C note C2
[label="tx can be sent by either party.",
textbgcolor="#FFFFCC"];
N >> O [label="script-coin-lock-pre-refund-tx"];
O abox O [label="Bid Script pre-refund tx in chain"];
|||;
O => O [label="Wait for pre-refund tx to confirm"];
O =>> N [label="Sends script-coin-lock-pre-refund-spend-tx"],
C note C2
[label="Refunds the script lock tx, with the offerer's cleartext signature the bidder can refund the noscript lock tx.",
textbgcolor="#FFFFCC"];
O abox O [label="Bid Failed, refunded"];
};
}

View File

@@ -7,16 +7,16 @@ Update only the code:
basicswap]$ git pull basicswap]$ git pull
$ cd docker $ cd docker
$ docker-compose build
$ export COINDATA_PATH=[PATH_TO] $ export COINDATA_PATH=[PATH_TO]
$ docker-compose build
$ docker-compose up $ docker-compose up
If the dependencies have changed the container must be built with `--no-cache`: If the dependencies have changed the container must be built with `--no-cache`:
basicswap]$ git pull basicswap]$ git pull
$ cd docker $ cd docker
$ docker-compose build --no-cache
$ export COINDATA_PATH=[PATH_TO] $ export COINDATA_PATH=[PATH_TO]
$ docker-compose build --no-cache
$ docker-compose up $ docker-compose up

View File

@@ -1,3 +1,4 @@
HTML_PORT=127.0.0.1:12700:12700 HTML_PORT=127.0.0.1:12700:12700
WS_PORT=127.0.0.1:11700:11700
#COINDATA_PATH=/var/data/coinswaps #COINDATA_PATH=/var/data/coinswaps
TZ=UTC TZ=UTC

View File

@@ -9,6 +9,7 @@ services:
- ${COINDATA_PATH}:/coindata - ${COINDATA_PATH}:/coindata
ports: ports:
- "${HTML_PORT}" # Expose only to localhost, see .env - "${HTML_PORT}" # Expose only to localhost, see .env
- "${WS_PORT}" # Expose only to localhost, see .env
environment: environment:
- TZ - TZ
logging: logging:

View File

@@ -11,6 +11,7 @@ services:
- ${COINDATA_PATH}:/coindata - ${COINDATA_PATH}:/coindata
ports: ports:
- "${HTML_PORT}" # Expose only to localhost, see .env - "${HTML_PORT}" # Expose only to localhost, see .env
- "${WS_PORT}" # Expose only to localhost, see .env
environment: environment:
- TZ - TZ
- TOR_PROXY_HOST - TOR_PROXY_HOST

View File

@@ -1,22 +0,0 @@
HTML_PORT=127.0.0.1:12700:12700
TZ=UTC
DATA_PATH=/mnt/hdd50/docker2
PART_RPC_HOST=particl_core
LTC_RPC_HOST=litecoin_core
BTC_RPC_HOST=bitcoin_core
PART_RPC_USER=particl_user
PART_RPC_PWD=particl_pwd
BTC_RPC_USER=bitcoin_user
BTC_RPC_PWD=bitcoin_pwd
LTC_RPC_USER=litecoin_user
LTC_RPC_PWD=litecoin_pwd
PART_DATA_DIR=/data/particl
LTC_DATA_DIR=/data/litecoin
BTC_DATA_DIR=/data/bitcoin
XMR_DATA_DIR=/data/monero_daemon
XMR_WALLETS_DIR=/data/monero_wallet
COINS_RPCBIND_IP=0.0.0.0

View File

@@ -1 +1,3 @@
.env .env
docker-compose-prepare.yml
docker-compose.yml

View File

@@ -5,7 +5,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoin --withoutcoins=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=bitcoin --withoutcoins=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:buster-slim FROM debian:bullseye-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV BITCOIN_DATA /data ENV BITCOIN_DATA /data

View File

@@ -0,0 +1,23 @@
version: '3.3'
networks:
default:
name: coinswap_network
services:
particl_core:
image: i_particl
build:
context: particl
dockerfile: Dockerfile
container_name: particl_core
volumes:
- ${DATA_PATH}/particl:/data
expose:
- ${PART_RPC_PORT}
- ${PART_ZMQ_PORT}
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
restart: unless-stopped

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
tor:
image: i_tor
container_name: tor
build:
context: ./tor
volumes:
- ${DATA_PATH}/tor/data:/var/lib/tor/
- ${DATA_PATH}/tor/torrc:/etc/tor/torrc
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
networks:
tor_net:
coinswap_network: 172.16.238.200

View File

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

View File

@@ -0,0 +1,21 @@
swapclient:
image: i_swapclient
build:
context: swapclient
dockerfile: Dockerfile
container_name: swapclient
volumes:
- ${DATA_PATH}/swapclient:/data
ports:
- "${HTML_PORT}" # Expose only to localhost, see .env
- "${WS_PORT}" # Expose only to localhost, see .env
environment:
- TZ
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
depends_on:
- particl_core
restart: unless-stopped

View File

@@ -0,0 +1,45 @@
swapprepare:
image: i_swapclient
build:
context: swapclient
dockerfile: Dockerfile
container_name: swapprepare
volumes:
- ${DATA_PATH}/swapclient:/data/swapclient
- ${DATA_PATH}/monero_daemon:/data/monero_daemon
- ${DATA_PATH}/monero_wallet:/data/monero_wallet
- ${DATA_PATH}/particl:/data/particl
- ${DATA_PATH}/bitcoin:/data/bitcoin
- ${DATA_PATH}/litecoin:/data/litecoin
environment:
- TZ
- UI_HTML_PORT
- COINS_RPCBIND_IP
- BASICSWAP_DATADIR
- PART_DATA_DIR
- PART_RPC_HOST
- PART_ZMQ_PORT
- PART_RPC_USER
- PART_RPC_PWD
- PART_RPC_PORT
- BTC_DATA_DIR
- BTC_RPC_HOST
- BTC_RPC_PORT
- BTC_RPC_USER
- BTC_RPC_PWD
- LTC_DATA_DIR
- LTC_RPC_HOST
- LTC_RPC_PORT
- LTC_RPC_USER
- LTC_RPC_PWD
- XMR_DATA_DIR
- XMR_RPC_HOST
- BASE_XMR_RPC_PORT
- BASE_XMR_ZMQ_PORT
- XMR_WALLETS_DIR
- XMR_WALLET_RPC_HOST
- BASE_XMR_WALLET_PORT
- XMR_WALLET_RPC_USER
- XMR_WALLET_RPC_PWD
- DEFAULT_XMR_RESTORE_HEIGHT
restart: "no"

View File

@@ -1,155 +0,0 @@
version: '3.3'
services:
particl_core:
image: i_particl
build:
context: particl
dockerfile: Dockerfile
container_name: particl_core
volumes:
- ${DATA_PATH}/particl:/data
#ports:
# - "51738:51738"
expose:
- 51735
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
#restart: unless-stopped
#bitcoin_core:
#image: i_bitcoin
#build:
#context: bitcoin
#dockerfile: Dockerfile
#container_name: bitcoin_core
#volumes:
#- ${DATA_PATH}/bitcoin:/data
##ports:
## - "8333:8333"
#expose:
#- 8332
#logging:
#driver: "json-file"
#options:
#max-size: "10m"
#max-file: "3"
#restart: unless-stopped
litecoin_core:
image: i_litecoin
build:
context: litecoin
dockerfile: Dockerfile
container_name: litecoin_core
volumes:
- ${DATA_PATH}/litecoin:/data
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
#restart: unless-stopped
#monero_daemon:
#image: i_monero_daemon
#build:
#context: monero_daemon
#dockerfile: Dockerfile
#container_name: monero_daemon
#volumes:
#- ${DATA_PATH}/monero_daemon:/data
#ports:
#- "18080:18080"
#expose:
#- 8332
#logging:
#driver: "json-file"
#options:
#max-size: "10m"
#max-file: "3"
#restart: unless-stopped
#monero_wallet:
#image: i_monero_wallet
#build:
#context: monero_wallet
#dockerfile: Dockerfile
#container_name: monero_wallet
#volumes:
#- ${DATA_PATH}/monero_wallet:/data
#expose:
#- 8332
#logging:
#driver: "json-file"
#options:
#max-size: "10m"
#max-file: "3"
#restart: unless-stopped
swapclient:
image: i_swapclient
build:
context: swapclient
dockerfile: Dockerfile
container_name: swapclient
volumes:
- ${DATA_PATH}/swapclient:/data
ports:
- "${HTML_PORT}" # Expose only to localhost, see .env
environment:
- TZ
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
depends_on:
- particl_core
restart: unless-stopped
swapprepare:
image: i_swapclient
build:
context: swapclient
dockerfile: Dockerfile
container_name: swapprepare
volumes:
- ${DATA_PATH}/swapclient:/data/swapclient
- ${DATA_PATH}/monero_daemon:/data/monero_daemon
- ${DATA_PATH}/monero_wallet:/data/monero_wallet
- ${DATA_PATH}/particl:/data/particl
- ${DATA_PATH}/bitcoin:/data/bitcoin
- ${DATA_PATH}/litecoin:/data/litecoin
environment:
- TZ
- PART_RPC_HOST
- LTC_RPC_HOST
- BTC_RPC_HOST
- PART_RPC_PORT
- LTC_RPC_PORT
- BTC_RPC_PORT
- XMR_RPC_HOST
- BASE_XMR_RPC_PORT
- BASE_XMR_ZMQ_PORT
- BASE_XMR_WALLET_PORT
- XMR_WALLET_RPC_HOST
- XMR_WALLET_RPC_USER
- XMR_WALLET_RPC_PWD
- DEFAULT_XMR_RESTORE_HEIGHT
- UI_HTML_PORT
- PART_ZMQ_PORT
- PART_RPC_USER
- PART_RPC_PWD
- BTC_RPC_USER
- BTC_RPC_PWD
- LTC_RPC_USER
- LTC_RPC_PWD
- PART_DATA_DIR
- LTC_DATA_DIR
- BTC_DATA_DIR
- XMR_DATA_DIR
- XMR_WALLETS_DIR
- COINS_RPCBIND_IP
restart: "no"
networks:
default:
external:
name: coinswap_network

View File

@@ -1,21 +1,37 @@
HTML_PORT=127.0.0.1:12700:12700 HTML_PORT=127.0.0.1:12700:12700
WS_PORT=127.0.0.1:11700:11700
TZ=UTC TZ=UTC
DATA_PATH=/var/swapdata/
PART_RPC_HOST=particl_core
LTC_RPC_HOST=litecoin_core
BTC_RPC_HOST=bitcoin_core
DATA_PATH=/var/swapdata/
BASICSWAP_DATADIR=/data/swapclient
COINS_RPCBIND_IP=0.0.0.0
PART_DATA_DIR=/data/particl
PART_RPC_HOST=particl_core
PART_RPC_PORT=19792
PART_ZMQ_PORT=20792
PART_RPC_USER=particl_user PART_RPC_USER=particl_user
PART_RPC_PWD=particl_pwd PART_RPC_PWD=particl_pwd
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_USER=litecoin_user
LTC_RPC_PWD=litecoin_pwd LTC_RPC_PWD=litecoin_pwd
PART_DATA_DIR=/data/particl
LTC_DATA_DIR=/data/litecoin
BTC_DATA_DIR=/data/bitcoin BTC_DATA_DIR=/data/bitcoin
XMR_DATA_DIR=/data/monero_daemon BTC_RPC_HOST=bitcoin_core
XMR_WALLETS_DIR=/data/monero_wallet BTC_RPC_PORT=19796
BTC_RPC_USER=bitcoin_user
BTC_RPC_PWD=bitcoin_pwd
COINS_RPCBIND_IP=0.0.0.0 XMR_DATA_DIR=/data/monero_daemon
XMR_RPC_HOST=monero_daemon
BASE_XMR_RPC_PORT=29798
XMR_WALLETS_DIR=/data/monero_wallet
XMR_WALLET_RPC_HOST=monero_wallet
BASE_XMR_WALLET_PORT=29998
XMR_WALLET_RPC_USER=xmr_wallet_user
XMR_WALLET_RPC_PWD=xmr_wallet_pwd

View File

@@ -3,7 +3,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=litecoin --withoutcoin=particl && \ RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=litecoin --withoutcoin=particl && \
find /coin_bin -name *.tar.gz -delete find /coin_bin -name *.tar.gz -delete
FROM debian:buster-slim FROM debian:bullseye-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
ENV LITECOIN_DATA /data ENV LITECOIN_DATA /data

View File

@@ -2,7 +2,7 @@ FROM i_swapclient as install_stage
RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=monero --withoutcoins=particl RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=monero --withoutcoins=particl
FROM debian:buster-slim FROM debian:bullseye-slim
COPY --from=install_stage /coin_bin . COPY --from=install_stage /coin_bin .
@@ -21,5 +21,4 @@ VOLUME $MONERO_DATA
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 18080 CMD ["/monero/monerod", "--non-interactive", "--config-file=/home/monero/.monero/monerod.conf", "--confirm-external-bind"]
CMD ["monerod", "--non-interactive", "--config-file=/home/monero/.monero/monerod.conf"]

View File

@@ -13,5 +13,4 @@ VOLUME $MONERO_DATA
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 18080 CMD ["/monero/monero-wallet-rpc", "--non-interactive", "--config-file=/data/monero_wallet.conf", "--confirm-external-bind"]
CMD ["monero-wallet-rpc", "--non-interactive", "--config-file=/data/monero_wallet.conf"]

Some files were not shown because too many files have changed in this diff Show More